v1.3.0: режим 1 vs All

Новый режим 1 vs All (от 15 игроков):
- 1 ТТшник против всех CT
- Админ выбирает игрока вручную или рандомно (подменю)
- ТТшник: 5000 HP, 500 брони, +25% скорости, $16000
- CT: XM1014 + HE + флеш + смок + incgrenade, покупка отключена
- ТТшник умер → CT побеждают
- ТТшник вышел → ничья (CS_TerminateRound), оружие восстанавливается
- После раунда команды восстанавливаются в исходные
- <15 игроков на старте → автоматическая отмена с уведомлением
This commit is contained in:
deidara
2026-05-01 18:26:01 +03:00
parent f047cf7667
commit e7f02da4a2
2 changed files with 357 additions and 5 deletions
+344 -3
View File
@@ -10,6 +10,7 @@
#define CR_PREFIX "\x04[ArcaneGame CR]\x01"
#define CR_REASON_MAX 128
#define MAX_SAVED_GRENADES 6
#define MIN_PLAYERS_ONEVSALL 15
enum CustomRoundType
{
@@ -23,7 +24,8 @@ enum CustomRoundType
CR_Deagle,
CR_DeagleHS,
CR_LowGravity,
CR_OneHP
CR_OneHP,
CR_OneVsAll
};
public Plugin myinfo =
@@ -31,7 +33,7 @@ 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",
version = "1.3.0",
url = "https://deidara.dev"
};
@@ -68,6 +70,12 @@ bool g_SavedDefuser[MAXPLAYERS + 1];
int g_RoundCounter = 0;
int g_LastCustomRoundNumber = -1000;
// 1vAll state
int g_OneVsAllChoice = 0; // userid выбранного админом T (0 = рандом)
int g_OneVsAllTUserId = 0; // userid реально выбранного T в текущем раунде
int g_OriginalTeam[MAXPLAYERS + 1]; // команда игрока до 1vAll
bool g_PendingTeamRestore = false; // нужно восстановить команды в начале следующего раунда
ConVar gCvarAccessFlag;
ConVar gCvarAccessUseOverrides;
ConVar gCvarCoinsEnable;
@@ -132,6 +140,20 @@ public void OnClientPutInServer(int client)
public void OnClientDisconnect(int client)
{
// Если ТТшник вышел во время 1vAll — раунд заканчивается ничьёй
if (g_CurrentRound == CR_OneVsAll && g_RoundLive
&& client > 0 && client <= MaxClients
&& GetClientUserId(client) == g_OneVsAllTUserId)
{
char name[MAX_NAME_LENGTH];
GetClientName(client, name, sizeof(name));
PrintToChatAll("%s \x02ТТшник \x03%s\x02 вышел — раунд завершён ничьёй, оружие будет восстановлено.", CR_PREFIX, name);
LogCRAction(0, "1vAll: ТТшник %s вышел, раунд завершён ничьёй", name);
g_OneVsAllTUserId = 0;
CS_TerminateRound(0.5, CSRoundEnd_RoundDraw, false);
}
FullResetClientState(client);
}
@@ -145,6 +167,7 @@ void FullResetClientState(int client)
g_PlayerParticipated[client] = false;
g_PlayerKills[client] = 0;
g_PlayerHeadshots[client] = 0;
g_OriginalTeam[client] = 0;
g_LoadoutSaved[client] = false;
g_RestoreLoadoutOnSpawn[client] = false;
@@ -367,6 +390,7 @@ void OpenMainMenu(int client)
menu.AddItem("8", "Ножевой раунд");
menu.AddItem("9", "Низкая гравитация");
menu.AddItem("10", "Режим 1 HP");
menu.AddItem("11", "1 vs All (от 15 игроков)");
menu.AddItem("c", "Отменить следующий кастомный раунд");
menu.ExitButton = true;
@@ -403,6 +427,14 @@ public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item)
}
int value = StringToInt(info);
// 1 vs All — открывает подменю выбора игрока
if (value == 11)
{
ShowOneVsAllMenu(client);
return 0;
}
CustomRoundType selected = CR_None;
switch (value)
@@ -427,6 +459,95 @@ public int MenuHandler_Main(Menu menu, MenuAction action, int client, int item)
return 0;
}
void ShowOneVsAllMenu(int client)
{
if (!HasCustomRoundsAccess(client))
{
return;
}
int activeCount = CountActivePlayers();
if (activeCount < MIN_PLAYERS_ONEVSALL)
{
PrintToChat(client, "%s \x02Для режима 1 vs All нужно \x04%d\x01\x02 игроков. Сейчас: \x04%d\x01\x02.",
CR_PREFIX, MIN_PLAYERS_ONEVSALL, activeCount);
return;
}
Menu menu = new Menu(MenuHandler_OneVsAll);
char title[128];
Format(title, sizeof(title), "1 vs All\nВыбор ТТшника (игроков: %d)", activeCount);
menu.SetTitle(title);
menu.AddItem("rand", "Случайный игрок");
char info[16], name[MAX_NAME_LENGTH];
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
if (GetClientTeam(i) < CS_TEAM_T)
{
continue;
}
Format(info, sizeof(info), "p:%d", GetClientUserId(i));
GetClientName(i, name, sizeof(name));
menu.AddItem(info, name);
}
menu.ExitButton = true;
menu.ExitBackButton = true;
menu.Display(client, 30);
}
public int MenuHandler_OneVsAll(Menu menu, MenuAction action, int client, int item)
{
if (action == MenuAction_End)
{
delete menu;
return 0;
}
if (action == MenuAction_Cancel)
{
if (item == MenuCancel_ExitBack)
{
OpenMainMenu(client);
}
return 0;
}
if (action != MenuAction_Select)
{
return 0;
}
if (!HasCustomRoundsAccess(client))
{
return 0;
}
char info[16];
menu.GetItem(item, info, sizeof(info));
if (StrEqual(info, "rand"))
{
g_OneVsAllChoice = 0; // 0 = рандом при старте раунда
}
else
{
// p:<userid>
g_OneVsAllChoice = StringToInt(info[2]);
}
QueueCustomRound(client, CR_OneVsAll);
return 0;
}
void QueueCustomRound(int client, CustomRoundType roundType)
{
if (!HasCustomRoundsAccess(client))
@@ -442,7 +563,8 @@ void QueueCustomRound(int client, CustomRoundType roundType)
return;
}
if (g_PendingRound == roundType)
// Для CR_OneVsAll разрешаем перевыбор (admin может менять цель)
if (g_PendingRound == roundType && roundType != CR_OneVsAll)
{
PrintToChat(client, "%s \x02Этот режим уже в очереди.", CR_PREFIX);
return;
@@ -496,8 +618,42 @@ public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
ResetAllPlayerStats();
// Восстанавливаем команды после предыдущего 1vAll (до спавна игроков)
if (g_PendingTeamRestore)
{
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
if (g_OriginalTeam[i] >= CS_TEAM_T && GetClientTeam(i) != g_OriginalTeam[i])
{
CS_SwitchTeam(i, g_OriginalTeam[i]);
}
g_OriginalTeam[i] = 0;
}
g_PendingTeamRestore = false;
g_OneVsAllTUserId = 0;
}
if (g_PendingRound != CR_None)
{
// Валидация для 1vAll: нужно минимум 15 активных игроков
if (g_PendingRound == CR_OneVsAll)
{
int activeCount = CountActivePlayers();
if (activeCount < MIN_PLAYERS_ONEVSALL)
{
PrintToChatAll("%s \x02Режим 1 vs All отменён: нужно минимум \x04%d\x01\x02 игроков (сейчас \x04%d\x01\x02).",
CR_PREFIX, MIN_PLAYERS_ONEVSALL, activeCount);
LogCRAction(0, "1vAll отменён (игроков %d < %d)", activeCount, MIN_PLAYERS_ONEVSALL);
g_PendingRound = CR_None;
return;
}
}
g_CurrentRound = g_PendingRound;
g_PendingRound = CR_None;
g_LastCustomRoundNumber = g_RoundCounter;
@@ -661,6 +817,12 @@ public Action Timer_ApplyRoundMode(Handle timer)
g_ModeApplied = true;
ApplyGlobalModeSettings(true);
if (g_CurrentRound == CR_OneVsAll)
{
SetupOneVsAll();
return Plugin_Stop;
}
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) || !IsPlayerAlive(client))
@@ -673,6 +835,169 @@ public Action Timer_ApplyRoundMode(Handle timer)
return Plugin_Stop;
}
void SetupOneVsAll()
{
int chosenT = ResolveOneVsAllTarget();
if (chosenT <= 0)
{
PrintToChatAll("%s \x02Не удалось выбрать ТТшника, режим 1 vs All отменён.", CR_PREFIX);
g_CurrentRound = CR_None;
return;
}
g_OneVsAllTUserId = GetClientUserId(chosenT);
// 1) Сохраняем команды и инвентарь у живых игроков ДО смены команд
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
int team = GetClientTeam(i);
if (team < CS_TEAM_T)
{
// Игрок в спеке — пропускаем, команду не меняем
g_OriginalTeam[i] = 0;
continue;
}
g_OriginalTeam[i] = team;
g_PlayerParticipated[i] = true;
if (IsPlayerAlive(i))
{
SaveClientLoadout(i);
}
}
g_PendingTeamRestore = true;
// 2) Меняем команды и респавним
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i))
{
continue;
}
if (g_OriginalTeam[i] < CS_TEAM_T)
{
continue;
}
int target = (i == chosenT) ? CS_TEAM_T : CS_TEAM_CT;
if (GetClientTeam(i) != target)
{
CS_SwitchTeam(i, target);
}
if (!IsPlayerAlive(i))
{
CS_RespawnPlayer(i);
}
}
// 3) Через короткую задержку применяем 1vAll-лоадауты (после респавна игроков)
CreateTimer(0.3, Timer_OneVsAll_ApplyLoadouts, GetClientUserId(chosenT), TIMER_FLAG_NO_MAPCHANGE);
PrintToChatAll("%s \x011 vs All! \x04%N\x01 против всех. У него: \x045000 HP\x01, \x04500 брони\x01, \x04+25%% скорости\x01.",
CR_PREFIX, chosenT);
LogCRAction(0, "1vAll: ТТшник %N (userid %d)", chosenT, g_OneVsAllTUserId);
}
public Action Timer_OneVsAll_ApplyLoadouts(Handle timer, any tUserId)
{
if (g_CurrentRound != CR_OneVsAll)
{
return Plugin_Stop;
}
int chosenT = GetClientOfUserId(tUserId);
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || !IsPlayerAlive(i) || IsFakeClient(i))
{
continue;
}
StripPlayerWeapons(i);
GivePlayerItem(i, "weapon_knife");
if (i == chosenT)
{
// ТТшник: 5000 HP, 500 брони, +25% скорость, $16000, без оружия (купит сам)
SetEntProp(i, Prop_Data, "m_iHealth", 5000);
SetEntProp(i, Prop_Send, "m_ArmorValue", 500);
SetEntProp(i, Prop_Send, "m_bHasHelmet", 1);
SetEntPropFloat(i, Prop_Send, "m_flLaggedMovementValue", 1.25);
SetEntProp(i, Prop_Send, "m_iAccount", 16000);
SetEntityGravity(i, 1.0);
}
else
{
// CT: XM1014 + 4 гранаты, обычные HP/броня, нормальная скорость
SetEntProp(i, Prop_Data, "m_iHealth", 100);
SetEntProp(i, Prop_Send, "m_ArmorValue", 100);
SetEntProp(i, Prop_Send, "m_bHasHelmet", 1);
SetEntPropFloat(i, Prop_Send, "m_flLaggedMovementValue", 1.0);
SetEntityGravity(i, 1.0);
GivePlayerItem(i, "weapon_xm1014");
GivePlayerItem(i, "weapon_hegrenade");
GivePlayerItem(i, "weapon_flashbang");
GivePlayerItem(i, "weapon_smokegrenade");
GivePlayerItem(i, "weapon_incgrenade");
}
}
return Plugin_Stop;
}
int ResolveOneVsAllTarget()
{
// 1) Если админ выбрал конкретного — пробуем его
if (g_OneVsAllChoice > 0)
{
int client = GetClientOfUserId(g_OneVsAllChoice);
if (client > 0 && IsClientInGame(client) && !IsFakeClient(client) && GetClientTeam(client) >= CS_TEAM_T)
{
return client;
}
}
// 2) Рандом из активных игроков
int candidates[MAXPLAYERS + 1];
int count = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) >= CS_TEAM_T)
{
candidates[count++] = i;
}
}
if (count == 0)
{
return -1;
}
return candidates[GetRandomInt(0, count - 1)];
}
int CountActivePlayers()
{
int count = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) >= CS_TEAM_T)
{
count++;
}
}
return count;
}
public Action Timer_ApplyModeToClient(Handle timer, any userid)
{
int client = GetClientOfUserId(userid);
@@ -800,6 +1125,7 @@ void ResetRoundState(bool fullReset)
if (IsClientInGame(client))
{
SetEntityGravity(client, 1.0);
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", 1.0);
}
ResetClientStats(client);
}
@@ -1071,6 +1397,9 @@ void RestoreClientLoadout(int client)
SetEntProp(client, Prop_Send, "m_bHasDefuser", g_SavedDefuser[client] ? 1 : 0);
}
// Сбрасываем скорость и здоровье до дефолтных
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", 1.0);
if (!gaveSomething)
{
GivePlayerItem(client, "weapon_knife");
@@ -1142,6 +1471,16 @@ public Action CommandListener_BuyBlock(int client, const char[] command, int arg
PrintCenterText(client, "Покупка отключена во время кастомного раунда");
return Plugin_Handled;
}
case CR_OneVsAll:
{
// ТТшник может покупать что угодно, остальным — нельзя
if (GetClientUserId(client) == g_OneVsAllTUserId)
{
return Plugin_Continue;
}
PrintCenterText(client, "Покупка отключена для CT в режиме 1 vs All");
return Plugin_Handled;
}
}
return Plugin_Continue;
@@ -1190,6 +1529,7 @@ void GetRoundDisplayName(CustomRoundType roundType, char[] buffer, int maxlen)
case CR_DeagleHS: strcopy(buffer, maxlen, "Deagle HS Only");
case CR_LowGravity: strcopy(buffer, maxlen, "Низкая гравитация");
case CR_OneHP: strcopy(buffer, maxlen, "1 HP");
case CR_OneVsAll: strcopy(buffer, maxlen, "1 vs All");
default: strcopy(buffer, maxlen, "Нет");
}
}
@@ -1226,6 +1566,7 @@ void GetRoundDescription(CustomRoundType roundType, char[] buffer, int maxlen)
case CR_DeagleHS: strcopy(buffer, maxlen, "Только хедшоты наносят урон");
case CR_LowGravity: strcopy(buffer, maxlen, "Низкая гравитация");
case CR_OneHP: strcopy(buffer, maxlen, "У всех 1 HP");
case CR_OneVsAll: strcopy(buffer, maxlen, "1 против всех. У ТТшника 5000 HP, 500 брони, +25% скорости. CT — XM1014 + 4 гранаты.");
default: strcopy(buffer, maxlen, "");
}
}