Daemon666 / CSB Stats Web Export Publiczny

Pushes end-of-map player stats to MySQL with one threaded multi-row INSERT for external ladder websites.

v1.0.0 96 pobrań GPL-3.0 Ostatnia aktualizacja Aug 23, 2026
csb_stats_webexport.sma v1.0.0 169 wierszy · 4.7 KB Surowy
1 /*
2 * CSB Stats Web Export
3 * Copyright (C) 2026 counter-strike-boost.com
4 *
5 * Pushes end-of-map player stats into a MySQL table so an external website
6 * can render ladders. At map end (plugin_end) all connected players are
7 * written in a single multi-row INSERT ... ON DUPLICATE KEY UPDATE through a
8 * threaded SQLx query, so the game thread never blocks on the database and
9 * the query count stays at one per map.
10 *
11 * Table layout (create once):
12 * CREATE TABLE csb_webstats (
13 * steamid VARCHAR(40) NOT NULL,
14 * name VARCHAR(32) NOT NULL DEFAULT '',
15 * kills INT NOT NULL DEFAULT 0,
16 * deaths INT NOT NULL DEFAULT 0,
17 * headshots INT NOT NULL DEFAULT 0,
18 * damage INT NOT NULL DEFAULT 0,
19 * seconds INT NOT NULL DEFAULT 0,
20 * PRIMARY KEY (steamid)
21 * );
22 *
23 * This program is free software: you can redistribute it and/or modify it under
24 * the terms of the GNU General Public License as published by the Free Software
25 * Foundation, either version 3 of the License, or (at your option) any later
26 * version. It is distributed in the hope that it will be useful, but WITHOUT
27 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
28 * FOR A PARTICULAR PURPOSE. See <https://www.gnu.org/licenses/> for details.
29 */
30
31 #include <amxmodx>
32 #include <hamsandwich>
33 #include <sqlx>
34
35 new const PLUGIN[] = "CSB Stats Web Export"
36 new const VERSION[] = "1.0.0"
37 new const AUTHOR[] = "counter-strike-boost.com"
38
39 new g_iKills[33]
40 new g_iDeaths[33]
41 new g_iHs[33]
42 new g_iDamage[33]
43 new Float:g_fJoined[33]
44
45 new g_pHost, g_pUser, g_pPass, g_pDb
46
47 public plugin_init()
48 {
49 register_plugin(PLUGIN, VERSION, AUTHOR)
50
51 g_pHost = register_cvar("csb_sql_host", "localhost")
52 g_pUser = register_cvar("csb_sql_user", "amxx")
53 g_pPass = register_cvar("csb_sql_pass", "")
54 g_pDb = register_cvar("csb_sql_db", "csb_stats")
55
56 register_event("DeathMsg", "onDeath", "a")
57 RegisterHam(Ham_TakeDamage, "player", "OnDamage", 1)
58 }
59
60 public client_putinserver(id)
61 {
62 g_iKills[id] = 0
63 g_iDeaths[id] = 0
64 g_iHs[id] = 0
65 g_iDamage[id] = 0
66 g_fJoined[id] = get_gametime()
67 }
68
69 public onDeath()
70 {
71 new killer = read_data(1)
72 new victim = read_data(2)
73 new hs = read_data(3)
74
75 if (victim < 1 || victim > 32)
76 return
77
78 g_iDeaths[victim]++
79
80 if (killer >= 1 && killer <= 32 && killer != victim
81 && get_user_team(killer) != get_user_team(victim))
82 {
83 g_iKills[killer]++
84
85 if (hs)
86 g_iHs[killer]++
87 }
88 }
89
90 public OnDamage(victim, inflictor, attacker, Float:damage, damagetype)
91 {
92 if (attacker < 1 || attacker > 32 || attacker == victim)
93 return HAM_IGNORED
94
95 if (get_user_team(attacker) == get_user_team(victim))
96 return HAM_IGNORED
97
98 g_iDamage[attacker] += floatround(damage)
99
100 return HAM_IGNORED
101 }
102
103 public plugin_end()
104 {
105 exportStats()
106 }
107
108 exportStats()
109 {
110 new host[64], user[64], pass[64], db[64]
111 get_pcvar_string(g_pHost, host, charsmax(host))
112 get_pcvar_string(g_pUser, user, charsmax(user))
113 get_pcvar_string(g_pPass, pass, charsmax(pass))
114 get_pcvar_string(g_pDb, db, charsmax(db))
115
116 new Handle:tuple = SQL_MakeDbTuple(host, user, pass, db)
117
118 static query[4096]
119 new len
120
121 len = formatex(query, charsmax(query),
122 "INSERT INTO csb_webstats (steamid, name, kills, deaths, headshots, damage, seconds) VALUES ")
123
124 new rows = 0
125
126 for (new id = 1; id <= 32; id++)
127 {
128 if (!is_user_connected(id) || is_user_bot(id))
129 continue
130
131 new auth[36], name[32], ename[64]
132 get_user_authid(id, auth, charsmax(auth))
133 get_user_name(id, name, charsmax(name))
134
135 /* cheap escape: single quotes are doubled */
136 copy(ename, charsmax(ename), name)
137 replace_all(ename, charsmax(ename), "'", "''")
138
139 new secs = floatround(get_gametime() - g_fJoined[id])
140
141 len += formatex(query[len], charsmax(query) - len,
142 "%s('%s','%s',%d,%d,%d,%d,%d)",
143 rows > 0 ? "," : "",
144 auth, ename, g_iKills[id], g_iDeaths[id], g_iHs[id], g_iDamage[id], secs)
145
146 rows++
147
148 if (len > charsmax(query) - 200)
149 break
150 }
151
152 if (rows < 1)
153 {
154 SQL_FreeHandle(tuple)
155 return
156 }
157
158 len += formatex(query[len], charsmax(query) - len,
159 " 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)")
160
161 SQL_ThreadQuery(tuple, "onExported", query)
162 }
163
164 public onExported(failstate, Handle:query, error[], errcode, data[], datasize)
165 {
166 if (failstate != TQUERY_SUCCESS)
167 log_amx("[CSB WebExport] query failed (%d): %s", errcode, error)
168 }
169