Files
custom-rounds/scripting/ArcaneGame_CustomRounds_Core.sp
T
deidara f047cf7667 v1.2.4: оставлен только PrintHintText, убран неработающий MOTD/CenterText
- Удалены ShowMOTDPanel и PrintCenterText (не показывались на CSGO/MyArena)
- Удалены Timer_RepeatOverlay, GetRoundUrlSlug, CR_MOTD_BASE_URL
- Уведомление о режиме теперь только через PrintHintText
2026-05-01 18:01:36 +03:00

1283 lines
36 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
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
};
public Plugin myinfo =
{
name = "ArcaneGame Custom Rounds Core",
author = "deidara.dev",
description = "Core plugin for custom rounds with AG Coin integration",
version = "1.2.4",
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;
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)
{
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_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("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);
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 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;
}
if (g_PendingRound == roundType)
{
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();
if (g_PendingRound != CR_None)
{
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);
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) || !IsPlayerAlive(client))
{
continue;
}
ApplyModeToClient(client);
}
return Plugin_Stop;
}
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);
}
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);
}
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;
}
}
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");
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");
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 на запись.");
}
}