Skip to content

Hit Events

Both calls need the internal running and needs_internal = true in script_info. Local player only.

p:get_hit_events([since_seq]) -> events, latest, dropped   -- or nil
p:get_hit_stats() -> table | nil

p:get_hit_events

Returns one event per hit you dealt or took, including troopers and objects. The numbers are the game's own values for that hit.

ReturnMeaning
eventsArray of events with seq > since_seq, oldest first, at most 32
latestNewest sequence number. Pass it back on the next call
droppedEvents overwritten before you read them

Keep a cursor:

lua
local ev, latest = me:get_hit_events(hit_seq)
if ev then
    for _, e in ipairs(ev) do
        -- ...
    end
    if latest then hit_seq = latest end
end

since_seq = 0 returns the last 32 events. To ignore history and count from now:

lua
hit_seq = select(2, me:get_hit_events(0)) or 0

Event fields

FieldTypeMeaning
seqintegerSequence number
timenumbersim_time() when the hit was recorded
is_dealtbooleanYou were the attacker
is_takenbooleanYou were the victim
victimplayerPlayer object. Absent when the victim is not a hero
attackerplayerPlayer object. Absent when the attacker is not a hero
victim_ptr, attacker_ptrintegerPresent for any victim or attacker
victim_index, attacker_indexintegerEntity indexes
damageintegerDamage applied
pre_damagenumberDamage before resists and shields
damage_absorbednumberAbsorbed portion
crit_damagenumberCrit share
effectivenessnumberRaw value
typeintegerRaw value. Carries the melee and headshot bits
citadel_typeintegerRaw value
flagsintegerRaw value
hitgroupinteger1 is the head. Ability hits can carry -1
victim_shield_max, victim_shield_newintegerVictim's shield before and after
victim_health_max, victim_health_newintegerVictim's max health and health after the hit
health_lostintegerHealth this hit removed
hitsintegerHit count on the event
server_tickintegerServer tick the hit landed on
ability_idintegerRaw value
victim_class, attacker_classintegerRaw value. 0 means not a hero
is_secondary_statbooleanRaw value
abilitystringDesigner name of the ability, "" when none
ability_index, inflictor_index, attacking_object_indexintegerEntity indexes
origin, directiontable{x, y, z}, present only when the event carried them

The raw values have no name table; you can compare them but not name them. Useful tests:

  • A hero was involved: e.victim_class ~= 0 or e.attacker_class ~= 0. Check the key exists first.
  • Melee: (e.type & 0x1) ~= 0.
  • Headshot, including ability headshots: e.hitgroup == 1, or e.crit_damage > 0, or the type bit 0x100000.

victim_health_new + health_lost is the target's health before the hit.

p:get_hit_stats

Cumulative counters for damage you dealt to enemy heroes. Troopers and objects are not counted here.

FieldMeaning
seqNewest event counted
hitsHits landed
head_hitsOf which headshots
total_damageDamage dealt
last_hitgroupHitgroup of the last hit, -1 until one lands
last_damageDamage of the last hit

seq lets you tell "no new damage" from "no data".

Notes

  • 32 events are kept. Poll less often than 32 hits arrive and the oldest are lost; dropped says how many.
  • Nothing new returns an empty table, not nil. The table is shared between calls; do not modify it.
  • All three returns are nil when the internal is not running, the flag is missing, or the player is not the local player.
  • victim and attacker are player objects only for heroes. A trooper hit has victim_ptr but no victim.
  • There is no callback for hits; poll from on_tick. on_shot in Events fires when you fire.

Example

Parry the first melee hit taken from a hero, once per cooldown:

lua
local hit_seq, last_parry, melee_taken, parries = 0, 0, 0, 0
local MELEE_BIT = 0x1

function on_tick()
    local me = local_player()
    if not me or not me:is_alive() or not internal.is_loaded() then return end
    local cooldown_ms = config.get_int("cooldown_ms", 1500)
    local t = clock()

    local ev, latest = me:get_hit_events(hit_seq)
    if ev then
        for _, e in ipairs(ev) do
            if e.is_taken and e.attacker_class and e.attacker_class ~= 0
               and e.type and (e.type & MELEE_BIT) ~= 0 then
                melee_taken = melee_taken + 1
                if (t - last_parry) * 1000 >= cooldown_ms then
                    last_parry = t
                    if input.parry() then parries = parries + 1 end
                end
            end
        end
        if latest then hit_seq = latest end
    end
end

input.parry() presses the player's real parry bind. See Input.