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_idintegerThe ability's token. game.token_name(e.ability_id) gives its name, see Tokens
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, so you can compare them but not name them. Useful tests:

  • A hero was involved: e.victim_class ~= 0 or e.attacker_class ~= 0.
  • 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
seqGoes up by one for each hit on an enemy hero, ability hits included. Not the same numbers as the event seq
hitsBullet hits landed. Ability, damage over time and item damage are not counted
head_hitsHow many of those bullet hits were headshots
total_damageDamage dealt from every source, abilities included
last_hitgroupHitgroup of the last hit, -1 until one lands. An ability hit also sets -1
last_damageDamage of the last hit

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

Notes ​

  • 32 events are kept. If more than 32 hits arrive between two calls, the oldest are lost and dropped says how many.
  • Nothing new returns an empty table, not nil. The table is shared between calls, so 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.
  • p:get_hit_events has no callback, so poll it from on_tick. For a callback on each damage message, use events.on("CCitadelUserMessage_Damage", fn). See Game Messages. on_shot in Events fires when you fire.
  • internal.hit_events_diag() returns the hit feed's counters, or nil when the internal is not running. count is how many events arrived since the internal loaded.

Example ​

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

lua
script_info = { name = "melee_parry", needs_internal = true }

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.