/*
* CSB Weapon Stats
* Copyright (C) 2026 counter-strike-boost.com
*
* Keeps a per-weapon breakdown for every player: kills, headshots and damage
* with each weapon (tracked from DeathMsg and Ham_TakeDamage), persisted per
* SteamID in nVault. /wstats opens a MOTD table with your numbers, your
* favourite weapon and the most-used weapon on the server this map.
*
* 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 <https://www.gnu.org/licenses/> for details.
*/
#include <amxmodx>
#include <hamsandwich>
#include <nvault>
new const PLUGIN[] = "CSB Weapon Stats"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define MAX_WEAPONS 20
new const g_szWeapons[][] =
{
"ak47", "m4a1", "awp", "deagle", "usp", "glock18", "famas", "galil",
"mp5navy", "p90", "scout", "aug", "sg552", "m3", "xm1014", "m249",
"knife", "grenade", "hegrenade", "flashbang"
}
enum WStat
{
WS_KILLS = 0,
WS_HS,
WS_DMG,
WS_MAX
}
new g_iStats[33][MAX_WEAPONS][WS_MAX]
new g_iMapKills[MAX_WEAPONS]
new g_iVault
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
register_cvar("csb_wstats_enabled", "1")
register_event("DeathMsg", "onDeath", "a")
RegisterHam(Ham_TakeDamage, "player", "OnDamage", 1)
register_clcmd("say /wstats", "cmdStats")
register_clcmd("say_team /wstats", "cmdStats")
g_iVault = nvault_open("csb_wstats")
}
public plugin_end()
{
if (g_iVault != -1)
nvault_close(g_iVault)
}
public client_putinserver(id)
{
for (new w = 0; w < MAX_WEAPONS; w++)
{
for (new s = 0; s < WS_MAX; s++)
g_iStats[id][w][s] = 0
}
loadPlayer(id)
}
public client_disconnect(id)
{
savePlayer(id)
}
public onDeath()
{
new killer = read_data(1)
new victim = read_data(2)
new hs = read_data(3)
new weapon[24]
read_data(4, weapon, charsmax(weapon))
if (killer < 1 || killer > 32 || killer == victim)
return
if (get_user_team(killer) == get_user_team(victim))
return
new w = weaponIndex(weapon)
if (w == -1)
return
g_iStats[killer][w][WS_KILLS]++
g_iMapKills[w]++
if (hs)
g_iStats[killer][w][WS_HS]++
}
public OnDamage(victim, inflictor, attacker, Float:damage, damagetype)
{
if (attacker < 1 || attacker > 32 || attacker == victim)
return HAM_IGNORED
if (get_user_team(attacker) == get_user_team(victim))
return HAM_IGNORED
new wid = get_user_weapon(attacker)
if (wid < 1 || wid > 29)
return HAM_IGNORED
new wname[24]
get_weaponname(wid, wname, charsmax(wname))
replace(wname, charsmax(wname), "weapon_", "")
new w = weaponIndex(wname)
if (w != -1)
g_iStats[attacker][w][WS_DMG] += floatround(damage)
return HAM_IGNORED
}
weaponIndex(const weapon[])
{
for (new w = 0; w < MAX_WEAPONS; w++)
{
if (equali(weapon, g_szWeapons[w]))
return w
}
return -1
}
public cmdStats(id)
{
new motd[1536], len
len = formatex(motd, charsmax(motd),
"<html><body bgcolor=#101010 text=#e0e0e0><h2>CSB Weapon Stats</h2><table border=1 cellpadding=5><tr><th>Weapon</th><th>Kills</th><th>HS</th><th>Damage</th></tr>")
new favW = -1, favKills = 0
for (new w = 0; w < MAX_WEAPONS; w++)
{
if (g_iStats[id][w][WS_KILLS] < 1 && g_iStats[id][w][WS_DMG] < 1)
continue
len += formatex(motd[len], charsmax(motd) - len,
"<tr><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>",
g_szWeapons[w], g_iStats[id][w][WS_KILLS],
g_iStats[id][w][WS_HS], g_iStats[id][w][WS_DMG])
if (g_iStats[id][w][WS_KILLS] > favKills)
{
favKills = g_iStats[id][w][WS_KILLS]
favW = w
}
}
len += formatex(motd[len], charsmax(motd) - len, "</table>")
if (favW != -1)
{
len += formatex(motd[len], charsmax(motd) - len,
"<p>Your favourite weapon: <b>%s</b> (%d kills)</p>",
g_szWeapons[favW], favKills)
}
/* server favourite this map */
new bestW = -1, bestKills = 0
for (new w = 0; w < MAX_WEAPONS; w++)
{
if (g_iMapKills[w] > bestKills)
{
bestKills = g_iMapKills[w]
bestW = w
}
}
if (bestW != -1)
{
len += formatex(motd[len], charsmax(motd) - len,
"<p>Most used weapon on the server this map: <b>%s</b> (%d kills)</p>",
g_szWeapons[bestW], bestKills)
}
formatex(motd[len], charsmax(motd) - len, "</body></html>")
show_motd(id, motd, "CSB Weapon Stats")
return PLUGIN_HANDLED
}
loadPlayer(id)
{
if (g_iVault == -1)
return
new auth[36], key[44], data[1024]
get_user_authid(id, auth, charsmax(auth))
if (auth[0] == 0 || equal(auth, "STEAM_ID_PENDING"))
return
formatex(key, charsmax(key), "wstats_%s", auth)
if (nvault_get(g_iVault, key, data, charsmax(data)) < 1)
return
/* flat "k hs dmg" triples per weapon */
new pos = 0, w = 0, s = 0
new val[16]
while (w < MAX_WEAPONS)
{
pos = argparse(data, pos, val, charsmax(val))
if (pos == -1)
break
g_iStats[id][w][s] = str_to_num(val)
s++
if (s >= WS_MAX)
{
s = 0
w++
}
}
}
savePlayer(id)
{
if (g_iVault == -1)
return
new auth[36], key[44]
get_user_authid(id, auth, charsmax(auth))
if (auth[0] == 0 || equal(auth, "STEAM_ID_PENDING"))
return
formatex(key, charsmax(key), "wstats_%s", auth)
new data[1024], len = 0
for (new w = 0; w < MAX_WEAPONS; w++)
{
len += formatex(data[len], charsmax(data) - len, "%d %d %d ",
g_iStats[id][w][WS_KILLS], g_iStats[id][w][WS_HS], g_iStats[id][w][WS_DMG])
}
nvault_set(g_iVault, key, data)
}