Files
custom-rounds/scripting/ArcaneGame_CustomRounds_Core.sp
T
deidara e7f02da4a2 v1.3.0: режим 1 vs All
Новый режим 1 vs All (от 15 игроков):
- 1 ТТшник против всех CT
- Админ выбирает игрока вручную или рандомно (подменю)
- ТТшник: 5000 HP, 500 брони, +25% скорости, $16000
- CT: XM1014 + HE + флеш + смок + incgrenade, покупка отключена
- ТТшник умер → CT побеждают
- ТТшник вышел → ничья (CS_TerminateRound), оружие восстанавливается
- После раунда команды восстанавливаются в исходные
- <15 игроков на старте → автоматическая отмена с уведомлением
2026-05-01 18:26:01 +03:00

1624 lines
47 KiB
SourcePawn
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma semicolon 1
#pragma newdecls required
#include <sourcemod>
#include <sdktools>
#include <cstrike>
#include <sdkhooks>
#include "agcoins_bridge"
#define CR_PREFIX "\x04[ArcaneGame CR]\x01"
#define CR_REASON_MAX 128
#define MAX_SAVED_GRENADES 6
#define MIN_PLAYERS_ONEVSALL 15
enum CustomRoundType
{
CR_None = 0,
CR_AWP,
CR_NoScope,
CR_HE,
CR_Knife,
CR_Scout,
CR_ScoutNoScope,
CR_Deagle,
CR_DeagleHS,
CR_LowGravity,
CR_OneHP,
CR_OneVsAll
};
public Plugin myinfo =
{
name = "ArcaneGame Custom Rounds Core",
author = "deidara.dev",
description = "Core plugin for custom rounds with AG Coin integration",
version = "1.3.0",
url = "https://deidara.dev"
};
CustomRoundType g_PendingRound = CR_None;
CustomRoundType g_CurrentRound = CR_None;
bool g_RoundLive = false;
bool g_ModeApplied = false;
bool g_PlayerParticipated[MAXPLAYERS + 1];
int g_PlayerKills[MAXPLAYERS + 1];
int g_PlayerHeadshots[MAXPLAYERS + 1];
bool g_LoadoutSaved[MAXPLAYERS + 1];
bool g_RestoreLoadoutOnSpawn[MAXPLAYERS + 1];
char g_SavedPrimary[MAXPLAYERS + 1][64];
int g_SavedPrimaryClip[MAXPLAYERS + 1];
int g_SavedPrimaryReserve[MAXPLAYERS + 1];
char g_SavedSecondary[MAXPLAYERS + 1][64];
int g_SavedSecondaryClip[MAXPLAYERS + 1];
int g_SavedSecondaryReserve[MAXPLAYERS + 1];
char g_SavedMelee[MAXPLAYERS + 1][64];
char g_SavedGrenades[MAXPLAYERS + 1][MAX_SAVED_GRENADES][32];
int g_SavedGrenadeCount[MAXPLAYERS + 1];
int g_SavedArmor[MAXPLAYERS + 1];
bool g_SavedHelmet[MAXPLAYERS + 1];
bool g_SavedDefuser[MAXPLAYERS + 1];
int g_RoundCounter = 0;
int g_LastCustomRoundNumber = -1000;
// 1vAll state
int g_OneVsAllChoice = 0; // userid выбранного админом T (0 = рандом)
int g_OneVsAllTUserId = 0; // userid реально выбранного T в текущем раунде
int g_OriginalTeam[MAXPLAYERS + 1]; // команда игрока до 1vAll
bool g_PendingTeamRestore = false; // нужно восстановить команды в начале следующего раунда
ConVar gCvarAccessFlag;
ConVar gCvarAccessUseOverrides;
ConVar gCvarCoinsEnable;
ConVar gCvarCoinsWin;
ConVar gCvarCoinsKill;
ConVar gCvarCoinsHeadshot;
ConVar gCvarCoinsSurvive;
ConVar gCvarLowGravityValue;
ConVar gCvarOneHPValue;
ConVar gCvarAnnounce;
ConVar gCvarShowMOTD;
ConVar gCvarCooldownRounds;
ConVar gCvarInfiniteAmmo;
int g_OldInfiniteAmmo = 0;
public void OnPluginStart()
{
RegConsoleCmd("sm_cr", Command_CR, "Opens Custom Rounds menu");
RegConsoleCmd("sm_cr_status", Command_CR_Status, "Show current/pending custom round and cooldown status");
HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
HookEvent("player_spawn", Event_PlayerSpawn, EventHookMode_Post);
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Post);
AddCommandListener(CommandListener_BuyBlock, "buy");
AddCommandListener(CommandListener_BuyBlock, "autobuy");
AddCommandListener(CommandListener_BuyBlock, "rebuy");
AddCommandListener(CommandListener_ZoomBlock, "zoom");
gCvarAccessFlag = CreateConVar("sm_cr_access_flag", "b", "Fallback admin flag for sm_cr access when using admin_overrides.cfg access. Example: b.", FCVAR_NONE);
gCvarAccessUseOverrides = CreateConVar("sm_cr_access_use_overrides", "1", "Allow access to sm_cr via admin_overrides.cfg in addition to DEIDARA and TESTER groups.", FCVAR_NONE, true, 0.0, true, 1.0);
gCvarCoinsEnable = CreateConVar("sm_cr_coins_enable", "1", "Enable AG Coin rewards for custom rounds.", FCVAR_NONE, true, 0.0, true, 1.0);
gCvarCoinsWin = CreateConVar("sm_cr_coins_win", "0", "Coins for winning a custom round.", FCVAR_NONE, true, 0.0);
gCvarCoinsKill = CreateConVar("sm_cr_coins_kill", "0", "Coins for each kill during a custom round.", FCVAR_NONE, true, 0.0);
gCvarCoinsHeadshot = CreateConVar("sm_cr_coins_headshot", "0", "Coins for each headshot during a custom round.", FCVAR_NONE, true, 0.0);
gCvarCoinsSurvive = CreateConVar("sm_cr_coins_survive", "0", "Coins for surviving a custom round win.", FCVAR_NONE, true, 0.0);
gCvarLowGravityValue = CreateConVar("sm_cr_lowgravity_value", "0.40", "Gravity value for Low Gravity round.", FCVAR_NONE, true, 0.1, true, 1.0);
gCvarOneHPValue = CreateConVar("sm_cr_onehp_value", "1", "Health value for One HP round.", FCVAR_NONE, true, 1.0, true, 100.0);
gCvarAnnounce = CreateConVar("sm_cr_announce", "1", "Show chat messages about custom rounds.", FCVAR_NONE, true, 0.0, true, 1.0);
gCvarShowMOTD = CreateConVar("sm_cr_show_motd", "1", "Show on-screen overlay (PrintHintText) when a custom round starts.", FCVAR_NONE, true, 0.0, true, 1.0);
gCvarCooldownRounds = CreateConVar("sm_cr_cooldown_rounds", "5", "Cooldown in rounds for regular admins between custom rounds. DEIDARA/TESTER and ROOT bypass.", FCVAR_NONE, true, 0.0);
gCvarInfiniteAmmo = FindConVar("sv_infinite_ammo");
AutoExecConfig(true, "ArcaneGame_CustomRounds_Core");
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
{
OnClientPutInServer(i);
}
}
}
public void OnClientPutInServer(int client)
{
FullResetClientState(client);
SDKHook(client, SDKHook_TraceAttack, Hook_TraceAttack);
}
public void OnClientDisconnect(int client)
{
// Если ТТшник вышел во время 1vAll — раунд заканчивается ничьёй
if (g_CurrentRound == CR_OneVsAll && g_RoundLive
&& client > 0 && client <= MaxClients
&& GetClientUserId(client) == g_OneVsAllTUserId)
{
char name[MAX_NAME_LENGTH];
GetClientName(client, name, sizeof(name));
PrintToChatAll("%s \x02ТТшник \x03%s\x02 вышел — раунд завершён ничьёй, оружие будет восстановлено.", CR_PREFIX, name);
LogCRAction(0, "1vAll: ТТшник %s вышел, раунд завершён ничьёй", name);
g_OneVsAllTUserId = 0;
CS_TerminateRound(0.5, CSRoundEnd_RoundDraw, false);
}
FullResetClientState(client);
}
void FullResetClientState(int client)
{
if (client < 1 || client > MaxClients)
{
return;
}
g_PlayerParticipated[client] = false;
g_PlayerKills[client] = 0;
g_PlayerHeadshots[client] = 0;
g_OriginalTeam[client] = 0;
g_LoadoutSaved[client] = false;
g_RestoreLoadoutOnSpawn[client] = false;
g_SavedPrimary[client][0] = '\0';
g_SavedPrimaryClip[client] = 0;
g_SavedPrimaryReserve[client] = 0;
g_SavedSecondary[client][0] = '\0';
g_SavedSecondaryClip[client] = 0;
g_SavedSecondaryReserve[client] = 0;
g_SavedMelee[client][0] = '\0';
g_SavedGrenadeCount[client] = 0;
for (int i = 0; i < MAX_SAVED_GRENADES; i++)
{
g_SavedGrenades[client][i][0] = '\0';
}
g_SavedArmor[client] = 0;
g_SavedHelmet[client] = false;
g_SavedDefuser[client] = false;
}
public void OnMapStart()
{
ResetRoundState(true);
g_RoundCounter = 0;
g_LastCustomRoundNumber = -1000;
}
public Action Command_CR(int client, int args)
{
if (client <= 0 || !IsClientInGame(client))
{
ReplyToCommand(client, "[CR] Команда доступна только в игре.");
return Plugin_Handled;
}
if (!HasCustomRoundsAccess(client))
{
PrintToChat(client, "%s \x02У тебя нет доступа к этому меню.", CR_PREFIX);
return Plugin_Handled;
}
OpenMainMenu(client);
return Plugin_Handled;
}
public Action Command_CR_Status(int client, int args)
{
if (client <= 0 || !IsClientInGame(client))
{
return Plugin_Handled;
}
char currentName[64], pendingName[64];
GetRoundDisplayName(g_CurrentRound, currentName, sizeof(currentName));
GetRoundDisplayName(g_PendingRound, pendingName, sizeof(pendingName));
PrintToChat(client, "%s Текущий: \x04%s\x01, следующий: \x04%s\x01.", CR_PREFIX, currentName, pendingName);
if (HasCustomRoundsAccess(client))
{
int remaining = GetCooldownRemaining(client);
if (remaining > 0)
{
PrintToChat(client, "%s До конца кулдауна: \x04%d\x01 раунд(ов).", CR_PREFIX, remaining);
}
else
{
PrintToChat(client, "%s Кулдаун: нет.", CR_PREFIX);
}
}
return Plugin_Handled;
}
bool HasCustomRoundsAccess(int client)
{
if (client <= 0 || !IsClientInGame(client))
{
return false;
}
if (IsFakeClient(client))
{
return false;
}
AdminId admin = GetUserAdmin(client);
if (admin == INVALID_ADMIN_ID)
{
return false;
}
// DEIDARA / TESTER — полный доступ независимо от флагов
if (IsClientInAllowedAdminGroup(admin, "DEIDARA") || IsClientInAllowedAdminGroup(admin, "TESTER"))
{
return true;
}
// Без групп — обязательно должны быть админ-флаги
// Иначе CheckCommandAccess может вернуть true, если admin_overrides.cfg содержит "sm_cr" с пустым флагом
int userFlags = GetUserFlagBits(client);
if (userFlags == 0)
{
return false;
}
if (!gCvarAccessUseOverrides.BoolValue)
{
return false;
}
int requiredFlags = ReadFlagStringToBits();
return CheckCommandAccess(client, "sm_cr", requiredFlags, false);
}
bool IsCooldownExempt(int client)
{
if (client <= 0 || !IsClientInGame(client))
{
return false;
}
AdminId admin = GetUserAdmin(client);
if (admin == INVALID_ADMIN_ID)
{
return false;
}
if ((GetUserFlagBits(client) & ADMFLAG_ROOT) != 0)
{
return true;
}
if (IsClientInAllowedAdminGroup(admin, "DEIDARA") || IsClientInAllowedAdminGroup(admin, "TESTER"))
{
return true;
}
return false;
}
int GetCooldownRemaining(int client)
{
if (IsCooldownExempt(client))
{
return 0;
}
int cd = gCvarCooldownRounds.IntValue;
if (cd <= 0)
{
return 0;
}
int passed = g_RoundCounter - g_LastCustomRoundNumber;
if (passed >= cd)
{
return 0;
}
return cd - passed;
}
bool IsClientInAllowedAdminGroup(AdminId admin, const char[] expectedGroupName)
{
char groupName[64];
int groupCount = GetAdminGroupCount(admin);
for (int i = 0; i < groupCount; i++)
{
GroupId groupId = GetAdminGroup(admin, i, groupName, sizeof(groupName));
if (groupId != INVALID_GROUP_ID && StrEqual(groupName, expectedGroupName, false))
{
return true;
}
}
return false;
}
int ReadFlagStringToBits()
{
char flagString[32];
gCvarAccessFlag.GetString(flagString, sizeof(flagString));
int bits = ReadFlagString(flagString);
if (bits == 0)
{
bits = ADMFLAG_GENERIC;
}
return bits;
}
void OpenMainMenu(int client)
{
Menu menu = new Menu(MenuHandler_Main);
char currentName[64], pendingName[64], title[256];
GetRoundDisplayName(g_CurrentRound, currentName, sizeof(currentName));
GetRoundDisplayName(g_PendingRound, pendingName, sizeof(pendingName));
int cooldown = GetCooldownRemaining(client);
if (cooldown > 0)
{
Format(title, sizeof(title), "Кастомные раунды\n \nТекущий: %s\nСледующий: %s\nКулдаун: %d р.", currentName, pendingName, cooldown);
}
else
{
Format(title, sizeof(title), "Кастомные раунды\n \nТекущий: %s\nСледующий: %s", currentName, pendingName);
}
menu.SetTitle(title);
menu.AddItem("1", "AWP Only");
menu.AddItem("2", "AWP NoScope");
menu.AddItem("3", "Scout Only");
menu.AddItem("4", "Scout NoScope");
menu.AddItem("5", "Deagle Only");
menu.AddItem("6", "Deagle HS Only");
menu.AddItem("7", "Только HE [беск. гранаты]");
menu.AddItem("8", "Ножевой раунд");
menu.AddItem("9", "Низкая гравитация");
menu.AddItem("10", "Режим 1 HP");
menu.AddItem("11", "1 vs All (от 15 игроков)");
menu.AddItem("c", "Отменить следующий кастомный раунд");
menu.ExitButton = true;
menu.Display(client, 20);
}
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;
}
// Защитная re-check: вдруг кто-то открыл меню вне Command_CR
if (!HasCustomRoundsAccess(client))
{
PrintToChat(client, "%s \x02У тебя нет доступа.", CR_PREFIX);
return 0;
}
char info[8];
menu.GetItem(item, info, sizeof(info));
if (StrEqual(info, "c"))
{
CancelPendingRound(client);
return 0;
}
int value = StringToInt(info);
// 1 vs All — открывает подменю выбора игрока
if (value == 11)
{
ShowOneVsAllMenu(client);
return 0;
}
CustomRoundType selected = CR_None;
switch (value)
{
case 1: selected = CR_AWP;
case 2: selected = CR_NoScope;
case 3: selected = CR_Scout;
case 4: selected = CR_ScoutNoScope;
case 5: selected = CR_Deagle;
case 6: selected = CR_DeagleHS;
case 7: selected = CR_HE;
case 8: selected = CR_Knife;
case 9: selected = CR_LowGravity;
case 10: selected = CR_OneHP;
}
if (selected != CR_None)
{
QueueCustomRound(client, selected);
}
return 0;
}
void ShowOneVsAllMenu(int client)
{
if (!HasCustomRoundsAccess(client))
{
return;
}
int activeCount = CountActivePlayers();
if (activeCount < MIN_PLAYERS_ONEVSALL)
{
PrintToChat(client, "%s \x02Для режима 1 vs All нужно \x04%d\x01\x02 игроков. Сейчас: \x04%d\x01\x02.",
CR_PREFIX, MIN_PLAYERS_ONEVSALL, activeCount);
return;
}
Menu menu = new Menu(MenuHandler_OneVsAll);
char title[128];
Format(title, sizeof(title), "1 vs All\nВыбор ТТшника (игроков: %d)", activeCount);
menu.SetTitle(title);
menu.AddItem("rand", "Случайный игрок");
char info[16], name[MAX_NAME_LENGTH];
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
if (GetClientTeam(i) < CS_TEAM_T)
{
continue;
}
Format(info, sizeof(info), "p:%d", GetClientUserId(i));
GetClientName(i, name, sizeof(name));
menu.AddItem(info, name);
}
menu.ExitButton = true;
menu.ExitBackButton = true;
menu.Display(client, 30);
}
public int MenuHandler_OneVsAll(Menu menu, MenuAction action, int client, int item)
{
if (action == MenuAction_End)
{
delete menu;
return 0;
}
if (action == MenuAction_Cancel)
{
if (item == MenuCancel_ExitBack)
{
OpenMainMenu(client);
}
return 0;
}
if (action != MenuAction_Select)
{
return 0;
}
if (!HasCustomRoundsAccess(client))
{
return 0;
}
char info[16];
menu.GetItem(item, info, sizeof(info));
if (StrEqual(info, "rand"))
{
g_OneVsAllChoice = 0; // 0 = рандом при старте раунда
}
else
{
// p:<userid>
g_OneVsAllChoice = StringToInt(info[2]);
}
QueueCustomRound(client, CR_OneVsAll);
return 0;
}
void QueueCustomRound(int client, CustomRoundType roundType)
{
if (!HasCustomRoundsAccess(client))
{
PrintToChat(client, "%s \x02У тебя нет доступа.", CR_PREFIX);
return;
}
int cooldown = GetCooldownRemaining(client);
if (cooldown > 0)
{
PrintToChat(client, "%s \x02Кулдаун: ещё \x04%d\x01\x02 раунд(ов) до запуска нового кастомного раунда.", CR_PREFIX, cooldown);
return;
}
// Для CR_OneVsAll разрешаем перевыбор (admin может менять цель)
if (g_PendingRound == roundType && roundType != CR_OneVsAll)
{
PrintToChat(client, "%s \x02Этот режим уже в очереди.", CR_PREFIX);
return;
}
g_PendingRound = roundType;
char roundName[64];
GetRoundDisplayName(roundType, roundName, sizeof(roundName));
if (gCvarAnnounce.BoolValue)
{
PrintToChatAll("%s \x01Администратор \x03%N\x01 выбрал режим: \x04%s\x01. Он начнётся в следующем раунде.", CR_PREFIX, client, roundName);
}
LogCRAction(client, "ВЫБРАЛ режим: %s", roundName);
}
void CancelPendingRound(int client)
{
if (!HasCustomRoundsAccess(client))
{
PrintToChat(client, "%s \x02У тебя нет доступа.", CR_PREFIX);
return;
}
if (g_PendingRound == CR_None)
{
PrintToChat(client, "%s \x02Сейчас нет запланированного кастомного раунда.", CR_PREFIX);
return;
}
char roundName[64];
GetRoundDisplayName(g_PendingRound, roundName, sizeof(roundName));
if (gCvarAnnounce.BoolValue)
{
PrintToChatAll("%s \x01Администратор \x03%N\x01 отменил режим: \x04%s\x01.", CR_PREFIX, client, roundName);
}
LogCRAction(client, "ОТМЕНИЛ режим: %s", roundName);
g_PendingRound = CR_None;
}
public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
g_RoundLive = true;
g_ModeApplied = false;
g_RoundCounter++;
ResetAllPlayerStats();
// Восстанавливаем команды после предыдущего 1vAll (до спавна игроков)
if (g_PendingTeamRestore)
{
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
if (g_OriginalTeam[i] >= CS_TEAM_T && GetClientTeam(i) != g_OriginalTeam[i])
{
CS_SwitchTeam(i, g_OriginalTeam[i]);
}
g_OriginalTeam[i] = 0;
}
g_PendingTeamRestore = false;
g_OneVsAllTUserId = 0;
}
if (g_PendingRound != CR_None)
{
// Валидация для 1vAll: нужно минимум 15 активных игроков
if (g_PendingRound == CR_OneVsAll)
{
int activeCount = CountActivePlayers();
if (activeCount < MIN_PLAYERS_ONEVSALL)
{
PrintToChatAll("%s \x02Режим 1 vs All отменён: нужно минимум \x04%d\x01\x02 игроков (сейчас \x04%d\x01\x02).",
CR_PREFIX, MIN_PLAYERS_ONEVSALL, activeCount);
LogCRAction(0, "1vAll отменён (игроков %d < %d)", activeCount, MIN_PLAYERS_ONEVSALL);
g_PendingRound = CR_None;
return;
}
}
g_CurrentRound = g_PendingRound;
g_PendingRound = CR_None;
g_LastCustomRoundNumber = g_RoundCounter;
}
if (g_CurrentRound != CR_None)
{
CreateTimer(0.2, Timer_ApplyRoundMode, _, TIMER_FLAG_NO_MAPCHANGE);
if (gCvarAnnounce.BoolValue)
{
char roundName[64];
GetRoundDisplayName(g_CurrentRound, roundName, sizeof(roundName));
PrintToChatAll("%s \x01Кастомный раунд начался: \x04%s\x01.", CR_PREFIX, roundName);
}
if (gCvarShowMOTD.BoolValue)
{
ShowFreezeImageToAll(g_CurrentRound);
}
}
}
public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
int winnerTeam = event.GetInt("winner");
if (g_CurrentRound != CR_None)
{
AwardEndRoundCoins(winnerTeam);
PrepareLoadoutRestoreForNextSpawn();
}
ResetRoundState(false);
g_RoundLive = false;
}
public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast)
{
int client = GetClientOfUserId(event.GetInt("userid"));
if (client <= 0 || !IsClientInGame(client))
{
return;
}
if (g_RestoreLoadoutOnSpawn[client] && g_CurrentRound == CR_None)
{
CreateTimer(0.15, Timer_RestoreClientLoadout, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
return;
}
if (g_CurrentRound == CR_None || !g_RoundLive)
{
return;
}
// Применяем мод per-spawn ТОЛЬКО если глобальный таймер уже отработал (опоздавший игрок).
// Иначе глобальный Timer_ApplyRoundMode сам всех обработает — без двойного применения.
if (!g_ModeApplied)
{
return;
}
CreateTimer(0.15, Timer_ApplyModeToClient, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
}
public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
if (g_CurrentRound == CR_None)
{
return;
}
int attacker = GetClientOfUserId(event.GetInt("attacker"));
int victim = GetClientOfUserId(event.GetInt("userid"));
bool headshot = event.GetBool("headshot");
if (attacker > 0 && attacker <= MaxClients && attacker != victim && IsClientInGame(attacker))
{
g_PlayerKills[attacker]++;
if (headshot)
{
g_PlayerHeadshots[attacker]++;
}
int killCoins = gCvarCoinsKill.IntValue;
if (killCoins > 0)
{
char reason[CR_REASON_MAX], roundName[64];
GetRoundDisplayName(g_CurrentRound, roundName, sizeof(roundName));
Format(reason, sizeof(reason), "CustomRound Kill (%s)", roundName);
CR_GiveCoins(attacker, killCoins, reason);
}
int headshotCoins = gCvarCoinsHeadshot.IntValue;
if (headshot && headshotCoins > 0)
{
char reason[CR_REASON_MAX], roundName[64];
GetRoundDisplayName(g_CurrentRound, roundName, sizeof(roundName));
Format(reason, sizeof(reason), "CustomRound Headshot (%s)", roundName);
CR_GiveCoins(attacker, headshotCoins, reason);
}
}
}
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 (client <= 0 || client > MaxClients || !IsClientInGame(client) || !IsPlayerAlive(client))
{
return Plugin_Continue;
}
if (g_CurrentRound == CR_NoScope || g_CurrentRound == CR_ScoutNoScope)
{
if (buttons & IN_ATTACK2)
{
buttons &= ~IN_ATTACK2;
return Plugin_Changed;
}
if (GetEntProp(client, Prop_Send, "m_bIsScoped") != 0)
{
SetEntProp(client, Prop_Send, "m_bIsScoped", 0);
SetEntProp(client, Prop_Send, "m_iFOV", 90);
}
}
return Plugin_Continue;
}
public Action Hook_TraceAttack(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &ammotype, int hitbox, int hitgroup)
{
if (g_CurrentRound != CR_DeagleHS)
{
return Plugin_Continue;
}
if (attacker <= 0 || attacker > MaxClients)
{
return Plugin_Continue;
}
// HITGROUP_HEAD = 1; всё остальное — 0 урона
if (hitgroup != 1)
{
damage = 0.0;
return Plugin_Changed;
}
return Plugin_Continue;
}
public Action Timer_ApplyRoundMode(Handle timer)
{
if (g_CurrentRound == CR_None)
{
return Plugin_Stop;
}
g_ModeApplied = true;
ApplyGlobalModeSettings(true);
if (g_CurrentRound == CR_OneVsAll)
{
SetupOneVsAll();
return Plugin_Stop;
}
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) || !IsPlayerAlive(client))
{
continue;
}
ApplyModeToClient(client);
}
return Plugin_Stop;
}
void SetupOneVsAll()
{
int chosenT = ResolveOneVsAllTarget();
if (chosenT <= 0)
{
PrintToChatAll("%s \x02Не удалось выбрать ТТшника, режим 1 vs All отменён.", CR_PREFIX);
g_CurrentRound = CR_None;
return;
}
g_OneVsAllTUserId = GetClientUserId(chosenT);
// 1) Сохраняем команды и инвентарь у живых игроков ДО смены команд
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
int team = GetClientTeam(i);
if (team < CS_TEAM_T)
{
// Игрок в спеке — пропускаем, команду не меняем
g_OriginalTeam[i] = 0;
continue;
}
g_OriginalTeam[i] = team;
g_PlayerParticipated[i] = true;
if (IsPlayerAlive(i))
{
SaveClientLoadout(i);
}
}
g_PendingTeamRestore = true;
// 2) Меняем команды и респавним
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
if (g_OriginalTeam[i] < CS_TEAM_T)
{
continue;
}
int target = (i == chosenT) ? CS_TEAM_T : CS_TEAM_CT;
if (GetClientTeam(i) != target)
{
CS_SwitchTeam(i, target);
}
if (!IsPlayerAlive(i))
{
CS_RespawnPlayer(i);
}
}
// 3) Через короткую задержку применяем 1vAll-лоадауты (после респавна игроков)
CreateTimer(0.3, Timer_OneVsAll_ApplyLoadouts, GetClientUserId(chosenT), TIMER_FLAG_NO_MAPCHANGE);
PrintToChatAll("%s \x011 vs All! \x04%N\x01 против всех. У него: \x045000 HP\x01, \x04500 брони\x01, \x04+25%% скорости\x01.",
CR_PREFIX, chosenT);
LogCRAction(0, "1vAll: ТТшник %N (userid %d)", chosenT, g_OneVsAllTUserId);
}
public Action Timer_OneVsAll_ApplyLoadouts(Handle timer, any tUserId)
{
if (g_CurrentRound != CR_OneVsAll)
{
return Plugin_Stop;
}
int chosenT = GetClientOfUserId(tUserId);
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || !IsPlayerAlive(i) || IsFakeClient(i))
{
continue;
}
StripPlayerWeapons(i);
GivePlayerItem(i, "weapon_knife");
if (i == chosenT)
{
// ТТшник: 5000 HP, 500 брони, +25% скорость, $16000, без оружия (купит сам)
SetEntProp(i, Prop_Data, "m_iHealth", 5000);
SetEntProp(i, Prop_Send, "m_ArmorValue", 500);
SetEntProp(i, Prop_Send, "m_bHasHelmet", 1);
SetEntPropFloat(i, Prop_Send, "m_flLaggedMovementValue", 1.25);
SetEntProp(i, Prop_Send, "m_iAccount", 16000);
SetEntityGravity(i, 1.0);
}
else
{
// CT: XM1014 + 4 гранаты, обычные HP/броня, нормальная скорость
SetEntProp(i, Prop_Data, "m_iHealth", 100);
SetEntProp(i, Prop_Send, "m_ArmorValue", 100);
SetEntProp(i, Prop_Send, "m_bHasHelmet", 1);
SetEntPropFloat(i, Prop_Send, "m_flLaggedMovementValue", 1.0);
SetEntityGravity(i, 1.0);
GivePlayerItem(i, "weapon_xm1014");
GivePlayerItem(i, "weapon_hegrenade");
GivePlayerItem(i, "weapon_flashbang");
GivePlayerItem(i, "weapon_smokegrenade");
GivePlayerItem(i, "weapon_incgrenade");
}
}
return Plugin_Stop;
}
int ResolveOneVsAllTarget()
{
// 1) Если админ выбрал конкретного — пробуем его
if (g_OneVsAllChoice > 0)
{
int client = GetClientOfUserId(g_OneVsAllChoice);
if (client > 0 && IsClientInGame(client) && !IsFakeClient(client) && GetClientTeam(client) >= CS_TEAM_T)
{
return client;
}
}
// 2) Рандом из активных игроков
int candidates[MAXPLAYERS + 1];
int count = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) >= CS_TEAM_T)
{
candidates[count++] = i;
}
}
if (count == 0)
{
return -1;
}
return candidates[GetRandomInt(0, count - 1)];
}
int CountActivePlayers()
{
int count = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) >= CS_TEAM_T)
{
count++;
}
}
return count;
}
public Action Timer_ApplyModeToClient(Handle timer, any userid)
{
int client = GetClientOfUserId(userid);
if (client <= 0 || !IsClientInGame(client) || !IsPlayerAlive(client))
{
return Plugin_Stop;
}
if (g_CurrentRound == CR_None)
{
return Plugin_Stop;
}
ApplyModeToClient(client);
return Plugin_Stop;
}
void ApplyModeToClient(int client)
{
g_PlayerParticipated[client] = true;
SaveClientLoadout(client);
switch (g_CurrentRound)
{
case CR_AWP:
{
StripPlayerWeapons(client);
GivePlayerItem(client, "weapon_knife");
GivePlayerItem(client, "weapon_awp");
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, 1.0);
}
case CR_NoScope:
{
StripPlayerWeapons(client);
GivePlayerItem(client, "weapon_knife");
GivePlayerItem(client, "weapon_awp");
SetEntProp(client, Prop_Send, "m_bIsScoped", 0);
SetEntProp(client, Prop_Send, "m_iFOV", 90);
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, 1.0);
}
case CR_HE:
{
StripPlayerWeapons(client);
GivePlayerItem(client, "weapon_knife");
GivePlayerItem(client, "weapon_hegrenade");
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, 1.0);
}
case CR_Knife:
{
StripPlayerWeapons(client);
GivePlayerItem(client, "weapon_knife");
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, 1.0);
}
case CR_Scout:
{
StripPlayerWeapons(client);
GivePlayerItem(client, "weapon_knife");
GivePlayerItem(client, "weapon_ssg08");
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, 1.0);
}
case CR_ScoutNoScope:
{
StripPlayerWeapons(client);
GivePlayerItem(client, "weapon_knife");
GivePlayerItem(client, "weapon_ssg08");
SetEntProp(client, Prop_Send, "m_bIsScoped", 0);
SetEntProp(client, Prop_Send, "m_iFOV", 90);
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, 1.0);
}
case CR_Deagle, CR_DeagleHS:
{
StripPlayerWeapons(client);
GivePlayerItem(client, "weapon_knife");
GivePlayerItem(client, "weapon_deagle");
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, 1.0);
}
case CR_LowGravity:
{
SetEntProp(client, Prop_Data, "m_iHealth", 100);
SetEntityGravity(client, gCvarLowGravityValue.FloatValue);
}
case CR_OneHP:
{
SetEntProp(client, Prop_Data, "m_iHealth", gCvarOneHPValue.IntValue);
SetEntityGravity(client, 1.0);
}
}
}
void ApplyGlobalModeSettings(bool enable)
{
if (gCvarInfiniteAmmo == null)
{
return;
}
if (enable)
{
g_OldInfiniteAmmo = gCvarInfiniteAmmo.IntValue;
if (g_CurrentRound == CR_HE)
{
gCvarInfiniteAmmo.IntValue = 1;
}
}
else
{
gCvarInfiniteAmmo.IntValue = g_OldInfiniteAmmo;
}
}
void ResetRoundState(bool fullReset)
{
ApplyGlobalModeSettings(false);
for (int client = 1; client <= MaxClients; client++)
{
if (IsClientInGame(client))
{
SetEntityGravity(client, 1.0);
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", 1.0);
}
ResetClientStats(client);
}
g_ModeApplied = false;
g_CurrentRound = CR_None;
if (fullReset)
{
g_PendingRound = CR_None;
}
}
void ResetAllPlayerStats()
{
for (int client = 1; client <= MaxClients; client++)
{
ResetClientStats(client);
}
}
void ResetClientStats(int client)
{
if (client < 1 || client > MaxClients)
{
return;
}
g_PlayerParticipated[client] = false;
g_PlayerKills[client] = 0;
g_PlayerHeadshots[client] = 0;
if (!g_RestoreLoadoutOnSpawn[client])
{
g_LoadoutSaved[client] = false;
g_SavedPrimary[client][0] = '\0';
g_SavedSecondary[client][0] = '\0';
g_SavedMelee[client][0] = '\0';
g_SavedGrenadeCount[client] = 0;
for (int i = 0; i < MAX_SAVED_GRENADES; i++)
{
g_SavedGrenades[client][i][0] = '\0';
}
g_SavedArmor[client] = 0;
g_SavedHelmet[client] = false;
g_SavedDefuser[client] = false;
}
}
void AwardEndRoundCoins(int winnerTeam)
{
int winCoins = gCvarCoinsWin.IntValue;
int surviveCoins = gCvarCoinsSurvive.IntValue;
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) || !g_PlayerParticipated[client])
{
continue;
}
if (winnerTeam >= CS_TEAM_T && GetClientTeam(client) == winnerTeam)
{
if (winCoins > 0)
{
char reason[CR_REASON_MAX], roundName[64];
GetRoundDisplayName(g_CurrentRound, roundName, sizeof(roundName));
Format(reason, sizeof(reason), "CustomRound Win (%s)", roundName);
CR_GiveCoins(client, winCoins, reason);
}
if (surviveCoins > 0 && IsPlayerAlive(client))
{
char reason[CR_REASON_MAX], roundName[64];
GetRoundDisplayName(g_CurrentRound, roundName, sizeof(roundName));
Format(reason, sizeof(reason), "CustomRound Survive (%s)", roundName);
CR_GiveCoins(client, surviveCoins, reason);
}
}
}
}
void SaveClientLoadout(int client)
{
if (g_LoadoutSaved[client])
{
return;
}
SaveWeaponSlot(client, 0, g_SavedPrimary[client], sizeof(g_SavedPrimary[]),
g_SavedPrimaryClip[client], g_SavedPrimaryReserve[client]);
SaveWeaponSlot(client, 1, g_SavedSecondary[client], sizeof(g_SavedSecondary[]),
g_SavedSecondaryClip[client], g_SavedSecondaryReserve[client]);
int meleeWeapon = GetPlayerWeaponSlot(client, 2);
if (meleeWeapon != -1 && IsValidEntity(meleeWeapon))
{
GetEntityClassname(meleeWeapon, g_SavedMelee[client], sizeof(g_SavedMelee[]));
}
else
{
g_SavedMelee[client][0] = '\0';
}
SaveAllGrenades(client);
g_SavedArmor[client] = GetEntProp(client, Prop_Send, "m_ArmorValue");
g_SavedHelmet[client] = GetEntProp(client, Prop_Send, "m_bHasHelmet") != 0;
g_SavedDefuser[client] = GetEntProp(client, Prop_Send, "m_bHasDefuser") != 0;
g_LoadoutSaved[client] = true;
}
void SaveWeaponSlot(int client, int slot, char[] classBuf, int classBufLen, int &clipOut, int &reserveOut)
{
classBuf[0] = '\0';
clipOut = 0;
reserveOut = 0;
int weapon = GetPlayerWeaponSlot(client, slot);
if (weapon == -1 || !IsValidEntity(weapon))
{
return;
}
GetEntityClassname(weapon, classBuf, classBufLen);
if (HasEntProp(weapon, Prop_Send, "m_iClip1"))
{
clipOut = GetEntProp(weapon, Prop_Send, "m_iClip1");
}
if (HasEntProp(weapon, Prop_Send, "m_iPrimaryReserveAmmoCount"))
{
reserveOut = GetEntProp(weapon, Prop_Send, "m_iPrimaryReserveAmmoCount");
}
}
void SaveAllGrenades(int client)
{
g_SavedGrenadeCount[client] = 0;
int offset = FindSendPropInfo("CCSPlayer", "m_hMyWeapons");
if (offset <= 0)
{
return;
}
char classname[32];
for (int i = 0; i < 64; i++)
{
int weapon = GetEntDataEnt2(client, offset + (i * 4));
if (weapon == -1 || !IsValidEntity(weapon))
{
continue;
}
GetEntityClassname(weapon, classname, sizeof(classname));
if (!IsGrenadeClassname(classname))
{
continue;
}
if (g_SavedGrenadeCount[client] < MAX_SAVED_GRENADES)
{
strcopy(g_SavedGrenades[client][g_SavedGrenadeCount[client]], 32, classname);
g_SavedGrenadeCount[client]++;
}
}
}
bool IsGrenadeClassname(const char[] classname)
{
return StrEqual(classname, "weapon_hegrenade")
|| StrEqual(classname, "weapon_flashbang")
|| StrEqual(classname, "weapon_smokegrenade")
|| StrEqual(classname, "weapon_molotov")
|| StrEqual(classname, "weapon_incgrenade")
|| StrEqual(classname, "weapon_decoy");
}
void PrepareLoadoutRestoreForNextSpawn()
{
for (int client = 1; client <= MaxClients; client++)
{
if (g_LoadoutSaved[client])
{
g_RestoreLoadoutOnSpawn[client] = true;
}
}
}
public Action Timer_RestoreClientLoadout(Handle timer, any userid)
{
int client = GetClientOfUserId(userid);
if (client <= 0 || !IsClientInGame(client) || !IsPlayerAlive(client))
{
return Plugin_Stop;
}
RestoreClientLoadout(client);
return Plugin_Stop;
}
void RestoreClientLoadout(int client)
{
if (!g_RestoreLoadoutOnSpawn[client])
{
return;
}
StripPlayerWeapons(client);
bool gaveSomething = false;
if (g_SavedMelee[client][0] != '\0')
{
GivePlayerItem(client, g_SavedMelee[client]);
gaveSomething = true;
}
if (g_SavedSecondary[client][0] != '\0')
{
int wep = GivePlayerItem(client, g_SavedSecondary[client]);
if (wep != -1 && IsValidEntity(wep))
{
if (HasEntProp(wep, Prop_Send, "m_iClip1"))
{
SetEntProp(wep, Prop_Send, "m_iClip1", g_SavedSecondaryClip[client]);
}
if (HasEntProp(wep, Prop_Send, "m_iPrimaryReserveAmmoCount"))
{
SetEntProp(wep, Prop_Send, "m_iPrimaryReserveAmmoCount", g_SavedSecondaryReserve[client]);
}
}
gaveSomething = true;
}
if (g_SavedPrimary[client][0] != '\0')
{
int wep = GivePlayerItem(client, g_SavedPrimary[client]);
if (wep != -1 && IsValidEntity(wep))
{
if (HasEntProp(wep, Prop_Send, "m_iClip1"))
{
SetEntProp(wep, Prop_Send, "m_iClip1", g_SavedPrimaryClip[client]);
}
if (HasEntProp(wep, Prop_Send, "m_iPrimaryReserveAmmoCount"))
{
SetEntProp(wep, Prop_Send, "m_iPrimaryReserveAmmoCount", g_SavedPrimaryReserve[client]);
}
}
gaveSomething = true;
}
for (int i = 0; i < g_SavedGrenadeCount[client]; i++)
{
if (g_SavedGrenades[client][i][0] != '\0')
{
GivePlayerItem(client, g_SavedGrenades[client][i]);
gaveSomething = true;
}
}
SetEntProp(client, Prop_Send, "m_ArmorValue", g_SavedArmor[client]);
SetEntProp(client, Prop_Send, "m_bHasHelmet", g_SavedHelmet[client] ? 1 : 0);
if (GetClientTeam(client) == CS_TEAM_CT)
{
SetEntProp(client, Prop_Send, "m_bHasDefuser", g_SavedDefuser[client] ? 1 : 0);
}
// Сбрасываем скорость и здоровье до дефолтных
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", 1.0);
if (!gaveSomething)
{
GivePlayerItem(client, "weapon_knife");
}
g_RestoreLoadoutOnSpawn[client] = false;
g_LoadoutSaved[client] = false;
g_SavedPrimary[client][0] = '\0';
g_SavedSecondary[client][0] = '\0';
g_SavedMelee[client][0] = '\0';
g_SavedGrenadeCount[client] = 0;
for (int i = 0; i < MAX_SAVED_GRENADES; i++)
{
g_SavedGrenades[client][i][0] = '\0';
}
g_SavedArmor[client] = 0;
g_SavedHelmet[client] = false;
g_SavedDefuser[client] = false;
}
bool CR_GiveCoins(int client, int amount, const char[] reason)
{
if (amount <= 0)
{
return false;
}
if (!gCvarCoinsEnable.BoolValue)
{
return false;
}
if (!IsClientInGame(client))
{
return false;
}
if (!AGC_IsLoaded())
{
return false;
}
if (!AGC_GetClientStatus(client))
{
return false;
}
bool result = AGC_AddCoins(client, amount, reason);
if (result)
{
PrintToChat(client, "%s \x01Ты получил \x04%d AG Coin\x01. Причина: \x03%s\x01.", CR_PREFIX, amount, reason);
}
return result;
}
public Action CommandListener_BuyBlock(int client, const char[] command, int argc)
{
if (client <= 0 || !IsClientInGame(client) || g_CurrentRound == CR_None)
{
return Plugin_Continue;
}
switch (g_CurrentRound)
{
case CR_AWP, CR_NoScope, CR_HE, CR_Knife, CR_Scout, CR_ScoutNoScope, CR_Deagle, CR_DeagleHS:
{
PrintCenterText(client, "Покупка отключена во время кастомного раунда");
return Plugin_Handled;
}
case CR_OneVsAll:
{
// ТТшник может покупать что угодно, остальным — нельзя
if (GetClientUserId(client) == g_OneVsAllTUserId)
{
return Plugin_Continue;
}
PrintCenterText(client, "Покупка отключена для CT в режиме 1 vs All");
return Plugin_Handled;
}
}
return Plugin_Continue;
}
public Action CommandListener_ZoomBlock(int client, const char[] command, int argc)
{
if (client <= 0 || !IsClientInGame(client))
{
return Plugin_Continue;
}
if (g_CurrentRound == CR_NoScope || g_CurrentRound == CR_ScoutNoScope)
{
return Plugin_Handled;
}
return Plugin_Continue;
}
void StripPlayerWeapons(int client)
{
int weapon;
for (int slot = 0; slot <= 4; slot++)
{
while ((weapon = GetPlayerWeaponSlot(client, slot)) != -1)
{
RemovePlayerItem(client, weapon);
AcceptEntityInput(weapon, "Kill");
}
}
}
void GetRoundDisplayName(CustomRoundType roundType, char[] buffer, int maxlen)
{
switch (roundType)
{
case CR_AWP: strcopy(buffer, maxlen, "AWP Only");
case CR_NoScope: strcopy(buffer, maxlen, "AWP NoScope");
case CR_HE: strcopy(buffer, maxlen, "HE Only");
case CR_Knife: strcopy(buffer, maxlen, "Ножевой раунд");
case CR_Scout: strcopy(buffer, maxlen, "Scout Only");
case CR_ScoutNoScope: strcopy(buffer, maxlen, "Scout NoScope");
case CR_Deagle: strcopy(buffer, maxlen, "Deagle Only");
case CR_DeagleHS: strcopy(buffer, maxlen, "Deagle HS Only");
case CR_LowGravity: strcopy(buffer, maxlen, "Низкая гравитация");
case CR_OneHP: strcopy(buffer, maxlen, "1 HP");
case CR_OneVsAll: strcopy(buffer, maxlen, "1 vs All");
default: strcopy(buffer, maxlen, "Нет");
}
}
void ShowFreezeImageToAll(CustomRoundType mode)
{
char title[64], description[128];
GetRoundDisplayName(mode, title, sizeof(title));
GetRoundDescription(mode, description, sizeof(description));
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
// PrintHintText — большое уведомление в правом нижнем углу со звуком
PrintHintText(i, "★ КАСТОМНЫЙ РАУНД ★\n%s\n\n%s", title, description);
}
}
void GetRoundDescription(CustomRoundType roundType, char[] buffer, int maxlen)
{
switch (roundType)
{
case CR_AWP: strcopy(buffer, maxlen, "Только AWP и нож");
case CR_NoScope: strcopy(buffer, maxlen, "AWP без прицела");
case CR_HE: strcopy(buffer, maxlen, "Только гранаты HE, бесконечный боезапас");
case CR_Knife: strcopy(buffer, maxlen, "Только ножи");
case CR_Scout: strcopy(buffer, maxlen, "Только SSG-08 и нож");
case CR_ScoutNoScope: strcopy(buffer, maxlen, "Scout без прицела");
case CR_Deagle: strcopy(buffer, maxlen, "Только Deagle и нож");
case CR_DeagleHS: strcopy(buffer, maxlen, "Только хедшоты наносят урон");
case CR_LowGravity: strcopy(buffer, maxlen, "Низкая гравитация");
case CR_OneHP: strcopy(buffer, maxlen, "У всех 1 HP");
case CR_OneVsAll: strcopy(buffer, maxlen, "1 против всех. У ТТшника 5000 HP, 500 брони, +25% скорости. CT — XM1014 + 4 гранаты.");
default: strcopy(buffer, maxlen, "");
}
}
void LogCRAction(int client, const char[] format, any ...)
{
char message[256];
VFormat(message, sizeof(message), format, 3);
char timestamp[32];
FormatTime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S");
char adminName[MAX_NAME_LENGTH], adminSteam[32];
if (client > 0 && IsClientInGame(client))
{
GetClientName(client, adminName, sizeof(adminName));
if (!GetClientAuthId(client, AuthId_Steam2, adminSteam, sizeof(adminSteam)))
{
strcopy(adminSteam, sizeof(adminSteam), "UNKNOWN");
}
}
else
{
strcopy(adminName, sizeof(adminName), "CONSOLE");
strcopy(adminSteam, sizeof(adminSteam), "CONSOLE");
}
char roleTag[16];
if (IsCooldownExempt(client))
{
strcopy(roleTag, sizeof(roleTag), "FULL");
}
else
{
strcopy(roleTag, sizeof(roleTag), "REG");
}
// Стандартный SM лог — для надёжности на shared-хостингах
LogMessage("[CR] [%s] %s (%s) -> %s [раунд %d]", roleTag, adminName, adminSteam, message, g_RoundCounter);
// Свой файл — через OpenFile с относительным путём (LogToFileEx с absolute путём от BuildPath
// на MyArena не создавал файл).
File logFile = OpenFile("addons/sourcemod/logs/custom_rounds.log", "a");
if (logFile != null)
{
logFile.WriteLine("[%s] [%s] %s (%s) -> %s [раунд %d]",
timestamp, roleTag, adminName, adminSteam, message, g_RoundCounter);
delete logFile;
}
else
{
LogError("[CR] Не удалось открыть addons/sourcemod/logs/custom_rounds.log на запись.");
}
}