Загрузка данных


/**
 * lw_bot_chaos_pack.sp
 * 
 * Версия: 4.0.0 - Исправлены критические ошибки, оптимизирована производительность.
 */

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>

#define PLUGIN_VERSION "4.0.0"

public Plugin myinfo = 
{
    name = "LW Bot Chaos Pack",
    author = "LW & Community",
    description = "Friendly Heavies, Cheater Snipers, Demoknights & Dance Epidemics!",
    version = PLUGIN_VERSION,
    url = "https://github.com/"
};

// ---------- Глобальные переменные ----------
bool g_bEnabled = true;
bool g_bLateLoaded = false;

// Настройки
float g_fFriendlyHeavyChance = 35.0;
float g_fCheaterSniperChance = 30.0;
float g_fDemoknightChance = 38.0;
float g_fInfectionChance = 50.0;
float g_fInfectionRadius = 500.0;
float g_fInfectionDuration = 40.0;
float g_fSpyChance = 80.0;
float g_fSandwichRange = 400.0;
float g_fSandwichCooldown = 3.0;
bool g_bDebug = false;

// Состояния ботов
bool g_bIsFriendly[MAXPLAYERS+1];
bool g_bIsCheaterSniper[MAXPLAYERS+1];
bool g_bIsDemoknight[MAXPLAYERS+1];
bool g_bIsInfected[MAXPLAYERS+1];
bool g_bIsParanoiaCooldown[MAXPLAYERS+1];
float g_fLastSandwichThrow[MAXPLAYERS+1];
float g_fNextThinkTime[MAXPLAYERS+1]; // для ограничения частоты обработки

// Состояния для читерного снайпера
enum CheaterState
{
    STATE_SEARCH,
    STATE_AIMING,
    STATE_SHOOTING
}
CheaterState g_iCheaterState[MAXPLAYERS+1];
float g_fAimStartTime[MAXPLAYERS+1];
float g_fAimDuration[MAXPLAYERS+1];

// Танцевальная эпидемия
bool g_bIsInfectionActive = false;
Handle g_hInfectionTimer = null;

// Конвары
ConVar g_cvEnabled;
ConVar g_cvFriendlyHeavyChance;
ConVar g_cvCheaterSniperChance;
ConVar g_cvDemoknightChance;
ConVar g_cvInfectionChance;
ConVar g_cvInfectionRadius;
ConVar g_cvInfectionDuration;
ConVar g_cvSpyChance;
ConVar g_cvSandwichRange;
ConVar g_cvSandwichCooldown;
ConVar g_cvDebug;

// ---------- Инициализация ----------
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    g_bLateLoaded = late;
    return APLRes_Success;
}

