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