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