public void OnPluginStart()
{
    g_cvEnabled = CreateConVar("lw_bvc_enabled", "1", "Включить/выключить плагин (1/0)", 0, true, 0.0, true, 1.0);
    g_cvFriendlyHeavyChance = CreateConVar("lw_bvc_friendly_heavy_chance", "35.0", "Шанс стать дружелюбным хеви (в %)", 0, true, 0.0, true, 100.0);
    g_cvCheaterSniperChance = CreateConVar("lw_bvc_cheater_sniper_chance", "30.0", "Шанс стать читерным снайпером (в %)", 0, true, 0.0, true, 100.0);
    g_cvDemoknightChance = CreateConVar("lw_bvc_demoknight_chance", "38.0", "Шанс стать демонайтом для подрывника (в %)", 0, true, 0.0, true, 100.0);
    g_cvInfectionChance = CreateConVar("lw_bvc_infection_chance", "50.0", "Шанс начать танцевальную эпидемию (в %)", 0, true, 0.0, true, 100.0);
    g_cvInfectionRadius = CreateConVar("lw_bvc_infection_radius", "500.0", "Радиус заражения танцем (в юнитах)", 0, true, 0.0);
    g_cvInfectionDuration = CreateConVar("lw_bvc_infection_duration", "40.0", "Длительность танцевальной эпидемии (в секундах)", 0, true, 0.0);
    g_cvSpyChance = CreateConVar("lw_bvc_spy_chance", "80.0", "Шанс крика 'Шпион!' при обнаружении врага (в %)", 0, true, 0.0, true, 100.0);
    g_cvSandwichRange = CreateConVar("lw_bvc_sandwich_range", "400.0", "Радиус кормления (в юнитах)", 0, true, 0.0);
    g_cvSandwichCooldown = CreateConVar("lw_bvc_sandwich_cooldown", "3.0", "Задержка между бросками (в секундах)", 0, true, 0.0);
    g_cvDebug = CreateConVar("lw_bvc_debug", "0", "Отладка (1/0)", 0, true, 0.0, true, 1.0);

    AutoExecConfig(true, "lw_bot_chaos_pack");

    g_cvEnabled.AddChangeHook(OnConVarChanged);
    g_cvFriendlyHeavyChance.AddChangeHook(OnConVarChanged);
    g_cvCheaterSniperChance.AddChangeHook(OnConVarChanged);
    g_cvDemoknightChance.AddChangeHook(OnConVarChanged);
    g_cvInfectionChance.AddChangeHook(OnConVarChanged);
    g_cvInfectionRadius.AddChangeHook(OnConVarChanged);
    g_cvInfectionDuration.AddChangeHook(OnConVarChanged);
    g_cvSpyChance.AddChangeHook(OnConVarChanged);
    g_cvSandwichRange.AddChangeHook(OnConVarChanged);
    g_cvSandwichCooldown.AddChangeHook(OnConVarChanged);
    g_cvDebug.AddChangeHook(OnConVarChanged);

    HookEvent("player_hurt", Event_PlayerHurt);
    HookEvent("player_healed", Event_PlayerHealed);
    HookEvent("player_spawn", Event_PlayerSpawn);
    HookEvent("round_start", Event_RoundStart);
    HookEvent("round_end", Event_RoundEnd);

    // Единый хук для всех ботов
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsFakeClient(i))
        {
            SDKHook(i, SDKHook_OnTakeDamage, OnTakeDamage);
            SDKHook(i, SDKHook_PostThink, OnPostThink);
        }
    }
    if (g_bLateLoaded)
    {
        for (int i = 1; i <= MaxClients; i++)
        {
            if (IsClientInGame(i) && IsFakeClient(i))
            {
                SDKHook(i, SDKHook_OnTakeDamage, OnTakeDamage);
                SDKHook(i, SDKHook_PostThink, OnPostThink);
            }
        }
    }

    // Основной таймер для обработки всех ботов (раз в 0.5 сек)
    CreateTimer(0.5, Timer_ProcessBots, _, TIMER_REPEAT);

    // Таймер для болталки (раз в секунду)
    CreateTimer(1.0, Timer_IdleChat, _, TIMER_REPEAT);

    PrintToServer("[LW BCP] Плагин загружен! Версия %s", PLUGIN_VERSION);
}

// ---------- Обработчики CVars ----------
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
    g_bEnabled = g_cvEnabled.BoolValue;
    g_fFriendlyHeavyChance = g_cvFriendlyHeavyChance.FloatValue;
    g_fCheaterSniperChance = g_cvCheaterSniperChance.FloatValue;
    g_fDemoknightChance = g_cvDemoknightChance.FloatValue;
    g_fInfectionChance = g_cvInfectionChance.FloatValue;
    g_fInfectionRadius = g_cvInfectionRadius.FloatValue;
    g_fInfectionDuration = g_cvInfectionDuration.FloatValue;
    g_fSpyChance = g_cvSpyChance.FloatValue;
    g_fSandwichRange = g_cvSandwichRange.FloatValue;
    g_fSandwichCooldown = g_cvSandwichCooldown.FloatValue;
    g_bDebug = g_cvDebug.BoolValue;

    // Если плагин отключили — возвращаем ботам нормальное оружие
    if (!g_bEnabled)
    {
        ResetAllBots();
    }
}

// ---------- Сброс оружия у всех ботов (при отключении) ----------
void ResetAllBots()
{
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsFakeClient(i))
        {
            ResetBotWeapons(i);
            g_bIsFriendly[i] = false;
            g_bIsCheaterSniper[i] = false;
            g_bIsDemoknight[i] = false;
        }
    }
}

