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:
@@ -4,7 +4,7 @@
|
||||
|
||||
## Функции
|
||||
|
||||
- 10 режимов кастомных раундов:
|
||||
- 11 режимов кастомных раундов:
|
||||
- **AWP Only** — только AWP + нож
|
||||
- **AWP NoScope** — AWP без прицела (zoom заблокирован)
|
||||
- **Scout Only** — только SSG-08 + нож
|
||||
@@ -15,6 +15,7 @@
|
||||
- **Ножевой раунд** — только ножи
|
||||
- **Низкая гравитация** — гравитация уменьшена (настраивается)
|
||||
- **1 HP** — у всех игроков минимальное здоровье
|
||||
- **1 vs All** (от 15 игроков) — 1 ТТшник против всех CT. У ТТшника 5000 HP, 500 брони, +25% скорости, $16000 на покупку любого оружия. CT — XM1014 + 4 гранаты (HE, флеш, смок, инсентари). Админ выбирает игрока вручную или рандомно.
|
||||
- Полное сохранение и восстановление инвентаря после кастомного раунда (основное оружие + патроны, запасное + патроны, нож, **все гранаты**, **броня**, **шлем**, **дефузер**)
|
||||
- Блокировка покупок во время режима
|
||||
- Блокировка зума для NoScope-режимов (через CommandListener + OnPlayerRunCmd)
|
||||
@@ -96,10 +97,20 @@ addons/sourcemod/logs/custom_rounds.log
|
||||
|
||||
## Версия
|
||||
|
||||
`1.2.4` — Автор: deidara.dev
|
||||
`1.3.0` — Автор: deidara.dev
|
||||
|
||||
### Changelog
|
||||
|
||||
- **1.3.0**
|
||||
- Добавлен режим **1 vs All** (требуется 15+ активных игроков):
|
||||
- 1 ТТшник против всех CT
|
||||
- Админ выбирает игрока вручную через подменю или рандомно
|
||||
- У ТТшника: 5000 HP, 500 брони, +25% скорости, $16000 (свободная покупка)
|
||||
- У CT: XM1014 + HE + флеш + смок + incgrenade, покупка отключена
|
||||
- Если ТТшник умрёт — CT побеждают (стандартная логика CSGO)
|
||||
- Если ТТшник вышел — раунд завершается ничьёй (`CS_TerminateRound`), оружие восстанавливается
|
||||
- После раунда команды восстанавливаются в исходные (CT возвращаются к T если были T до 1vAll)
|
||||
- Если игроков стало <15 в момент старта — режим автоматически отменяется с уведомлением в чат
|
||||
- **1.2.4**
|
||||
- Убраны `ShowMOTDPanel` и `PrintCenterText` (на CSGO/MyArena не показывались стабильно)
|
||||
- Оставлен только `PrintHintText` — он работает у всех игроков
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user