/* * CSB Rank System * Copyright (C) 2026 counter-strike-boost.com * * Persistent per-SteamID statistics (kills, deaths, headshots, damage dealt, * rounds played and time on the server) stored in nVault. Stats are loaded when * a client authorises and saved when he leaves or the map ends. A weighted * score turns the raw numbers into a named rank, shown with "say /rank", and a * small set of natives lets other CSB plugins read a player's score and his * position on the current server. * * Inspired by the classic CSStats / rank plugins (e.g. Zhorka's rank system). * This is an independent GPL re-implementation; no original code is 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 new const PLUGIN[] = "CSB Rank System" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define NO_VAULT (-1) enum _:Stats { ST_KILLS, ST_DEATHS, ST_HS, ST_DAMAGE, ST_ROUNDS, ST_TIME /* seconds */ } new g_iStats[33][Stats] new g_iConnect[33] new g_szAuth[33][35] new bool:g_bLoaded[33] new g_iVault = NO_VAULT new const g_szRanks[][] = { "Newbie", "Rookie", "Soldier", "Veteran", "Elite", "Master", "Legend" } new const g_iRankScore[] = { 0, 250, 1000, 3000, 7000, 15000, 30000 } public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) register_event("DeathMsg", "onDeath", "a") register_event("HLTV", "onNewRound", "a", "1=0", "2=0") RegisterHam(Ham_TakeDamage, "player", "fw_TakeDamage") register_clcmd("say /rank", "cmdRank") register_clcmd("say_team /rank", "cmdRank") register_clcmd("say /stats", "cmdRank") } public plugin_natives() { register_native("csb_get_score", "native_get_score") register_native("csb_get_position", "native_get_position") register_native("csb_get_kills", "native_get_kills") } public plugin_cfg() { g_iVault = nvault_open("csb_ranks") if (g_iVault == NO_VAULT) log_amx("[CSB Rank] could not open nVault 'csb_ranks' - stats will not persist.") } public plugin_end() { /* save everyone still on the server, then close the vault */ for (new id = 1; id <= 32; id++) if (g_bLoaded[id]) saveStats(id) if (g_iVault != NO_VAULT) nvault_close(g_iVault) } /* ---------- load / save ---------- */ public client_authorized(id) { resetSlot(id) 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 && g_szAuth[id][0]) { new buffer[64] if (nvault_get(g_iVault, g_szAuth[id], buffer, charsmax(buffer))) unpackStats(id, buffer) } g_iConnect[id] = get_systime() g_bLoaded[id] = true } public client_disconnected(id) { if (g_bLoaded[id]) saveStats(id) resetSlot(id) } saveStats(id) { if (g_iVault == NO_VAULT || !g_szAuth[id][0]) return /* fold the current session's playtime in before writing */ if (g_iConnect[id]) { g_iStats[id][ST_TIME] += get_systime() - g_iConnect[id] g_iConnect[id] = get_systime() } new buffer[64] formatex(buffer, charsmax(buffer), "%d %d %d %d %d %d", g_iStats[id][ST_KILLS], g_iStats[id][ST_DEATHS], g_iStats[id][ST_HS], g_iStats[id][ST_DAMAGE], g_iStats[id][ST_ROUNDS], g_iStats[id][ST_TIME]) nvault_pset(g_iVault, g_szAuth[id], buffer) } resetSlot(id) { for (new i = 0; i < Stats; i++) g_iStats[id][i] = 0 g_iConnect[id] = 0 g_szAuth[id][0] = 0 g_bLoaded[id] = false } unpackStats(id, const buffer[]) { new part[6][12] new count = 0, i = 0, j = 0 while (buffer[i] && count < 6) { if (buffer[i] == ' ') { part[count][j] = 0 count++ j = 0 i++ continue } if (j < charsmax(part[])) part[count][j++] = buffer[i] i++ } if (count < 6) part[count][j] = 0 g_iStats[id][ST_KILLS] = str_to_num(part[0]) g_iStats[id][ST_DEATHS] = str_to_num(part[1]) g_iStats[id][ST_HS] = str_to_num(part[2]) g_iStats[id][ST_DAMAGE] = str_to_num(part[3]) g_iStats[id][ST_ROUNDS] = str_to_num(part[4]) g_iStats[id][ST_TIME] = str_to_num(part[5]) } /* ---------- gameplay hooks ---------- */ public onDeath() { new killer = read_data(1) new victim = read_data(2) new headshot = read_data(3) if (victim >= 1 && victim <= 32 && g_bLoaded[victim]) g_iStats[victim][ST_DEATHS]++ if (killer >= 1 && killer <= 32 && killer != victim && g_bLoaded[killer]) { g_iStats[killer][ST_KILLS]++ if (headshot) g_iStats[killer][ST_HS]++ } } public fw_TakeDamage(victim, inflictor, attacker, Float:damage, damagebits) { if (attacker < 1 || attacker > 32 || attacker == victim || !g_bLoaded[attacker]) return HAM_IGNORED g_iStats[attacker][ST_DAMAGE] += floatround(damage) return HAM_IGNORED } public onNewRound() { for (new id = 1; id <= 32; id++) if (g_bLoaded[id]) g_iStats[id][ST_ROUNDS]++ } /* ---------- score / rank ---------- */ scoreOf(id) { return g_iStats[id][ST_KILLS] * 2 + g_iStats[id][ST_HS] + g_iStats[id][ST_DAMAGE] / 50 + g_iStats[id][ST_ROUNDS] - g_iStats[id][ST_DEATHS] } rankTitle(score, title[], len) { new idx = 0 for (new i = 0; i < sizeof(g_iRankScore); i++) if (score >= g_iRankScore[i]) idx = i copy(title, len, g_szRanks[idx]) } positionOf(id) { new myScore = scoreOf(id) new pos = 1 new players[32], num get_players(players, num, "ch") for (new i = 0; i < num; i++) { new pid = players[i] if (pid == id || !g_bLoaded[pid]) continue if (scoreOf(pid) > myScore) pos++ } return pos } public cmdRank(id) { if (!g_bLoaded[id]) { client_print(id, print_chat, "[CSB] Your stats are not loaded yet.") return PLUGIN_HANDLED } new score = scoreOf(id) new title[24] rankTitle(score, title, charsmax(title)) new online, dummy[32], num get_players(dummy, num, "ch") online = num new mins = g_iStats[id][ST_TIME] / 60 if (g_iConnect[id]) mins += (get_systime() - g_iConnect[id]) / 60 client_print_color(id, print_team_default, "^x04[CSB]^x03 Rank:^x04 %s^x01 - score^x04 %d^x01 - position^x04 %d^x01/%d", title, score, positionOf(id), online) client_print(id, print_chat, "[CSB] Kills: %d | Deaths: %d | HS: %d | Damage: %d | Rounds: %d | Time: %d min", g_iStats[id][ST_KILLS], g_iStats[id][ST_DEATHS], g_iStats[id][ST_HS], g_iStats[id][ST_DAMAGE], g_iStats[id][ST_ROUNDS], mins) return PLUGIN_HANDLED } /* ---------- natives ---------- */ public native_get_score(plugin, params) { new id = get_param(1) if (id < 1 || id > 32 || !g_bLoaded[id]) return 0 return scoreOf(id) } public native_get_position(plugin, params) { new id = get_param(1) if (id < 1 || id > 32 || !g_bLoaded[id]) return 0 return positionOf(id) } public native_get_kills(plugin, params) { new id = get_param(1) if (id < 1 || id > 32 || !g_bLoaded[id]) return 0 return g_iStats[id][ST_KILLS] }