AFK Control

AFK Control 12.08.2025

Нет прав для скачивания
Код:
#include <amxmisc>
#include <reapi>
#include <xs>

// Helper macro
#define VALID_PLAYER(%0)          (1 <= %0 <= MaxClients)
#define VALID_TEAM(%0)            (TEAM_TERRORIST <= get_member(%0, m_iTeam) <= TEAM_CT)

#define FStringNull(%0)           (!%0[0] || %0[0] == '0' || strlen(%0) <= 0)
#define IS_MP3(%0)                (equal(%0[strlen(%0) - 4], ".mp3"))
#define CVAR(%0)                  CVAR__%0

enum (<<=1) {
    eNotification_SayText = 1,
    eNotification_Hud,
    eNotification_Sound,
};

enum (<<=1) {
    eReset_Button = 1,
    eReset_Position,
    eReset_Aim,
    eReset_SayText,
    eReset_Menu,
};

// Cvars
new CVAR__SaveTime;
new CVAR__MinPlayers;
new CVAR__AFKPercent;
new CVAR__NotifyTimes;
new CVAR__NotifyIsAFK;
new CVAR__ResetTime;
new CVAR__OnlyKick;
new Float:CVAR__Fragsub;
new CVAR__Notification;
new CVAR__Notify[64];
new CVAR__Alert[64];
new CVAR__AlertStage;
new CVAR__TransferC4;
new CVAR__MoneySave;
new CVAR__ScenarioMode;
new CVAR__ScenarioModeMessages;
new Float:CVAR__MaxTime;
new CVAR__AFKReason[64];

// Forwards
new g_iForward_Stage;
new g_iForward_Action;
new g_iForward_Toggle;
new g_iForward_Return;

// Vars
new g_pAFKFlag;
new Float:g_LastActivity[33], Float:g_SaveActivity[33];
new Float:g_vecOldOrigin[33][3], Float:g_vecOldViewAngle[33][3];
new g_AFKStage[33];

public plugin_natives()
{
    register_native("afkc_is_user_afk", "native_afkc_is_user_afk");
    register_native("afkc_get_stage", "native_afkc_get_stage");
    register_native("afkc_update_activity", "native_afkc_update_activity");
}

public plugin_precache()
{
    register_plugin("AFK Control", "12.08.2025", "@emmajule");
 
    if (!parseINI()) {
        set_fail_state("Something went wrong. Check plugin settings!");
        return;
    }
 
    precache_sound_ex(CVAR(Notify));
    precache_sound_ex(CVAR(Alert));
}

public plugin_init()
{
    register_dictionary("afk_control.txt");
    register_event("TeamInfo", "Event_TeamInfo", "a", "1>0", "2=SPECTATOR");
    // register_menu("Show_AfkControlMenu", 1<<0, "Handle_AfkControlMenu");
    register_clcmd("menuselect", "clcmd_menuselect");
 
    RegisterHookChain(RG_CBasePlayer_PreThink, "CBasePlayer_PreThink", true);
    RegisterHookChain(RG_CBasePlayer_Killed, "CBasePlayer_Killed", false);
    RegisterHookChain(RG_CSGameRules_CheckWinConditions, "CSGameRules_CheckWinConditions", true);
 
    g_iForward_Stage = CreateMultiForward("afkc_stage", ET_IGNORE, FP_CELL, FP_FLOAT, FP_VAL_BYREF);
    g_iForward_Action = CreateMultiForward("afkc_action", ET_CONTINUE, FP_CELL);
    g_iForward_Toggle = CreateMultiForward("afkc_toggle", ET_IGNORE, FP_CELL, FP_CELL);
}

public client_putinserver(id)
{
    g_SaveActivity[id] = g_LastActivity[id] = 0.0;
}

/*
public clcmd_hostsay(id)
{
    g_LastActivity[id] = 0.0;
}
*/

public clcmd_menuselect(id)
{
    if (!(CVAR(ResetTime) & eReset_Menu)) {
        return 0;
    }
 
    g_LastActivity[id] = 0.0;
    return 0;
}

public Event_TeamInfo()
{
    new pClient = read_data(1);
 
    if (!VALID_PLAYER(pClient)) {
        return;
    }
 
    g_SaveActivity[pClient] = g_LastActivity[pClient] = 0.0;
}

