diff --git a/README.md b/README.md index 518a8c7..1842043 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # Super Admin Menu -Расширенное меню администратора для CS:GO серверов на SourceMod. Открывается одной командой и предоставляет все нужные инструменты управления игроками. +Расширенное меню администратора для CS:GO серверов на SourceMod. Открывается одной командой и предоставляет все нужные инструменты управления игроками с разграничением прав. ## Функции - Выдача снаряжения (сет с оружием): AK-47, M4A4, M4A1-S, AWP, Deagle Only - Выдача денег: 3000 / 5000 / 10000 / 16000$ - Респавн игроков (себе / всем / конкретному игроку) -- Восстановление 100 HP + 100 Armor (только группы DEIDARA / TESTER) +- Восстановление 100 HP + 100 Armor - Невидимость для администратора с сокрытием оружия - Телепорт к игроку / телепорт игрока к себе - Телепорт на плент A или B (автоопределение по карте) @@ -17,10 +17,9 @@ - Drug-эффект (дёргание экрана + случайный FOV) - Случайный FOV - Инверсия движения - - Инверсия мыши - Сильное сотрясение экрана - - Ослепление на 2 секунды -- Все действия логируются в чат онлайн-администраторам +- Уведомления о действиях в чате членам групп DEIDARA / TESTER +- Логирование действий обычных админов в файл ## Зависимости @@ -38,18 +37,51 @@ | Команда | Доступ | Описание | |---|---|---| -| `!sadmin` / `sm_sadmin` | Root или группа DEIDARA/TESTER | Открыть супер-админ меню | +| `!sadmin` / `sm_sadmin` | Группа DEIDARA / TESTER или обычный админ (флаги `a`–`j`) | Открыть супер-админ меню | ## Уровни доступа | Уровень | Возможности | |---|---| -| Root (`ADMFLAG_ROOT`) | Все функции меню | -| Группа **DEIDARA** | Все функции меню | -| Группа **TESTER** | Все функции меню | +| Группа **DEIDARA** | Все функции меню (полный доступ) | +| Группа **TESTER** | Все функции меню (полный доступ) | +| Обычный админ (любые флаги `abcdefghij`, без `z`) | Только меню «Эффекты игрока» | +| Без админ-флагов | Доступ запрещён | -> Базовые функции (сет, деньги, респавн) доступны всем Root-админам. Расширенные функции (невидимость, телепорт, эффекты, восстановление HP) — только группам DEIDARA и TESTER. +> Группы DEIDARA / TESTER определяются по имени группы в `admin_groups.cfg` (регистр не важен) — флаги роли не нужны. +> Обычные админы — все, у кого есть любой флаг кроме `z` (Root) и кто не состоит в группах DEIDARA/TESTER. Им доступно только подменю «Эффекты игрока» с полным набором эффектов. + +## Логирование + +Все действия **обычных админов** в подменю «Эффекты игрока» автоматически пишутся в лог-файл: + +``` +addons/sourcemod/logs/super_admin_menu.log +``` + +Формат записи: +``` +[YYYY-MM-DD HH:MM:SS] АдминИмя (STEAM_X:Y:Z) -> Действие -> ВКЛ/ВЫКЛ на игроке Имя (STEAM_X:Y:Z) +``` + +Примеры: +``` +[2026-05-01 18:42:11] AdminBob (STEAM_1:0:12345) -> 0 урона -> ВКЛ на игроке player1 (STEAM_1:1:67890) +[2026-05-01 18:42:34] AdminBob (STEAM_1:0:12345) -> Тряска экрана на игроке player2 (STEAM_1:0:55555) +``` + +Действия членов групп DEIDARA / TESTER в файл **не пишутся** (они отображаются только в чате другим членам этих групп через `PrintToRootAdmins`). ## Версия -`1.2` — Автор: OpenAI + deidara.dev +`1.3` — Автор: OpenAI + deidara.dev + +### Changelog + +- **1.3** + - Разграничение прав: обычные админы (флаги `a`–`j`, без `z`) теперь имеют доступ только к меню «Эффекты игрока» + - Полный доступ — только у групп DEIDARA / TESTER + - Добавлено логирование действий обычных админов в `addons/sourcemod/logs/super_admin_menu.log` + - Удалён эффект «Ослепление на 2 секунды» + - Удалён эффект «Инверсия мыши» +- **1.2** — Базовая версия меню diff --git a/scripting/super_admin_menu.sp b/scripting/super_admin_menu.sp index bbc5aca..89e34b4 100644 --- a/scripting/super_admin_menu.sp +++ b/scripting/super_admin_menu.sp @@ -15,7 +15,6 @@ 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]; @@ -25,7 +24,7 @@ public Plugin myinfo = name = "Super Admin Menu", author = "OpenAI + deidara.dev", description = "Единое супер-админ меню по команде sm_sadmin", - version = "1.2" + version = "1.3" }; public void OnPluginStart() @@ -62,7 +61,6 @@ public void OnClientDisconnect(int client) g_bDrugEffect[client] = false; g_bRandomFov[client] = false; g_bInvertMovement[client] = false; - g_bInvertMouse[client] = false; } @@ -107,8 +105,7 @@ public Action Command_SuperAdminMenu(int client, int args) return Plugin_Handled; } - bool hasRootFlag = (GetUserFlagBits(client) & ADMFLAG_ROOT) != 0; - if (!hasRootFlag && !IsClientInAllowedGroup(client)) + if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client)) { PrintToChat(client, "[SM] У тебя нет доступа к этому меню."); return Plugin_Handled; @@ -126,12 +123,11 @@ void ShowMainMenu(int client) bool isDeidara = IsClientInAllowedGroup(client); - menu.AddItem("sets", "Выдать сет"); - menu.AddItem("money", "Выдать деньги"); - menu.AddItem("respawn", "Респавн"); - if (isDeidara) { + menu.AddItem("sets", "Выдать сет"); + menu.AddItem("money", "Выдать деньги"); + menu.AddItem("respawn", "Респавн"); menu.AddItem("restore", "Восстановить 100 HP / 100 Armor"); menu.AddItem("invis", g_bInvisible[client] ? "Выключить невидимость" : "Включить невидимость"); menu.AddItem("tp_to", "Телепорт к игроку"); @@ -139,6 +135,11 @@ void ShowMainMenu(int client) menu.AddItem("plant", "Телепорт на плент"); menu.AddItem("effects", "Эффекты игрока"); } + else + { + // Обычный админ: только Эффекты игрока + menu.AddItem("effects", "Эффекты игрока"); + } menu.Display(client, MENU_TIME); } @@ -159,6 +160,20 @@ public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item) bool isDeidara = IsClientInAllowedGroup(client); + // Обычные админы могут открыть только меню эффектов + if (!isDeidara && !StrEqual(info, "effects")) + { + PrintToChat(client, "[SM] У тебя нет доступа к этому пункту."); + ShowMainMenu(client); + return 0; + } + + if (StrEqual(info, "effects")) + { + ShowEffectTargetMenu(client); + return 0; + } + if (StrEqual(info, "sets")) { ShowTargetMenu(client, "set"); @@ -171,11 +186,6 @@ public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item) { ShowTargetMenu(client, "respawn"); } - else if (!isDeidara) - { - PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); - ShowMainMenu(client); - } else if (StrEqual(info, "restore")) { ShowTargetMenu(client, "restore"); @@ -197,10 +207,6 @@ public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item) { ShowPlantMenu(client); } - else if (StrEqual(info, "effects")) - { - ShowEffectTargetMenu(client); - } return 0; } @@ -875,9 +881,9 @@ void StripWeapons(int client) void ShowEffectTargetMenu(int client) { - if (!IsClientInAllowedGroup(client)) + if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client)) { - PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + PrintToChat(client, "[SM] У тебя нет доступа к этому меню."); ShowMainMenu(client); return; } @@ -915,12 +921,12 @@ void ShowEffectTargetMenu(int client) public int MenuHandler_EffectTarget(Menu menu, MenuAction action, int client, int item) { - if (!IsClientInAllowedGroup(client)) + if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client)) { if (action == MenuAction_End) delete menu; else if (action == MenuAction_Select) - PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + PrintToChat(client, "[SM] У тебя нет доступа к этому меню."); return 0; } @@ -949,9 +955,9 @@ public int MenuHandler_EffectTarget(Menu menu, MenuAction action, int client, in void ShowEffectMenu(int client) { - if (!IsClientInAllowedGroup(client)) + if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client)) { - PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + PrintToChat(client, "[SM] У тебя нет доступа к этому меню."); ShowMainMenu(client); return; } @@ -977,20 +983,18 @@ void ShowEffectMenu(int client) 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 (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client)) { if (action == MenuAction_End) delete menu; else if (action == MenuAction_Select) - PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); + PrintToChat(client, "[SM] У тебя нет доступа к этому меню."); return 0; } @@ -1025,41 +1029,37 @@ public int MenuHandler_EffectMenu(Menu menu, MenuAction action, int client, int { g_bZeroDamageAttack[target] = !g_bZeroDamageAttack[target]; PrintToRootAdmins("[SM] %N: 0 урона для %N -> %s", client, target, g_bZeroDamageAttack[target] ? "ВКЛ" : "ВЫКЛ"); + LogAdminAction(client, target, "0 урона -> %s", g_bZeroDamageAttack[target] ? "ВКЛ" : "ВЫКЛ"); } else if (StrEqual(info, "recoil")) { g_bSuperRecoil[target] = !g_bSuperRecoil[target]; PrintToRootAdmins("[SM] %N: супер-отдача для %N -> %s", client, target, g_bSuperRecoil[target] ? "ВКЛ" : "ВЫКЛ"); + LogAdminAction(client, target, "Супер-отдача -> %s", g_bSuperRecoil[target] ? "ВКЛ" : "ВЫКЛ"); } else if (StrEqual(info, "drug")) { ToggleDrugEffect(target); PrintToRootAdmins("[SM] %N: drug-эффект для %N -> %s", client, target, g_bDrugEffect[target] ? "ВКЛ" : "ВЫКЛ"); + LogAdminAction(client, target, "Drug-эффект -> %s", g_bDrugEffect[target] ? "ВКЛ" : "ВЫКЛ"); } else if (StrEqual(info, "randomfov")) { ToggleRandomFov(target); PrintToRootAdmins("[SM] %N: случайный FOV для %N -> %s", client, target, g_bRandomFov[target] ? "ВКЛ" : "ВЫКЛ"); + LogAdminAction(client, target, "Случайный FOV -> %s", 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] ? "ВКЛ" : "ВЫКЛ"); + LogAdminAction(client, target, "Инверсия движения -> %s", g_bInvertMovement[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); + LogAdminAction(client, target, "Тряска экрана"); } ShowEffectMenu(client); @@ -1106,12 +1106,6 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 changed = true; } - if (g_bInvertMouse[client]) - { - mouse[1] = -mouse[1]; - changed = true; - } - return changed ? Plugin_Changed : Plugin_Continue; } @@ -1268,16 +1262,6 @@ void ApplyStrongShake(int 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); @@ -1317,6 +1301,25 @@ bool IsClientInAllowedGroup(int client) return false; } +// Обычный админ: имеет любой админ-флаг, но НЕ имеет ROOT и НЕ в группах DEIDARA/TESTER +bool IsRegularAdmin(int client) +{ + if (!IsValidClient(client)) + return false; + + if (IsClientInAllowedGroup(client)) + return false; + + int flags = GetUserFlagBits(client); + if (flags == 0) + return false; + + if ((flags & ADMFLAG_ROOT) != 0) + return false; + + return true; +} + void PrintToRootAdmins(const char[] format, any ...) { char buffer[256]; @@ -1334,4 +1337,50 @@ void PrintToRootAdmins(const char[] format, any ...) bool IsValidClient(int client) { return (client > 0 && client <= MaxClients && IsClientInGame(client)); +} + +// Логируем действие обычного админа в файл addons/sourcemod/logs/super_admin_menu.log. +// Действия DEIDARA/TESTER не логируются. +void LogAdminAction(int client, int target, const char[] format, any ...) +{ + if (!IsRegularAdmin(client)) + return; + + char message[256]; + VFormat(message, sizeof(message), format, 4); + + char path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "logs/super_admin_menu.log"); + + char timestamp[32]; + FormatTime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S"); + + char adminName[MAX_NAME_LENGTH], adminSteam[32]; + GetClientName(client, adminName, sizeof(adminName)); + if (!GetClientAuthId(client, AuthId_Steam2, adminSteam, sizeof(adminSteam))) + strcopy(adminSteam, sizeof(adminSteam), "UNKNOWN"); + + char targetInfo[128]; + if (IsValidClient(target)) + { + char targetName[MAX_NAME_LENGTH], targetSteam[32]; + GetClientName(target, targetName, sizeof(targetName)); + if (!GetClientAuthId(target, AuthId_Steam2, targetSteam, sizeof(targetSteam))) + strcopy(targetSteam, sizeof(targetSteam), "UNKNOWN"); + Format(targetInfo, sizeof(targetInfo), "%s (%s)", targetName, targetSteam); + } + else + { + strcopy(targetInfo, sizeof(targetInfo), "UNKNOWN"); + } + + File logFile = OpenFile(path, "a"); + if (logFile == null) + { + LogError("[super_admin_menu] Не удалось открыть %s для записи.", path); + return; + } + + logFile.WriteLine("[%s] %s (%s) -> %s на игроке %s", timestamp, adminName, adminSteam, message, targetInfo); + delete logFile; } \ No newline at end of file