/* * CSB Stats Web Export * Copyright (C) 2026 counter-strike-boost.com * * Pushes end-of-map player stats into a MySQL table so an external website * can render ladders. At map end (plugin_end) all connected players are * written in a single multi-row INSERT ... ON DUPLICATE KEY UPDATE through a * threaded SQLx query, so the game thread never blocks on the database and * the query count stays at one per map. * * Table layout (create once): * CREATE TABLE csb_webstats ( * steamid VARCHAR(40) NOT NULL, * name VARCHAR(32) NOT NULL DEFAULT '', * kills INT NOT NULL DEFAULT 0, * deaths INT NOT NULL DEFAULT 0, * headshots INT NOT NULL DEFAULT 0, * damage INT NOT NULL DEFAULT 0, * seconds INT NOT NULL DEFAULT 0, * PRIMARY KEY (steamid) * ); * * 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 Stats Web Export" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" new g_iKills[33] new g_iDeaths[33] new g_iHs[33] new g_iDamage[33] new Float:g_fJoined[33] new g_pHost, g_pUser, g_pPass, g_pDb public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pHost = register_cvar("csb_sql_host", "localhost") g_pUser = register_cvar("csb_sql_user", "amxx") g_pPass = register_cvar("csb_sql_pass", "") g_pDb = register_cvar("csb_sql_db", "csb_stats") register_event("DeathMsg", "onDeath", "a") RegisterHam(Ham_TakeDamage, "player", "OnDamage", 1) } public client_putinserver(id) { g_iKills[id] = 0 g_iDeaths[id] = 0 g_iHs[id] = 0 g_iDamage[id] = 0 g_fJoined[id] = get_gametime() } public onDeath() { new killer = read_data(1) new victim = read_data(2) new hs = read_data(3) if (victim < 1 || victim > 32) return g_iDeaths[victim]++ if (killer >= 1 && killer <= 32 && killer != victim && get_user_team(killer) != get_user_team(victim)) { g_iKills[killer]++ if (hs) g_iHs[killer]++ } } 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 g_iDamage[attacker] += floatround(damage) return HAM_IGNORED } public plugin_end() { exportStats() } exportStats() { new host[64], user[64], pass[64], db[64] get_pcvar_string(g_pHost, host, charsmax(host)) get_pcvar_string(g_pUser, user, charsmax(user)) get_pcvar_string(g_pPass, pass, charsmax(pass)) get_pcvar_string(g_pDb, db, charsmax(db)) new Handle:tuple = SQL_MakeDbTuple(host, user, pass, db) static query[4096] new len len = formatex(query, charsmax(query), "INSERT INTO csb_webstats (steamid, name, kills, deaths, headshots, damage, seconds) VALUES ") new rows = 0 for (new id = 1; id <= 32; id++) { if (!is_user_connected(id) || is_user_bot(id)) continue new auth[36], name[32], ename[64] get_user_authid(id, auth, charsmax(auth)) get_user_name(id, name, charsmax(name)) /* cheap escape: single quotes are doubled */ copy(ename, charsmax(ename), name) replace_all(ename, charsmax(ename), "'", "''") new secs = floatround(get_gametime() - g_fJoined[id]) len += formatex(query[len], charsmax(query) - len, "%s('%s','%s',%d,%d,%d,%d,%d)", rows > 0 ? "," : "", auth, ename, g_iKills[id], g_iDeaths[id], g_iHs[id], g_iDamage[id], secs) rows++ if (len > charsmax(query) - 200) break } if (rows < 1) { SQL_FreeHandle(tuple) return } len += formatex(query[len], charsmax(query) - len, " ON DUPLICATE KEY UPDATE name=VALUES(name), kills=kills+VALUES(kills), deaths=deaths+VALUES(deaths), headshots=headshots+VALUES(headshots), damage=damage+VALUES(damage), seconds=seconds+VALUES(seconds)") SQL_ThreadQuery(tuple, "onExported", query) } public onExported(failstate, Handle:query, error[], errcode, data[], datasize) { if (failstate != TQUERY_SUCCESS) log_amx("[CSB WebExport] query failed (%d): %s", errcode, error) }