public CBasePlayer_Killed(const id, attacker, gib)
{
    if (!CVAR(SaveTime)) {
        g_SaveActivity[id] = get_gametime() - g_LastActivity[id];
    }
 
    g_LastActivity[id] = 0.0;
    return HC_CONTINUE;
}

public CBasePlayer_PreThink(const id)
{
    // Учет живых игроков
    AFK__Think(id);
}

AFK__Think(const id)
{
    if (!is_user_alive(id)) {
        return;
    }
 
    new Float:fGameTime = get_gametime();
    if (CheckActivityInGame(id) || !g_LastActivity[id])
    {
        g_LastActivity[id] = fGameTime;
        // iWarnings = 0;
    }
 
    if (g_SaveActivity[id] > 0.0)
    {
        // g_LastActivity[id] = g_LastActivity[id] - (fGameTime - g_SaveActivity[id]);
        g_LastActivity[id] = g_LastActivity[id] - g_SaveActivity[id];
        g_SaveActivity[id] = 0.0;
    }
 
    new currentStage = g_AFKStage[id];
    new Float:fLastMovement = fGameTime - g_LastActivity[id];
    new AFKStage = min(100, floatround(fLastMovement * 100.0 / CVAR(MaxTime), floatround_tozero));
    new bIsWarning = (AFKStage % (100 / CVAR(NotifyTimes)) == 0);
 
    if (currentStage == AFKStage) {
        return;
    }
    else {
        // g_AFKStage[id] = AFKStage;
    }
 
    // forward afkc_stage(id, Float:lastActivity, &stage)
    ExecuteForward(g_iForward_Stage, _, id, g_LastActivity[id], AFKStage);
 
    if (AFKStage < GetAFKTime() <= currentStage)
    {
        if (!FStringNull(CVAR(Alert)) && currentStage >= CVAR(AlertStage))
        {
            client_cmd(id, ";mp3 stop");
        }
    
        // forward afkc_toggle(id, bool:status)
        ExecuteForward(g_iForward_Toggle, _, id, false);
    }
 
    g_AFKStage[id] = AFKStage;
 
    if (AFKStage <= 0) {
        return;
    }
 
    // Иммунитет от действия плагина
    if (hasAccess(id, g_pAFKFlag) || get_playersnum() < CVAR(MinPlayers))
    {
        g_LastActivity[id] = 0.0;
        return;
    }
 
    if (afkc_is_user_afk(id) && get_member(id, m_bHasC4))
    {
        if (CVAR(TransferC4) == 1 || CVAR(TransferC4) == 2 && !transfer_c4(id))
        {
            rg_drop_item(id, "weapon_c4");
        }
    }
 
    // if (fLastMovement >= CVAR(MaxTime)
    if (AFKStage >= 100)
    {
        // forward afkc_action(id)
        ExecuteForward(g_iForward_Action, g_iForward_Return, id);
    
        if (g_iForward_Return > PLUGIN_CONTINUE)
        {
            // g_LastActivity[id] = 0.0;
            return;
        }
    
        if (CVAR(OnlyKick))
        {
            // AFK_KICK = ^3%n ^1был кикнут с сервера, причина:^4 %s
            client_print_color(0, id, "%l %l", "AFK_PREFIX", "AFK_KICK", id, CVAR(AFKReason));
            kickPlayer(id, CVAR(AFKReason));
            return;
        }
    
        // Перевод в зрители
        set_entvar(id, var_frags, Float:get_entvar(id, var_frags) - CVAR(Fragsub));
        rg_disappear(id);
        rg_join_team(id, TEAM_SPECTATOR);
    
        // AFK_TRANSFER_TO_SPECTATE = ^3%n^1 переведен в наблюдение за простой!
        client_print_color(0, id, "%l %l", "AFK_PREFIX", "AFK_TRANSFER_TO_SPECTATE", id);
    
        if (!CVAR(MoneySave))
        {
            rg_add_account(id, get_cvar_num("mp_startmoney"), AS_SET, false);
        }
    
        if (!FStringNull(CVAR(Alert)))
        {
            client_cmd(id, ";mp3 stop");
        }
    
        // Out of code
        return;
    }
 
    // В этот момент игрок получает статус AFK
    if (AFKStage == GetAFKTime())
    {
        // forward afkc_toggle(id, bool:status)
        ExecuteForward(g_iForward_Toggle, _, id, true);
    
        if (bIsWarning)
        {
        
        }
    
        if (CVAR(ScenarioMode) != 0)
        {
            rg_check_win_conditions();
        }
    }
 
    if (AFKStage == CVAR(AlertStage))
    {
        PlaySoundToClients(id, CVAR(Alert));
    }
 
    // Уведомления
    if (bIsWarning)
    {
        if (CVAR(NotifyIsAFK) && !afkc_is_user_afk(id)) {
            return;
        }
    
        if (CVAR(Notification) & eNotification_SayText)
        {
            // AFK_NOTIFY_CHAT = ^3 Вы не проявляете активность! Через^4 %.0f сек.^3 вы будете кикнуты!
            client_print_color(id, print_team_red, "%l %l", "AFK_PREFIX", "AFK_NOTIFY_CHAT", CVAR(MaxTime) - fLastMovement);
        }
    
        if (CVAR(Notification) & eNotification_Hud)
        {
            // AFK_NOTIFY_HUD = Вы не проявляете активность!^nЧерез %.0f сек. вы будете кикнуты!
            set_hudmessage(0, 200, 200, -1.0, 0.75, 1, 2.0, 3.0, 0.2, 0.4);
            show_hudmessage(id, "%l", "AFK_NOTIFY_HUD", CVAR(MaxTime) - fLastMovement);
        }
    
        if (CVAR(Notification) & eNotification_Sound)
        {
            PlaySoundToClients(id, CVAR(Notify));
        }
    }
}

