commit 74ac6c06caa70b28a7de458764f0fd76227e301f Author: deidara Date: Fri May 1 06:57:30 2026 +0300 Initial commit: super-admin-menu plugin with documentation and config diff --git a/README.md b/README.md new file mode 100644 index 0000000..518a8c7 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Super Admin Menu + +Расширенное меню администратора для CS:GO серверов на SourceMod. Открывается одной командой и предоставляет все нужные инструменты управления игроками. + +## Функции + +- Выдача снаряжения (сет с оружием): AK-47, M4A4, M4A1-S, AWP, Deagle Only +- Выдача денег: 3000 / 5000 / 10000 / 16000$ +- Респавн игроков (себе / всем / конкретному игроку) +- Восстановление 100 HP + 100 Armor (только группы DEIDARA / TESTER) +- Невидимость для администратора с сокрытием оружия +- Телепорт к игроку / телепорт игрока к себе +- Телепорт на плент A или B (автоопределение по карте) +- Эффекты на игрока: + - 0 урона при стрельбе + - Супер-отдача + - Drug-эффект (дёргание экрана + случайный FOV) + - Случайный FOV + - Инверсия движения + - Инверсия мыши + - Сильное сотрясение экрана + - Ослепление на 2 секунды +- Все действия логируются в чат онлайн-администраторам + +## Зависимости + +- [SourceMod](https://www.sourcemod.net/) 1.10+ +- [SDKHooks](https://wiki.alliedmods.net/SDK_Hooks) (входит в SourceMod) +- [CS:Strike](https://wiki.alliedmods.net/CSStrike) (входит в SourceMod) + +## Установка + +1. Скомпилировать `scripting/super_admin_menu.sp` +2. Положить `.smx` в `addons/sourcemod/plugins/` +3. Перезапустить сервер или загрузить плагин: `sm plugins load super_admin_menu` + +## Команды + +| Команда | Доступ | Описание | +|---|---|---| +| `!sadmin` / `sm_sadmin` | Root или группа DEIDARA/TESTER | Открыть супер-админ меню | + +## Уровни доступа + +| Уровень | Возможности | +|---|---| +| Root (`ADMFLAG_ROOT`) | Все функции меню | +| Группа **DEIDARA** | Все функции меню | +| Группа **TESTER** | Все функции меню | + +> Базовые функции (сет, деньги, респавн) доступны всем Root-админам. Расширенные функции (невидимость, телепорт, эффекты, восстановление HP) — только группам DEIDARA и TESTER. + +## Версия + +`1.2` — Автор: OpenAI + deidara.dev diff --git a/scripting/super_admin_menu.sp b/scripting/super_admin_menu.sp new file mode 100644 index 0000000..bbc5aca --- /dev/null +++ b/scripting/super_admin_menu.sp @@ -0,0 +1,1337 @@ +#include +#include +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define MENU_TIME MENU_TIME_FOREVER + +int g_iSelectedTargetUserId[MAXPLAYERS + 1]; +bool g_bInvisible[MAXPLAYERS + 1]; +bool g_bZeroDamageAttack[MAXPLAYERS + 1]; +bool g_bSuperRecoil[MAXPLAYERS + 1]; +bool g_bDrugEffect[MAXPLAYERS + 1]; +bool g_bRandomFov[MAXPLAYERS + 1]; +bool g_bInvertMovement[MAXPLAYERS + 1]; +bool g_bInvertMouse[MAXPLAYERS + 1]; +Handle g_hDrugTimer[MAXPLAYERS + 1]; +Handle g_hRandomFovTimer[MAXPLAYERS + 1]; +int g_iEffectTargetUserId[MAXPLAYERS + 1]; + +public Plugin myinfo = +{ + name = "Super Admin Menu", + author = "OpenAI + deidara.dev", + description = "Единое супер-админ меню по команде sm_sadmin", + version = "1.2" +}; + +public void OnPluginStart() +{ + RegConsoleCmd("sm_sadmin", Command_SuperAdminMenu, "Открыть супер-админ меню"); + HookEvent("player_spawn", OnPlayerSpawn, EventHookMode_Post); + HookEvent("weapon_fire", OnWeaponFire, EventHookMode_Post); + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i)) + OnClientPutInServer(i); + } +} + +public void OnClientPutInServer(int client) +{ + SDKHook(client, SDKHook_SetTransmit, Hook_SetTransmit); + SDKHook(client, SDKHook_OnTakeDamage, Hook_OnTakeDamage); + SDKHook(client, SDKHook_OnTakeDamageAlive, Hook_OnTakeDamage); + g_hDrugTimer[client] = null; + g_hRandomFovTimer[client] = null; +} + +public void OnClientDisconnect(int client) +{ + StopDrugEffect(client); + StopRandomFov(client); + g_iSelectedTargetUserId[client] = 0; + g_iEffectTargetUserId[client] = 0; + g_bInvisible[client] = false; + g_bZeroDamageAttack[client] = false; + g_bSuperRecoil[client] = false; + g_bDrugEffect[client] = false; + g_bRandomFov[client] = false; + g_bInvertMovement[client] = false; + g_bInvertMouse[client] = false; +} + + +public void OnPlayerSpawn(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + if (!IsValidClient(client)) + return; + + if (g_bInvisible[client]) + { + SetEntityRenderMode(client, RENDER_TRANSCOLOR); + SetEntityRenderColor(client, 255, 255, 255, 0); + HidePlayerWeapons(client, true); + } + + if (g_bRandomFov[client]) + SetEntProp(client, Prop_Send, "m_iFOV", GetRandomInt(65, 125)); + else if (!g_bDrugEffect[client]) + SetEntProp(client, Prop_Send, "m_iFOV", 90); +} + +public Action Hook_SetTransmit(int entity, int viewer) +{ + if (entity < 1 || entity > MaxClients) + return Plugin_Continue; + + if (!g_bInvisible[entity]) + return Plugin_Continue; + + if (entity == viewer) + return Plugin_Continue; + + return Plugin_Handled; +} + +public Action Command_SuperAdminMenu(int client, int args) +{ + if (!IsValidClient(client)) + { + ReplyToCommand(client, "[SM] Эту команду можно использовать только из игры."); + return Plugin_Handled; + } + + bool hasRootFlag = (GetUserFlagBits(client) & ADMFLAG_ROOT) != 0; + if (!hasRootFlag && !IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] У тебя нет доступа к этому меню."); + return Plugin_Handled; + } + + ShowMainMenu(client); + return Plugin_Handled; +} + +void ShowMainMenu(int client) +{ + Menu menu = new Menu(MenuHandler_Main); + menu.SetTitle("Супер-Админ Меню"); + menu.ExitButton = true; + + bool isDeidara = IsClientInAllowedGroup(client); + + menu.AddItem("sets", "Выдать сет"); + menu.AddItem("money", "Выдать деньги"); + menu.AddItem("respawn", "Респавн"); + + if (isDeidara) + { + menu.AddItem("restore", "Восстановить 100 HP / 100 Armor"); + menu.AddItem("invis", g_bInvisible[client] ? "Выключить невидимость" : "Включить невидимость"); + menu.AddItem("tp_to", "Телепорт к игроку"); + menu.AddItem("tp_here", "Телепортировать игрока к себе"); + menu.AddItem("plant", "Телепорт на плент"); + menu.AddItem("effects", "Эффекты игрока"); + } + + menu.Display(client, MENU_TIME); +} + +public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item) +{ + if (action == MenuAction_End) + { + delete menu; + return 0; + } + + if (action != MenuAction_Select) + return 0; + + char info[32]; + menu.GetItem(item, info, sizeof(info)); + + bool isDeidara = IsClientInAllowedGroup(client); + + if (StrEqual(info, "sets")) + { + ShowTargetMenu(client, "set"); + } + else if (StrEqual(info, "money")) + { + ShowTargetMenu(client, "money"); + } + else if (StrEqual(info, "respawn")) + { + ShowTargetMenu(client, "respawn"); + } + else if (!isDeidara) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + ShowMainMenu(client); + } + else if (StrEqual(info, "restore")) + { + ShowTargetMenu(client, "restore"); + } + else if (StrEqual(info, "invis")) + { + ToggleInvisibility(client); + ShowMainMenu(client); + } + else if (StrEqual(info, "tp_to")) + { + ShowTargetMenu(client, "tp_to"); + } + else if (StrEqual(info, "tp_here")) + { + ShowTargetMenu(client, "tp_here"); + } + else if (StrEqual(info, "plant")) + { + ShowPlantMenu(client); + } + else if (StrEqual(info, "effects")) + { + ShowEffectTargetMenu(client); + } + + return 0; +} + +void ShowTargetMenu(int client, const char[] actionType) +{ + if (!IsClientInAllowedGroup(client) + && (StrEqual(actionType, "restore") || StrEqual(actionType, "tp_to") || StrEqual(actionType, "tp_here"))) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + ShowMainMenu(client); + return; + } + + Menu menu = new Menu(MenuHandler_TargetSelect); + + char title[128]; + Format(title, sizeof(title), "Выбор игрока [%s]", actionType); + menu.SetTitle(title); + menu.ExitButton = true; + menu.ExitBackButton = true; + + char itemInfo[32]; + char name[MAX_NAME_LENGTH]; + int added = 0; + + if (StrEqual(actionType, "restore") || StrEqual(actionType, "respawn") || StrEqual(actionType, "set")) + { + Format(itemInfo, sizeof(itemInfo), "%s:self", actionType); + menu.AddItem(itemInfo, "Себе"); + added++; + } + + if (StrEqual(actionType, "restore") || StrEqual(actionType, "respawn")) + { + Format(itemInfo, sizeof(itemInfo), "%s:all", actionType); + menu.AddItem(itemInfo, "Всем"); + added++; + } + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || IsFakeClient(i)) + continue; + + if (StrEqual(actionType, "set") && !IsPlayerAlive(i)) + continue; + + GetClientName(i, name, sizeof(name)); + Format(itemInfo, sizeof(itemInfo), "%s:%d", actionType, GetClientUserId(i)); + menu.AddItem(itemInfo, name); + added++; + } + + if (added == 0) + { + delete menu; + PrintToChat(client, "[SM] Нет доступных игроков."); + ShowMainMenu(client); + return; + } + + menu.Display(client, MENU_TIME); +} + +public int MenuHandler_TargetSelect(Menu menu, MenuAction action, int client, int item) +{ + if (action == MenuAction_End) + { + delete menu; + return 0; + } + + if (action == MenuAction_Cancel) + { + if (item == MenuCancel_ExitBack) + ShowMainMenu(client); + return 0; + } + + if (action != MenuAction_Select) + return 0; + + char info[32], parts[2][16]; + menu.GetItem(item, info, sizeof(info)); + ExplodeString(info, ":", parts, sizeof(parts), sizeof(parts[])); + + if (StrEqual(parts[0], "set")) + { + if (StrEqual(parts[1], "self")) + g_iSelectedTargetUserId[client] = GetClientUserId(client); + else + g_iSelectedTargetUserId[client] = StringToInt(parts[1]); + ShowSetMenu(client); + } + else if (StrEqual(parts[0], "money")) + { + g_iSelectedTargetUserId[client] = StringToInt(parts[1]); + ShowMoneyMenu(client); + } + else if (StrEqual(parts[0], "restore")) + { + HandleRestoreSelection(client, parts[1]); + } + else if (StrEqual(parts[0], "respawn")) + { + HandleRespawnSelection(client, parts[1]); + } + else if (StrEqual(parts[0], "tp_to")) + { + HandleTeleportToPlayer(client, StringToInt(parts[1])); + } + else if (StrEqual(parts[0], "tp_here")) + { + HandleTeleportPlayerHere(client, StringToInt(parts[1])); + } + + return 0; +} + +void ShowSetMenu(int client) +{ + int target = GetClientOfUserId(g_iSelectedTargetUserId[client]); + if (!IsValidClient(target) || !IsPlayerAlive(target)) + { + PrintToChat(client, "[SM] Игрок недоступен."); + ShowMainMenu(client); + return; + } + + char title[128]; + Format(title, sizeof(title), "Выдать сет игроку: %N", target); + + Menu menu = new Menu(MenuHandler_Set); + menu.SetTitle(title); + menu.ExitButton = true; + menu.ExitBackButton = true; + + menu.AddItem("ak", "AK-47"); + menu.AddItem("m4a4", "M4A4"); + menu.AddItem("m4a1s", "M4A1-S"); + menu.AddItem("awp", "AWP"); + menu.AddItem("pistol", "Deagle Only"); + + menu.Display(client, MENU_TIME); +} + +public int MenuHandler_Set(Menu menu, MenuAction action, int client, int item) +{ + if (action == MenuAction_End) + { + delete menu; + return 0; + } + + if (action == MenuAction_Cancel) + { + if (item == MenuCancel_ExitBack) + ShowTargetMenu(client, "set"); + return 0; + } + + if (action != MenuAction_Select) + return 0; + + int target = GetClientOfUserId(g_iSelectedTargetUserId[client]); + if (!IsValidClient(target) || !IsPlayerAlive(target)) + { + PrintToChat(client, "[SM] Игрок недоступен."); + ShowMainMenu(client); + return 0; + } + + char info[32]; + menu.GetItem(item, info, sizeof(info)); + + GiveFullLoadout(target, info); + PrintToRootAdmins("[SM] %N выдал сет игроку %N.", client, target); + ShowSetMenu(client); + return 0; +} + +void ShowMoneyMenu(int client) +{ + int target = GetClientOfUserId(g_iSelectedTargetUserId[client]); + if (!IsValidClient(target)) + { + PrintToChat(client, "[SM] Игрок недоступен."); + ShowMainMenu(client); + return; + } + + char title[128]; + Format(title, sizeof(title), "Выдать деньги игроку: %N", target); + + Menu menu = new Menu(MenuHandler_Money); + menu.SetTitle(title); + menu.ExitButton = true; + menu.ExitBackButton = true; + + menu.AddItem("3000", "3000$"); + menu.AddItem("5000", "5000$"); + menu.AddItem("10000", "10000$"); + menu.AddItem("16000", "16000$"); + + menu.Display(client, MENU_TIME); +} + +public int MenuHandler_Money(Menu menu, MenuAction action, int client, int item) +{ + if (action == MenuAction_End) + { + delete menu; + return 0; + } + + if (action == MenuAction_Cancel) + { + if (item == MenuCancel_ExitBack) + ShowTargetMenu(client, "money"); + return 0; + } + + if (action != MenuAction_Select) + return 0; + + int target = GetClientOfUserId(g_iSelectedTargetUserId[client]); + if (!IsValidClient(target)) + { + PrintToChat(client, "[SM] Игрок недоступен."); + ShowMainMenu(client); + return 0; + } + + char info[16]; + menu.GetItem(item, info, sizeof(info)); + + int amount = StringToInt(info); + int currentMoney = GetEntProp(target, Prop_Send, "m_iAccount"); + int newMoney = currentMoney + amount; + if (newMoney > 16000) + newMoney = 16000; + + SetEntProp(target, Prop_Send, "m_iAccount", newMoney); + + PrintToRootAdmins("[SM] %N выдал %d$ игроку %N. Теперь у него %d$.", client, amount, target, newMoney); + ShowMoneyMenu(client); + return 0; +} + +void HandleRestoreSelection(int client, const char[] targetInfo) +{ + if (!IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + ShowMainMenu(client); + return; + } + + if (StrEqual(targetInfo, "self")) + { + RestorePlayer(client); + PrintToRootAdmins("[SM] %N восстановил себе 100 HP / 100 Armor.", client); + } + else if (StrEqual(targetInfo, "all")) + { + int restored = 0; + for (int i = 1; i <= MaxClients; i++) + { + if (IsValidClient(i) && IsPlayerAlive(i)) + { + RestorePlayer(i); + restored++; + } + } + PrintToRootAdmins("[SM] %N восстановил всех живых игроков (%d).", client, restored); + } + else + { + int target = GetClientOfUserId(StringToInt(targetInfo)); + if (!IsValidClient(target) || !IsPlayerAlive(target)) + { + PrintToChat(client, "[SM] Игрок недоступен или мертв."); + } + else + { + RestorePlayer(target); + PrintToRootAdmins("[SM] %N восстановил игроку %N 100 HP / 100 Armor.", client, target); + } + } + + ShowMainMenu(client); +} + +void HandleRespawnSelection(int client, const char[] targetInfo) +{ + if (StrEqual(targetInfo, "self")) + { + if (CanRespawnPlayer(client)) + { + CS_RespawnPlayer(client); + PrintToRootAdmins("[SM] %N возродил себя.", client); + } + else + { + PrintToChat(client, "[SM] Вы уже живы или не выбрали команду."); + } + } + else if (StrEqual(targetInfo, "all")) + { + int respawned = 0; + for (int i = 1; i <= MaxClients; i++) + { + if (CanRespawnPlayer(i)) + { + CS_RespawnPlayer(i); + respawned++; + } + } + PrintToRootAdmins("[SM] %N возродил игроков: %d.", client, respawned); + } + else + { + int target = GetClientOfUserId(StringToInt(targetInfo)); + if (!CanRespawnPlayer(target)) + { + PrintToChat(client, "[SM] Игрок недоступен, уже жив или не выбрал команду."); + } + else + { + CS_RespawnPlayer(target); + PrintToRootAdmins("[SM] %N возродил игрока %N.", client, target); + } + } + + ShowMainMenu(client); +} + +void ToggleInvisibility(int client) +{ + if (!IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + return; + } + + if (!IsPlayerAlive(client)) + { + PrintToChat(client, "[SM] Невидимость можно включать только когда ты жив."); + return; + } + + g_bInvisible[client] = !g_bInvisible[client]; + + if (g_bInvisible[client]) + { + SetEntityRenderMode(client, RENDER_TRANSCOLOR); + SetEntityRenderColor(client, 255, 255, 255, 0); + SetEntProp(client, Prop_Send, "m_bSpotted", 0); + HidePlayerWeapons(client, true); + PrintToRootAdmins("[SM] %N включил невидимость.", client); + } + else + { + SetEntityRenderMode(client, RENDER_NORMAL); + SetEntityRenderColor(client, 255, 255, 255, 255); + HidePlayerWeapons(client, false); + PrintToRootAdmins("[SM] %N выключил невидимость.", client); + } +} + +void HidePlayerWeapons(int client, bool hide) +{ + for (int slot = 0; slot <= 4; slot++) + { + int weapon = GetPlayerWeaponSlot(client, slot); + if (weapon != -1 && IsValidEntity(weapon)) + { + SetEntityRenderMode(weapon, RENDER_TRANSCOLOR); + SetEntityRenderColor(weapon, 255, 255, 255, hide ? 0 : 255); + } + } +} + +void HandleTeleportToPlayer(int client, int targetUserId) +{ + if (!IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + return; + } + + int target = GetClientOfUserId(targetUserId); + if (!IsValidClient(target) || !IsPlayerAlive(target) || !IsPlayerAlive(client)) + { + PrintToChat(client, "[SM] Телепорт невозможен. Ты или выбранный игрок мертвы/недоступны."); + ShowMainMenu(client); + return; + } + + float pos[3], ang[3], fwdVec[3], flatAng[3]; + GetClientAbsOrigin(target, pos); + GetClientAbsAngles(target, ang); + + // Позиция: 20 юнитов перед целью (по горизонтали) + flatAng[0] = 0.0; flatAng[1] = ang[1]; flatAng[2] = 0.0; + GetAngleVectors(flatAng, fwdVec, NULL_VECTOR, NULL_VECTOR); + pos[0] += fwdVec[0] * 20.0; + pos[1] += fwdVec[1] * 20.0; + + // Смотрим на цель (разворот на 180°) + float faceAng[3]; + faceAng[0] = 0.0; + faceAng[1] = ang[1] + 180.0; + if (faceAng[1] > 180.0) faceAng[1] -= 360.0; + if (faceAng[1] <= -180.0) faceAng[1] += 360.0; + faceAng[2] = 0.0; + + TeleportEntity(client, pos, faceAng, NULL_VECTOR); + PrintToRootAdmins("[SM] %N телепортировался к игроку %N.", client, target); + ShowMainMenu(client); +} + +void HandleTeleportPlayerHere(int client, int targetUserId) +{ + if (!IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + return; + } + + int target = GetClientOfUserId(targetUserId); + if (!IsValidClient(target) || !IsPlayerAlive(target) || !IsPlayerAlive(client)) + { + PrintToChat(client, "[SM] Телепорт невозможен. Ты или выбранный игрок мертвы/недоступны."); + ShowMainMenu(client); + return; + } + + float pos[3], ang[3], fwdVec[3], flatAng[3]; + GetClientAbsOrigin(client, pos); + GetClientAbsAngles(client, ang); + + // Позиция: 20 юнитов перед собой (по горизонтали) + flatAng[0] = 0.0; flatAng[1] = ang[1]; flatAng[2] = 0.0; + GetAngleVectors(flatAng, fwdVec, NULL_VECTOR, NULL_VECTOR); + pos[0] += fwdVec[0] * 20.0; + pos[1] += fwdVec[1] * 20.0; + + // Игрок смотрит на нас (разворот на 180°) + float faceAng[3]; + faceAng[0] = 0.0; + faceAng[1] = ang[1] + 180.0; + if (faceAng[1] > 180.0) faceAng[1] -= 360.0; + if (faceAng[1] <= -180.0) faceAng[1] += 360.0; + faceAng[2] = 0.0; + + TeleportEntity(target, pos, faceAng, NULL_VECTOR); + PrintToRootAdmins("[SM] %N телепортировал игрока %N к себе.", client, target); + ShowMainMenu(client); +} + +void ShowPlantMenu(int client) +{ + if (!IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + ShowMainMenu(client); + return; + } + + Menu menu = new Menu(MenuHandler_Plant); + menu.SetTitle("Телепорт на плент"); + menu.ExitButton = true; + menu.ExitBackButton = true; + + menu.AddItem("A", "Плент A"); + menu.AddItem("B", "Плент B"); + menu.Display(client, MENU_TIME); +} + +public int MenuHandler_Plant(Menu menu, MenuAction action, int client, int item) +{ + if (action == MenuAction_End) + { + delete menu; + return 0; + } + + if (action == MenuAction_Cancel) + { + if (item == MenuCancel_ExitBack) + ShowMainMenu(client); + return 0; + } + + if (action != MenuAction_Select) + return 0; + + char info[8]; + menu.GetItem(item, info, sizeof(info)); + + if (!IsPlayerAlive(client)) + { + PrintToChat(client, "[SM] Для телепорта на плент нужно быть живым."); + ShowMainMenu(client); + return 0; + } + + float origin[3]; + if (FindBombsiteCenter(info[0], origin)) + { + origin[2] += 10.0; + TeleportEntity(client, origin, NULL_VECTOR, NULL_VECTOR); + PrintToRootAdmins("[SM] %N телепортировался на плент %s.", client, info); + } + else + { + PrintToChat(client, "[SM] Плент %s не найден на этой карте.", info); + } + + ShowMainMenu(client); + return 0; +} + +// Проверяет, совпадает ли имя сущности с нужным плентом (A или B) +bool IsBombsiteNameMatch(int entity, char siteLetter) +{ + char name[64]; + GetEntPropString(entity, Prop_Data, "m_iName", name, sizeof(name)); + if (name[0] == '\0') + return false; + + char lo[2]; + lo[0] = CharToLower(siteLetter); + lo[1] = '\0'; + + char pat1[32], pat2[32], pat3[32], pat4[32]; + Format(pat1, sizeof(pat1), "bombsite_%s", lo); // bombsite_a / bombsite_b + Format(pat2, sizeof(pat2), "bombsite%s", lo); // bombsitea / bombsiteb + Format(pat3, sizeof(pat3), "site_%s", lo); // site_a / site_b + Format(pat4, sizeof(pat4), "bomb_%s", lo); // bomb_a / bomb_b + + return (StrContains(name, pat1, false) != -1 + || StrContains(name, pat2, false) != -1 + || StrContains(name, pat3, false) != -1 + || StrContains(name, pat4, false) != -1); +} + +// Центр brush-сущности через абсолютный origin + локальные bounds +void GetBrushCenter(int entity, float out[3]) +{ + float absOrigin[3], mins[3], maxs[3]; + GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", absOrigin); + GetEntPropVector(entity, Prop_Send, "m_vecMins", mins); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", maxs); + out[0] = absOrigin[0] + (mins[0] + maxs[0]) / 2.0; + out[1] = absOrigin[1] + (mins[1] + maxs[1]) / 2.0; + out[2] = absOrigin[2] + maxs[2]; // верхняя граница, игрок встанет сверху +} + +bool FindBombsiteCenter(char siteLetter, float origin[3]) +{ + // Проход 1: func_bomb_target (brush) с совпадением имени + int entity = -1; + while ((entity = FindEntityByClassname(entity, "func_bomb_target")) != -1) + { + if (IsBombsiteNameMatch(entity, siteLetter)) + { + GetBrushCenter(entity, origin); + return true; + } + } + + // Проход 2: info_bomb_target (point entity) с совпадением имени + entity = -1; + while ((entity = FindEntityByClassname(entity, "info_bomb_target")) != -1) + { + if (IsBombsiteNameMatch(entity, siteLetter)) + { + GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", origin); + return true; + } + } + + // Проход 3: безымянный/нестандартный — берём первый/второй по порядку + // A = первый найденный, B = второй найденный + entity = -1; + int found = 0; + int target = (siteLetter == 'A') ? 1 : 2; + while ((entity = FindEntityByClassname(entity, "func_bomb_target")) != -1) + { + found++; + if (found == target) + { + GetBrushCenter(entity, origin); + return true; + } + } + + return false; +} + +void RestorePlayer(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + return; + + SetEntityHealth(client, 100); + SetEntProp(client, Prop_Send, "m_ArmorValue", 100); + SetEntProp(client, Prop_Send, "m_bHasHelmet", 1); +} + +bool CanRespawnPlayer(int client) +{ + return IsValidClient(client) && !IsPlayerAlive(client) && GetClientTeam(client) >= CS_TEAM_T; +} + +void GiveFullLoadout(int client, const char[] type) +{ + StripWeapons(client); + + SetEntProp(client, Prop_Send, "m_ArmorValue", 100); + SetEntProp(client, Prop_Send, "m_bHasHelmet", 1); + + if (GetClientTeam(client) == CS_TEAM_CT) + SetEntProp(client, Prop_Send, "m_bHasDefuser", 1); + + if (StrEqual(type, "ak")) + GivePlayerItem(client, "weapon_ak47"); + else if (StrEqual(type, "m4a4")) + GivePlayerItem(client, "weapon_m4a1"); + else if (StrEqual(type, "m4a1s")) + GivePlayerItem(client, "weapon_m4a1_silencer"); + else if (StrEqual(type, "awp")) + GivePlayerItem(client, "weapon_awp"); + + GivePlayerItem(client, "weapon_deagle"); + GivePlayerItem(client, "weapon_knife"); + + if (!StrEqual(type, "pistol")) + { + GivePlayerItem(client, "weapon_hegrenade"); + GivePlayerItem(client, "weapon_smokegrenade"); + GivePlayerItem(client, "weapon_flashbang"); + GivePlayerItem(client, "weapon_flashbang"); + + if (GetClientTeam(client) == CS_TEAM_T) + GivePlayerItem(client, "weapon_molotov"); + else + GivePlayerItem(client, "weapon_incgrenade"); + } + + if (g_bInvisible[client]) + HidePlayerWeapons(client, true); +} + +void StripWeapons(int client) +{ + int weapon; + for (int slot = 0; slot <= 4; slot++) + { + while ((weapon = GetPlayerWeaponSlot(client, slot)) != -1) + { + RemovePlayerItem(client, weapon); + AcceptEntityInput(weapon, "Kill"); + } + } +} + + + +void ShowEffectTargetMenu(int client) +{ + if (!IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + ShowMainMenu(client); + return; + } + + Menu menu = new Menu(MenuHandler_EffectTarget); + menu.SetTitle("Эффекты: выбор игрока"); + menu.ExitButton = true; + menu.ExitBackButton = true; + + char info[16]; + char name[MAX_NAME_LENGTH]; + int added = 0; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || IsFakeClient(i)) + continue; + + GetClientName(i, name, sizeof(name)); + IntToString(GetClientUserId(i), info, sizeof(info)); + menu.AddItem(info, name); + added++; + } + + if (added == 0) + { + delete menu; + PrintToChat(client, "[SM] Нет доступных игроков."); + ShowMainMenu(client); + return; + } + + menu.Display(client, MENU_TIME); +} + +public int MenuHandler_EffectTarget(Menu menu, MenuAction action, int client, int item) +{ + if (!IsClientInAllowedGroup(client)) + { + if (action == MenuAction_End) + delete menu; + else if (action == MenuAction_Select) + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + return 0; + } + + if (action == MenuAction_End) + { + delete menu; + return 0; + } + + if (action == MenuAction_Cancel) + { + if (item == MenuCancel_ExitBack) + ShowMainMenu(client); + return 0; + } + + if (action != MenuAction_Select) + return 0; + + char info[16]; + menu.GetItem(item, info, sizeof(info)); + g_iEffectTargetUserId[client] = StringToInt(info); + ShowEffectMenu(client); + return 0; +} + +void ShowEffectMenu(int client) +{ + if (!IsClientInAllowedGroup(client)) + { + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + ShowMainMenu(client); + return; + } + + int target = GetClientOfUserId(g_iEffectTargetUserId[client]); + if (!IsValidClient(target)) + { + PrintToChat(client, "[SM] Игрок недоступен."); + ShowMainMenu(client); + return; + } + + char title[128]; + Format(title, sizeof(title), "Эффекты для игрока: %N", target); + + Menu menu = new Menu(MenuHandler_EffectMenu); + menu.SetTitle(title); + menu.ExitButton = true; + menu.ExitBackButton = true; + + menu.AddItem("nodamage", g_bZeroDamageAttack[target] ? "Выключить 0 урона" : "Включить 0 урона"); + menu.AddItem("recoil", g_bSuperRecoil[target] ? "Выключить супер-отдачу" : "Включить супер-отдачу"); + menu.AddItem("drug", g_bDrugEffect[target] ? "Выключить drug-эффект" : "Включить drug-эффект"); + menu.AddItem("randomfov", g_bRandomFov[target] ? "Выключить случайный FOV" : "Включить случайный FOV"); + menu.AddItem("invertmove", g_bInvertMovement[target] ? "Выключить инверсию движения" : "Включить инверсию движения"); + menu.AddItem("invertmouse", g_bInvertMouse[target] ? "Выключить инверсию мыши" : "Включить инверсию мыши"); + menu.AddItem("shake", "Сильно тряхнуть экран"); + menu.AddItem("blind", "Ослепить на 2 сек"); + menu.Display(client, MENU_TIME); +} + +public int MenuHandler_EffectMenu(Menu menu, MenuAction action, int client, int item) +{ + if (!IsClientInAllowedGroup(client)) + { + if (action == MenuAction_End) + delete menu; + else if (action == MenuAction_Select) + PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + return 0; + } + + if (action == MenuAction_End) + { + delete menu; + return 0; + } + + if (action == MenuAction_Cancel) + { + if (item == MenuCancel_ExitBack) + ShowEffectTargetMenu(client); + return 0; + } + + if (action != MenuAction_Select) + return 0; + + int target = GetClientOfUserId(g_iEffectTargetUserId[client]); + if (!IsValidClient(target)) + { + PrintToChat(client, "[SM] Игрок недоступен."); + ShowMainMenu(client); + return 0; + } + + char info[16]; + menu.GetItem(item, info, sizeof(info)); + + if (StrEqual(info, "nodamage")) + { + g_bZeroDamageAttack[target] = !g_bZeroDamageAttack[target]; + PrintToRootAdmins("[SM] %N: 0 урона для %N -> %s", client, target, g_bZeroDamageAttack[target] ? "ВКЛ" : "ВЫКЛ"); + } + else if (StrEqual(info, "recoil")) + { + g_bSuperRecoil[target] = !g_bSuperRecoil[target]; + PrintToRootAdmins("[SM] %N: супер-отдача для %N -> %s", client, target, g_bSuperRecoil[target] ? "ВКЛ" : "ВЫКЛ"); + } + else if (StrEqual(info, "drug")) + { + ToggleDrugEffect(target); + PrintToRootAdmins("[SM] %N: drug-эффект для %N -> %s", client, target, g_bDrugEffect[target] ? "ВКЛ" : "ВЫКЛ"); + } + else if (StrEqual(info, "randomfov")) + { + ToggleRandomFov(target); + PrintToRootAdmins("[SM] %N: случайный FOV для %N -> %s", client, target, g_bRandomFov[target] ? "ВКЛ" : "ВЫКЛ"); + } + else if (StrEqual(info, "invertmove")) + { + g_bInvertMovement[target] = !g_bInvertMovement[target]; + PrintToRootAdmins("[SM] %N: инверсия движения для %N -> %s", client, target, g_bInvertMovement[target] ? "ВКЛ" : "ВЫКЛ"); + } + else if (StrEqual(info, "invertmouse")) + { + g_bInvertMouse[target] = !g_bInvertMouse[target]; + PrintToRootAdmins("[SM] %N: инверсия мыши для %N -> %s", client, target, g_bInvertMouse[target] ? "ВКЛ" : "ВЫКЛ"); + } + else if (StrEqual(info, "shake")) + { + ApplyStrongShake(target); + PrintToRootAdmins("[SM] %N сильно тряхнул экран игрока %N.", client, target); + } + else if (StrEqual(info, "blind")) + { + BlindPlayer(target, 2.0); + PrintToRootAdmins("[SM] %N ослепил игрока %N на 2 сек.", client, target); + } + + ShowEffectMenu(client); + return 0; +} + +public Action Hook_OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) +{ + if (attacker > 0 && attacker <= MaxClients && IsClientInGame(attacker) && g_bZeroDamageAttack[attacker]) + { + damage = 0.0; + damagetype = 0; + return Plugin_Changed; + } + + return Plugin_Continue; +} + + +public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + return Plugin_Continue; + + bool changed = false; + + if (g_bInvertMovement[client]) + { + vel[0] = -vel[0]; + vel[1] = -vel[1]; + + bool hasForward = (buttons & IN_FORWARD) != 0; + bool hasBack = (buttons & IN_BACK) != 0; + bool hasMoveLeft = (buttons & IN_MOVELEFT) != 0; + bool hasMoveRight= (buttons & IN_MOVERIGHT) != 0; + + buttons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT); + + if (hasForward) buttons |= IN_BACK; + if (hasBack) buttons |= IN_FORWARD; + if (hasMoveLeft) buttons |= IN_MOVERIGHT; + if (hasMoveRight) buttons |= IN_MOVELEFT; + + changed = true; + } + + if (g_bInvertMouse[client]) + { + mouse[1] = -mouse[1]; + changed = true; + } + + return changed ? Plugin_Changed : Plugin_Continue; +} + +public void OnWeaponFire(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + if (!IsValidClient(client) || !IsPlayerAlive(client) || !g_bSuperRecoil[client]) + return; + + float recoil[3]; + recoil[0] = GetRandomFloat(-18.0, -10.0); + recoil[1] = GetRandomFloat(-8.0, 8.0); + recoil[2] = 0.0; + + SetEntPropVector(client, Prop_Send, "m_aimPunchAngle", recoil); + SetEntPropVector(client, Prop_Send, "m_viewPunchAngle", recoil); + ApplyStrongShake(client); +} + + +void ToggleRandomFov(int client) +{ + if (!IsValidClient(client)) + return; + + if (g_bRandomFov[client]) + { + StopRandomFov(client); + return; + } + + g_bRandomFov[client] = true; + + if (g_hRandomFovTimer[client] != null) + { + KillTimer(g_hRandomFovTimer[client]); + g_hRandomFovTimer[client] = null; + } + + if (IsPlayerAlive(client)) + SetEntProp(client, Prop_Send, "m_iFOV", GetRandomInt(65, 125)); + + g_hRandomFovTimer[client] = CreateTimer(1.0, Timer_RandomFov, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); +} + +void StopRandomFov(int client) +{ + if (client < 1 || client > MaxClients) + return; + + g_bRandomFov[client] = false; + + if (g_hRandomFovTimer[client] != null) + { + KillTimer(g_hRandomFovTimer[client]); + g_hRandomFovTimer[client] = null; + } + + if (IsValidClient(client) && !g_bDrugEffect[client]) + SetEntProp(client, Prop_Send, "m_iFOV", 90); +} + +public Action Timer_RandomFov(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (!IsValidClient(client) || !IsPlayerAlive(client) || !g_bRandomFov[client]) + { + if (client > 0 && client <= MaxClients) + g_hRandomFovTimer[client] = null; + return Plugin_Stop; + } + + SetEntProp(client, Prop_Send, "m_iFOV", GetRandomInt(65, 125)); + return Plugin_Continue; +} + +void ToggleDrugEffect(int client) +{ + if (!IsValidClient(client)) + return; + + if (g_bDrugEffect[client]) + { + StopDrugEffect(client); + return; + } + + g_bDrugEffect[client] = true; + if (g_hDrugTimer[client] != null) + { + KillTimer(g_hDrugTimer[client]); + g_hDrugTimer[client] = null; + } + g_hDrugTimer[client] = CreateTimer(0.8, Timer_DrugEffect, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); +} + +void StopDrugEffect(int client) +{ + if (client < 1 || client > MaxClients) + return; + + g_bDrugEffect[client] = false; + + if (g_hDrugTimer[client] != null) + { + KillTimer(g_hDrugTimer[client]); + g_hDrugTimer[client] = null; + } + + if (IsValidClient(client) && !g_bRandomFov[client]) + SetEntProp(client, Prop_Send, "m_iFOV", 90); +} + +public Action Timer_DrugEffect(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (!IsValidClient(client) || !IsPlayerAlive(client) || !g_bDrugEffect[client]) + { + if (client > 0 && client <= MaxClients) + g_hDrugTimer[client] = null; + return Plugin_Stop; + } + + float punch[3]; + punch[0] = GetRandomFloat(-7.0, 7.0); + punch[1] = GetRandomFloat(-7.0, 7.0); + punch[2] = 0.0; + SetEntPropVector(client, Prop_Send, "m_viewPunchAngle", punch); + SetEntPropVector(client, Prop_Send, "m_aimPunchAngle", punch); + SetEntProp(client, Prop_Send, "m_iFOV", GetRandomInt(80, 115)); + ApplyStrongShake(client); + return Plugin_Continue; +} + +void ApplyStrongShake(int client) +{ + if (!IsValidClient(client)) + return; + + int shake = CreateEntityByName("env_shake"); + if (shake == -1) + return; + + DispatchKeyValueFloat(shake, "amplitude", 20.0); + DispatchKeyValueFloat(shake, "radius", 300.0); + DispatchKeyValueFloat(shake, "duration", 1.2); + DispatchKeyValueFloat(shake, "frequency", 255.0); + DispatchSpawn(shake); + + float pos[3]; + GetClientAbsOrigin(client, pos); + TeleportEntity(shake, pos, NULL_VECTOR, NULL_VECTOR); + AcceptEntityInput(shake, "StartShake", client, client); + CreateTimer(1.5, Timer_KillEntity, EntIndexToEntRef(shake), TIMER_FLAG_NO_MAPCHANGE); +} + +void BlindPlayer(int client, float duration) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + return; + + // Используем встроенные пропы флэшбанга CS:GO — никакого usermsgs, безопасно + SetEntPropFloat(client, Prop_Send, "m_flFlashDuration", duration); + SetEntPropFloat(client, Prop_Send, "m_flFlashMaxAlpha", 255.0); +} + +public Action Timer_KillEntity(Handle timer, any entRef) +{ + int ent = EntRefToEntIndex(entRef); + if (ent != INVALID_ENT_REFERENCE && ent != -1 && IsValidEntity(ent)) + AcceptEntityInput(ent, "Kill"); + return Plugin_Stop; +} + + +#define SADMIN_NOTIFY_GROUP_1 "DEIDARA" +#define SADMIN_NOTIFY_GROUP_2 "TESTER" + +bool IsClientInAllowedGroup(int client) +{ + if (!IsValidClient(client)) + return false; + + AdminId admin = GetUserAdmin(client); + if (admin == INVALID_ADMIN_ID) + return false; + + int groupCount = GetAdminGroupCount(admin); + char groupName[64]; + + for (int i = 0; i < groupCount; i++) + { + GroupId gid = GetAdminGroup(admin, i, groupName, sizeof(groupName)); + if (gid != INVALID_GROUP_ID) + { + if (StrEqual(groupName, SADMIN_NOTIFY_GROUP_1, false) || StrEqual(groupName, SADMIN_NOTIFY_GROUP_2, false)) + { + return true; + } + } + } + + return false; +} + +void PrintToRootAdmins(const char[] format, any ...) +{ + char buffer[256]; + VFormat(buffer, sizeof(buffer), format, 2); + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInAllowedGroup(i)) + { + PrintToChat(i, "%s", buffer); + } + } +} + +bool IsValidClient(int client) +{ + return (client > 0 && client <= MaxClients && IsClientInGame(client)); +} \ No newline at end of file