#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 enum CustomRoundType { CR_None = 0, CR_AWP, CR_NoScope, CR_HE, CR_Knife, CR_Scout, 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.1.0", url = "https://deidara.dev" }; CustomRoundType g_PendingRound = CR_None; CustomRoundType g_CurrentRound = CR_None; bool g_RoundLive = false; bool g_ModeApplied = false; bool g_PlayerParticipated[MAXPLAYERS + 1]; int g_PlayerKills[MAXPLAYERS + 1]; int g_PlayerHeadshots[MAXPLAYERS + 1]; bool g_LoadoutSaved[MAXPLAYERS + 1]; bool g_RestoreLoadoutOnSpawn[MAXPLAYERS + 1]; char g_SavedPrimary[MAXPLAYERS + 1][64]; char g_SavedSecondary[MAXPLAYERS + 1][64]; char g_SavedMelee[MAXPLAYERS + 1][64]; ConVar gCvarAccessFlag; ConVar gCvarAccessUseOverrides; ConVar gCvarCoinsEnable; ConVar gCvarCoinsWin; ConVar gCvarCoinsKill; ConVar gCvarCoinsHeadshot; ConVar gCvarCoinsSurvive; ConVar gCvarLowGravityValue; ConVar gCvarOneHPValue; ConVar gCvarAnnounce; ConVar gCvarInfiniteAmmo; int g_OldInfiniteAmmo = 0; public void OnPluginStart() { RegConsoleCmd("sm_cr", Command_CR, "Opens Custom Rounds menu"); 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); gCvarInfiniteAmmo = FindConVar("sv_infinite_ammo"); AutoExecConfig(true, "ArcaneGame_CustomRounds_Core"); } public void OnMapStart() { ResetRoundState(true); } public void OnClientDisconnect(int client) { ResetClientStats(client); } 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; } bool HasCustomRoundsAccess(int client) { if (client <= 0 || !IsClientInGame(client)) { return false; } AdminId admin = GetUserAdmin(client); if (admin == INVALID_ADMIN_ID) { return false; } if (IsClientInAllowedAdminGroup(admin, "DEIDARA") || IsClientInAllowedAdminGroup(admin, "TESTER")) { return true; } if (!GetConVarBool(gCvarAccessUseOverrides)) { return false; } int requiredFlags = ReadFlagStringToBits(); return CheckCommandAccess(client, "sm_cr", requiredFlags, false); } 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]; char pendingName[64]; char title[256]; GetRoundDisplayName(g_CurrentRound, currentName, sizeof(currentName)); GetRoundDisplayName(g_PendingRound, pendingName, sizeof(pendingName)); Format(title, sizeof(title), "Кастомные раунды\n \nТекущий: %s\nСледующий: %s", currentName, pendingName); menu.SetTitle(title); menu.AddItem("1", "AWP Only"); menu.AddItem("2", "AWP Only [NoScope]"); menu.AddItem("3", "Только HE [беск. гранаты]"); menu.AddItem("4", "Ножевой раунд"); menu.AddItem("5", "Только Scout"); menu.AddItem("6", "Низкая гравитация"); menu.AddItem("7", "Режим 1 HP"); menu.AddItem("8", "Отменить следующий кастомный раунд"); 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; } char info[8]; menu.GetItem(item, info, sizeof(info)); int value = StringToInt(info); switch (value) { case 1: QueueCustomRound(client, CR_AWP); case 2: QueueCustomRound(client, CR_NoScope); case 3: QueueCustomRound(client, CR_HE); case 4: QueueCustomRound(client, CR_Knife); case 5: QueueCustomRound(client, CR_Scout); case 6: QueueCustomRound(client, CR_LowGravity); case 7: QueueCustomRound(client, CR_OneHP); case 8: CancelPendingRound(client); } return 0; } void QueueCustomRound(int client, CustomRoundType roundType) { g_PendingRound = roundType; if (GetConVarBool(gCvarAnnounce)) { char roundName[64]; GetRoundDisplayName(roundType, roundName, sizeof(roundName)); PrintToChatAll("%s \x01Администратор \x03%N\x01 выбрал режим: \x04%s\x01. Он начнётся в следующем раунде.", CR_PREFIX, client, roundName); } } void CancelPendingRound(int client) { if (g_PendingRound == CR_None) { PrintToChat(client, "%s \x02Сейчас нет запланированного кастомного раунда.", CR_PREFIX); return; } if (GetConVarBool(gCvarAnnounce)) { char roundName[64]; GetRoundDisplayName(g_PendingRound, roundName, sizeof(roundName)); PrintToChatAll("%s \x01Администратор \x03%N\x01 отменил режим: \x04%s\x01.", CR_PREFIX, client, roundName); } g_PendingRound = CR_None; } public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { g_RoundLive = true; g_ModeApplied = false; ResetAllPlayerStats(); if (g_PendingRound != CR_None) { g_CurrentRound = g_PendingRound; g_PendingRound = CR_None; } if (g_CurrentRound != CR_None) { CreateTimer(0.2, Timer_ApplyRoundMode, _, TIMER_FLAG_NO_MAPCHANGE); if (GetConVarBool(gCvarAnnounce)) { char roundName[64]; GetRoundDisplayName(g_CurrentRound, roundName, sizeof(roundName)); PrintToChatAll("%s \x01Кастомный раунд начался: \x04%s\x01.", CR_PREFIX, roundName); } } } 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; } 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 = GetConVarInt(gCvarCoinsKill); if (killCoins > 0) { char reason[CR_REASON_MAX]; char roundName[64]; GetRoundDisplayName(g_CurrentRound, roundName, sizeof(roundName)); Format(reason, sizeof(reason), "CustomRound Kill (%s)", roundName); CR_GiveCoins(attacker, killCoins, reason); } int headshotCoins = GetConVarInt(gCvarCoinsHeadshot); if (headshot && headshotCoins > 0) { char reason[CR_REASON_MAX]; char 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) { 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", 0); } } 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", 0); 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_LowGravity: { SetEntProp(client, Prop_Data, "m_iHealth", 100); SetEntityGravity(client, GetConVarFloat(gCvarLowGravityValue)); } case CR_OneHP: { SetEntProp(client, Prop_Data, "m_iHealth", GetConVarInt(gCvarOneHPValue)); SetEntityGravity(client, 1.0); } } } void GiveDefaultCombatLoadout(int client) { GivePlayerItem(client, "weapon_glock"); GivePlayerItem(client, "weapon_ak47"); int team = GetClientTeam(client); if (team == CS_TEAM_CT) { StripPlayerWeapons(client); GivePlayerItem(client, "weapon_knife"); GivePlayerItem(client, "weapon_hkp2000"); GivePlayerItem(client, "weapon_m4a1"); } } 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'; } } void AwardEndRoundCoins(int winnerTeam) { int winCoins = GetConVarInt(gCvarCoinsWin); int surviveCoins = GetConVarInt(gCvarCoinsSurvive); 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]; char 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]; char 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; } SaveWeaponClassnameFromSlot(client, 0, g_SavedPrimary[client], sizeof(g_SavedPrimary[])); SaveWeaponClassnameFromSlot(client, 1, g_SavedSecondary[client], sizeof(g_SavedSecondary[])); SaveWeaponClassnameFromSlot(client, 2, g_SavedMelee[client], sizeof(g_SavedMelee[])); g_LoadoutSaved[client] = true; } void SaveWeaponClassnameFromSlot(int client, int slot, char[] buffer, int maxlen) { buffer[0] = '\0'; int weapon = GetPlayerWeaponSlot(client, slot); if (weapon == -1 || !IsValidEntity(weapon)) { return; } GetEntityClassname(weapon, buffer, maxlen); } 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') { GivePlayerItem(client, g_SavedSecondary[client]); gaveSomething = true; } if (g_SavedPrimary[client][0] != '\0') { GivePlayerItem(client, g_SavedPrimary[client]); gaveSomething = true; } 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'; } bool CR_GiveCoins(int client, int amount, const char[] reason) { if (amount <= 0) { return false; } if (!GetConVarBool(gCvarCoinsEnable)) { 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: { 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) { 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 Only [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_LowGravity: strcopy(buffer, maxlen, "Низкая гравитация"); case CR_OneHP: strcopy(buffer, maxlen, "1 HP"); default: strcopy(buffer, maxlen, "Нет"); } }