/*
* CSB Anti Flood
* Copyright (C) 2026 counter-strike-boost.com
*
* Rate-limits chat, radio and console command spam per player using a simple
* token bucket for each command class. A player who exceeds the allowed rate
* has the offending command swallowed and is warned; repeated flooding earns a
* short automatic gag so the rest of the server gets some quiet.
*
* Inspired by the classic "Anti Flood" plugins for AMX Mod X. 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
new const PLUGIN[] = "CSB Anti Flood"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
enum _:Bucket
{
B_CHAT = 0,
B_RADIO,
B_CMD,
B_COUNT
}
new g_pEnabled, g_pChatRate, g_pRadioRate, g_pCmdRate, g_pWindow, g_pGagTime, g_pMaxWarn
new g_iCount[33][B_COUNT]
new Float:g_flReset[33][B_COUNT]
new g_iWarnings[33]
new Float:g_flGagUntil[33]
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_flood_enabled", "1")
g_pChatRate = register_cvar("csb_flood_chat", "4") /* say/say_team per window */
g_pRadioRate = register_cvar("csb_flood_radio", "3") /* radio/menu per window */
g_pCmdRate = register_cvar("csb_flood_cmd", "10") /* other console cmds/window */
g_pWindow = register_cvar("csb_flood_window", "3.0") /* window length in seconds */
g_pGagTime = register_cvar("csb_flood_gagtime", "20") /* gag seconds after abuse */
g_pMaxWarn = register_cvar("csb_flood_maxwarn", "4") /* warnings before a gag */
}
public client_putinserver(id)
{
resetPlayer(id)
}
public client_disconnected(id)
{
resetPlayer(id)
}
resetPlayer(id)
{
for (new b = 0; b < B_COUNT; b++)
{
g_iCount[id][b] = 0
g_flReset[id][b] = 0.0
}
g_iWarnings[id] = 0
g_flGagUntil[id] = 0.0
}
public client_command(id)
{
if (!get_pcvar_num(g_pEnabled) || !is_user_connected(id) || is_user_bot(id))
return PLUGIN_CONTINUE
new cmd[24]
read_argv(0, cmd, charsmax(cmd))
new bucket = classify(cmd)
if (bucket == -1)
return PLUGIN_CONTINUE
new Float:now = get_gametime()
/* a still-active gag blocks chat and radio outright */
if (bucket != B_CMD && g_flGagUntil[id] > now)
{
client_print_color(id, print_team_default, "^x04[CSB]^x01 You are muted for flooding (%d s left).",
floatround(g_flGagUntil[id] - now, floatround_ceil))
return PLUGIN_HANDLED
}
new limit = bucketLimit(bucket)
if (limit <= 0)
return PLUGIN_CONTINUE
if (now >= g_flReset[id][bucket])
{
g_iCount[id][bucket] = 0
g_flReset[id][bucket] = now + get_pcvar_float(g_pWindow)
}
g_iCount[id][bucket]++
if (g_iCount[id][bucket] > limit)
{
onFlood(id, bucket)
return PLUGIN_HANDLED
}
return PLUGIN_CONTINUE
}
classify(const cmd[])
{
if (equali(cmd, "say") || equali(cmd, "say_team"))
return B_CHAT
if (equali(cmd, "menuselect") || (equal(cmd, "radio", 5)))
return B_RADIO
/* ignore movement / build-in stuff that the engine spams legitimately */
if (equali(cmd, "fullupdate") || equali(cmd, "nextspawn") || equali(cmd, "spectate")
|| equali(cmd, "jointeam") || equali(cmd, "joinclass") || equali(cmd, "chooseteam"))
return B_CMD
return -1
}
bucketLimit(bucket)
{
switch (bucket)
{
case B_CHAT: return get_pcvar_num(g_pChatRate)
case B_RADIO: return get_pcvar_num(g_pRadioRate)
case B_CMD: return get_pcvar_num(g_pCmdRate)
}
return 0
}
onFlood(id, bucket)
{
g_iWarnings[id]++
new maxWarn = get_pcvar_num(g_pMaxWarn)
if (maxWarn > 0 && g_iWarnings[id] >= maxWarn && bucket != B_CMD)
{
new gag = get_pcvar_num(g_pGagTime)
if (gag > 0)
{
g_flGagUntil[id] = get_gametime() + float(gag)
g_iWarnings[id] = 0
new name[32]
get_user_name(id, name, charsmax(name))
client_print_color(id, print_team_default, "^x04[CSB]^x01 You have been muted for^x04 %d^x01 seconds for flooding.", gag)
log_amx("[CSB Anti Flood] auto-muted %s for %d s", name, gag)
return
}
}
client_print_color(id, print_team_default, "^x04[CSB]^x01 Slow down - you are sending %s too fast.",
bucket == B_CHAT ? "messages" : (bucket == B_RADIO ? "radio commands" : "commands"))
}