Promotiraj s'rv'r
csb-spinbot-detect / addons / amxmodx / scripting / csb_spinbot_detect.sma
121 lines 3.2 KiB Pawn Raw Permalink History
/*
 * CSB Spinbot Detection
 * Copyright (C) 2026 counter-strike-boost.com
 *
 * Samples pev_v_angle each frame and flags spinbot / anti-aim: a yaw that
 * rotates faster than a human can move the mouse, or a pitch outside the legal
 * -89..89 range, over several sustained frames.
 *
 * 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 <https://www.gnu.org/licenses/> for details.
 */

#include <amxmodx>
#include <fakemeta>

new const PLUGIN[] = "CSB Spinbot Detection"
new const VERSION[] = "1.0.0"
new const AUTHOR[]  = "counter-strike-boost.com"

new g_pEnabled, g_pMaxYaw, g_pStrikes, g_pAction

new Float:g_fLastYaw[33]
new bool:g_bHasLast[33]
new g_iStrikes[33]

public plugin_init()
{
    register_plugin(PLUGIN, VERSION, AUTHOR)

    g_pEnabled = register_cvar("csb_spin_enabled", "1")
    g_pMaxYaw  = register_cvar("csb_spin_maxyaw", "50.0")
    g_pStrikes = register_cvar("csb_spin_strikes", "10")
    g_pAction  = register_cvar("csb_spin_action", "0")

    register_forward(FM_PlayerPreThink, "fwPreThink")
}

public client_putinserver(id)
{
    g_bHasLast[id] = false
    g_iStrikes[id] = 0
}

public fwPreThink(id)
{
    if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id) || is_user_bot(id))
        return FMRES_IGNORED

    new Float:angles[3]
    pev(id, pev_v_angle, angles)

    new bool:bad = false
    new reason[24]

    // legal pitch is clamped to [-89, 89]; anything beyond is a hack angle
    if (angles[0] > 89.5 || angles[0] < -89.5)
    {
        bad = true
        copy(reason, charsmax(reason), "illegal pitch")
    }
    else if (g_bHasLast[id])
    {
        new Float:delta = angles[1] - g_fLastYaw[id]

        // normalise to the shortest signed turn
        while (delta > 180.0)  delta -= 360.0
        while (delta < -180.0) delta += 360.0

        if (delta < 0.0)
            delta = -delta

        if (delta > get_pcvar_float(g_pMaxYaw))
        {
            bad = true
            copy(reason, charsmax(reason), "impossible yaw speed")
        }
    }

    g_fLastYaw[id] = angles[1]
    g_bHasLast[id] = true

    if (bad)
    {
        g_iStrikes[id]++

        if (g_iStrikes[id] >= get_pcvar_num(g_pStrikes))
        {
            flag(id, reason)
            g_iStrikes[id] = 0
        }
    }
    else if (g_iStrikes[id] > 0)
    {
        g_iStrikes[id]--
    }

    return FMRES_IGNORED
}

flag(id, const reason[])
{
    new name[32], authid[35]
    get_user_name(id, name, charsmax(name))
    get_user_authid(id, authid, charsmax(authid))

    log_amx("[CSB Spinbot] %s <%s> flagged: %s", name, authid, reason)

    if (get_pcvar_num(g_pAction) == 1)
    {
        server_cmd("kick #%d ^"CSB: spinbot / anti-aim detected^"", get_user_userid(id))
        server_exec()
    }
    else
    {
        client_print(0, print_chat, "[CSB] %s is suspected of using spinbot / anti-aim (%s).", name, reason)
    }
}