Targeting ​
Target selection helpers and aim math. All functions are in the targeting.* namespace.
Functions ​
| Function | Returns | Description |
|---|---|---|
targeting.find_closest_by_fov(max_fov, max_dist) | player | nil | Nearest target within max_fov degrees of crosshair AND within max_dist meters |
targeting.find_closest_by_distance(meters) | player | nil | Nearest target within meters |
targeting.find_all_in_range(meters) | table | All players within meters |
targeting.get_fov(player, bone) | number | Degrees from crosshair to that bone on that player |
targeting.is_on_target(player, [radius]) | boolean | True if the crosshair is on the player within radius. radius defaults to 6.0. The unit isn't a FOV angle; treat it as a tolerance value and tune empirically |
targeting.compute_aim_delta(player, bone, [proj_speed], [smooth_speed]) | vec2 | Aim delta with projectile prediction |
compute_aim_delta is overloaded; the first argument can also be a vec3 world position directly:
targeting.compute_aim_delta(world_pos)
targeting.compute_aim_delta(player, bone, proj_speed, smooth_speed)compute_aim_delta ​
Returns a vec2 with the pixel delta needed to aim at the given target, accounting for projectile travel time and gravity. Pass to input.move_mouse(...) to nudge the crosshair.
proj_speed defaults to instant (no leading) if omitted. smooth_speed controls how aggressively the delta moves toward the target across multiple ticks; higher values move slower. Omit it for an unsmoothed, full-distance delta.
local target = targeting.find_closest_by_fov(8.0, 30.0)
if target and target:is_visible() then
local proj_speed = local_player():get_active_projectile_speed()
local delta = targeting.compute_aim_delta(target, "head", proj_speed, 6.0)
input.move_mouse(delta.x, delta.y)
endFor "snap to target" behavior in one call, use input.snap_to instead. It calls compute_aim_delta internally and applies the mouse move for you.
fire_and_hold ​
Fires an ability or item at a target and maintains aim lock for a duration after firing. Handles the common "fire then hold lock" pattern as a coroutine, yields internally, so local variables and script state are preserved across the hold.
Useful for any ability with a windup or projectile travel time where the cast-time aim matters more than the key-press-time aim. Haze's Sleep Dagger is the canonical case: pressing the key doesn't release the dagger immediately, and if your aim drifts during the throw animation the projectile misses. fire_and_hold keeps the snap locked through the animation. See the Haze Auto Dagger example for complete usage.
Parameters:
target(player). The player to aim at.slot(number). The ability or item slot to fire (slot.ability1–slot.ability4,slot.item1–slot.item4).opts(table, optional). Configuration:bone. Bone to aim at. Default:bone.chest.hold_time(number). Milliseconds to maintain aim after firing. Default:200.projectile_velocity(number). Projectile speed in game units per second, for lead prediction. Set for projectile-based abilities (Vindicta stake, Grey Talon arrow, etc.). Leave at the default0for hitscan abilities. No leading is applied.
Returns: nothing (nil). If the target dies mid-hold, the function returns early but does not signal this to the caller. Check target:is_alive() after the call if your script needs to react to the early break.
Behavior:
- Presses the key bound to the given slot.
- Continues aiming at the target's bone for
hold_timemilliseconds. - If the target dies during the hold, releases early.
- Yields each tick during the hold, call this from
on_tickwith awhile true+coroutine.yield()pattern.
Example:
function on_tick()
while true do
local target = targeting.find_closest_by_fov(10, 30)
if target and target:is_targetable() then
local angle = targeting.get_fov(target, bone.chest)
if angle and angle < 1.5 then
fire_and_hold(target, slot.ability1, {
bone = bone.chest,
hold_time = 200
})
else
snap_to_target(target, { bone = bone.chest })
end
end
coroutine.yield()
end
endNotes:
- This function blocks (yields) for the duration of
hold_time. Code after it resumes on the next tick after the hold completes. - Works with both abilities (
slot.ability1–slot.ability4) and items (slot.item1–slot.item4). - Pair with
is_ability_ready()oris_item_ready()to avoid firing on cooldown.
Custom filtering ​
The targeting helpers (find_closest_by_fov, find_closest_by_distance, find_all_in_range) already filter for alive, valid enemies. You only need to write your own loop if you want extra conditions on top of that, like requiring the target be visible or targetable:
local function find_enemy(max_fov)
local best, best_fov = nil, max_fov
for _, p in ipairs(get_players()) do
if p:is_alive() and p:is_targetable() and p:is_visible() then
local fov = targeting.get_fov(p, "head")
if fov < best_fov then
best, best_fov = p, fov
end
end
end
return best
endget_players() already returns only enemies, so you don't need to call is_enemy() here. Teammates live on a separate accessor, get_teammates(), and are always populated.