#pragma semicolon 1 #pragma newdecls required #include #include #include #include #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.1", 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 slug[32]; GetRoundUrlSlug(mode, slug, sizeof(slug)); if (slug[0] == '\0') { return; } char url[256]; Format(url, sizeof(url), "%s%s.html", CR_MOTD_BASE_URL, slug); char title[64]; GetRoundDisplayName(mode, title, sizeof(title)); KeyValues kv = new KeyValues("data"); kv.SetString("title", title); kv.SetNum("type", 2); // MOTDPANEL_TYPE_URL kv.SetString("msg", url); kv.SetNum("customsvr", 1); for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGame(i) || IsFakeClient(i)) { continue; } ShowVGUIPanel(i, "info", kv, true); } delete kv; } void LogCRAction(int client, const char[] format, any ...) { char message[256]; VFormat(message, sizeof(message), format, 3); char path[PLATFORM_MAX_PATH]; BuildPath(Path_SM, path, sizeof(path), CR_LOG_FILE); 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 лог (L.log) — для гарантии что запись проходит LogMessage("[CR] [%s] %s (%s) -> %s [раунд %d]", roleTag, adminName, adminSteam, message, g_RoundCounter); // Основной лог в отдельный файл LogToFileEx(path, "[%s] [%s] %s (%s) -> %s [раунд %d]", timestamp, roleTag, adminName, adminSteam, message, g_RoundCounter); }