public CSGameRules_CheckWinConditions()
{
    if (CVAR(ScenarioMode) <= 0) {
        return;
    }
 
    if (get_member_game(m_bRoundTerminating)) {
        return;
    }
 
    new AFKCount[TeamName];
    new TeamName:team;
    // static AFKNotify[TeamName];
    for (new i = MaxClients; i > 0; --i)
    {
        if (!is_user_alive(i)) {
            continue;
        }
    
        team = get_member(i, m_iTeam);
    
        if (afkc_is_user_afk(i))
        {
            AFKCount[team]++;
        }
        else
        {
            //
            AFKCount[team] = -1337;
        }
    }
 
    if (AFKCount[TEAM_TERRORIST] > 0)
    {
        for (new i; i < CVAR(ScenarioModeMessages); i++) {
            // AFK_TERRORIST_LEFT = Все оставшийся игроки команды^3 TERRORIST^1 находятся^4 AFK !!!
            client_print_color(0, print_team_red, "%l %l", "AFK_PREFIX", "AFK_TERRORIST_LEFT", AFKCount[TEAM_TERRORIST]);
        }
    
        if (CVAR(ScenarioMode) == 2) {
            rg_round_end(get_cvar_float("mp_round_restart_delay"), WINSTATUS_CTS, ROUND_CTS_WIN, .trigger = true);
        }
    }
 
    if (AFKCount[TEAM_CT] > 0)
    {
        for (new i; i < CVAR(ScenarioModeMessages); i++) {
            // AFK_CT_LEFT = Все оставшийся игроки команды^3 CT^1 находятся^4 AFK !!!
            client_print_color(0, print_team_blue, "%l %l", "AFK_PREFIX", "AFK_CT_LEFT", AFKCount[TEAM_CT]);
        }
    
        if (CVAR(ScenarioMode) == 2) {
            rg_round_end(get_cvar_float("mp_round_restart_delay"), WINSTATUS_TERRORISTS, ROUND_TERRORISTS_WIN, .trigger = true);
        }
    }
}

