Shots to Kill
Prints the nearest enemy's name with the number of body and head shots your gun needs to kill them, the per hit damage and the health pool behind that number, and a warning when the target is beyond your falloff range.
What this shows
get_statsworks on any player. Your row carriesbullet_damage; the enemy's carries resists, shield and barrier.- Two guards. Either read can be
nilwhile the other answers, so each is checked on its own. - Absent, not zero.
bullet_damageis missing on other players' rows, so the check isif not ms.bullet_damage, not a comparison with 0. - Colors are packed integers, the same values
draw.color(...)returns.draw.texttakes a scale as its fifth argument.
The formula
per_hit = bullet_damage * (1 - bullet_resist/100) * (1 + taken_inc/100) * (1 - taken_red/100)
pool = health + bullet_shield + barrier
shots = ceil(pool / per_hit)
head = ceil(pool / (per_hit * 1.65))1.65 is an observed head multiplier, not a value the API returns. p:get_distance() is in metres and falloff_min is in game units, 39.37 per metre.
Script
lua
-- shots_to_kill.lua
-- Bullets to kill on the nearest enemy, from the internal stat feed.
-- Falloff is ignored past falloff_min, so the number reads optimistic at range.
script_info = {
name = "shots_to_kill",
author = "tsuki",
version = "1.0",
description = "bullets-to-kill readout for the nearest enemy (needs the internal)",
needs_internal = true,
}
local WHITE, DIM, GREEN, ORANGE = 0xFFFFFFFF, 0xFFA8A8A8, 0xFF7FE07F, 0xFF3C96FF
local HEAD_MULT = 1.65
local function nearest_enemy()
local best, bd = nil, 1e12
local list = get_players and get_players() or nil
if type(list) ~= "table" then return nil end
for _, p in ipairs(list) do
local ok_e, is_e = pcall(function() return p:is_enemy() end)
local ok_a, is_a = pcall(function() return p:is_alive() end)
if ok_e and is_e and ok_a and is_a then
local ok_d, d = pcall(function() return p:get_distance() end)
d = (ok_d and type(d) == "number") and d or 1e11
if d < bd then bd, best = d, p end
end
end
return best, bd
end
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 ok_h, hp = pcall(function() return e:get_health() end)
hp = (ok_h and type(hp) == "number") and hp or (es.max_health or 0)
local pool = hp + (es.bullet_shield or 0) + (es.barrier or 0)
local shots = math.ceil(pool / per)
local head = math.ceil(pool / (per * HEAD_MULT))
local ok_n, name = pcall(function() return e:hero_name() end)
name = (ok_n and name) or "enemy"
local past = (ms.falloff_min or 0) > 0 and d and (d * 39.37) > ms.falloff_min
line(string.format("%s %d body / %d head", name, shots, head), GREEN, 1.4)
line(string.format("%.1f per hit (%.1f x (1-%.0f%%)) pool %.0f = hp %.0f + shield %.0f + barrier %.0f",
per, B, R * 100, pool, hp, es.bullet_shield or 0, es.barrier or 0), DIM)
if past then line(string.format("beyond falloff (%.0fm > %.0fm): optimistic", d, ms.falloff_min / 39.37), ORANGE) end
endNotes
- Falloff is not modelled. Past
falloff_minthe number reads optimistic; that is what the orange line is for. - The
dmg_taken_incanddmg_taken_redterms are rarely non zero. They stay in the formula but do not expect them to change the result. - The
pcallwrappers exist because a player object is only valid for the current tick. Storep:entity_ptr()and look the player up again next tick. - To compare the prediction with what actually landed, see Hit Events. Every stat key and its unit is on Stats.