Skip to content

Abrams Auto Charge ​

Hold mouse-5 to snap to the closest enemy in your FOV, charge them with ability 2, and keep tracking through the charge until either the charge connects, the target dies, or the target becomes untargetable.

What this shows ​

  • Hero-locked script. Setting id = hero_id.abrams means the script only runs when you're playing Abrams. Switch heroes and it goes idle on its own.
  • Modifier-driven state. The script doesn't try to time the charge with clock(). It watches the local player's bullcharging modifier. When Abrams is mid-charge, that modifier is active. As soon as it ends (hit, miss, or cancel), the script naturally exits the tracking phase.
  • Hit detection via modifier. The enemy gets a chargedragenemy modifier the moment the charge connects. Reading that on the target tells you the hit landed without polling positions or doing geometry.
  • Identity guard. Player indices can shift mid-frame as entities spawn/despawn. Caching the hero name when you lock and checking it every tick prevents the "I was tracking Haze, suddenly the script thinks I'm tracking Lash" failure mode.
  • Zero-bone guard. Bone positions can briefly read as (0, 0, 0) during certain animation frames or just after a respawn. Snapping to origin would yank your camera underground. The check skips those frames.

Script ​

lua
-- Abrams Auto Charge
-- Press mouse5 -> snap to closest enemy -> charge -> track until hit
--
-- API reference:
--   hero_id.abrams             , hero filter, script only runs on Abrams
--   slot.ability2              , ability slot (0-3 abilities, 4-7 items)
--   bone.head                  , named bone (head, neck, chest, spine_1, pelvis, etc.)
--   VK.XBUTTON2                , mouse5 virtual key code
--
--   local_player()             , returns local player
--   targeting.find_closest_by_fov(fov_deg, range_m), best target in FOV cone
--   snap_to_target(player, {bone = bone.X})        , instant aim snap
--   press_ability(slot.X)      , press ability keybind
--
--   player:has_modifier(name)  , substring match on active modifiers
--   player:is_alive()          , alive check
--   player:is_targetable()     , alive + not immune
--   player:hero_name()         , hero name string
--   player:get_distance()      , distance in meters
--   player:bone_pos(bone)      , bone world position
--
--   toast(text, dur, {bg, text, outline, scale}), styled notification
--   clock()                    , high-res timer (seconds)
--   input.is_key_held(vk)      , true while key held

name = "Abrams Auto Charge"
id   = hero_id.abrams

settings = {
    { key = "fov",       label = "FOV (degrees)", type = "float", default = 15.0, min = 1.0, max = 30.0 },
    { key = "max_range", label = "Max Range (m)", type = "float", default = 30.0, min = 5.0, max = 100.0 },
}

local locked_target = nil
local locked_hero = nil
local lock_time = 0

function on_tick()
    local lp = local_player()
    if not lp or not lp:is_alive() then
        locked_target = nil
        locked_hero = nil
        return
    end

    local charging = lp:has_modifier("bullcharging")
    local now = clock()

    -- Phase 2: mid-charge tracking
    if locked_target then
        local in_grace = (now - lock_time) < 0.3

        if charging or in_grace then
            -- index shift guard: make sure we're still aimed at the right hero
            if locked_target:hero_name() ~= locked_hero then
                locked_target = nil
                locked_hero = nil
                return
            end

            -- hit: enemy received charge drag modifier
            if locked_target:has_modifier("chargedragenemy") then
                toast(string.format("Charged %s!", locked_hero), 3, {
                    bg = {r=20, g=60, b=20, a=220},
                    text = {r=100, g=255, b=100},
                })
                locked_target = nil
                locked_hero = nil
                return
            end

            -- target lost
            if not locked_target:is_alive() or not locked_target:is_targetable() then
                toast("Target lost", 2, {
                    bg = {r=80, g=10, b=10, a=220},
                    text = {r=255, g=80, b=80},
                })
                locked_target = nil
                locked_hero = nil
                return
            end

            -- zero bone guard + track
            local bp = locked_target:bone_pos(bone.head)
            if bp and (bp.x ~= 0 or bp.y ~= 0 or bp.z ~= 0) then
                snap_to_target(locked_target, { bone = bone.head })
            end
            return
        end

        -- charge ended
        locked_target = nil
        locked_hero = nil
    end

    -- Phase 1: initiate
    if not input.is_key_held(VK.XBUTTON2) then return end
    if not lp:is_ability_ready(slot.ability2) then return end

    local target = targeting.find_closest_by_fov(
        config.get_float("fov"),
        config.get_float("max_range")
    )
    if not target or not target:is_targetable() then return end

    -- lock + snap + fire
    locked_target = target
    locked_hero = target:hero_name()
    lock_time = now
    snap_to_target(target, { bone = bone.head })
    press_ability(slot.ability2)

    toast(string.format("Charging %s!", locked_hero), 2, {
        bg = {r=15, g=25, b=60, a=220},
        text = {r=120, g=180, b=255},
    })
end

Adapting this to other heroes ​

The pattern (lock on input → snap to target → fire ability → track via modifier → release on hit/miss) applies to most ability-with-travel-time charges. Things you'd change for a different hero:

  • id = hero_id.X, which hero this binds to. Drop the line entirely to make it hero-agnostic.
  • slot.abilityN, which ability slot triggers (and which one's readiness you check).
  • The two modifier names: the "I am using this ability" modifier on the local player, and the "I was hit by it" modifier on the target. Both are easiest to find using the Debugging Modifiers workflow, turn on the live modifier dump, do the thing in-game, see what shows up.
  • bone.head for the snap target. Head is right for upper-body abilities; for AOE drops you'd want bone.pelvis or just use the player position.

The grace period (0.3 seconds) exists because some abilities have a brief animation lock-in window before the modifier appears on the local player. Without it, the script would exit tracking before the charge even starts. If you're adapting this to an ability with a longer windup, bump that number up.

Not affiliated with Valve Corporation.