void ResetBotWeapons(int client)
{
    // Удаляем всё оружие
    RemoveAllWeapons(client);
    // Даём стандартное оружие для класса
    TFClassType class = TF2_GetPlayerClass(client);
    switch (class)
    {
        case TFClass_Scout:    GivePlayerItem(client, "tf_weapon_scattergun");
        case TFClass_Soldier:  GivePlayerItem(client, "tf_weapon_rocketlauncher");
        case TFClass_Pyro:     GivePlayerItem(client, "tf_weapon_flamethrower");
        case TFClass_DemoMan:  GivePlayerItem(client, "tf_weapon_grenadelauncher");
        case TFClass_Heavy:    GivePlayerItem(client, "tf_weapon_minigun");
        case TFClass_Engineer: GivePlayerItem(client, "tf_weapon_shotgun_primary");
        case TFClass_Medic:    GivePlayerItem(client, "tf_weapon_syringegun_medic");
        case TFClass_Sniper:   GivePlayerItem(client, "tf_weapon_sniperrifle");
        case TFClass_Spy:      GivePlayerItem(client, "tf_weapon_revolver");
    }
    // Также даём пистолет/медик-ган и т.д. можно не добавлять, главное чтоб не был безоружным.
}

// ---------- События ----------
public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast)
{
    int client = GetClientOfUserId(event.GetInt("userid"));
    if (!client || !IsFakeClient(client) || !g_bEnabled) return;

    // Сброс состояний
    g_bIsFriendly[client] = false;
    g_bIsCheaterSniper[client] = false;
    g_bIsDemoknight[client] = false;
    g_bIsInfected[client] = false;
    g_bIsParanoiaCooldown[client] = false;
    g_iCheaterState[client] = STATE_SEARCH;
    g_fLastSandwichThrow[client] = 0.0;
    g_fNextThinkTime[client] = 0.0;

    // Снимаем хуки, если были (на всякий случай)
    SDKUnhook(client, SDKHook_PostThink, OnCheaterSniperThink); // если остались старые

    TFClassType class = TF2_GetPlayerClass(client);

    // 1. Дружелюбный хеви
    if (class == TFClass_Heavy && GetRandomFloat(0.0, 100.0) <= g_fFriendlyHeavyChance)
    {
        MakeFriendlyHeavy(client);
        return;
    }

    // 2. Читерный снайпер (любой класс, кроме Heavy)
    if (class != TFClass_Heavy && GetRandomFloat(0.0, 100.0) <= g_fCheaterSniperChance)
    {
        MakeCheaterSniper(client);
        return;
    }

    // 3. Демонайт (только подрывник)
    if (class == TFClass_DemoMan && GetRandomFloat(0.0, 100.0) <= g_fDemoknightChance)
    {
        MakeDemoknight(client);
        return;
    }
}

public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
    StopInfection();
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsFakeClient(i))
        {
            g_bIsInfected[i] = false;
            g_fNextThinkTime[i] = 0.0;
        }
    }
}

public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
    StopInfection();
}

public void Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast)
{
    int victim = GetClientOfUserId(event.GetInt("userid"));
    if (!victim || !IsFakeClient(victim) || !g_bEnabled || g_bIsFriendly[victim]) return;
    if (GetRandomFloat(0.0, 100.0) <= 50.0 && IsPlayerAlive(victim))
    {
        FakeClientCommand(victim, "voicemenu 0 0"); // "Medic!"
        if (g_bDebug) PrintToServer("[LW BCP] Бот %N крикнул 'Medic!'", victim);
    }
}

public void Event_PlayerHealed(Event event, const char[] name, bool dontBroadcast)
{
    int patient = GetClientOfUserId(event.GetInt("patient"));
    if (!patient || !IsFakeClient(patient) || !g_bEnabled || g_bIsFriendly[patient]) return;
    if (IsPlayerAlive(patient))
    {
        FakeClientCommand(patient, "voicemenu 0 1"); // "Thanks!"
        if (g_bDebug) PrintToServer("[LW BCP] Бот %N сказал 'Thanks!'", patient);
    }
}

