2026-05-01 17:16:23 +03:00
# pragma semicolon 1
2026-05-01 06:57:31 +03:00
# 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
2026-05-01 17:16:23 +03:00
# define MAX_SAVED_GRENADES 6
2026-05-01 18:26:01 +03:00
# define MIN_PLAYERS_ONEVSALL 15
2026-05-01 06:57:31 +03:00
enum CustomRoundType
{
CR_None = 0 ,
CR_AWP ,
CR_NoScope ,
CR_HE ,
CR_Knife ,
CR_Scout ,
2026-05-01 17:16:23 +03:00
CR_ScoutNoScope ,
CR_Deagle ,
CR_DeagleHS ,
2026-05-01 06:57:31 +03:00
CR_LowGravity ,
2026-05-01 18:26:01 +03:00
CR_OneHP ,
CR_OneVsAll
2026-05-01 06:57:31 +03:00
} ;
public Plugin myinfo =
{
name = " ArcaneGame Custom Rounds Core " ,
author = " deidara.dev " ,
description = " Core plugin for custom rounds with AG Coin integration " ,
2026-05-01 18:26:01 +03:00
version = " 1.3.0 " ,
2026-05-01 06:57:31 +03:00
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 ] ;
2026-05-01 17:16:23 +03:00
2026-05-01 06:57:31 +03:00
char g_SavedPrimary [ MAXPLAYERS + 1 ] [ 64 ] ;
2026-05-01 17:16:23 +03:00
int g_SavedPrimaryClip [ MAXPLAYERS + 1 ] ;
int g_SavedPrimaryReserve [ MAXPLAYERS + 1 ] ;
2026-05-01 06:57:31 +03:00
char g_SavedSecondary [ MAXPLAYERS + 1 ] [ 64 ] ;
2026-05-01 17:16:23 +03:00
int g_SavedSecondaryClip [ MAXPLAYERS + 1 ] ;
int g_SavedSecondaryReserve [ MAXPLAYERS + 1 ] ;
2026-05-01 06:57:31 +03:00
char g_SavedMelee [ MAXPLAYERS + 1 ] [ 64 ] ;
2026-05-01 17:16:23 +03:00
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 ;
2026-05-01 18:26:01 +03:00
// 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 ; // нужно восстановить команды в начале следующего раунда
2026-05-01 06:57:31 +03:00
ConVar gCvarAccessFlag ;
ConVar gCvarAccessUseOverrides ;
ConVar gCvarCoinsEnable ;
ConVar gCvarCoinsWin ;
ConVar gCvarCoinsKill ;
ConVar gCvarCoinsHeadshot ;
ConVar gCvarCoinsSurvive ;
ConVar gCvarLowGravityValue ;
ConVar gCvarOneHPValue ;
ConVar gCvarAnnounce ;
2026-05-01 17:16:23 +03:00
ConVar gCvarShowMOTD ;
ConVar gCvarCooldownRounds ;
2026-05-01 06:57:31 +03:00
ConVar gCvarInfiniteAmmo ;
int g_OldInfiniteAmmo = 0 ;
public void OnPluginStart ( )
{
RegConsoleCmd ( " sm_cr " , Command_CR , " Opens Custom Rounds menu " ) ;
2026-05-01 17:16:23 +03:00
RegConsoleCmd ( " sm_cr_status " , Command_CR_Status , " Show current/pending custom round and cooldown status " ) ;
2026-05-01 06:57:31 +03:00
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 ) ;
2026-05-01 18:01:36 +03:00
gCvarShowMOTD = CreateConVar ( " sm_cr_show_motd " , " 1 " , " Show on-screen overlay (PrintHintText) when a custom round starts. " , FCVAR_NONE , true , 0.0 , true , 1.0 ) ;
2026-05-01 17:16:23 +03:00
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 ) ;
2026-05-01 06:57:31 +03:00
gCvarInfiniteAmmo = FindConVar ( " sv_infinite_ammo " ) ;
AutoExecConfig ( true , " ArcaneGame_CustomRounds_Core " ) ;
2026-05-01 17:16:23 +03:00
for ( int i = 1 ; i < = MaxClients ; i + + )
{
if ( IsClientInGame ( i ) )
{
OnClientPutInServer ( i ) ;
}
}
2026-05-01 06:57:31 +03:00
}
2026-05-01 17:16:23 +03:00
public void OnClientPutInServer ( int client )
2026-05-01 06:57:31 +03:00
{
2026-05-01 17:16:23 +03:00
FullResetClientState ( client ) ;
SDKHook ( client , SDKHook_TraceAttack , Hook_TraceAttack ) ;
2026-05-01 06:57:31 +03:00
}
public void OnClientDisconnect ( int client )
{
2026-05-01 18:26:01 +03:00
// Если ТТшник вышел во время 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 ) ;
}
2026-05-01 17:16:23 +03:00
FullResetClientState ( client ) ;
}
void FullResetClientState ( int client )
{
if ( client < 1 | | client > MaxClients )
{
return ;
}
g_PlayerParticipated [ client ] = false ;
g_PlayerKills [ client ] = 0 ;
g_PlayerHeadshots [ client ] = 0 ;
2026-05-01 18:26:01 +03:00
g_OriginalTeam [ client ] = 0 ;
2026-05-01 17:16:23 +03:00
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 ;
2026-05-01 06:57:31 +03:00
}
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 ;
}
2026-05-01 17:16:23 +03:00
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 ;
}
2026-05-01 06:57:31 +03:00
bool HasCustomRoundsAccess ( int client )
{
if ( client < = 0 | | ! IsClientInGame ( client ) )
{
return false ;
}
2026-05-01 17:34:07 +03:00
if ( IsFakeClient ( client ) )
{
return false ;
}
2026-05-01 06:57:31 +03:00
AdminId admin = GetUserAdmin ( client ) ;
if ( admin = = INVALID_ADMIN_ID )
{
return false ;
}
2026-05-01 17:34:07 +03:00
// DEIDARA / TESTER — полный доступ независимо от флагов
2026-05-01 06:57:31 +03:00
if ( IsClientInAllowedAdminGroup ( admin , " DEIDARA " ) | | IsClientInAllowedAdminGroup ( admin , " TESTER " ) )
{
return true ;
}
2026-05-01 17:34:07 +03:00
// Без групп — обязательно должны быть админ-флаги
// Иначе CheckCommandAccess может вернуть true, если admin_overrides.cfg содержит "sm_cr" с пустым флагом
int userFlags = GetUserFlagBits ( client ) ;
if ( userFlags = = 0 )
{
return false ;
}
2026-05-01 17:16:23 +03:00
if ( ! gCvarAccessUseOverrides . BoolValue )
2026-05-01 06:57:31 +03:00
{
return false ;
}
int requiredFlags = ReadFlagStringToBits ( ) ;
return CheckCommandAccess ( client , " sm_cr " , requiredFlags , false ) ;
}
2026-05-01 17:16:23 +03:00
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 ;
}
2026-05-01 06:57:31 +03:00
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 ) ;
2026-05-01 17:16:23 +03:00
char currentName [ 64 ] , pendingName [ 64 ] , title [ 256 ] ;
2026-05-01 06:57:31 +03:00
GetRoundDisplayName ( g_CurrentRound , currentName , sizeof ( currentName ) ) ;
GetRoundDisplayName ( g_PendingRound , pendingName , sizeof ( pendingName ) ) ;
2026-05-01 17:16:23 +03:00
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 ) ;
}
2026-05-01 06:57:31 +03:00
menu . SetTitle ( title ) ;
menu . AddItem ( " 1 " , " AWP Only " ) ;
2026-05-01 17:16:23 +03:00
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 " ) ;
2026-05-01 18:26:01 +03:00
menu . AddItem ( " 11 " , " 1 vs All (от 15 игроков) " ) ;
2026-05-01 17:16:23 +03:00
menu . AddItem ( " c " , " Отменить следующий кастомный раунд " ) ;
2026-05-01 06:57:31 +03:00
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 ;
}
2026-05-01 17:34:07 +03:00
// Защитная re-check: вдруг кто-то открыл меню вне Command_CR
if ( ! HasCustomRoundsAccess ( client ) )
{
PrintToChat ( client , " %s \x02 У тебя нет доступа." , CR_PREFIX ) ;
return 0 ;
}
2026-05-01 06:57:31 +03:00
char info [ 8 ] ;
menu . GetItem ( item , info , sizeof ( info ) ) ;
2026-05-01 17:16:23 +03:00
if ( StrEqual ( info , " c " ) )
{
CancelPendingRound ( client ) ;
return 0 ;
}
2026-05-01 06:57:31 +03:00
int value = StringToInt ( info ) ;
2026-05-01 18:26:01 +03:00
// 1 vs All — открывает подменю выбора игрока
if ( value = = 11 )
{
ShowOneVsAllMenu ( client ) ;
return 0 ;
}
2026-05-01 17:16:23 +03:00
CustomRoundType selected = CR_None ;
2026-05-01 06:57:31 +03:00
switch ( value )
{
2026-05-01 17:16:23 +03:00
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 ) ;
2026-05-01 06:57:31 +03:00
}
return 0 ;
}
2026-05-01 18:26:01 +03:00
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 ;
}
2026-05-01 06:57:31 +03:00
void QueueCustomRound ( int client , CustomRoundType roundType )
{
2026-05-01 17:34:07 +03:00
if ( ! HasCustomRoundsAccess ( client ) )
{
PrintToChat ( client , " %s \x02 У тебя нет доступа." , CR_PREFIX ) ;
return ;
}
2026-05-01 17:16:23 +03:00
int cooldown = GetCooldownRemaining ( client ) ;
if ( cooldown > 0 )
{
PrintToChat ( client , " %s \x02 Кулдаун: ещё \x04 %d \x01 \x02 раунд(ов) до запуска нового кастомного раунда. " , CR_PREFIX , cooldown ) ;
return ;
}
2026-05-01 18:26:01 +03:00
// Для CR_OneVsAll разрешаем перевыбор (admin может менять цель)
if ( g_PendingRound = = roundType & & roundType ! = CR_OneVsAll )
2026-05-01 17:16:23 +03:00
{
PrintToChat ( client , " %s \x02 Этот режим уже в очереди. " , CR_PREFIX ) ;
return ;
}
2026-05-01 06:57:31 +03:00
g_PendingRound = roundType ;
2026-05-01 17:16:23 +03:00
char roundName [ 64 ] ;
GetRoundDisplayName ( roundType , roundName , sizeof ( roundName ) ) ;
if ( gCvarAnnounce . BoolValue )
2026-05-01 06:57:31 +03:00
{
PrintToChatAll ( " %s \x01 Администратор \x03 %N \x01 выбрал режим: \x04 %s \x01 . Он начнётся в следующем раунде. " , CR_PREFIX , client , roundName ) ;
}
2026-05-01 17:16:23 +03:00
LogCRAction ( client , " ВЫБРАЛ режим: %s " , roundName ) ;
2026-05-01 06:57:31 +03:00
}
void CancelPendingRound ( int client )
{
2026-05-01 17:34:07 +03:00
if ( ! HasCustomRoundsAccess ( client ) )
{
PrintToChat ( client , " %s \x02 У тебя нет доступа." , CR_PREFIX ) ;
return ;
}
2026-05-01 06:57:31 +03:00
if ( g_PendingRound = = CR_None )
{
PrintToChat ( client , " %s \x02 Сейчас нет запланированного кастомного раунда. " , CR_PREFIX ) ;
return ;
}
2026-05-01 17:16:23 +03:00
char roundName [ 64 ] ;
GetRoundDisplayName ( g_PendingRound , roundName , sizeof ( roundName ) ) ;
if ( gCvarAnnounce . BoolValue )
2026-05-01 06:57:31 +03:00
{
PrintToChatAll ( " %s \x01 Администратор \x03 %N \x01 отменил режим: \x04 %s \x01 . " , CR_PREFIX , client , roundName ) ;
}
2026-05-01 17:16:23 +03:00
LogCRAction ( client , " ОТМЕНИЛ режим: %s " , roundName ) ;
2026-05-01 06:57:31 +03:00
g_PendingRound = CR_None ;
}
public void Event_RoundStart ( Event event , const char [ ] name , bool dontBroadcast )
{
g_RoundLive = true ;
g_ModeApplied = false ;
2026-05-01 17:16:23 +03:00
g_RoundCounter + + ;
2026-05-01 06:57:31 +03:00
ResetAllPlayerStats ( ) ;
2026-05-01 18:26:01 +03:00
// Восстанавливаем команды после предыдущего 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 ;
}
2026-05-01 06:57:31 +03:00
if ( g_PendingRound ! = CR_None )
{
2026-05-01 18:26:01 +03:00
// Валидация для 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 ;
}
}
2026-05-01 06:57:31 +03:00
g_CurrentRound = g_PendingRound ;
g_PendingRound = CR_None ;
2026-05-01 17:16:23 +03:00
g_LastCustomRoundNumber = g_RoundCounter ;
2026-05-01 06:57:31 +03:00
}
if ( g_CurrentRound ! = CR_None )
{
CreateTimer ( 0.2 , Timer_ApplyRoundMode , _ , TIMER_FLAG_NO_MAPCHANGE ) ;
2026-05-01 17:16:23 +03:00
if ( gCvarAnnounce . BoolValue )
2026-05-01 06:57:31 +03:00
{
char roundName [ 64 ] ;
GetRoundDisplayName ( g_CurrentRound , roundName , sizeof ( roundName ) ) ;
PrintToChatAll ( " %s \x01 Кастомный раунд начался: \x04 %s \x01 . " , CR_PREFIX , roundName ) ;
}
2026-05-01 17:16:23 +03:00
if ( gCvarShowMOTD . BoolValue )
{
ShowFreezeImageToAll ( g_CurrentRound ) ;
}
2026-05-01 06:57:31 +03:00
}
}
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 ;
}
2026-05-01 17:16:23 +03:00
// Применяем мод per-spawn ТОЛЬКО если глобальный таймер уже отработал (опоздавший игрок).
// Иначе глобальный Timer_ApplyRoundMode сам всех обработает — без двойного применения.
if ( ! g_ModeApplied )
{
return ;
}
2026-05-01 06:57:31 +03:00
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 ] + + ;
}
2026-05-01 17:16:23 +03:00
int killCoins = gCvarCoinsKill . IntValue ;
2026-05-01 06:57:31 +03:00
if ( killCoins > 0 )
{
2026-05-01 17:16:23 +03:00
char reason [ CR_REASON_MAX ] , roundName [ 64 ] ;
2026-05-01 06:57:31 +03:00
GetRoundDisplayName ( g_CurrentRound , roundName , sizeof ( roundName ) ) ;
Format ( reason , sizeof ( reason ) , " CustomRound Kill (%s) " , roundName ) ;
CR_GiveCoins ( attacker , killCoins , reason ) ;
}
2026-05-01 17:16:23 +03:00
int headshotCoins = gCvarCoinsHeadshot . IntValue ;
2026-05-01 06:57:31 +03:00
if ( headshot & & headshotCoins > 0 )
{
2026-05-01 17:16:23 +03:00
char reason [ CR_REASON_MAX ] , roundName [ 64 ] ;
2026-05-01 06:57:31 +03:00
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 ;
}
2026-05-01 17:16:23 +03:00
if ( g_CurrentRound = = CR_NoScope | | g_CurrentRound = = CR_ScoutNoScope )
2026-05-01 06:57:31 +03:00
{
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 ) ;
2026-05-01 17:16:23 +03:00
SetEntProp ( client , Prop_Send , " m_iFOV " , 90 ) ;
2026-05-01 06:57:31 +03:00
}
}
return Plugin_Continue ;
}
2026-05-01 17:16:23 +03:00
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 ;
}
2026-05-01 06:57:31 +03:00
public Action Timer_ApplyRoundMode ( Handle timer )
{
if ( g_CurrentRound = = CR_None )
{
return Plugin_Stop ;
}
g_ModeApplied = true ;
ApplyGlobalModeSettings ( true ) ;
2026-05-01 18:26:01 +03:00
if ( g_CurrentRound = = CR_OneVsAll )
{
SetupOneVsAll ( ) ;
return Plugin_Stop ;
}
2026-05-01 06:57:31 +03:00
for ( int client = 1 ; client < = MaxClients ; client + + )
{
if ( ! IsClientInGame ( client ) | | ! IsPlayerAlive ( client ) )
{
continue ;
}
ApplyModeToClient ( client ) ;
}
return Plugin_Stop ;
}
2026-05-01 18:26:01 +03:00
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 против всех. У него: \x0450 00 HP \x01 , \x0450 0 брони \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 ;
}
2026-05-01 06:57:31 +03:00
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 ) ;
2026-05-01 17:16:23 +03:00
SetEntProp ( client , Prop_Send , " m_iFOV " , 90 ) ;
2026-05-01 06:57:31 +03:00
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 ) ;
}
2026-05-01 17:16:23 +03:00
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 ) ;
}
2026-05-01 06:57:31 +03:00
case CR_LowGravity :
{
SetEntProp ( client , Prop_Data , " m_iHealth " , 100 ) ;
2026-05-01 17:16:23 +03:00
SetEntityGravity ( client , gCvarLowGravityValue . FloatValue ) ;
2026-05-01 06:57:31 +03:00
}
case CR_OneHP :
{
2026-05-01 17:16:23 +03:00
SetEntProp ( client , Prop_Data , " m_iHealth " , gCvarOneHPValue . IntValue ) ;
2026-05-01 06:57:31 +03:00
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 ) ;
2026-05-01 18:26:01 +03:00
SetEntPropFloat ( client , Prop_Send , " m_flLaggedMovementValue " , 1.0 ) ;
2026-05-01 06:57:31 +03:00
}
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' ;
2026-05-01 17:16:23 +03:00
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 ;
2026-05-01 06:57:31 +03:00
}
}
void AwardEndRoundCoins ( int winnerTeam )
{
2026-05-01 17:16:23 +03:00
int winCoins = gCvarCoinsWin . IntValue ;
int surviveCoins = gCvarCoinsSurvive . IntValue ;
2026-05-01 06:57:31 +03:00
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 )
{
2026-05-01 17:16:23 +03:00
char reason [ CR_REASON_MAX ] , roundName [ 64 ] ;
2026-05-01 06:57:31 +03:00
GetRoundDisplayName ( g_CurrentRound , roundName , sizeof ( roundName ) ) ;
Format ( reason , sizeof ( reason ) , " CustomRound Win (%s) " , roundName ) ;
CR_GiveCoins ( client , winCoins , reason ) ;
}
if ( surviveCoins > 0 & & IsPlayerAlive ( client ) )
{
2026-05-01 17:16:23 +03:00
char reason [ CR_REASON_MAX ] , roundName [ 64 ] ;
2026-05-01 06:57:31 +03:00
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 ;
}
2026-05-01 17:16:23 +03:00
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 ;
2026-05-01 06:57:31 +03:00
g_LoadoutSaved [ client ] = true ;
}
2026-05-01 17:16:23 +03:00
void SaveWeaponSlot ( int client , int slot , char [ ] classBuf , int classBufLen , int & clipOut , int & reserveOut )
2026-05-01 06:57:31 +03:00
{
2026-05-01 17:16:23 +03:00
classBuf [ 0 ] = '\0' ;
clipOut = 0 ;
reserveOut = 0 ;
2026-05-01 06:57:31 +03:00
int weapon = GetPlayerWeaponSlot ( client , slot ) ;
if ( weapon = = - 1 | | ! IsValidEntity ( weapon ) )
{
return ;
}
2026-05-01 17:16:23 +03:00
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 " ) ;
2026-05-01 06:57:31 +03:00
}
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' )
{
2026-05-01 17:16:23 +03:00
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 ] ) ;
}
}
2026-05-01 06:57:31 +03:00
gaveSomething = true ;
}
if ( g_SavedPrimary [ client ] [ 0 ] ! = '\0' )
{
2026-05-01 17:16:23 +03:00
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 ] ) ;
}
}
2026-05-01 06:57:31 +03:00
gaveSomething = true ;
}
2026-05-01 17:16:23 +03:00
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 ) ;
}
2026-05-01 18:26:01 +03:00
// Сбрасываем скорость и здоровье до дефолтных
SetEntPropFloat ( client , Prop_Send , " m_flLaggedMovementValue " , 1.0 ) ;
2026-05-01 06:57:31 +03:00
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' ;
2026-05-01 17:16:23 +03:00
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 ;
2026-05-01 06:57:31 +03:00
}
bool CR_GiveCoins ( int client , int amount , const char [ ] reason )
{
if ( amount < = 0 )
{
return false ;
}
2026-05-01 17:16:23 +03:00
if ( ! gCvarCoinsEnable . BoolValue )
2026-05-01 06:57:31 +03:00
{
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 )
{
2026-05-01 17:16:23 +03:00
case CR_AWP , CR_NoScope , CR_HE , CR_Knife , CR_Scout , CR_ScoutNoScope , CR_Deagle , CR_DeagleHS :
2026-05-01 06:57:31 +03:00
{
PrintCenterText ( client , " Покупка отключена во время кастомного раунда " ) ;
return Plugin_Handled ;
}
2026-05-01 18:26:01 +03:00
case CR_OneVsAll :
{
// ТТшник может покупать что угодно, остальным — нельзя
if ( GetClientUserId ( client ) = = g_OneVsAllTUserId )
{
return Plugin_Continue ;
}
PrintCenterText ( client , " Покупка отключена для CT в режиме 1 vs All " ) ;
return Plugin_Handled ;
}
2026-05-01 06:57:31 +03:00
}
return Plugin_Continue ;
}
public Action CommandListener_ZoomBlock ( int client , const char [ ] command , int argc )
{
if ( client < = 0 | | ! IsClientInGame ( client ) )
{
return Plugin_Continue ;
}
2026-05-01 17:16:23 +03:00
if ( g_CurrentRound = = CR_NoScope | | g_CurrentRound = = CR_ScoutNoScope )
2026-05-01 06:57:31 +03:00
{
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 " ) ;
2026-05-01 17:16:23 +03:00
case CR_NoScope : strcopy ( buffer , maxlen , " AWP NoScope " ) ;
2026-05-01 06:57:31 +03:00
case CR_HE : strcopy ( buffer , maxlen , " HE Only " ) ;
case CR_Knife : strcopy ( buffer , maxlen , " Ножевой раунд " ) ;
case CR_Scout : strcopy ( buffer , maxlen , " Scout Only " ) ;
2026-05-01 17:16:23 +03:00
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 " ) ;
2026-05-01 06:57:31 +03:00
case CR_LowGravity : strcopy ( buffer , maxlen , " Низкая гравитация " ) ;
case CR_OneHP : strcopy ( buffer , maxlen , " 1 HP " ) ;
2026-05-01 18:26:01 +03:00
case CR_OneVsAll : strcopy ( buffer , maxlen , " 1 vs All " ) ;
2026-05-01 06:57:31 +03:00
default : strcopy ( buffer , maxlen , " Нет " ) ;
}
2026-05-01 17:16:23 +03:00
}
void ShowFreezeImageToAll ( CustomRoundType mode )
{
2026-05-01 17:54:25 +03:00
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 ;
}
2026-05-01 18:01:36 +03:00
// PrintHintText — большое уведомление в правом нижнем углу со звуком
2026-05-01 17:54:25 +03:00
PrintHintText ( i , " ★ КАСТОМНЫЙ РАУНД ★ \n %s \n \n %s " , title , description ) ;
}
2026-05-01 17:47:45 +03:00
}
2026-05-01 17:16:23 +03:00
2026-05-01 17:47:45 +03:00
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" ) ;
2026-05-01 18:26:01 +03:00
case CR_OneVsAll : strcopy ( buffer , maxlen , " 1 против всех. У ТТшника 5000 HP, 500 брони, +25% скорости. CT — XM1014 + 4 гранаты. " ) ;
2026-05-01 17:47:45 +03:00
default : strcopy ( buffer , maxlen , " " ) ;
}
2026-05-01 17:16:23 +03:00
}
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 " ) ;
}
2026-05-01 17:47:45 +03:00
// Стандартный SM лог — для надёжности на shared-хостингах
2026-05-01 17:34:07 +03:00
LogMessage ( " [CR] [%s] %s (%s) -> %s [раунд %d] " , roleTag , adminName , adminSteam , message , g_RoundCounter ) ;
2026-05-01 17:47:45 +03:00
// Свой файл — через 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 на запись. " ) ;
}
2026-05-01 17:16:23 +03:00
}