/*
* 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)
}