// ---------- Создание специальных ботов ----------
void MakeFriendlyHeavy(int client)
{
    g_bIsFriendly[client] = true;
    RemoveAllWeapons(client);
    int sandwich = GivePlayerItem(client, "tf_weapon_lunchbox");
    if (sandwich != -1)
    {
        SetEntProp(sandwich, Prop_Send, "m_iItemDefinitionIndex", 42);
        EquipPlayerWeapon(client, sandwich);
    }
    int fists = GivePlayerItem(client, "tf_weapon_fists");
    if (fists != -1)
    {
        SetEntProp(fists, Prop_Send, "m_iItemDefinitionIndex", 0);
        EquipPlayerWeapon(client, fists);
    }
    SetCurrentWeapon(client, fists);
    g_fLastSandwichThrow[client] = 0.0;

    if (GetRandomFloat(0.0, 100.0) <= g_fInfectionChance)
        StartKazotskyInfection(client);

    if (g_bDebug) PrintToServer("[LW BCP] Хеви %N стал дружелюбным!", client);
}

void MakeCheaterSniper(int client)
{
    TF2_SetPlayerClass(client, TFClass_Sniper);
    RemoveAllWeapons(client);
    int rifle = GivePlayerItem(client, "tf_weapon_sniperrifle");
    if (rifle != -1)
    {
        SetEntProp(rifle, Prop_Send, "m_iItemDefinitionIndex", 14);
        EquipPlayerWeapon(client, rifle);
    }
    g_bIsCheaterSniper[client] = true;
    g_iCheaterState[client] = STATE_SEARCH;
    if (g_bDebug) PrintToServer("[LW BCP] Бот %N стал читерным снайпером!", client);
}

void MakeDemoknight(int client)
{
    TF2_SetPlayerClass(client, TFClass_DemoMan);
    RemoveAllWeapons(client);
    int boots = GivePlayerItem(client, "tf_weapon_boots");
    if (boots != -1) { SetEntProp(boots, Prop_Send, "m_iItemDefinitionIndex", 44); EquipPlayerWeapon(client, boots); }
    int shield = GivePlayerItem(client, "tf_weapon_chargable_melee_weapon");
    if (shield != -1) { SetEntProp(shield, Prop_Send, "m_iItemDefinitionIndex", 131); EquipPlayerWeapon(client, shield); }
    int sword = GivePlayerItem(client, "tf_weapon_sword");
    if (sword != -1) { SetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex", 132); EquipPlayerWeapon(client, sword); }
    SetCurrentWeapon(client, sword);
    g_bIsDemoknight[client] = true;
    if (g_bDebug) PrintToServer("[LW BCP] Бот %N стал демонайтом!", client);
}

// ---------- Танцевальная эпидемия (упрощённая) ----------
void StartKazotskyInfection(int client)
{
    if (!g_bEnabled || !IsClientInGame(client) || !IsPlayerAlive(client) || g_bIsInfectionActive) return;
    // Просто заставляем бота сделать насмешку (не Kazotsky, но хоть что-то)
    FakeClientCommand(client, "taunt");
    g_bIsInfectionActive = true;
    g_hInfectionTimer = CreateTimer(g_fInfectionDuration, Timer_StopInfection);
    if (g_bDebug) PrintToServer("[LW BCP] Хеви %N начал эпидемию!", client);
    SpreadInfection(client);
}

void SpreadInfection(int client)
{
    float pos[3]; GetClientAbsOrigin(client, pos);
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i) || i == client || g_bIsInfected[i]) continue;
        float tpos[3]; GetClientAbsOrigin(i, tpos);
        if (GetVectorDistance(pos, tpos) <= g_fInfectionRadius)
        {
            g_bIsInfected[i] = true;
            FakeClientCommand(i, "taunt");
            if (g_bDebug) PrintToServer("[LW BCP] %N заразился!", i);
            CreateTimer(0.5, Timer_SpreadInfection, GetClientUserId(i));
        }
    }
}

void StopInfection()
{
    if (g_hInfectionTimer != null)
    {
        KillTimer(g_hInfectionTimer);
        g_hInfectionTimer = null;
    }
    g_bIsInfectionActive = false;
    for (int i = 1; i <= MaxClients; i++) if (IsClientInGame(i)) g_bIsInfected[i] = false;
    if (g_bDebug) PrintToServer("[LW BCP] Эпидемия остановлена!");
}
public Action Timer_StopInfection(Handle timer) { StopInfection(); return Plugin_Stop; }
public Action Timer_SpreadInfection(Handle timer, int userid)
{
    int client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client) && IsPlayerAlive(client) && g_bIsInfectionActive)
        SpreadInfection(client);
    return Plugin_Stop;
}

