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


/**
 * lw_bot_chaos_pack.sp
 * 
 * Описание: Плагин для TF2, который добавляет ботам:
 * - Дружелюбных хеви (35%) с сэндвичами, которые автоматически кормят врагов
 * - Читерных снайперов (30%) с аимботом, кручением и хэдшотами
 * - Демонайтов (38% от подрывников) с сапогами, щитом и мечом
 * - Танцевальную эпидемию (50% у дружелюбных хеви) с Kazotsky Kick
 * - Панику по шпионам и голосовые команды для всех ботов
 * 
 * Версия: 3.1.0
 */

#pragma semicolon 1
#pragma newdecls required

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

#define PLUGIN_VERSION "3.1.0"

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

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

// Настройки
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];

// Состояния для читерного снайпера
enum CheaterState
{
    STATE_SEARCH,
    STATE_AIMING,
    STATE_SHOOTING
}
CheaterState g_iCheaterState[MAXPLAYERS+1];
float g_fAimStartTime[MAXPLAYERS+1];
float g_fAimDuration[MAXPLAYERS+1];
float g_fLastShotTime[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;

// ID насмешки Kazotsky Kick
#define KAZOTSKY_KICK_TAUNT_ID 1157

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

public void OnPluginStart()
{
    // Регистрация CVars
    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");
    
    // Подписка на изменения CVars
    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))
        {
            OnBotCreated(i);
        }
    }
    
    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);
            }
        }
    }
    
    // Таймер для случайных болталок
    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;
}

// ---------- События ----------
public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast)
{
    int client = GetClientOfUserId(event.GetInt("userid"));
    if (!client || !IsFakeClient(client)) return;
    if (!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;
    
    // Определяем класс бота
    TFClassType class = TF2_GetPlayerClass(client);
    
    // 1. Проверка на дружелюбного хеви (только для Heavy)
    if (class == TFClass_Heavy)
    {
        if (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. Проверка на демонайта (только для Demoman)
    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)
{
    // Сбрасываем всё при начале раунда
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsFakeClient(i))
        {
            g_bIsFriendly[i] = false;
            g_bIsCheaterSniper[i] = false;
            g_bIsDemoknight[i] = false;
            g_bIsInfected[i] = false;
            g_bIsParanoiaCooldown[i] = false;
            g_iCheaterState[i] = STATE_SEARCH;
            g_fLastSandwichThrow[i] = 0.0;
        }
    }
    
    // Останавливаем танцевальную эпидемию
    StopInfection();
}

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)) return;
    if (!g_bEnabled) return;
    if (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)) return;
    if (!g_bEnabled) return;
    if (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;
    g_fLastSandwichThrow[client] = 0.0;
    
    // Удаляем всё оружие
    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);
    
    // Шанс начать танцевальную эпидемию
    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;
    
    // Добавляем хук для управления поведением
    SDKHook(client, SDKHook_PostThink, OnCheaterSniperThink);
    
    if (g_bDebug) PrintToServer("[LW BCP] Бот %N стал читерным снайпером!", client);
}

void MakeDemoknight(int client)
{
    // Убеждаемся, что это подрывник
    TF2_SetPlayerClass(client, TFClass_DemoMan);
    
    // Удаляем всё оружие
    RemoveAllWeapons(client);
    
    // Выдаём Сапоги (Wee Booties)
    int boots = GivePlayerItem(client, "tf_weapon_boots");
    if (boots != -1)
    {
        SetEntProp(boots, Prop_Send, "m_iItemDefinitionIndex", 44);
        EquipPlayerWeapon(client, boots);
    }
    
    // Выдаём Щит (Splendid Screen)
    int shield = GivePlayerItem(client, "tf_weapon_chargable_melee_weapon");
    if (shield != -1)
    {
        SetEntProp(shield, Prop_Send, "m_iItemDefinitionIndex", 131);
        EquipPlayerWeapon(client, shield);
    }
    
    // Выдаём Меч (Eyelander)
    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) return;
    if (!IsClientInGame(client) || !IsPlayerAlive(client)) return;
    if (g_bIsInfectionActive) return;
    
    // Заставляем хеви танцевать
    ForceTaunt(client, KAZOTSKY_KICK_TAUNT_ID);
    
    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 clientPos[3];
    GetClientAbsOrigin(client, clientPos);
    
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i)) continue;
        if (i == client) continue;
        if (g_bIsInfected[i]) continue;
        
        float targetPos[3];
        GetClientAbsOrigin(i, targetPos);
        float dist = GetVectorDistance(clientPos, targetPos);
        
        if (dist <= g_fInfectionRadius)
        {
            g_bIsInfected[i] = true;
            ForceTaunt(i, KAZOTSKY_KICK_TAUNT_ID);
            if (g_bDebug) PrintToServer("[LW BCP] %N заразился танцем от %N", i, client);
            
            // Распространяем дальше
            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)) return Plugin_Stop;
    if (!g_bIsInfectionActive) return Plugin_Stop;
    
    SpreadInfection(client);
    return Plugin_Stop;
}

