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