/* * CSB Accuracy Tracker * Copyright (C) 2026 counter-strike-boost.com * * Counts shots fired (Ham_Weapon_PrimaryAttack) and bullet hits * (Ham_TakeDamage with DMG_BULLET) per weapon for every player, keeps per-map * and lifetime totals (lifetime persisted in nVault per SteamID) and prints a * console table with /acc. Useful on public servers for spotting who actually * hits what they shoot at. * * Inspired by the weapon accuracy reporting of AMXX StatsX; independent GPL * re-implementation, no original code reused. * * 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 #include new const PLUGIN[] = "CSB Accuracy Tracker" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #if !defined DMG_BULLET #define DMG_BULLET (1<<1) #endif #define MAX_WEAPONS 24 new const g_szWeapons[][] = { "weapon_glock18", "weapon_usp", "weapon_p228", "weapon_deagle", "weapon_fiveseven", "weapon_elite", "weapon_m3", "weapon_xm1014", "weapon_tmp", "weapon_mac10", "weapon_mp5navy", "weapon_ump45", "weapon_p90", "weapon_galil", "weapon_famas", "weapon_ak47", "weapon_m4a1", "weapon_scout", "weapon_sg552", "weapon_aug", "weapon_awp", "weapon_sg550", "weapon_g3sg1", "weapon_m249" } new g_iMapShots[33][MAX_WEAPONS] new g_iMapHits[33][MAX_WEAPONS] new g_iLifeShots[33][MAX_WEAPONS] new g_iLifeHits[33][MAX_WEAPONS] new g_iLastWeapon[33] new g_iVault public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) register_cvar("csb_acc_enabled", "1") for (new w = 0; w < MAX_WEAPONS; w++) RegisterHam(Ham_Weapon_PrimaryAttack, g_szWeapons[w], "OnFire", 1) RegisterHam(Ham_TakeDamage, "player", "OnDamage", 1) register_clcmd("say /acc", "cmdAcc") register_clcmd("say_team /acc", "cmdAcc") g_iVault = nvault_open("csb_accuracy") } public plugin_end() { if (g_iVault != -1) nvault_close(g_iVault) } public client_putinserver(id) { for (new w = 0; w < MAX_WEAPONS; w++) { g_iMapShots[id][w] = 0 g_iMapHits[id][w] = 0 g_iLifeShots[id][w] = 0 g_iLifeHits[id][w] = 0 } loadPlayer(id) } public client_disconnect(id) { savePlayer(id) } public OnFire(ent) { new id = pev(ent, pev_owner) if (id < 1 || id > 32 || !is_user_connected(id)) return HAM_IGNORED new w = weaponIndex(ent) if (w == -1) return HAM_IGNORED g_iLastWeapon[id] = w g_iMapShots[id][w]++ g_iLifeShots[id][w]++ return HAM_IGNORED } public OnDamage(victim, inflictor, attacker, Float:damage, damagetype) { if (attacker < 1 || attacker > 32 || attacker == victim) return HAM_IGNORED if (!(damagetype & DMG_BULLET)) return HAM_IGNORED if (get_user_team(attacker) == get_user_team(victim)) return HAM_IGNORED new w = g_iLastWeapon[attacker] if (w < 0 || w >= MAX_WEAPONS) return HAM_IGNORED g_iMapHits[attacker][w]++ g_iLifeHits[attacker][w]++ return HAM_IGNORED } weaponIndex(ent) { static cls[32] pev(ent, pev_classname, cls, charsmax(cls)) for (new w = 0; w < MAX_WEAPONS; w++) { if (equal(cls, g_szWeapons[w])) return w } return -1 } public cmdAcc(id) { client_print(id, print_console, " ") client_print(id, print_console, "----- CSB Accuracy (this map / lifetime) -----") for (new w = 0; w < MAX_WEAPONS; w++) { if (g_iMapShots[id][w] == 0 && g_iLifeShots[id][w] == 0) continue new name[24] copy(name, charsmax(name), g_szWeapons[w]) replace(name, charsmax(name), "weapon_", "") new Float:mAcc = g_iMapShots[id][w] > 0 ? 100.0 * float(g_iMapHits[id][w]) / float(g_iMapShots[id][w]) : 0.0 new Float:lAcc = g_iLifeShots[id][w] > 0 ? 100.0 * float(g_iLifeHits[id][w]) / float(g_iLifeShots[id][w]) : 0.0 client_print(id, print_console, "%-12s map %4d/%4d (%5.1f%%) life %5d/%5d (%5.1f%%)", name, g_iMapHits[id][w], g_iMapShots[id][w], mAcc, g_iLifeHits[id][w], g_iLifeShots[id][w], lAcc) } client_print(id, print_chat, "[CSB] Accuracy table printed to your console.") return PLUGIN_HANDLED } loadPlayer(id) { if (g_iVault == -1) return new auth[36], key[44], data[1536] get_user_authid(id, auth, charsmax(auth)) if (auth[0] == 0 || equal(auth, "STEAM_ID_PENDING")) return formatex(key, charsmax(key), "acc_%s", auth) if (nvault_get(g_iVault, key, data, charsmax(data)) < 1) return /* data: "shots0 hits0 shots1 hits1 ..." */ new pos = 0, w = 0 new val[16] while (w < MAX_WEAPONS) { pos = argparse(data, pos, val, charsmax(val)) if (pos == -1) break g_iLifeShots[id][w] = str_to_num(val) pos = argparse(data, pos, val, charsmax(val)) if (pos == -1) break g_iLifeHits[id][w] = str_to_num(val) 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), "acc_%s", auth) new data[1536], len len = 0 for (new w = 0; w < MAX_WEAPONS; w++) { len += formatex(data[len], charsmax(data) - len, "%d %d ", g_iLifeShots[id][w], g_iLifeHits[id][w]) } nvault_set(g_iVault, key, data) }