Stats
Needs the internal running and needs_internal = true in script_info.
p:get_stats() -> table | nilReturns the player's combat stats with every item and modifier applied. Works on any player object, not just the local player. Percent fields are percentages: 18.0 means 18 percent. Without the internal, p:get_stat_deltas() gives what your items and buffs add to your own stats, without the hero's base.
Keys on every player
| Key | Meaning | Unit |
|---|---|---|
max_health | Max health | HP |
bullet_resist | Bullet damage reduction | percent |
spirit_resist | Spirit damage reduction | percent |
melee_resist | Melee damage reduction | percent |
debuff_resist | Debuff resist | percent |
bullet_shield | Shield pool in front of health | HP |
barrier | Pool that soaks every damage type | HP |
crit_taken_scale | Crit damage received | multiplier, 1.0 baseline |
slow_resist | Slow resistance | percent |
ability_level | Ability level stat | number |
dmg_taken_inc | All damage taken increase | percent |
bullet_dmg_taken_inc | Bullet damage taken increase | percent |
spirit_dmg_taken_inc | Spirit damage taken increase | percent |
dmg_taken_red | All damage taken reduction | percent |
bullet_dmg_red_pct | Bullet damage reduction | percent |
ability_dmg_red_pct | Ability damage reduction | percent |
seq | Changes when any row in the feed was refreshed | integer |
entry_seq | Changes when this row was refreshed | integer |
The six damage-taken keys, dmg_taken_inc through ability_dmg_red_pct, are usually zero.
Keys on the local player only
Only present on the local player. On anyone else they are absent, not zero, so test with if s.bullet_damage ~= nil.
weapon_power_bonus, spirit_power, bullet_damage, fire_rate_bonus, clip_size, health_regen, move_speed, light_melee, heavy_melee, bullet_lifesteal, spirit_lifesteal, falloff_min, falloff_max
weapon_power_bonusandfire_rate_bonusare bonuses over the hero base, so 0 is the baseline.bullet_damageis damage per bullet before resists and falloff.falloff_minandfalloff_maxare in game units.p:get_distance()is in metres, so multiply it by 39.37 to compare. They include your items.
Your weapon and headshot numbers, also only on your own row:
| Key | Meaning |
|---|---|
crit_bonus_scale | A multiplier on your headshot bonus from items and effects. 1 means no change. It scales only the part of the headshot multiplier above 1 |
bullets_per_shot | Pellets per trigger pull |
falloff_end_scale | Damage multiplier past the falloff end |
crit_bonus_start, crit_bonus_end | Your weapon's headshot multiplier at the start and at the end of its range |
crit_bonus_start_range, crit_bonus_end_range | That range, in game units |
weapon_falloff_start, weapon_falloff_end | The weapon's own falloff range in game units, without your items |
can_crit | true when the weapon can headshot |
bullets_per_shot through can_crit are missing when your weapon can't be read, so check them first. The damage preview does this math for you.
Returns nil when
- the internal is not running
needs_internal = trueis missing- nothing called
p:get_stats()in the last 2 seconds. The first call after a gap returnsnilwhile the feed starts up again - the player is not in the feed yet
- the player is dead. The local player stays in the feed while dead
Notes
- Rows refresh within 100 ms.
- The same table is returned until
entry_seqchanges. Do not write into it, because other scripts get the same table. - 16 players maximum.
- The player does not need to be visible.
internal.stats_diag()returns the stat feed's counters, ornilwhen the internal is not running.countis how many players are in the feed andwantedis whether a script asked in the last 2 seconds.
Example
Bullets to kill on the nearest enemy, from Shots to Kill:
function on_tick()
local x, y = 40, 460
local function line(t, c, sc) draw.text(x, y, t, c or WHITE, sc or 1.15); y = y + 18 end
if not internal.is_loaded() then line("shots_to_kill: internal not loaded", DIM); return end
local me = local_player and local_player() or nil
local ms = me and me:get_stats() or nil
if not ms or not ms.bullet_damage then line("shots_to_kill: waiting for local stats", DIM); return end
local e, d = nearest_enemy()
local es = e and e:get_stats() or nil
if not e or not es then line("shots_to_kill: no enemy stats yet", DIM); return end
local B = ms.bullet_damage
local R = (es.bullet_resist or 0) / 100
local inc = ((es.dmg_taken_inc or 0) + (es.bullet_dmg_taken_inc or 0)) / 100
local red = ((es.dmg_taken_red or 0) + (es.bullet_dmg_red_pct or 0)) / 100
local per = B * (1 - R) * (1 + inc) * (1 - red)
if per < 0.1 then line("shots_to_kill: per-hit damage ~0", DIM); return end
local pool = (e:get_health() or es.max_health or 0) + (es.bullet_shield or 0) + (es.barrier or 0)
line(string.format("%d body", math.ceil(pool / per)), GREEN, 1.4)
endbullet_damage is only on your own row and the resists are on the enemy's, so one read can be nil while the other has a value. nearest_enemy, WHITE, DIM and GREEN are defined in that script. p:get_shot_damage() below also handles headshots, shotgun pellets and falloff.
Damage preview
These two methods work out your damage on a player using the game's own numbers. Like get_stats, they need the internal running and needs_internal = true.
Your gun
p:get_shot_damage([distance_m]) -> table | nilThe damage of your next gun shot on this player. distance_m is the distance in metres, used for damage falloff and for your weapon's headshot range. Leave it out to use the distance from your camera to this player's head.
It counts your close and long range weapon bonuses at that distance, such as Close Quarters, Point Blank, Long Range and Sharpshooter. When you play Haze, it counts your Fixation stacks on this player, which uses one Ability Values pair. It also counts their Rat Armor and effects that change how much damage they take, such as Venator's self-heal channel. It does not count headshot items' flat bonuses (Headhunter, Headshot Booster), on-hit effects (Mystic Shot, Tesla Bullets, Lucky Shot) or damage over time.
| Key | Type | Meaning |
|---|---|---|
body | number | One trigger pull with every pellet on the body, after their bullet resist and damage modifiers |
head | number | The same on the head |
per_bullet | number | One pellet after falloff, with your range bonus and Fixation, before their resists |
bullets | number | Pellets per trigger pull |
falloff | number | The falloff multiplier at this distance. 1 means no falloff |
head_mult | number | The headshot multiplier used, from your weapon's headshot range and both players' headshot scales |
pool | number | Their health plus bullet shield plus barrier plus Rat Armor |
shots | integer | Body shots to kill |
head_shots | integer | Headshots to kill |
distance | number | The distance used, in metres |
range_mult | number | The close or long range weapon bonus used at this distance. 1 means none |
fixation | number | Haze's Fixation bonus per bullet on this player, before falloff. 0 when you are not playing Haze, have no stacks on them, or the value is not read yet |
fixation_stacks | integer | Your Fixation stacks on this player |
Returns nil when:
- the internal is not running, or the flag is missing
- you are not in a match
- your stats or this player's are not in the feed yet. The first call starts the stat feed, and the answer arrives on a later tick, usually within 100 ms
- this player is dead
- the shot would deal no damage to this player
function on_tick()
local y = 300
for _, e in ipairs(get_players()) do
if e:is_enemy() then
local s = e:get_shot_damage()
if s then
draw.text(40, y, string.format("%s: %.0f body, %.0f head, %d shots", e:hero_name(), s.body, s.head, s.shots), 0xFFFFFFFF)
y = y + 18
end
end
end
endYour abilities
p:get_ability_damage(slot [, property [, kind]]) -> dealt, casts | nilWhat your ability in slot (0 to 3) would deal to this player, after their resist and damage modifiers. property is the damage property to read, "Damage" by default. kind is the type of damage the ability deals:
kind | Resist used |
|---|---|
"spirit" (default) | Their spirit resist |
"bullet" | Their bullet resist. Their bullet shield also counts toward casts |
"pure" | None |
Any other string counts as "spirit", so write them in lower case. Your own damage bonuses count: outgoing damage and all damage, plus your spirit damage bonus for "spirit" or your bullet damage bonus for "bullet". So do their Rat Armor and effects that change how much damage they take. If the ability also deals a share of the target's missing health, that gets added a moment after the first answer.
It returns two values: dealt, the damage after their resists, and casts, how many uses it takes to kill them (health plus barrier plus Rat Armor, and their bullet shield for "bullet").
It does not count Mystic Burst, your Escalating Exposure, the target's Spellbreaker or Shiv's Bloodletting. It also does not know when the target cannot die or cannot take that damage right now, for example during Metal Skin. If your script needs those, check them yourself. For the target's state, use modifier flags such as modifier_flag.UNKILLABLE, modifier_flag.TECH_INVULNERABLE and modifier_flag.BULLET_INVULNERABLE.
Returns nil when:
- the internal is not running, or the flag is missing
- you are not in a match
- your ability slot is empty, has no such property, or its value has not arrived yet
- this player's stats are not in the feed yet
- this player is dead
- the damage works out to 0
It uses Ability Values pairs: two for each slot you ask about, plus one for each other property name you pass.
local dealt, casts = enemy:get_ability_damage(0)
if dealt then print(string.format("ability 1 deals %.0f, %d casts to kill", dealt, casts)) end
-- an ability that deals bullet damage
local b_dealt, b_casts = enemy:get_ability_damage(2, "Damage", "bullet")
if b_dealt then print(string.format("ability 3 deals %.0f, %d casts to kill", b_dealt, b_casts)) end