// ---------- Основной таймер для обработки всех ботов ----------
public Action Timer_ProcessBots(Handle timer)
{
    if (!g_bEnabled) return Plugin_Continue;
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsFakeClient(i) || !IsPlayerAlive(i)) continue;
        // Обрабатываем только если прошло достаточно времени с последней обработки (не чаще 0.5 сек)
        if (GetGameTime() - g_fNextThinkTime[i] < 0.5) continue;
        g_fNextThinkTime[i] = GetGameTime();

        if (g_bIsFriendly[i])
        {
            OnFriendlyHeavyThink(i);
        }
        else if (g_bIsCheaterSniper[i])
        {
            OnCheaterSniperThink(i);
        }
        else if (g_bIsDemoknight[i])
        {
            // Демонайт пока без специальной логики (можно добавить позже)
        }
    }
    return Plugin_Continue;
}

// ---------- Логика дружелюбного хеви (теперь в таймере) ----------
void OnFriendlyHeavyThink(int client)
{
    if (GetGameTime() - g_fLastSandwichThrow[client] < g_fSandwichCooldown) return;
    int target = FindHungryEnemy(client, g_fSandwichRange);
    if (target == -1) return;

    // Повернуться к цели
    float targetPos[3], clientPos[3];
    GetClientEyePosition(target, targetPos);
    GetClientEyePosition(client, clientPos);
    float vec[3]; MakeVectorFromPoints(clientPos, targetPos, vec);
    float ang[3]; GetVectorAngles(vec, ang);
    TeleportEntity(client, NULL_VECTOR, ang, NULL_VECTOR);

    // Бросок сэндвича
    FakeClientCommand(client, "+attack2");
    CreateTimer(0.2, Timer_StopAttack2, GetClientUserId(client));
    g_fLastSandwichThrow[client] = GetGameTime();
    if (g_bDebug) PrintToServer("[LW BCP] Хеви %N кинул сэндвич в %N", client, target);
}

int FindHungryEnemy(int client, float radius)
{
    float pos[3]; GetClientAbsOrigin(client, pos);
    int best = -1; float bestDist = radius + 1.0;
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i) || i == client || GetClientTeam(i) == GetClientTeam(client)) continue;
        float tpos[3]; GetClientAbsOrigin(i, tpos);
        float dist = GetVectorDistance(pos, tpos);
        if (dist > radius || !IsVisibleTo(client, i)) continue;
        if (IsLookingAt(i, client, 90.0) && dist < bestDist)
        {
            bestDist = dist;
            best = i;
        }
    }
    return best;
}
public Action Timer_StopAttack2(Handle timer, int userid)
{
    int client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client) && IsPlayerAlive(client))
        FakeClientCommand(client, "-attack2");
    return Plugin_Stop;
}

// ---------- Логика читерного снайпера (теперь в таймере) ----------
void OnCheaterSniperThink(int client)
{
    switch (g_iCheaterState[client])
    {
        case STATE_SEARCH:
        {
            int target = FindCheaterSniperTarget(client);
            if (target != -1)
            {
                g_iCheaterState[client] = STATE_AIMING;
                g_fAimStartTime[client] = GetGameTime();
                g_fAimDuration[client] = GetRandomFloat(1.0, 3.0);
                float ang[3]; GetClientEyeAngles(client, ang);
                ang[0] = 89.0;
                TeleportEntity(client, NULL_VECTOR, ang, NULL_VECTOR);
                if (g_bDebug) PrintToServer("[LW BCP] Снайпер %N начал прицел на %N", client, target);
            }
        }
        case STATE_AIMING:
        {
            float ang[3]; GetClientEyeAngles(client, ang);
            ang[1] += GetRandomFloat(-15.0, 15.0);
            ang[0] = 89.0 + GetRandomFloat(-2.0, 2.0);
            TeleportEntity(client, NULL_VECTOR, ang, NULL_VECTOR);
            if (GetGameTime() - g_fAimStartTime[client] >= g_fAimDuration[client])
            {
                g_iCheaterState[client] = STATE_SHOOTING;
                int target = FindCheaterSniperTarget(client);
                if (target != -1)
                {
                    AimAtHeadInstant(client, target);
                    CreateTimer(0.15, Timer_CheaterShot, GetClientUserId(client));
                }
                else
                {
                    g_iCheaterState[client] = STATE_SEARCH;
                }
            }
        }
        case STATE_SHOOTING: { /* ждём таймер */ }
    }
}

