/*
* CSB Admin Transfer
* Copyright (C) 2026 counter-strike-boost.com
*
* amx_transfer <target> <t|ct|spec> moves a player between teams with
* cs_set_user_team, keeping their frags, and optionally respawns them with
* Ham_CS_RoundRespawn. amx_transfermenu lists players by team. The team score
* is never touched and every move is announced.
*
* Inspired by the team commands in the AMX Mod X Admin Commands plugin.
* Independent GPL re-implementation.
*
* 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 <amxmisc>
#include <cstrike>
#include <hamsandwich>
new const PLUGIN[] = "CSB Admin Transfer"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
new g_pRespawn
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pRespawn = register_cvar("csb_transfer_respawn", "1")
register_concmd("amx_transfer", "cmdTransfer", ADMIN_LEVEL_A, "<target> <t|ct|spec> - move a player to a team")
register_concmd("amx_transfermenu", "cmdMenu", ADMIN_LEVEL_A, "- pick a player and a team from a menu")
}
parseTeam(const arg[], &CsTeams:team)
{
if (equali(arg, "t") || equali(arg, "ter") || equali(arg, "terrorist"))
team = CS_TEAM_T
else if (equali(arg, "ct") || equali(arg, "counter"))
team = CS_TEAM_CT
else if (equali(arg, "spec") || equali(arg, "spectator"))
team = CS_TEAM_SPECTATOR
else
return 0
return 1
}
teamName(CsTeams:team, out[], len)
{
switch (team)
{
case CS_TEAM_T: copy(out, len, "Terrorists")
case CS_TEAM_CT: copy(out, len, "Counter-Terrorists")
case CS_TEAM_SPECTATOR: copy(out, len, "Spectator")
default: copy(out, len, "Unassigned")
}
}
doTransfer(admin, target, CsTeams:team)
{
new CsTeams:cur = cs_get_user_team(target)
if (cur == team)
{
client_print(admin, print_chat, "[CSB] That player is already on that team.")
return
}
/* cs_set_user_team keeps the player's frags and deaths intact */
cs_set_user_team(target, team)
if (team != CS_TEAM_SPECTATOR && get_pcvar_num(g_pRespawn) && is_user_alive(target))
ExecuteHamB(Ham_CS_RoundRespawn, target)
new aname[32], tname[32], tteam[24]
admName(admin, aname, charsmax(aname))
get_user_name(target, tname, charsmax(tname))
teamName(team, tteam, charsmax(tteam))
client_print(0, print_chat, "[CSB] %s moved %s to %s.", aname, tname, tteam)
log_amx("[CSB Transfer] %s moved %s to %s", aname, tname, tteam)
}
admName(id, out[], len)
{
if (id)
get_user_name(id, out, len)
else
copy(out, len, "CONSOLE")
}
public cmdTransfer(id, level, cid)
{
if (!cmd_access(id, level, cid, 3))
return PLUGIN_HANDLED
new arg[32], teamArg[16]
read_argv(1, arg, charsmax(arg))
read_argv(2, teamArg, charsmax(teamArg))
new target = cmd_target(id, arg, CMDTARGET_OBEY_IMMUNITY)
if (!target)
return PLUGIN_HANDLED
new CsTeams:team
if (!parseTeam(teamArg, team))
{
console_print(id, "[CSB] Team must be one of: t, ct, spec.")
return PLUGIN_HANDLED
}
doTransfer(id, target, team)
return PLUGIN_HANDLED
}
public cmdMenu(id, level, cid)
{
if (!cmd_access(id, level, cid, 1))
return PLUGIN_HANDLED
showPlayers(id)
return PLUGIN_HANDLED
}
showPlayers(id)
{
new menu = menu_create("\yTransfer - pick a player", "menuPlayers")
new players[32], num, pid, name[32], info[8], line[64], team[24]
get_players(players, num)
for (new i = 0; i < num; i++)
{
pid = players[i]
get_user_name(pid, name, charsmax(name))
teamName(cs_get_user_team(pid), team, charsmax(team))
num_to_str(pid, info, charsmax(info))
formatex(line, charsmax(line), "%s \r(%s)", name, team)
menu_additem(menu, line, info, 0)
}
menu_display(id, menu, 0)
}
public menuPlayers(id, menu, item)
{
if (item == MENU_EXIT || item < 0)
{
menu_destroy(menu)
return PLUGIN_HANDLED
}
new info[8], name[32], access, callback
menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
menu_destroy(menu)
new target = str_to_num(info)
if (target < 1 || !is_user_connected(target))
{
client_print(id, print_chat, "[CSB] That player left.")
return PLUGIN_HANDLED
}
showTeams(id, target)
return PLUGIN_HANDLED
}
showTeams(id, target)
{
new tname[32]
get_user_name(target, tname, charsmax(tname))
new title[64]
formatex(title, charsmax(title), "\yMove %s to:", tname)
new menu = menu_create(title, "menuTeams")
new info[16]
formatex(info, charsmax(info), "%d t", target)
menu_additem(menu, "\wTerrorists", info, 0)
formatex(info, charsmax(info), "%d ct", target)
menu_additem(menu, "\wCounter-Terrorists", info, 0)
formatex(info, charsmax(info), "%d spec", target)
menu_additem(menu, "\wSpectator", info, 0)
menu_display(id, menu, 0)
}
public menuTeams(id, menu, item)
{
if (item == MENU_EXIT || item < 0)
{
menu_destroy(menu)
return PLUGIN_HANDLED
}
new info[16], name[32], access, callback
menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
menu_destroy(menu)
new targetArg[8], teamArg[8]
strtok(info, targetArg, charsmax(targetArg), teamArg, charsmax(teamArg), ' ')
new target = str_to_num(targetArg)
if (target < 1 || !is_user_connected(target))
{
client_print(id, print_chat, "[CSB] That player left.")
return PLUGIN_HANDLED
}
new CsTeams:team
if (!parseTeam(teamArg, team))
return PLUGIN_HANDLED
doTransfer(id, target, team)
return PLUGIN_HANDLED
}