transfer_c4(const id)
{
    // new aPlayers[32], iCount;
    // rg_initialize_player_counts(iCount, _, _, _);
    new Float:dist, Float:best_dist;
    new player;
 
    for (new i = MaxClients; i > 0; --i)
    {
        if (i == id) {
            continue;
        }
    
        if (!is_user_alive(i)) {
            continue;
        }
    
        if (get_member(i, m_iTeam) != TEAM_TERRORIST) {
            continue;
        }
    
        if (afkc_is_user_afk(i)) {
            continue;
        }
    
        dist = rg_entity_range(id, i);
        // aPlayers[iCount++] = i;
    
        if (!best_dist || dist < best_dist)
        {
            best_dist = dist;
            player = i;
        }
    }
 
    if (!player) {
        return false;
    }
 
    // SortIntegers(aPlayers, iCount, Sort_Random);
    // new pClient = aPlayers[random_num(0, iCount - 1)];
    return rg_transfer_c4(id, player);
}

parseINI()
{
    new path[PLATFORM_MAX_PATH];
    get_configsdir(path, charsmax(path));
    strcat(path, "/afk_control.ini", charsmax(path));
 
    if (!file_exists(path)) {
        return false;
    }
 
    new INIParser:parser = INI_CreateParser();

    if (parser == Invalid_INIParser) {
        return false;
    }
 
    INI_SetReaders(parser, "INI_Values");
    INI_ParseFile(parser, path);
    INI_DestroyParser(parser);
 
    return true;
}

public bool:INI_Values(INIParser:handle, const key[], const value[])
{
    // server_print("key: %s | value: %s", key, value);
    if (equal(key, "SAVE_TIME"))
    {
        CVAR(SaveTime) = str_to_num(value);
    }
    if (equal(key, "IMMUNITY"))
    {
        // copy(CVAR(Access, 15, value);
        g_pAFKFlag = read_flags_ex(value);
    }
    if (equal(key, "MIN_PLAYERS"))
    {
        CVAR(MinPlayers) = str_to_num(value);
    }
    if (equal(key, "MAX_TIME"))
    {
        CVAR(MaxTime) = str_to_float(value);
    }
    if (equal(key, "AFK_PERCENT"))
    {
        CVAR(AFKPercent) = min(99, str_to_num(value));
    }
    if (equal(key, "WARNINGS"))
    {
        CVAR(NotifyTimes) = str_to_num(value);
    }
    if (equal(key, "WARN_ONLY_IF_AFK"))
    {
        CVAR(NotifyIsAFK) = str_to_num(value);
    }
    if (equal(key, "RESET_TIME"))
    {
        CVAR(ResetTime) = read_flags(value);
    }
    if (equal(key, "KICK"))
    {
        CVAR(OnlyKick) = str_to_num(value);
    }
    if (equal(key, "SUB_FRAG"))
    {
        CVAR(Fragsub) = str_to_float(value);
    }
    if (equal(key, "NOTIFICATION"))
    {
        CVAR(Notification) = read_flags(value);
    }
    if (equal(key, "SAMPLE_NOTIFY"))
    {
        copy(CVAR(Notify), 63, value);
    }
    if (equal(key, "ALERT_NOTIFY") && !FStringNull(value))
    {
        if (!IS_MP3(value))
        {
            server_print("[AFK Control] Только .mp3 формат доступен для настройки ALERT_NOTIFY");
            return true;
        }
    
        copy(CVAR(Alert), 63, value);
    }
    if (equal(key, "ALERT_PERCENT"))
    {
        CVAR(AlertStage) = str_to_num(value);
    }
    if (equal(key, "BOMB_TRANSFER"))
    {
        CVAR(TransferC4) = str_to_num(value);
    }
    if (equal(key, "MONEY_SAVE"))
    {
        CVAR(MoneySave) = str_to_num(value);
    }
    if (equal(key, "SCENARIO_MODE"))
    {
        CVAR(ScenarioMode) = str_to_num(value);
    }
    if (equal(key, "SCENARIO_CHAT_TIMES"))
    {
        CVAR(ScenarioModeMessages) = clamp(str_to_num(value), 1, 5);
    }
    if (equal(key, "AFK_REASON"))
    {
        copy(CVAR(AFKReason), 63, value);
    }
 
    return true;
}

