Skip to content

Targeting ​

Target selection helpers and aim math. All functions are in the targeting.* namespace.

Functions ​

FunctionReturnsDescription
targeting.find_closest_by_fov(max_fov, max_dist)player | nilNearest target within max_fov degrees of crosshair AND within max_dist meters
targeting.find_closest_by_distance(meters)player | nilNearest target within meters
targeting.find_all_in_range(meters)tableAll players within meters
targeting.get_fov(player, bone)numberDegrees from crosshair to that bone on that player
targeting.is_on_target(player, [radius])booleanTrue 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])vec2Aim 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.

lua
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)
end

For "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 default 0 for 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:

  1. Presses the key bound to the given slot.
  2. Continues aiming at the target's bone for hold_time milliseconds.
  3. If the target dies during the hold, releases early.
  4. Yields each tick during the hold, call this from on_tick with a while true + coroutine.yield() pattern.

Example:

lua
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
end

Notes:

  • 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() or is_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:

lua
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
end

get_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.

Not affiliated with Valve Corporation.