int FindCheaterSniperTarget(int client)
{
    int target = -1;
    float bestDist = 9999.0;
    float pos[3]; GetClientAbsOrigin(client, pos);
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i) || i == client || GetClientTeam(i) == GetClientTeam(client)) continue;
        if (!IsVisibleTo(client, i)) continue;
        float tpos[3]; GetClientAbsOrigin(i, tpos);
        float dist = GetVectorDistance(pos, tpos);
        // Приоритет 1: дружелюбный хеви
        if (g_bIsFriendly[i])
        {
            if (dist < bestDist) { bestDist = dist; target = i; }
            // Если нашли хеви, дальше не ищем (это приоритет)
            // Но может быть несколько хеви, выбираем ближайшего
            continue;
        }
        // Приоритет 2: снайпер
        if (TF2_GetPlayerClass(i) == TFClass_Sniper && dist < bestDist)
        {
            bestDist = dist;
            target = i;
        }
    }
    return target;
}

void AimAtHeadInstant(int client, int target)
{
    float targetPos[3], clientPos[3];
    GetClientEyePosition(target, targetPos);
    GetClientEyePosition(client, clientPos);
    float vec[3]; MakeVectorFromPoints(clientPos, targetPos, vec);
    float ang[3]; GetVectorAngles(vec, ang);
    TeleportEntity(client, NULL_VECTOR, ang, NULL_VECTOR);
}

public Action Timer_CheaterShot(Handle timer, int userid)
{
    int client = GetClientOfUserId(userid);
    if (!client || !IsClientInGame(client) || !IsPlayerAlive(client)) return Plugin_Stop;
    FakeClientCommand(client, "+attack");
    CreateTimer(0.1, Timer_StopAttack, userid);
    g_iCheaterState[client] = STATE_SEARCH;
    if (g_bDebug) PrintToServer("[LW BCP] Снайпер %N выстрелил!", client);
    return Plugin_Stop;
}
public Action Timer_StopAttack(Handle timer, int userid)
{
    int client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client) && IsPlayerAlive(client))
        FakeClientCommand(client, "-attack");
    return Plugin_Stop;
}

// ---------- Хуки защиты ----------
public Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom)
{
    if (!g_bEnabled || !IsFakeClient(victim) || !g_bIsFriendly[victim]) return Plugin_Continue;
    if (!attacker || attacker == victim || !IsClientInGame(attacker)) return Plugin_Continue;
    if (IsFakeClient(attacker))
    {
        if (TF2_GetPlayerClass(attacker) != TFClass_Soldier)
        {
            if (g_bDebug) PrintToServer("[LW BCP] Хеви %N защищён от бота %N", victim, attacker);
            return Plugin_Handled;
        }
    }
    // Игроки и солдаты-боты могут наносить урон
    return Plugin_Continue;
}

public void OnPostThink(int client)
{
    // Этот хук используется только для паранойи (один раз в 0.5 сек, но можно оставить)
    // Для экономии мы не будем здесь делать тяжёлых операций.
    if (!g_bEnabled || !IsFakeClient(client) || !IsPlayerAlive(client)) return;
    if (g_bIsFriendly[client] || g_bIsCheaterSniper[client] || g_bIsDemoknight[client]) return;

    // Паранойя: только для обычных ботов, раз в 5-10 секунд
    if (!g_bIsParanoiaCooldown[client] && GetRandomFloat(0.0, 100.0) <= 1.0) // 1% шанс за кадр, но кадр ~0.02 сек -> не слишком часто
    {
        int target = FindRandomTarget(client, 1500.0);
        if (target != -1 && IsLookingAt(client, target, 60.0))
        {
            if (GetRandomFloat(0.0, 100.0) <= 40.0)
            {
                FakeClientCommand(client, "voicemenu 1 1");
                g_bIsParanoiaCooldown[client] = true;
                CreateTimer(GetRandomFloat(5.0, 10.0), Timer_ResetParanoia, GetClientUserId(client));
            }
        }
    }
}
public Action Timer_ResetParanoia(Handle timer, int userid)
{
    int client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client)) g_bIsParanoiaCooldown[client] = false;
    return Plugin_Stop;
}