// ---------- Читерный снайпер ----------
public void OnCheaterSniperThink(int client)
{
    if (!g_bEnabled) return;
    if (!IsClientInGame(client) || !IsPlayerAlive(client)) return;
    if (!g_bIsCheaterSniper[client]) return;
    
    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 angles[3];
                GetClientEyeAngles(client, angles);
                angles[0] = 89.0;
                TeleportEntity(client, NULL_VECTOR, angles, NULL_VECTOR);
                
                if (g_bDebug) PrintToServer("[LW BCP] Снайпер %N начал прицеливание на %N", client, target);
            }
        }
        case STATE_AIMING:
        {
            // Крутимся на месте
            float angles[3];
            GetClientEyeAngles(client, angles);
            angles[1] += GetRandomFloat(-15.0, 15.0);
            angles[0] = 89.0 + GetRandomFloat(-2.0, 2.0);
            TeleportEntity(client, NULL_VECTOR, angles, 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 closestDist = 9999.0;
    float clientPos[3];
    GetClientAbsOrigin(client, clientPos);
    
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i)) continue;
        if (i == client) continue;
        if (GetClientTeam(i) == GetClientTeam(client)) continue;
        if (!IsVisibleTo(client, i)) continue;
        
        float enemyPos[3];
        GetClientAbsOrigin(i, enemyPos);
        float dist = GetVectorDistance(clientPos, enemyPos);
        
        // Приоритет 1: Дружелюбный хеви
        if (g_bIsFriendly[i] && dist < closestDist)
        {
            closestDist = dist;
            target = i;
            continue;
        }
        
        // Приоритет 2: Снайпер (включая других читерных)
        if (TF2_GetPlayerClass(i) == TFClass_Sniper && dist < closestDist)
        {
            closestDist = dist;
            target = i;
        }
    }
    
    return target;
}

void AimAtHeadInstant(int client, int target)
{
    float targetPos[3], clientPos[3];
    GetClientEyePosition(target, targetPos);
    GetClientEyePosition(client, clientPos);
    
    float vecToTarget[3];
    MakeVectorFromPoints(clientPos, targetPos, vecToTarget);
    GetVectorAngles(vecToTarget, vecToTarget);
    
    TeleportEntity(client, NULL_VECTOR, vecToTarget, 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;
}

// ---------- Логика дружелюбного хеви: кормление сэндвичем ----------
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 vecToTarget[3];
    MakeVectorFromPoints(clientPos, targetPos, vecToTarget);
    GetVectorAngles(vecToTarget, vecToTarget);
    TeleportEntity(client, NULL_VECTOR, vecToTarget, NULL_VECTOR);
    
    // Бросаем сэндвич (используем attack2)
    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 clientPos[3];
    GetClientAbsOrigin(client, clientPos);
    int bestTarget = -1;
    float bestDist = radius + 1.0;
    
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i)) continue;
        if (i == client) continue;
        if (GetClientTeam(i) == GetClientTeam(client)) continue;
        
        float targetPos[3];
        GetClientAbsOrigin(i, targetPos);
        float dist = GetVectorDistance(clientPos, targetPos);
        if (dist > radius) continue;
        if (!IsVisibleTo(client, i)) continue;
        
        // Проверяем, смотрит ли враг на хеви (угол обзора 90 градусов)
        if (IsLookingAt(i, client, 90.0))
        {
            if (dist < bestDist)
            {
                bestDist = dist;
                bestTarget = i;
            }
        }
    }
    
    return bestTarget;
}

public Action Timer_StopAttack2(Handle timer, int userid)
{
    int client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client) && IsPlayerAlive(client))
    {
        FakeClientCommand(client, "-attack2");
    }
    return Plugin_Stop;
}

