/* * CSB Team Balance * Copyright (C) 2026 counter-strike-boost.com * * At the end of every round this compares the two team sizes and, if the * difference is larger than a cvar, moves the required number of players from * the bigger team to the smaller one. By default it moves the lowest-scoring * players so the strongest players are left where they are; an optional * skill mode uses frags minus deaths instead of the scoreboard score. * * Inspired by "Auto Team Balance" by Johnny got his gun. This is an * independent GPL re-implementation; no original code is reused. * * 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 the GNU General * Public License for more details: . */ #include #include new const PLUGIN[] = "CSB Team Balance" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" new g_pEnabled, g_pMaxDiff, g_pSkill, g_pImmunity public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_balance_enabled", "1") g_pMaxDiff = register_cvar("csb_balance_maxdiff", "2") g_pSkill = register_cvar("csb_balance_skill", "0") g_pImmunity = register_cvar("csb_balance_immunity", "1") register_logevent("evRoundEnd", 2, "1=Round_End") } public evRoundEnd() { if (!get_pcvar_num(g_pEnabled)) return balance() } balance() { new tPlayers[32], tNum = 0 new ctPlayers[32], ctNum = 0 new players[32], num, pid get_players(players, num, "ch") for (new i = 0; i < num; i++) { pid = players[i] switch (cs_get_user_team(pid)) { case CS_TEAM_T: tPlayers[tNum++] = pid case CS_TEAM_CT: ctPlayers[ctNum++] = pid } } new diff = tNum - ctNum new absdiff = diff < 0 ? -diff : diff if (absdiff <= get_pcvar_num(g_pMaxDiff)) return new toMove = absdiff / 2 if (toMove < 1) return new CsTeams:destTeam new moveList[32], moveCount if (diff > 0) { /* T is bigger */ moveCount = tNum for (new i = 0; i < tNum; i++) moveList[i] = tPlayers[i] destTeam = CS_TEAM_CT } else { moveCount = ctNum for (new i = 0; i < ctNum; i++) moveList[i] = ctPlayers[i] destTeam = CS_TEAM_T } sortByScoreAsc(moveList, moveCount) new moved = 0 for (new i = 0; i < moveCount && moved < toMove; i++) { pid = moveList[i] if (get_pcvar_num(g_pImmunity) && (get_user_flags(pid) & ADMIN_IMMUNITY)) continue switchPlayer(pid, destTeam) moved++ } if (moved > 0) client_print_color(0, print_team_default, "^x04[CSB]^x01 Team Balance: moved^x04 %d^x01 player%s to even the teams.", moved, moved == 1 ? "" : "s") } switchPlayer(id, CsTeams:team) { /* Round has ended, so players are switched now and will respawn on the new team at the start of the next round - no forced death needed. */ cs_set_user_team(id, team) new name[32] get_user_name(id, name, charsmax(name)) log_amx("[CSB Team Balance] moved %s to %s", name, team == CS_TEAM_T ? "TERRORIST" : "CT") } sortByScoreAsc(list[], count) { /* simple selection sort - lists are at most 16 per team */ for (new i = 0; i < count - 1; i++) { new min = i for (new j = i + 1; j < count; j++) { if (scoreOf(list[j]) < scoreOf(list[min])) min = j } if (min != i) { new tmp = list[i] list[i] = list[min] list[min] = tmp } } } scoreOf(id) { if (get_pcvar_num(g_pSkill)) return get_user_frags(id) - cs_get_user_deaths(id) return get_user_frags(id) }