/*
* CSB Vote Kick
* Copyright (C) 2026 counter-strike-boost.com
*
* Lets players start a vote to kick a named player. A Yes/No menu is shown to
* everyone; when the timer ends the target is kicked if the yes ratio passes a
* cvar. Abuse is limited by a per-starter cooldown (kept in a Trie by SteamID),
* a minimum-player check and admin immunity on the target.
*
* Inspired by community vote-kick plugins (original concept by v3x).
* Independent GPL re-implementation; no original code 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
new const PLUGIN[] = "CSB Vote Kick"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
new g_pEnabled, g_pRatio, g_pCooldown, g_pMinPlayers, g_pDuration
new Trie:g_tCooldown
new bool:g_bVoting = false
new g_iTargetUid = 0
new g_szTargetName[32]
new g_iYes, g_iNo
new bool:g_bVoted[33]
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_vk_enabled", "1")
g_pRatio = register_cvar("csb_vk_ratio", "0.6")
g_pCooldown = register_cvar("csb_vk_cooldown", "300")
g_pMinPlayers = register_cvar("csb_vk_minplayers", "4")
g_pDuration = register_cvar("csb_vk_duration", "20")
register_clcmd("say /votekick", "cmdVoteKick")
register_clcmd("say_team /votekick", "cmdVoteKick")
g_tCooldown = TrieCreate()
}
public plugin_end()
{
TrieDestroy(g_tCooldown)
}
findByName(const needle[])
{
new players[32], num, pid, found = 0, matches = 0
get_players(players, num)
new name[32]
for (new i = 0; i < num; i++)
{
pid = players[i]
get_user_name(pid, name, charsmax(name))
if (containi(name, needle) != -1)
{
found = pid
matches++
}
}
if (matches != 1)
return 0
return found
}
findByUserid(uid)
{
new players[32], num, pid
get_players(players, num)
for (new i = 0; i < num; i++)
{
pid = players[i]
if (get_user_userid(pid) == uid)
return pid
}
return 0
}
public cmdVoteKick(id)
{
if (!get_pcvar_num(g_pEnabled))
return PLUGIN_HANDLED
if (g_bVoting)
{
client_print(id, print_chat, "[CSB] A vote is already running.")
return PLUGIN_HANDLED
}
new args[128]
read_args(args, charsmax(args))
remove_quotes(args)
/* strip the "/votekick" token, keep the rest as the target name */
new name[64]
new pos = contain(args, " ")
if (pos == -1)
{
client_print(id, print_chat, "[CSB] Usage: say /votekick ")
return PLUGIN_HANDLED
}
copy(name, charsmax(name), args[pos + 1])
trim(name)
if (!name[0])
{
client_print(id, print_chat, "[CSB] Usage: say /votekick ")
return PLUGIN_HANDLED
}
new numPlayers = get_playersnum()
if (numPlayers < get_pcvar_num(g_pMinPlayers))
{
client_print(id, print_chat, "[CSB] Not enough players for a vote kick.")
return PLUGIN_HANDLED
}
/* per-starter cooldown by SteamID */
new authid[35]
get_user_authid(id, authid, charsmax(authid))
new nextAllowed
if (TrieGetCell(g_tCooldown, authid, nextAllowed) && get_systime() < nextAllowed)
{
client_print(id, print_chat, "[CSB] You must wait before starting another vote kick.")
return PLUGIN_HANDLED
}
new target = findByName(name)
if (!target)
{
client_print(id, print_chat, "[CSB] No single player matches '%s'.", name)
return PLUGIN_HANDLED
}
if (target == id)
{
client_print(id, print_chat, "[CSB] You cannot vote to kick yourself.")
return PLUGIN_HANDLED
}
if (get_user_flags(target) & ADMIN_IMMUNITY)
{
client_print(id, print_chat, "[CSB] That player is immune to vote kicks.")
return PLUGIN_HANDLED
}
/* start the vote */
g_bVoting = true
g_iTargetUid = get_user_userid(target)
get_user_name(target, g_szTargetName, charsmax(g_szTargetName))
g_iYes = 0
g_iNo = 0
new players[32], num, pid
get_players(players, num, "c")
new title[96]
formatex(title, charsmax(title), "\yKick \r%s\y?^n\wStarted by a player.", g_szTargetName)
for (new i = 0; i < num; i++)
{
pid = players[i]
g_bVoted[pid] = false
new menu = menu_create(title, "voteHandler")
menu_additem(menu, "\wYes, kick", "1", 0)
menu_additem(menu, "\wNo", "0", 0)
menu_setprop(menu, MPROP_EXIT, MEXIT_NEVER)
menu_display(pid, menu, 0)
}
new duration = get_pcvar_num(g_pDuration)
if (duration < 5)
duration = 5
client_print(0, print_chat, "[CSB] Vote started to kick %s. You have %d seconds.", g_szTargetName, duration)
/* set the cooldown now so the starter cannot spam even a failed vote */
new nextTime = get_systime() + get_pcvar_num(g_pCooldown)
TrieSetCell(g_tCooldown, authid, nextTime)
set_task(float(duration), "voteEnd")
return PLUGIN_HANDLED
}
public voteHandler(id, menu, item)
{
if (item == MENU_EXIT || item < 0)
{
menu_destroy(menu)
return PLUGIN_HANDLED
}
new info[4], name[16], access, callback
menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
menu_destroy(menu)
if (!g_bVoting || g_bVoted[id])
return PLUGIN_HANDLED
g_bVoted[id] = true
if (str_to_num(info) == 1)
g_iYes++
else
g_iNo++
return PLUGIN_HANDLED
}
public voteEnd()
{
g_bVoting = false
new total = g_iYes + g_iNo
new Float:ratio = total ? float(g_iYes) / float(total) : 0.0
new target = findByUserid(g_iTargetUid)
if (ratio >= get_pcvar_float(g_pRatio) && g_iYes > 0)
{
client_print(0, print_chat, "[CSB] Vote passed (%d yes / %d no). Kicking %s.", g_iYes, g_iNo, g_szTargetName)
if (target)
{
server_cmd("kick #%d ^"Vote kicked by the players^"", g_iTargetUid)
log_amx("[CSB VoteKick] %s was vote-kicked (%d/%d)", g_szTargetName, g_iYes, g_iNo)
}
}
else
{
client_print(0, print_chat, "[CSB] Vote failed (%d yes / %d no). %s stays.", g_iYes, g_iNo, g_szTargetName)
}
g_iTargetUid = 0
}