// ---------- Хуки для защиты дружелюбных хеви ----------
public void OnBotCreated(int client)
{
    SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);
    SDKHook(client, SDKHook_PostThink, OnPostThink);
}

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) return Plugin_Continue;
    if (!IsFakeClient(victim)) return Plugin_Continue;
    if (!g_bIsFriendly[victim]) return Plugin_Continue;
    if (!attacker || attacker == victim) return Plugin_Continue;
    if (!IsClientInGame(attacker)) return Plugin_Continue;

    // Проверяем, является ли атакующий ботом
    bool isAttackerBot = IsFakeClient(attacker);
    
    // Если атакующий — бот
    if (isAttackerBot)
    {
        TFClassType attackerClass = TF2_GetPlayerClass(attacker);
        // Только Солдат-бот может наносить урон дружелюбному хеви
        if (attackerClass == TFClass_Soldier)
        {
            return Plugin_Continue; // разрешаем урон
        }
        else
        {
            // Все остальные боты не наносят урон
            if (g_bDebug) PrintToServer("[LW BCP] Дружелюбный хеви %N защищен от бота %N", victim, attacker);
            return Plugin_Handled; // блокируем урон
        }
    }
    else
    {
        // Атакующий — реальный игрок. Разрешаем урон (игрок может стрелять)
        return Plugin_Continue;
    }
}

public void OnPostThink(int client)
{
    if (!g_bEnabled) return;
    if (!IsFakeClient(client)) return;
    if (!IsPlayerAlive(client)) return;
    
    // ----- Логика для дружелюбного Хеви: кормление врагов -----
    if (g_bIsFriendly[client])
    {
        OnFriendlyHeavyThink(client);
        return; // Дружелюбный хеви не участвует в паранойе и других механиках
    }
    
    // ----- Паранойя для всех остальных ботов (кроме читерных снайперов и демонайтов) -----
    if (!g_bIsCheaterSniper[client] && !g_bIsDemoknight[client])
    {
        if (!g_bIsParanoiaCooldown[client] && GetRandomFloat(0.0, 100.0) <= 2.0)
        {
            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"); // "Spy!"
                    g_bIsParanoiaCooldown[client] = true;
                    if (g_bDebug) PrintToServer("[LW BCP] Бот %N параноит и кричит 'Шпион!' на %N", client, target);
                    CreateTimer(GetRandomFloat(5.0, 10.0), Timer_ResetParanoiaCooldown, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
                }
            }
        }
    }
}

public Action Timer_ResetParanoiaCooldown(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);
    }
}

void ForceTaunt(int client, int tauntId)
{
    FakeClientCommand(client, "taunt");
    
    // Для ботов дополнительно устанавливаем анимацию
    if (IsFakeClient(client))
    {
        SetEntProp(client, Prop_Send, "m_nSequence", 15);
    }
}

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 clientPos[3];
    GetClientAbsOrigin(client, clientPos);
    
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsClientInGame(i) || !IsPlayerAlive(i)) continue;
        if (i == client) continue;
        if (GetClientTeam(i) != GetClientTeam(client)) continue;
        
        float targetPos[3];
        GetClientAbsOrigin(i, targetPos);
        if (GetVectorDistance(clientPos, targetPos) <= 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 dotProduct = GetVectorDotProduct(lookVec, vecToTarget);
    float angle = RadToDeg(ArcCosine(dotProduct));
    
    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)) continue;
        if (!IsPlayerAlive(i)) continue;
        
        if (g_bIsFriendly[i])
        {
            // Дружелюбные хеви болтают позитивно
            if (GetRandomFloat(0.0, 100.0) <= 5.0)
            {
                int cmd = GetRandomInt(0, 1);
                if (cmd == 0)
                    FakeClientCommand(i, "voicemenu 0 1"); // "Thanks!"
                else
                    FakeClientCommand(i, "voicemenu 2 0"); // "Yes!"
                if (g_bDebug) PrintToServer("[LW BCP] Дружелюбный хеви %N болтает", i);
            }
        }
        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);
                if (g_bDebug) PrintToServer("[LW BCP] Бот %N болтает (меню %d, команда %d)", i, menu, cmd);
            }
        }
    }
    
    return Plugin_Continue;
}

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