/* * CSB Round Backup * Copyright (C) 2026 counter-strike-boost.com * * Saves a snapshot of the match at the start of every round: the team scores and, * per player, SteamID, team, money and frags. The snapshot is written to a file * under the AMXX data dir. amx_restore reloads that snapshot after a * crash or a disputed round: it re-applies each player's team and money, resends * the scoreboard TeamScore messages and restarts the round. * * 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 #include new const PLUGIN[] = "CSB Round Backup" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" new g_pEnabled, g_pKeep new g_iRound new g_iScoreT, g_iScoreCT new g_szDir[160] public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_backup_enabled", "1") g_pKeep = register_cvar("csb_backup_keep", "30") register_logevent("eventRoundStart", 2, "1=Round_Start") register_event("TeamScore", "eventTeamScore", "a") register_concmd("amx_restore", "cmdRestore", ADMIN_MAP, " - restore a saved round snapshot") register_concmd("amx_backups", "cmdList", ADMIN_MAP, "- show the current round number") get_localinfo("amxx_datadir", g_szDir, charsmax(g_szDir)) if (!g_szDir[0]) copy(g_szDir, charsmax(g_szDir), "addons/amxmodx/data") } pathFor(round, out[], len) { formatex(out, len, "%s/csb_backup_%d.txt", g_szDir, round) } public eventTeamScore() { new team[4], scoreStr[8] read_data(1, team, charsmax(team)) read_data(2, scoreStr, charsmax(scoreStr)) new score = str_to_num(scoreStr) if (team[0] == 'C' || team[0] == 'c') g_iScoreCT = score else g_iScoreT = score } public eventRoundStart() { if (!get_pcvar_num(g_pEnabled)) return g_iRound++ new path[192] pathFor(g_iRound, path, charsmax(path)) new fh = fopen(path, "wt") if (!fh) return fprintf(fh, "SCORE %d %d^n", g_iScoreT, g_iScoreCT) new players[32], num get_players(players, num, "c") for (new i = 0; i < num; i++) { new id = players[i] new authid[35] get_user_authid(id, authid, charsmax(authid)) if (!authid[0] || equal(authid, "BOT")) continue new team = _csTeamInt(id) new money = cs_get_user_money(id) new frags = get_user_frags(id) fprintf(fh, "P %s %d %d %d^n", authid, team, money, frags) } fclose(fh) /* drop the oldest snapshot outside the keep window */ new keep = get_pcvar_num(g_pKeep) if (keep > 0) { new old[192] pathFor(g_iRound - keep, old, charsmax(old)) if (file_exists(old)) delete_file(old) } } _csTeamInt(id) { new CsTeams:t = cs_get_user_team(id) switch (t) { case CS_TEAM_T: return 1 case CS_TEAM_CT: return 2 } return 0 } findByAuth(const authid[]) { new players[32], num get_players(players, num, "c") for (new i = 0; i < num; i++) { new a[35] get_user_authid(players[i], a, charsmax(a)) if (equal(a, authid)) return players[i] } return 0 } public cmdRestore(id, level, cid) { if (!cmd_access(id, level, cid, 2)) return PLUGIN_HANDLED new arg[8] read_argv(1, arg, charsmax(arg)) new round = str_to_num(arg) new path[192] pathFor(round, path, charsmax(path)) new fh = fopen(path, "rt") if (!fh) { console_print(id, "[CSB] No snapshot for round %d.", round) return PLUGIN_HANDLED } new line[128], restored = 0 while (!feof(fh)) { fgets(fh, line, charsmax(line)) trim(line) if (!line[0]) continue if (line[0] == 'S') { new tag[8], t[8], ct[8] parse(line, tag, charsmax(tag), t, charsmax(t), ct, charsmax(ct)) g_iScoreT = str_to_num(t) g_iScoreCT = str_to_num(ct) pushScores() } else if (line[0] == 'P') { new tag[4], authid[35], teamStr[4], moneyStr[10], fragStr[10] parse(line, tag, charsmax(tag), authid, charsmax(authid), teamStr, charsmax(teamStr), moneyStr, charsmax(moneyStr), fragStr, charsmax(fragStr)) new pl = findByAuth(authid) if (pl) { new team = str_to_num(teamStr) if (team == 1) cs_set_user_team(pl, CS_TEAM_T) else if (team == 2) cs_set_user_team(pl, CS_TEAM_CT) cs_set_user_money(pl, str_to_num(moneyStr)) set_user_frags(pl, str_to_num(fragStr)) restored++ } } } fclose(fh) client_print(0, print_chat, "^x04[CSB]^x01 Round %d restored (%d players). Restarting.", round, restored) server_cmd("sv_restart 1") return PLUGIN_HANDLED } pushScores() { new msgid = get_user_msgid("TeamScore") message_begin(MSG_ALL, msgid) write_string("TERRORIST") write_short(g_iScoreT) message_end() message_begin(MSG_ALL, msgid) write_string("CT") write_short(g_iScoreCT) message_end() } public cmdList(id, level, cid) { if (!cmd_access(id, level, cid, 1)) return PLUGIN_HANDLED console_print(id, "[CSB] Current round is %d. Snapshots kept: last %d.", g_iRound, get_pcvar_num(g_pKeep)) return PLUGIN_HANDLED }