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 (measured to the chest bone) 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. Returns 9999 when the player is invalid, the bone is out of range, the camera is unavailable, or the bone argument type is invalid
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])vec2Aim delta with projectile prediction
targeting.smoothness_compensation(speed)numberConverts a smoothing speed in (0, 1] to a compensation factor. The input speed is clamped to [0.01, 1] before dividing, so the returned 1/speed ranges [1, 100]

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)

compute_aim_delta ​

Returns a vec2 with the pixel delta needed to aim at the given target, accounting for projectile travel time (linear velocity lead only - no gravity). Pass to input.move_mouse(...) to nudge the crosshair.

proj_speed defaults to instant (no leading) if omitted.

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)
    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 moves the mouse in one step.

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.

Use for abilities where cast-time aim matters more than 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:

  • Yields each tick for hold_time milliseconds; code after it resumes on the first tick after the hold.
  • 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.