/*
* CSB AFK Kicker
* Copyright (C) 2026 counter-strike-boost.com
*
* Detects idle players by watching their buttons and view angles in
* FM_PlayerPreThink, warns them, moves them to spectator and finally kicks them
* when the server is busy enough to need the slot.
*
* Inspired by "AFK Manager" by KRoTaL. 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 for details.
*/
#include
#include
#include
#include
#include
new const PLUGIN[] = "CSB AFK Kicker"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
new g_pEnabled, g_pWarnTime, g_pSpecTime, g_pKickTime, g_pKickSlots, g_pImmunity, g_pGrace
new Float:g_fLastActivity[33]
new Float:g_fJoinTime[33]
new Float:g_fLastAngles[33][3]
new g_iLastButtons[33]
new bool:g_bWarned[33]
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_afk_enabled", "1")
g_pWarnTime = register_cvar("csb_afk_warn", "45") /* seconds idle before the first warning */
g_pSpecTime = register_cvar("csb_afk_spec", "75") /* seconds idle before being moved to spectator */
g_pKickTime = register_cvar("csb_afk_kick", "150") /* seconds idle before being kicked */
g_pKickSlots = register_cvar("csb_afk_kick_minplayers", "12") /* only kick when at least this many players are on */
g_pImmunity = register_cvar("csb_afk_immunity", "a") /* access flag that is never touched */
g_pGrace = register_cvar("csb_afk_grace", "30") /* seconds after connecting that are always ignored */
register_forward(FM_PlayerPreThink, "fwPlayerPreThink")
set_task(2.0, "taskCheckAfk", 0, "", 0, "b")
}
public client_putinserver(id)
{
resetPlayer(id)
}
public client_disconnected(id)
{
resetPlayer(id)
}
resetPlayer(id)
{
g_fLastActivity[id] = get_gametime()
g_fJoinTime[id] = get_gametime()
g_iLastButtons[id] = 0
g_bWarned[id] = false
g_fLastAngles[id][0] = 0.0
g_fLastAngles[id][1] = 0.0
g_fLastAngles[id][2] = 0.0
}
public fwPlayerPreThink(id)
{
if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id))
return FMRES_IGNORED
new buttons = pev(id, pev_button)
new Float:angles[3]
pev(id, pev_v_angle, angles)
if (buttons != g_iLastButtons[id]
|| floatabs(angles[0] - g_fLastAngles[id][0]) > 0.5
|| floatabs(angles[1] - g_fLastAngles[id][1]) > 0.5)
{
g_iLastButtons[id] = buttons
g_fLastAngles[id][0] = angles[0]
g_fLastAngles[id][1] = angles[1]
g_fLastAngles[id][2] = angles[2]
g_fLastActivity[id] = get_gametime()
g_bWarned[id] = false
}
return FMRES_IGNORED
}
public taskCheckAfk()
{
if (!get_pcvar_num(g_pEnabled))
return
new players[32], num, id
get_players(players, num, "ch")
new Float:now = get_gametime()
new Float:grace = float(get_pcvar_num(g_pGrace))
new Float:warn = float(get_pcvar_num(g_pWarnTime))
new Float:spec = float(get_pcvar_num(g_pSpecTime))
new Float:kick = float(get_pcvar_num(g_pKickTime))
new minPlayers = get_pcvar_num(g_pKickSlots)
new flagStr[8]
get_pcvar_string(g_pImmunity, flagStr, charsmax(flagStr))
new immunity = read_flags(flagStr)
for (new i = 0; i < num; i++)
{
id = players[i]
if (immunity && (get_user_flags(id) & immunity))
continue
if (now - g_fJoinTime[id] < grace)
continue
new CsTeams:team = cs_get_user_team(id)
if (team != CS_TEAM_T && team != CS_TEAM_CT)
continue
new Float:idle = now - g_fLastActivity[id]
if (kick > 0.0 && idle >= kick && num >= minPlayers)
{
new name[32]
get_user_name(id, name, charsmax(name))
announce("^x04[CSB]^x03 %s^x01 was kicked for being AFK.", name)
log_amx("[CSB AFK] kicked %s after %d seconds idle", name, floatround(idle))
server_cmd("kick #%d ^"AFK for too long^"", get_user_userid(id))
resetPlayer(id)
continue
}
if (spec > 0.0 && idle >= spec)
{
new name[32]
get_user_name(id, name, charsmax(name))
announce("^x04[CSB]^x03 %s^x01 was moved to spectator (AFK).", name)
log_amx("[CSB AFK] moved %s to spectator after %d seconds idle", name, floatround(idle))
if (is_user_alive(id))
user_kill(id, 1)
cs_set_user_team(id, CS_TEAM_SPECTATOR)
/* keep counting from now so the kick timer is measured from the move */
g_fLastActivity[id] = now
g_bWarned[id] = false
continue
}
if (warn > 0.0 && idle >= warn && !g_bWarned[id])
{
g_bWarned[id] = true
new Float:left = spec - idle
if (left < 1.0)
left = 1.0
client_print(id, print_center, "You are AFK. Move within %d seconds or you will be moved to spectator.", floatround(left))
client_print(id, print_chat, "[CSB] You are AFK. Move within %d seconds or you will be moved to spectator.", floatround(left))
}
}
}
announce(const fmt[], any:...)
{
new buf[192]
vformat(buf, charsmax(buf), fmt, 2)
new players[32], num, msgid = get_user_msgid("SayText")
get_players(players, num, "ch")
for (new i = 0; i < num; i++)
{
message_begin(MSG_ONE, msgid, _, players[i])
write_byte(players[i])
write_string(buf)
message_end()
}
}