13958f1e33
ShowHudText в CSGO не работает (это HL2-функция). Используем PrintHintText (угол со звуком) + PrintCenterText (центр) с повторами на 2/4/6 сек таймерами.
1351 lines
39 KiB
SourcePawn
1351 lines
39 KiB
SourcePawn
#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 CR_LOG_FILE "logs/custom_rounds.log"
|
||
#define CR_MOTD_BASE_URL "http://37.228.88.57/cr/"
|
||
#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.3",
|
||
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 MOTD image during freezetime 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 GetRoundUrlSlug(CustomRoundType roundType, char[] buffer, int maxlen)
|
||
{
|
||
switch (roundType)
|
||
{
|
||
case CR_AWP: strcopy(buffer, maxlen, "awp");
|
||
case CR_NoScope: strcopy(buffer, maxlen, "awp-noscope");
|
||
case CR_HE: strcopy(buffer, maxlen, "he");
|
||
case CR_Knife: strcopy(buffer, maxlen, "knife");
|
||
case CR_Scout: strcopy(buffer, maxlen, "scout");
|
||
case CR_ScoutNoScope: strcopy(buffer, maxlen, "scout-noscope");
|
||
case CR_Deagle: strcopy(buffer, maxlen, "deagle");
|
||
case CR_DeagleHS: strcopy(buffer, maxlen, "deagle-hs");
|
||
case CR_LowGravity: strcopy(buffer, maxlen, "lowgrav");
|
||
case CR_OneHP: strcopy(buffer, maxlen, "onehp");
|
||
default: strcopy(buffer, maxlen, "");
|
||
}
|
||
}
|
||
|
||
void ShowFreezeImageToAll(CustomRoundType mode)
|
||
{
|
||
char title[64], description[128];
|
||
GetRoundDisplayName(mode, title, sizeof(title));
|
||
GetRoundDescription(mode, description, sizeof(description));
|
||
|
||
char slug[32];
|
||
GetRoundUrlSlug(mode, slug, sizeof(slug));
|
||
|
||
char url[256];
|
||
if (slug[0] != '\0')
|
||
{
|
||
Format(url, sizeof(url), "%s%s.html", CR_MOTD_BASE_URL, slug);
|
||
}
|
||
|
||
for (int i = 1; i <= MaxClients; i++)
|
||
{
|
||
if (!IsClientInGame(i) || IsFakeClient(i))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 1) MOTD URL — для тех у кого включён HTML MOTD на клиенте
|
||
if (slug[0] != '\0')
|
||
{
|
||
ShowMOTDPanel(i, title, url, MOTDPANEL_TYPE_URL);
|
||
}
|
||
|
||
// 2) PrintHintText — уведомление в правом нижнем углу со звуком
|
||
PrintHintText(i, "★ КАСТОМНЫЙ РАУНД ★\n%s\n\n%s", title, description);
|
||
|
||
// 3) PrintCenterText — большой текст в центре экрана (первый показ)
|
||
PrintCenterText(i, "★ %s ★\n%s", title, description);
|
||
}
|
||
|
||
// Повторяем PrintCenterText на 2-й, 4-й, 6-й секундах чтобы держать сообщение видимым
|
||
// на протяжении freezetime (в CSGO он обычно 6-15 сек)
|
||
CreateTimer(2.0, Timer_RepeatOverlay, view_as<int>(mode), TIMER_FLAG_NO_MAPCHANGE);
|
||
CreateTimer(4.0, Timer_RepeatOverlay, view_as<int>(mode), TIMER_FLAG_NO_MAPCHANGE);
|
||
CreateTimer(6.0, Timer_RepeatOverlay, view_as<int>(mode), TIMER_FLAG_NO_MAPCHANGE);
|
||
}
|
||
|
||
public Action Timer_RepeatOverlay(Handle timer, any modeRaw)
|
||
{
|
||
CustomRoundType mode = view_as<CustomRoundType>(modeRaw);
|
||
if (mode != g_CurrentRound)
|
||
{
|
||
return Plugin_Stop;
|
||
}
|
||
|
||
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;
|
||
}
|
||
PrintCenterText(i, "★ %s ★\n%s", title, description);
|
||
}
|
||
|
||
return Plugin_Stop;
|
||
}
|
||
|
||
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 на запись.");
|
||
}
|
||
}
|