/*
* CSB Headshot Counter
* Copyright (C) 2026 counter-strike-boost.com
*
* Tracks headshots per round, per map and lifetime (nVault) from the DeathMsg
* headshot flag, shows a small HUD counter, announces the round's best headshot
* hunter, and exposes say /hs to print a player's headshot ratio.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version. It is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See for details.
*/
#include
#include
#include
new const PLUGIN[] = "CSB Headshot Counter"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define NO_VAULT (-1)
new g_iVault = NO_VAULT
new g_pEnabled, g_pHud
new g_iRound[33]
new g_iMap[33]
new g_iLifeHs[33]
new g_iLifeKills[33]
new g_szAuth[33][35]
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_hs_enabled", "1")
g_pHud = register_cvar("csb_hs_hud", "1")
register_event("DeathMsg", "eventDeath", "a", "1>0")
register_logevent("eventRoundEnd", 2, "1=Round_End")
register_clcmd("say /hs", "cmdHs")
register_clcmd("say_team /hs", "cmdHs")
}
public plugin_cfg()
{
g_iVault = nvault_open("csb_headshots")
if (g_iVault == NO_VAULT)
log_amx("[CSB HS] could not open nVault 'csb_headshots' - lifetime stats disabled.")
}
public plugin_end()
{
if (g_iVault != NO_VAULT)
nvault_close(g_iVault)
}
public client_authorized(id)
{
g_iRound[id] = 0
g_iMap[id] = 0
g_iLifeHs[id] = 0
g_iLifeKills[id] = 0
g_szAuth[id][0] = 0
if (is_user_bot(id) || is_user_hltv(id))
return
get_user_authid(id, g_szAuth[id], charsmax(g_szAuth[]))
if (g_iVault == NO_VAULT)
return
new value[32]
if (nvault_get(g_iVault, g_szAuth[id], value, charsmax(value)))
{
new hs[16], kills[16]
strtok(value, hs, charsmax(hs), kills, charsmax(kills), ' ')
g_iLifeHs[id] = str_to_num(hs)
g_iLifeKills[id] = str_to_num(kills)
}
}
public client_disconnected(id)
{
saveLifetime(id)
}
saveLifetime(id)
{
if (g_iVault == NO_VAULT || !g_szAuth[id][0])
return
new value[32]
formatex(value, charsmax(value), "%d %d", g_iLifeHs[id], g_iLifeKills[id])
nvault_pset(g_iVault, g_szAuth[id], value)
}
public eventDeath()
{
if (!get_pcvar_num(g_pEnabled))
return
new killer = read_data(1)
new victim = read_data(2)
new hs = read_data(3)
if (killer < 1 || killer > 32 || killer == victim)
return
g_iLifeKills[killer]++
if (hs)
{
g_iRound[killer]++
g_iMap[killer]++
g_iLifeHs[killer]++
if (get_pcvar_num(g_pHud) && is_user_connected(killer))
{
set_hudmessage(0, 220, 255, 0.02, 0.55, 0, 0.0, 3.0, 0.1, 0.2, -1)
show_hudmessage(killer, "Headshots this map: %d", g_iMap[killer])
}
}
}
public eventRoundEnd()
{
new best = 0, bestHs = 0
new players[32], num, pid
get_players(players, num, "c")
for (new i = 0; i < num; i++)
{
pid = players[i]
if (g_iRound[pid] > bestHs)
{
bestHs = g_iRound[pid]
best = pid
}
g_iRound[pid] = 0
}
if (best && bestHs >= 2)
{
new name[32]
get_user_name(best, name, charsmax(name))
client_print(0, print_chat, "[CSB] Best headshot hunter this round: %s with %d headshot(s).", name, bestHs)
}
}
public cmdHs(id)
{
new pct = 0
if (g_iLifeKills[id] > 0)
pct = (g_iLifeHs[id] * 100) / g_iLifeKills[id]
client_print(id, print_chat, "[CSB] Headshots - this map: %d | lifetime: %d of %d kills (%d%% HS ratio).",
g_iMap[id], g_iLifeHs[id], g_iLifeKills[id], pct)
return PLUGIN_HANDLED
}