Skip to content

Commit

Permalink
Clean up pause time counter logic:
Browse files Browse the repository at this point in the history
1. Remove g_PauseTimeUsed
2. Only fire necessary timers
3. Ensure fixed pause configuration takes precedence over combined max pause time
4. Don't allow unpausing fixed pauses (doesn't work)
5. Redirect sm_tech to admin pause if used in console
6. Prevent direct calls to mp_pause_match and mp_unpause_match, force sm_pause/sm_unpause
  • Loading branch information
nickdnk committed Jul 22, 2022
1 parent 67bb1f5 commit 9a9ef34
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 113 deletions.
17 changes: 16 additions & 1 deletion scripting/get5.sp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ ArrayList g_CvarNames = null;
ArrayList g_CvarValues = null;
bool g_InScrimMode = false;
bool g_HasKnifeRoundStarted = false;
bool g_IsChangingPauseState = false; // Used to prevent mp_pause_match and mp_unpause_match from being called directly.
float g_FixedPauseUnpauseTimerTime = 0.0; // on round start, this creates the unpause hook if the game has fixed-duration pause.
Get5Team g_PausingTeam = Get5Team_None; // The team that last called for a pause.

/** Other state **/
Get5State g_GameState = Get5State_None;
Expand Down Expand Up @@ -190,7 +193,6 @@ int g_TeamPausesUsed[MATCHTEAM_COUNT];
int g_TeamTechPausesUsed[MATCHTEAM_COUNT];
int g_TechPausedTimeOverride[MATCHTEAM_COUNT];
int g_TeamGivenTechPauseCommand[MATCHTEAM_COUNT];
int g_PauseTimeUsed = 0;
int g_ReadyTimeWaitingUsed = 0;
char g_DefaultTeamColors[][] = {
TEAM1_COLOR,
Expand Down Expand Up @@ -258,6 +260,7 @@ Handle g_OnRoundStatsUpdated = INVALID_HANDLE;
Handle g_OnSeriesInit = INVALID_HANDLE;
Handle g_OnSeriesResult = INVALID_HANDLE;
Handle g_OnSidePicked = INVALID_HANDLE;
Handle g_FixedPauseTimer = INVALID_HANDLE;

#include "get5/util.sp"
#include "get5/version.sp"
Expand Down Expand Up @@ -516,6 +519,8 @@ public void OnPluginStart() {
AddCommandListener(Command_Coach, "coach");
AddCommandListener(Command_JoinTeam, "jointeam");
AddCommandListener(Command_JoinGame, "joingame");
AddCommandListener(Command_PauseOrUnpauseMatch, "mp_pause_match");
AddCommandListener(Command_PauseOrUnpauseMatch, "mp_unpause_match");

/** Setup data structures **/
g_MapPoolList = new ArrayList(PLATFORM_MAX_PATH);
Expand Down Expand Up @@ -1301,6 +1306,12 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad

public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast) {
LogDebug("Event_FreezeEnd");
// If there's a changelevel command during a pause, we need to ensure that this
// handle gets reset. FreezeEnd is called immediately as the map loads, and the timer is always set with
// TIMER_FLAG_NO_MAPCHANGE, so its callback will not fire.
g_FixedPauseTimer = INVALID_HANDLE;
g_PausingTeam = Get5Team_None;

// We always want this to be correct, regardless of game state.
g_RoundStartedTime = GetEngineTime();
if (g_GameState == Get5State_Live) {
Expand Down Expand Up @@ -1346,6 +1357,10 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas
g_BombPlantedTime = 0.0;
g_BombSiteLastPlanted = Get5BombSite_Unknown;

// If a pause was pending with a fixed timer, begin counting down as the round starts.
// This function does nothing if no pending timer value was set.
StartFixedPauseUnpauseTimer();

if (g_GameState != Get5State_Live) {
g_RoundNumber = -1; // Round number always -1 if not yet live.
return;
Expand Down
4 changes: 2 additions & 2 deletions scripting/get5/backups.sp
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ public void RestoreGet5Backup() {
EndWarmup();
EndWarmup();
ServerCommand("mp_restartgame 5");
PauseGame(Get5Team_None, Get5PauseType_Backup, 1);
PauseGame(Get5Team_None, Get5PauseType_Backup);
if (g_CoachingEnabledCvar.BoolValue) {
CreateTimer(6.0, Timer_SwapCoaches);
}
Expand All @@ -377,7 +377,7 @@ public Action Timer_SwapCoaches(Handle timer) {
}

public Action Time_StartRestore(Handle timer) {
PauseGame(Get5Team_None, Get5PauseType_Backup, 1);
PauseGame(Get5Team_None, Get5PauseType_Backup);

char tempValveBackup[PLATFORM_MAX_PATH];
GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN);
Expand Down
8 changes: 2 additions & 6 deletions scripting/get5/mapveto.sp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ public void CreateVeto() {
g_VetoCaptains[Get5Team_2] = GetTeamCaptain(Get5Team_2);
ResetReadyStatus();
if (g_PauseOnVetoCvar.BoolValue) {
if (g_PausingEnabledCvar.BoolValue) {
PauseGame(Get5Team_None, Get5PauseType_Admin, 1);
} else {
ServerCommand("mp_pause_match");
}
PauseGame(Get5Team_None, Get5PauseType_Admin);
}
CreateTimer(1.0, Timer_VetoCountdown, _, TIMER_REPEAT);
}
Expand Down Expand Up @@ -50,7 +46,7 @@ public void VetoFinished() {
Get5_MessageToAll("%t", "MapDecidedInfoMessage");

if (IsPaused()) {
ServerCommand("mp_unpause_match");
UnpauseGame(Get5Team_None);
}

// Use total series score as starting point, to not print skipped maps
Expand Down
175 changes: 119 additions & 56 deletions scripting/get5/pausing.sp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ public bool Pauseable() {
return g_GameState >= Get5State_KnifeRound && g_PausingEnabledCvar.BoolValue;
}

public bool PauseGame(Get5Team team, Get5PauseType type, int pausesLeft) {
stock void PauseGame(Get5Team team, Get5PauseType type, int pausesLeft = 1, float fixedPauseTime = 0.0) {
if (type == Get5PauseType_None) {
LogError("PauseGame() called with Get5PauseType_None. Please call UnpauseGame() instead.");
UnpauseGame(team);
return;
}

Get5MatchPausedEvent event = new Get5MatchPausedEvent(g_MatchID, g_MapNumber, team, type);

LogDebug("Calling Get5_OnMatchPaused()");
Expand All @@ -13,10 +19,35 @@ public bool PauseGame(Get5Team team, Get5PauseType type, int pausesLeft) {

EventLogger_LogAndDeleteEvent(event);

return Pause(type, g_FixedPauseTimeCvar.IntValue, Get5TeamToCSTeam(team), pausesLeft);
g_PauseType = type;
g_PausingTeam = team;
g_IsChangingPauseState = true;
ServerCommand("mp_pause_match");
CreateTimer(0.1, Timer_ResetPauseRestriction);

// Setting props for anything other than fixed-time tactical pauses causes a bunch of issues.
// We did try messing around with m_bTechnicalTimeOut for tech pauses, but to no avail.
if (fixedPauseTime < 1 || type != Get5PauseType_Tactical) {
return;
}

Get5Side side = view_as<Get5Side>(Get5TeamToCSTeam(team));
if (side == Get5Side_T) {
GameRules_SetProp("m_bTerroristTimeOutActive", true);
GameRules_SetPropFloat("m_flTerroristTimeOutRemaining", fixedPauseTime);
GameRules_SetProp("m_nTerroristTimeOuts", pausesLeft);
} else if (side == Get5Side_CT) {
GameRules_SetProp("m_bCTTimeOutActive", true);
GameRules_SetPropFloat("m_flCTTimeOutRemaining", fixedPauseTime);
GameRules_SetProp("m_nCTTimeOuts", pausesLeft);
}
}

public Action Timer_ResetPauseRestriction(Handle timer, int data) {
g_IsChangingPauseState = false;
}

public void UnpauseGame(Get5Team team) {
stock void UnpauseGame(Get5Team team) {
Get5MatchUnpausedEvent event = new Get5MatchUnpausedEvent(g_MatchID, g_MapNumber, team);

LogDebug("Calling Get5_OnMatchUnpaused()");
Expand All @@ -27,17 +58,34 @@ public void UnpauseGame(Get5Team team) {

EventLogger_LogAndDeleteEvent(event);

Unpause();
g_PauseType = Get5PauseType_None;
g_PausingTeam = Get5Team_None;
delete g_FixedPauseTimer;
g_IsChangingPauseState = true;
ServerCommand("mp_unpause_match");
CreateTimer(0.1, Timer_ResetPauseRestriction);
}

public Action Command_PauseOrUnpauseMatch(int client, const char[] command, int argc) {
if (g_GameState == Get5State_None || g_IsChangingPauseState) {
return Plugin_Continue;
}
ReplyToCommand(client, "Get5 prevents calls to %s. Administrators should use sm_pause/sm_unpause.", command);
return Plugin_Stop;
}

public Action Command_TechPause(int client, int args) {
if (!g_AllowTechPauseCvar.BoolValue || !Pauseable() || IsPaused()) {
if (client == 0) {
// Redirect admin use of sm_tech to regular pause. We only have one type of admin pause.
return Command_Pause(client, args);
}

if (!g_AllowTechPauseCvar.BoolValue) {
Get5_MessageToAll("%t", "TechPausesNotEnabled");
return Plugin_Handled;
}

if (client == 0) {
PauseGame(Get5Team_None, Get5PauseType_Admin, 1);
Get5_MessageToAll("%t", "AdminForceTechPauseInfoMessage");
if (!Pauseable() || IsPaused()) {
return Plugin_Handled;
}

Expand Down Expand Up @@ -75,7 +123,7 @@ public Action Command_TechPause(int client, int args) {
}
}

PauseGame(team, Get5PauseType_Tech, 1);
PauseGame(team, Get5PauseType_Tech);
Get5_MessageToAll("%t", "MatchTechPausedByTeamMessage", client);

return Plugin_Handled;
Expand All @@ -87,7 +135,7 @@ public Action Command_Pause(int client, int args) {
}

if (client == 0) {
PauseGame(Get5Team_None, Get5PauseType_Admin, 1);
PauseGame(Get5Team_None, Get5PauseType_Admin);
Get5_MessageToAll("%t", "AdminForcePauseInfoMessage");
return Plugin_Handled;
}
Expand All @@ -104,10 +152,12 @@ public Action Command_Pause(int client, int args) {
return Plugin_Handled;
}

int maxPauseTime = g_MaxPauseTimeCvar.IntValue;
if (maxPauseTime > 0 && g_TeamPauseTimeUsed[team] >= maxPauseTime && IsPlayerTeam(team)) {
Get5_Message(client, "%t", "MaxPausesTimeUsedInfoMessage", maxPauseTime, pausePeriodString);
return Plugin_Handled;
if (!g_FixedPauseTimeCvar.BoolValue) {
int maxPauseTime = g_MaxPauseTimeCvar.IntValue;
if (maxPauseTime > 0 && g_TeamPauseTimeUsed[team] >= maxPauseTime && IsPlayerTeam(team)) {
Get5_Message(client, "%t", "MaxPausesTimeUsedInfoMessage", maxPauseTime, pausePeriodString);
return Plugin_Handled;
}
}

g_TeamReadyForUnpause[Get5Team_1] = false;
Expand All @@ -120,20 +170,32 @@ public Action Command_Pause(int client, int args) {
pausesLeft = g_MaxPausesCvar.IntValue - g_TeamPausesUsed[team] - 1;
}

// If the pause will need explicit resuming, we will create a timer to poll the pause status.
bool need_resume = PauseGame(team, Get5PauseType_Tactical, pausesLeft);
float fixedPauseTime = g_FixedPauseTimeCvar.FloatValue;
if (fixedPauseTime > 0.0 && fixedPauseTime < 15.0) {
fixedPauseTime = 15.0; // Don't allow less than 15 second pauses.
}

PauseGame(team, Get5PauseType_Tactical, pausesLeft, fixedPauseTime);

if (IsPlayer(client)) {
Get5_MessageToAll("%t", "MatchPausedByTeamMessage", client);
}

if (IsPlayerTeam(team)) {
if (need_resume) {
g_PauseTimeUsed = g_PauseTimeUsed + g_FixedPauseTimeCvar.IntValue - 1;
if (fixedPauseTime > 0) {
// If the pause time is fixed, we create a timer that fires 100ms before the in-game countdown (set by PauseGame)
// would have unpaused the game. This gives us the correct countdown but prevents firing the unpause command twice.
// The global handle is reset when the game unpauses and mp_pause_match and mp_unpause_match are both blocked to
// prevent users from messing up this logic.
g_FixedPauseUnpauseTimerTime = fixedPauseTime - 0.1;
if (InFreezeTime()) { // Don't start counting until freeze time begins.
StartFixedPauseUnpauseTimer();
} // else the counter starts on round start
} else if (g_MaxPauseTimeCvar.IntValue > 0) {
// Else, if we have a maximum pause time (sum of seconds), we will not use the in-game pause timer, but count
// down manually and unpause when the team runs out of pause time.
CreateTimer(1.0, Timer_PauseTimeCheck, team, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
// This timer is used to only fire off the Unpause event.
CreateTimer(1.0, Timer_UnpauseEventCheck, team, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
}
} // else pauses are unlimited un duration and must be unpaused explicitly by both teams; no timer required.

g_TeamPausesUsed[team]++;

Expand All @@ -146,7 +208,7 @@ public Action Command_Pause(int client, int args) {
if (pausesLeft == 1 && g_MaxPausesCvar.IntValue > 0) {
Get5_MessageToAll("%t", "OnePauseLeftInfoMessage", g_FormattedTeamNames[team], pausesLeft,
pausePeriodString);
} else if (g_MaxPausesCvar.IntValue > 0) {
} else {
Get5_MessageToAll("%t", "PausesLeftInfoMessage", g_FormattedTeamNames[team], pausesLeft,
pausePeriodString);
}
Expand All @@ -156,6 +218,17 @@ public Action Command_Pause(int client, int args) {
return Plugin_Handled;
}

stock void StartFixedPauseUnpauseTimer() {
if (g_FixedPauseUnpauseTimerTime == 0) {
LogDebug("Ignoring StartFixedPauseUnpauseTimer() call because g_FixedPauseUnpauseTimerTime was zero.");
return;
}
LogDebug("Starting fixed pause countdown with %f seconds.", g_FixedPauseUnpauseTimerTime);
delete g_FixedPauseTimer; // Never run the same timer twice; logically this shouldn't happen, but just to be sure.
g_FixedPauseTimer = CreateTimer(g_FixedPauseUnpauseTimerTime, Timer_UnpauseBeforeGameUnpause, g_PausingTeam, TIMER_FLAG_NO_MAPCHANGE);
g_FixedPauseUnpauseTimerTime = 0.0; // Reset
}

public Action Timer_TechPauseOverrideCheck(Handle timer, int data) {
Get5Team team = view_as<Get5Team>(data);
int maxTechPauseTime = g_MaxTechPauseTime.IntValue;
Expand Down Expand Up @@ -203,44 +276,29 @@ public Action Timer_TechPauseOverrideCheck(Handle timer, int data) {
}

// Someone can call pause during a round and will set this timer.
// Keep running timer until we are paused.
// Keep running timer until we are unpaused.
return Plugin_Continue;
}

public Action Timer_UnpauseEventCheck(Handle timer, int data) {
if (!Pauseable()) {
g_PauseTimeUsed = 0;
return Plugin_Stop;
}

// Unlimited pause time.
if (g_MaxPauseTimeCvar.IntValue <= 0) {
// Reset state.
g_PauseTimeUsed = 0;
return Plugin_Stop;
}

if (!InFreezeTime()) {
// Someone can call pause during a round and will set this timer.
// Keep running timer until we are paused.
return Plugin_Continue;
} else {
if (g_PauseTimeUsed <= 0) {
Get5Team team = view_as<Get5Team>(data);
UnpauseGame(team);
// Reset state
g_PauseTimeUsed = 0;
return Plugin_Stop;
}
g_PauseTimeUsed--;
LogDebug("Subtracting time used. Current time = %d", g_PauseTimeUsed);
public Action Timer_UnpauseBeforeGameUnpause(Handle timer, Get5Team team) {
g_FixedPauseTimer = INVALID_HANDLE; // Always remove global reference to the timer.
if (!Pauseable() || !IsPaused()) {
LogError("Fixed pause timer unexpectedly found no paused state when it expired.");
return Plugin_Handled;
}
LogDebug("Unpausing tactical pause before in-game timer runs out.");
// We have to do this as UnpauseGame() kills the timer that calls this function, and SourceMod will error if we kill
// a timer inside its own callback.
RequestFrame(UnpauseInsideTimer, team);
return Plugin_Handled;
}

return Plugin_Continue;
public void UnpauseInsideTimer(Get5Team team) {
UnpauseGame(team);
}

public Action Timer_PauseTimeCheck(Handle timer, int data) {
if (!Pauseable() || !IsPaused() || g_FixedPauseTimeCvar.BoolValue) {
public Action Timer_PauseTimeCheck(Handle timer, Get5Team team) {
if (!Pauseable() || !IsPaused()) {
return Plugin_Stop;
}
int maxPauseTime = g_MaxPauseTimeCvar.IntValue;
Expand All @@ -254,10 +312,10 @@ public Action Timer_PauseTimeCheck(Handle timer, int data) {
Format(pausePeriodString, sizeof(pausePeriodString), " %t", "PausePeriodSuffix");
}

Get5Team team = view_as<Get5Team>(data);
int timeLeft = maxPauseTime - g_TeamPauseTimeUsed[team];
// Only count against the team's pause time if we're actually in the freezetime
// pause and they haven't requested an unpause yet.
// If the team that called the pause has indicated they are ready, no more time should be subtracted from their
// maximum pause time, but the timer must keep running as they could go back to not-ready-for-unpause before the
// other team unpauses, in which case we would keep counting their seconds used.
if (InFreezeTime() && !g_TeamReadyForUnpause[team]) {
g_TeamPauseTimeUsed[team]++;

Expand All @@ -281,6 +339,11 @@ public Action Command_Unpause(int client, int args) {
if (!IsPaused())
return Plugin_Handled;

if (g_FixedPauseTimer != INVALID_HANDLE) {
Get5_MessageToAll("%t", "FixedPauseCannotBeUnpaused");
return Plugin_Handled;
}

if (g_PauseType == Get5PauseType_Admin && client != 0) {
Get5_MessageToAll("%t", "UserCannotUnpauseAdmin");
return Plugin_Handled;
Expand Down
Loading

0 comments on commit 9a9ef34

Please sign in to comment.