bool:CheckActivityInGame(const id)
{
    new bool:bAFK = true;
    static Float:vecPos[3], Float:vecAngle[3];
    get_entvar(id, var_origin, vecPos);
    get_entvar(id, var_v_angle, vecAngle);
 
    if (CVAR(ResetTime) & eReset_Button)
    {
        if (get_entvar(id, var_button) > 0)
        {
            bAFK = false;
        }
    }
 
    if (CVAR(ResetTime) & eReset_Position)
    {
        if (!xs_vec_equal(vecPos, g_vecOldOrigin[id]))
        {
            bAFK = false;
        }
    }
 
    if (CVAR(ResetTime) & eReset_Aim)
    {
        new Float:deltaPitch = (g_vecOldViewAngle[id][0] - vecAngle[0]);
        new Float:deltaYaw = (g_vecOldViewAngle[id][1] - vecAngle[1]);
    
        if (floatabs(deltaYaw) >= 0.1 && floatabs(deltaPitch) >= 0.1)
        {
            bAFK = false;
        }
    }
 
    if (CVAR(ResetTime) & eReset_SayText)
    {
        if (get_gametime() - Float:get_member(id, m_flLastTalk) < 0.75)
        {
            bAFK = false;
        }
    }
 
    xs_vec_copy(vecPos, g_vecOldOrigin[id]);
    xs_vec_copy(vecAngle, g_vecOldViewAngle[id]);
 
    return !bAFK;
}

stock hasAccess(id, level)
{
    if (level == ADMIN_ALL) {
        return 0;
    }

    // return ((get_user_flags(id) & level) == level);
    return (get_user_flags(id) & level);
}

stock read_flags_ex(const flags[])
{
    if (FStringNull(flags)) {
        return ADMIN_ALL;
    }
 
    return read_flags(flags);
}

stock precache_sound_ex(sound[])
{
    if (FStringNull(sound)) {
        return;
    }
 
    if (IS_MP3(sound))
    {
        precache_generic(sound);
    }
    else
    {
        // precache_sound(sound[6]);
        precache_sound(sound);
    }
}

stock afkc_is_user_afk(const id)
{
    if (g_AFKStage[id] >= GetAFKTime()) {
        return true;
    }
 
    return false;
}

kickPlayer(const id, const reason[] = "")
{
    // rh_drop_client(id, reason);
    server_cmd("kick #%d ^"%s^"", get_user_userid(id), reason);
    server_exec();
}

PlaySoundToClients(const id, const sound[])
{
    if (IS_MP3(sound)) {
        client_cmd(id, "mp3 play ^"%s^"", sound);
    }
    else {
        client_cmd(id, "spk ^"%s^"", sound);
    }
}

GetAFKTime()
{
    new perc = CVAR(AFKPercent);
 
    if (perc <= 0) {
        perc = 100 / CVAR(NotifyTimes);
    }
 
    return perc;
}

// Same as fm_entity_range (fakemeta_util.inc)
stock Float:rg_entity_range(ent1, ent2)
{
    static Float:origin1[3], Float:origin2[3];
    get_entvar(ent1, var_origin, origin1);
    get_entvar(ent2, var_origin, origin2);

    return get_distance_f(origin1, origin2);
}

/**
 * Check if player is afk
 *
 * @param id            Player index
 *
 * @return              true on success, false otherwise
 */
public native_afkc_is_user_afk(plugin, argc)
{
    new id = get_param(1);
 
    if (!VALID_PLAYER(id)) {
        return false;
    }
 
    return afkc_is_user_afk(id);
}

/**
 * Gets the current afk player status stage
 *
 * @param id            Player index
 *
 * @return              AFC stage as a percentage (from 0 to 100)
 */
public native_afkc_get_stage(plugin, argc)
{
    new id = get_param(1);
 
    if (!VALID_PLAYER(id)) {
        return 0;
    }
 
    return g_AFKStage[id];
}

/**
 * Instant update of the player's last activity time
 *
 * @note The second parameter should include local game time (func: get_gametime())
 *       Use 0 to reset activity time
 *
 * @note The activity time is calculated for each new frame of the player
 *
 * @param id            Player index
 * @param time          New time
 *
 * @return              true on success, false otherwise
 */
public native_afkc_update_activity(plugin, argc)
{
    new id = get_param(1);
    new Float:time = get_param_f(2);
 
    if (!VALID_PLAYER(id)) {
        return false;
    }
 
    g_LastActivity[id] = time;
    return true;
}
Назад
Верх