/*
* CSB AutoMix 5v5
* Copyright (C) 2026 counter-strike-boost.com
*
* Automatic 5v5 mix. Players type /mix to join a queue; when ten are waiting the
* plugin builds two balanced teams by rank score (a self-declared native, with a
* frags fallback), moves everyone with cs_set_user_team, plays a knife round and
* then starts the live match. If a mix player leaves mid-setup the match is
* paused and the queue re-opened.
*
* Inspired by AutoMix by MaGNuM. 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 for details.
*/
#include
#include
#include
/* optional external ranking; falls back to frags if the module is absent */
native csb_get_rank_score(id)
new const PLUGIN[] = "CSB AutoMix 5v5"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define STATE_IDLE 0
#define STATE_KNIFE 1
#define STATE_LIVE 2
#define NEEDED 10
new g_pEnabled, g_pNeeded
new bool:g_bInMix[33]
new g_iState
new bool:g_bHasRank
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_mix_enabled", "1")
g_pNeeded = register_cvar("csb_mix_needed", "10")
register_clcmd("say /mix", "cmdMix")
register_clcmd("say_team /mix", "cmdMix")
register_clcmd("say /unmix", "cmdUnMix")
register_clcmd("say_team /unmix", "cmdUnMix")
register_event("ResetHUD", "eventSpawn", "b")
register_logevent("eventRoundEnd", 2, "1=Round_End")
g_iState = STATE_IDLE
}
public plugin_cfg()
{
/* detect the optional ranking native once modules are loaded */
g_bHasRank = false
}
rankOf(id)
{
if (g_bHasRank)
return csb_get_rank_score(id)
return get_user_frags(id)
}
public client_disconnected(id)
{
if (!g_bInMix[id])
return
g_bInMix[id] = false
if (g_iState != STATE_IDLE)
{
g_iState = STATE_IDLE
client_print(0, print_chat, "^x04[CSB Mix]^x01 A player left. Match paused, queue re-opened. Type /mix.")
}
}
public cmdMix(id)
{
if (!get_pcvar_num(g_pEnabled))
{
client_print(id, print_chat, "^x04[CSB Mix]^x01 Mix is disabled.")
return PLUGIN_HANDLED
}
if (g_iState != STATE_IDLE)
{
client_print(id, print_chat, "^x04[CSB Mix]^x01 A match is already in progress.")
return PLUGIN_HANDLED
}
if (g_bInMix[id])
{
client_print(id, print_chat, "^x04[CSB Mix]^x01 You are already in the queue.")
return PLUGIN_HANDLED
}
g_bInMix[id] = true
new count = queueCount()
new need = neededCount()
client_print(0, print_chat, "^x04[CSB Mix]^x01 %n joined the mix (%d/%d).", id, count, need)
if (count >= need)
startMix()
return PLUGIN_HANDLED
}
public cmdUnMix(id)
{
if (!g_bInMix[id])
return PLUGIN_HANDLED
g_bInMix[id] = false
client_print(0, print_chat, "^x04[CSB Mix]^x01 %n left the queue.", id)
return PLUGIN_HANDLED
}
neededCount()
{
new n = get_pcvar_num(g_pNeeded)
if (n < 2)
n = 2
if (n > 32)
n = 32
return n
}
queueCount()
{
new c = 0
for (new id = 1; id <= 32; id++)
{
if (g_bInMix[id] && is_user_connected(id))
c++
}
return c
}
startMix()
{
new ids[32], n = 0
for (new id = 1; id <= 32; id++)
{
if (g_bInMix[id] && is_user_connected(id))
ids[n++] = id
}
/* sort by rank score, descending (selection sort) */
for (new i = 0; i < n - 1; i++)
{
new best = i
for (new j = i + 1; j < n; j++)
{
if (rankOf(ids[j]) > rankOf(ids[best]))
best = j
}
if (best != i)
{
new tmp = ids[i]
ids[i] = ids[best]
ids[best] = tmp
}
}
/* snake draft into two teams for balance */
new tScore = 0, ctScore = 0
for (new i = 0; i < n; i++)
{
new id = ids[i]
new sc = rankOf(id)
if (tScore <= ctScore)
{
cs_set_user_team(id, CS_TEAM_T)
tScore += sc
}
else
{
cs_set_user_team(id, CS_TEAM_CT)
ctScore += sc
}
}
g_iState = STATE_KNIFE
client_print(0, print_chat, "^x04[CSB Mix]^x01 Teams are set. ^x03KNIFE ROUND^x01 - winner picks side!")
server_cmd("sv_restart 1")
}
public eventSpawn(id)
{
if (g_iState != STATE_KNIFE || !g_bInMix[id] || !is_user_alive(id))
return
/* knife-only loadout during the knife round */
strip_user_weapons(id)
give_item(id, "weapon_knife")
}
public eventRoundEnd()
{
if (g_iState == STATE_KNIFE)
{
g_iState = STATE_LIVE
client_print(0, print_chat, "^x04[CSB Mix]^x01 Knife round over. ^x03LIVE^x01 - good luck!")
server_cmd("sv_restart 1")
}
}