Twój serwer tutaj Licytuj w cotygodniowej aukcji Promuj serwer
csb-stats-webexport / addons / amxmodx / scripting / csb_stats_webexport.sma
168 lines 4.7 KiB Pawn Raw Permalink History
/*
 * 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 <https://www.gnu.org/licenses/> for details.
 */

#include <amxmodx>
#include <hamsandwich>
#include <sqlx>

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