Загрузка данных
/**
* 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] Плагин выгружен!");
}