v1.3: разграничение прав, логирование, удалены blind/invertmouse

- Обычные админы (флаги abcdefghij без z) видят только меню Эффекты игрока
- Группы DEIDARA/TESTER сохраняют полный доступ
- Действия обычных админов логируются в addons/sourcemod/logs/super_admin_menu.log
- Удалён эффект Ослепление на 2 сек
- Удалён эффект Инверсия мыши
This commit is contained in:
deidara
2026-05-01 16:03:59 +03:00
parent 74ac6c06ca
commit e350f7a82e
2 changed files with 146 additions and 65 deletions
+43 -11
View File
@@ -1,13 +1,13 @@
# Super Admin Menu # Super Admin Menu
Расширенное меню администратора для CS:GO серверов на SourceMod. Открывается одной командой и предоставляет все нужные инструменты управления игроками. Расширенное меню администратора для CS:GO серверов на SourceMod. Открывается одной командой и предоставляет все нужные инструменты управления игроками с разграничением прав.
## Функции ## Функции
- Выдача снаряжения (сет с оружием): AK-47, M4A4, M4A1-S, AWP, Deagle Only - Выдача снаряжения (сет с оружием): AK-47, M4A4, M4A1-S, AWP, Deagle Only
- Выдача денег: 3000 / 5000 / 10000 / 16000$ - Выдача денег: 3000 / 5000 / 10000 / 16000$
- Респавн игроков (себе / всем / конкретному игроку) - Респавн игроков (себе / всем / конкретному игроку)
- Восстановление 100 HP + 100 Armor (только группы DEIDARA / TESTER) - Восстановление 100 HP + 100 Armor
- Невидимость для администратора с сокрытием оружия - Невидимость для администратора с сокрытием оружия
- Телепорт к игроку / телепорт игрока к себе - Телепорт к игроку / телепорт игрока к себе
- Телепорт на плент A или B (автоопределение по карте) - Телепорт на плент A или B (автоопределение по карте)
@@ -17,10 +17,9 @@
- Drug-эффект (дёргание экрана + случайный FOV) - Drug-эффект (дёргание экрана + случайный FOV)
- Случайный 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** | Все функции меню (полный доступ) |
| Группа **DEIDARA** | Все функции меню | | Группа **TESTER** | Все функции меню (полный доступ) |
| Группа **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** — Базовая версия меню
+102 -53
View File
@@ -15,7 +15,6 @@ bool g_bSuperRecoil[MAXPLAYERS + 1];
bool g_bDrugEffect[MAXPLAYERS + 1]; bool g_bDrugEffect[MAXPLAYERS + 1];
bool g_bRandomFov[MAXPLAYERS + 1]; bool g_bRandomFov[MAXPLAYERS + 1];
bool g_bInvertMovement[MAXPLAYERS + 1]; bool g_bInvertMovement[MAXPLAYERS + 1];
bool g_bInvertMouse[MAXPLAYERS + 1];
Handle g_hDrugTimer[MAXPLAYERS + 1]; Handle g_hDrugTimer[MAXPLAYERS + 1];
Handle g_hRandomFovTimer[MAXPLAYERS + 1]; Handle g_hRandomFovTimer[MAXPLAYERS + 1];
int g_iEffectTargetUserId[MAXPLAYERS + 1]; int g_iEffectTargetUserId[MAXPLAYERS + 1];
@@ -25,7 +24,7 @@ public Plugin myinfo =
name = "Super Admin Menu", name = "Super Admin Menu",
author = "OpenAI + deidara.dev", author = "OpenAI + deidara.dev",
description = "Единое супер-админ меню по команде sm_sadmin", description = "Единое супер-админ меню по команде sm_sadmin",
version = "1.2" version = "1.3"
}; };
public void OnPluginStart() public void OnPluginStart()
@@ -62,7 +61,6 @@ public void OnClientDisconnect(int client)
g_bDrugEffect[client] = false; g_bDrugEffect[client] = false;
g_bRandomFov[client] = false; g_bRandomFov[client] = false;
g_bInvertMovement[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; return Plugin_Handled;
} }
bool hasRootFlag = (GetUserFlagBits(client) & ADMFLAG_ROOT) != 0; if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client))
if (!hasRootFlag && !IsClientInAllowedGroup(client))
{ {
PrintToChat(client, "[SM] У тебя нет доступа к этому меню."); PrintToChat(client, "[SM] У тебя нет доступа к этому меню.");
return Plugin_Handled; return Plugin_Handled;
@@ -126,12 +123,11 @@ void ShowMainMenu(int client)
bool isDeidara = IsClientInAllowedGroup(client); bool isDeidara = IsClientInAllowedGroup(client);
if (isDeidara)
{
menu.AddItem("sets", "Выдать сет"); menu.AddItem("sets", "Выдать сет");
menu.AddItem("money", "Выдать деньги"); menu.AddItem("money", "Выдать деньги");
menu.AddItem("respawn", "Респавн"); menu.AddItem("respawn", "Респавн");
if (isDeidara)
{
menu.AddItem("restore", "Восстановить 100 HP / 100 Armor"); menu.AddItem("restore", "Восстановить 100 HP / 100 Armor");
menu.AddItem("invis", g_bInvisible[client] ? "Выключить невидимость" : "Включить невидимость"); menu.AddItem("invis", g_bInvisible[client] ? "Выключить невидимость" : "Включить невидимость");
menu.AddItem("tp_to", "Телепорт к игроку"); menu.AddItem("tp_to", "Телепорт к игроку");
@@ -139,6 +135,11 @@ void ShowMainMenu(int client)
menu.AddItem("plant", "Телепорт на плент"); menu.AddItem("plant", "Телепорт на плент");
menu.AddItem("effects", "Эффекты игрока"); menu.AddItem("effects", "Эффекты игрока");
} }
else
{
// Обычный админ: только Эффекты игрока
menu.AddItem("effects", "Эффекты игрока");
}
menu.Display(client, MENU_TIME); 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); 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")) if (StrEqual(info, "sets"))
{ {
ShowTargetMenu(client, "set"); ShowTargetMenu(client, "set");
@@ -171,11 +186,6 @@ public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item)
{ {
ShowTargetMenu(client, "respawn"); ShowTargetMenu(client, "respawn");
} }
else if (!isDeidara)
{
PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER.");
ShowMainMenu(client);
}
else if (StrEqual(info, "restore")) else if (StrEqual(info, "restore"))
{ {
ShowTargetMenu(client, "restore"); ShowTargetMenu(client, "restore");
@@ -197,10 +207,6 @@ public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item)
{ {
ShowPlantMenu(client); ShowPlantMenu(client);
} }
else if (StrEqual(info, "effects"))
{
ShowEffectTargetMenu(client);
}
return 0; return 0;
} }
@@ -875,9 +881,9 @@ void StripWeapons(int client)
void ShowEffectTargetMenu(int client) void ShowEffectTargetMenu(int client)
{ {
if (!IsClientInAllowedGroup(client)) if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client))
{ {
PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); PrintToChat(client, "[SM] У тебя нет доступа к этому меню.");
ShowMainMenu(client); ShowMainMenu(client);
return; return;
} }
@@ -915,12 +921,12 @@ void ShowEffectTargetMenu(int client)
public int MenuHandler_EffectTarget(Menu menu, MenuAction action, int client, int item) public int MenuHandler_EffectTarget(Menu menu, MenuAction action, int client, int item)
{ {
if (!IsClientInAllowedGroup(client)) if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client))
{ {
if (action == MenuAction_End) if (action == MenuAction_End)
delete menu; delete menu;
else if (action == MenuAction_Select) else if (action == MenuAction_Select)
PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); PrintToChat(client, "[SM] У тебя нет доступа к этому меню.");
return 0; return 0;
} }
@@ -949,9 +955,9 @@ public int MenuHandler_EffectTarget(Menu menu, MenuAction action, int client, in
void ShowEffectMenu(int client) void ShowEffectMenu(int client)
{ {
if (!IsClientInAllowedGroup(client)) if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client))
{ {
PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); PrintToChat(client, "[SM] У тебя нет доступа к этому меню.");
ShowMainMenu(client); ShowMainMenu(client);
return; return;
} }
@@ -977,20 +983,18 @@ void ShowEffectMenu(int client)
menu.AddItem("drug", g_bDrugEffect[target] ? "Выключить drug-эффект" : "Включить drug-эффект"); menu.AddItem("drug", g_bDrugEffect[target] ? "Выключить drug-эффект" : "Включить drug-эффект");
menu.AddItem("randomfov", g_bRandomFov[target] ? "Выключить случайный FOV" : "Включить случайный FOV"); menu.AddItem("randomfov", g_bRandomFov[target] ? "Выключить случайный FOV" : "Включить случайный FOV");
menu.AddItem("invertmove", g_bInvertMovement[target] ? "Выключить инверсию движения" : "Включить инверсию движения"); menu.AddItem("invertmove", g_bInvertMovement[target] ? "Выключить инверсию движения" : "Включить инверсию движения");
menu.AddItem("invertmouse", g_bInvertMouse[target] ? "Выключить инверсию мыши" : "Включить инверсию мыши");
menu.AddItem("shake", "Сильно тряхнуть экран"); menu.AddItem("shake", "Сильно тряхнуть экран");
menu.AddItem("blind", "Ослепить на 2 сек");
menu.Display(client, MENU_TIME); menu.Display(client, MENU_TIME);
} }
public int MenuHandler_EffectMenu(Menu menu, MenuAction action, int client, int item) public int MenuHandler_EffectMenu(Menu menu, MenuAction action, int client, int item)
{ {
if (!IsClientInAllowedGroup(client)) if (!IsClientInAllowedGroup(client) && !IsRegularAdmin(client))
{ {
if (action == MenuAction_End) if (action == MenuAction_End)
delete menu; delete menu;
else if (action == MenuAction_Select) else if (action == MenuAction_Select)
PrintToChat(client, "[SM] Этот пункт доступен только группам DEIDARA и TESTER."); PrintToChat(client, "[SM] У тебя нет доступа к этому меню.");
return 0; return 0;
} }
@@ -1025,41 +1029,37 @@ public int MenuHandler_EffectMenu(Menu menu, MenuAction action, int client, int
{ {
g_bZeroDamageAttack[target] = !g_bZeroDamageAttack[target]; g_bZeroDamageAttack[target] = !g_bZeroDamageAttack[target];
PrintToRootAdmins("[SM] %N: 0 урона для %N -> %s", client, 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")) else if (StrEqual(info, "recoil"))
{ {
g_bSuperRecoil[target] = !g_bSuperRecoil[target]; g_bSuperRecoil[target] = !g_bSuperRecoil[target];
PrintToRootAdmins("[SM] %N: супер-отдача для %N -> %s", client, 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")) else if (StrEqual(info, "drug"))
{ {
ToggleDrugEffect(target); ToggleDrugEffect(target);
PrintToRootAdmins("[SM] %N: drug-эффект для %N -> %s", client, target, g_bDrugEffect[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")) else if (StrEqual(info, "randomfov"))
{ {
ToggleRandomFov(target); ToggleRandomFov(target);
PrintToRootAdmins("[SM] %N: случайный FOV для %N -> %s", client, target, g_bRandomFov[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")) else if (StrEqual(info, "invertmove"))
{ {
g_bInvertMovement[target] = !g_bInvertMovement[target]; g_bInvertMovement[target] = !g_bInvertMovement[target];
PrintToRootAdmins("[SM] %N: инверсия движения для %N -> %s", client, target, g_bInvertMovement[target] ? "ВКЛ" : "ВЫКЛ"); PrintToRootAdmins("[SM] %N: инверсия движения для %N -> %s", client, target, g_bInvertMovement[target] ? "ВКЛ" : "ВЫКЛ");
} LogAdminAction(client, target, "Инверсия движения -> %s", 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")) else if (StrEqual(info, "shake"))
{ {
ApplyStrongShake(target); ApplyStrongShake(target);
PrintToRootAdmins("[SM] %N сильно тряхнул экран игрока %N.", client, target); PrintToRootAdmins("[SM] %N сильно тряхнул экран игрока %N.", client, target);
} LogAdminAction(client, target, "Тряска экрана");
else if (StrEqual(info, "blind"))
{
BlindPlayer(target, 2.0);
PrintToRootAdmins("[SM] %N ослепил игрока %N на 2 сек.", client, target);
} }
ShowEffectMenu(client); ShowEffectMenu(client);
@@ -1106,12 +1106,6 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
changed = true; changed = true;
} }
if (g_bInvertMouse[client])
{
mouse[1] = -mouse[1];
changed = true;
}
return changed ? Plugin_Changed : Plugin_Continue; 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); 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) public Action Timer_KillEntity(Handle timer, any entRef)
{ {
int ent = EntRefToEntIndex(entRef); int ent = EntRefToEntIndex(entRef);
@@ -1317,6 +1301,25 @@ bool IsClientInAllowedGroup(int client)
return false; 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 ...) void PrintToRootAdmins(const char[] format, any ...)
{ {
char buffer[256]; char buffer[256];
@@ -1335,3 +1338,49 @@ bool IsValidClient(int client)
{ {
return (client > 0 && client <= MaxClients && IsClientInGame(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;
}