// ---------- Вспомогательные функции ----------
void RemoveAllWeapons(int client)
{
    for (int i = 0; i < 5; i++)
    {
        int weapon = GetPlayerWeaponSlot(client, i);
        if (weapon != -1)
        {
            RemovePlayerItem(client, weapon);
            AcceptEntityInput(weapon, "Kill");
        }
    }
}
void SetCurrentWeapon(int client, int weapon)
{
    if (weapon != -1) SetEntPropEnt(client, Prop_Send, "m_hActiveWeapon", weapon);
}
bool IsVisibleTo(int client, int target)
{
    float start[3], end[3];
    GetClientEyePosition(client, start);
    GetClientEyePosition(target, end);
    TR_TraceRayFilter(start, end, MASK_VISIBLE, RayType_End, TraceRayFilter, target);
    return !TR_DidHit();
}
public bool TraceRayFilter(int entity, int mask, int data) { return entity != data; }
int FindRandomTarget(int client, float radius)
{
    int targets[MAXPLAYERS]; int count = 0;
    float pos[3]; GetClientAbsOrigin(client, pos);
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i) || i == client || GetClientTeam(i) != GetClientTeam(client)) continue;
        float tpos[3]; GetClientAbsOrigin(i, tpos);
        if (GetVectorDistance(pos, tpos) <= radius) targets[count++] = i;
    }
    if (count == 0) return -1;
    return targets[GetRandomInt(0, count-1)];
}
bool IsLookingAt(int client, int target, float degrees)
{
    float clientEyeAngles[3], clientPos[3], targetPos[3], vecToTarget[3];
    GetClientEyeAngles(client, clientEyeAngles);
    GetClientAbsOrigin(client, clientPos);
    GetClientAbsOrigin(target, targetPos);
    MakeVectorFromPoints(clientPos, targetPos, vecToTarget);
    NormalizeVector(vecToTarget, vecToTarget);
    float lookVec[3];
    GetAngleVectors(clientEyeAngles, lookVec, NULL_VECTOR, NULL_VECTOR);
    NormalizeVector(lookVec, lookVec);
    float dot = GetVectorDotProduct(lookVec, vecToTarget);
    float angle = RadToDeg(ArcCosine(dot));
    return angle <= degrees;
}

// ---------- Таймер болталки ----------
public Action Timer_IdleChat(Handle timer)
{
    if (!g_bEnabled) return Plugin_Continue;
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsFakeClient(i) || !IsPlayerAlive(i)) continue;
        if (g_bIsFriendly[i])
        {
            if (GetRandomFloat(0.0, 100.0) <= 5.0)
            {
                int cmd = GetRandomInt(0, 1);
                FakeClientCommand(i, cmd ? "voicemenu 0 1" : "voicemenu 2 0");
            }
        }
        else if (!g_bIsCheaterSniper[i] && !g_bIsDemoknight[i])
        {
            if (GetRandomFloat(0.0, 100.0) <= 2.0)
            {
                int menu = GetRandomInt(0, 2);
                int cmd = GetRandomInt(0, 4);
                if (menu == 0 && cmd > 4) cmd = 4;
                if (menu == 1 && cmd > 4) cmd = 4;
                if (menu == 2 && cmd > 1) cmd = 1;
                FakeClientCommand(i, "voicemenu %d %d", menu, cmd);
            }
        }
    }
    return Plugin_Continue;
}

// ---------- Завершение ----------
public void OnPluginEnd()
{
    StopInfection();
    ResetAllBots();
    PrintToServer("[LW BCP] Плагин выгружен.");
}