Skip to content

Commit

Permalink
Fix mp_match_restart_delay problems related to restoring cvars
Browse files Browse the repository at this point in the history
Cleaned up duplicate code used to start recording, moved all code to recording.sp
prevent invalid handle if RestoreCvars is called twice
  • Loading branch information
nickdnk committed Aug 11, 2022
1 parent 2082686 commit b046f02
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 134 deletions.
110 changes: 28 additions & 82 deletions scripting/get5.sp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ Handle g_OnSidePicked = INVALID_HANDLE;
#include "get5/natives.sp"
#include "get5/pausing.sp"
#include "get5/readysystem.sp"
#include "get5/recording.sp"
#include "get5/stats.sp"
#include "get5/teamlogic.sp"
#include "get5/tests.sp"
Expand Down Expand Up @@ -1111,6 +1112,22 @@ public Action Timer_ReplenishMoney(Handle timer, int client) {

public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast) {
LogDebug("Event_MatchOver");
if (g_GameState == Get5State_None) {
return Plugin_Continue;
}

// This ensures that the mp_match_restart_delay is not shorter
// than what is required for the GOTV recording to finish.
ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay");
if (mp_match_restart_delay != INVALID_HANDLE) {
int requiredDelay = GetTvDelay() + MATCH_END_DELAY_AFTER_TV;
if (requiredDelay > mp_match_restart_delay.IntValue) {
LogDebug("Extended mp_match_restart_delay from %d to %d to ensure GOTV can finish recording.",
mp_match_restart_delay.IntValue, requiredDelay);
mp_match_restart_delay.IntValue = requiredDelay;
}
}

if (g_GameState == Get5State_Live) {
// Figure out who won
int t1score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1));
Expand Down Expand Up @@ -1263,83 +1280,23 @@ public void EndSeries(Get5Team winningTeam, bool printWinnerMessage) {
Call_Finish();

EventLogger_LogAndDeleteEvent(event);

RestoreCvars(g_MatchConfigChangedCvars);
ChangeState(Get5State_None);
}

stock void StopRecording(bool forceStop = false) {
if (IsTVEnabled()) {
if (forceStop) {
LogDebug("Ending GOTV recording immediately by force.");
StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName);
return;
}
int tvDelay = GetTvDelay();
if (tvDelay > 0) {
LogDebug("Starting timer that will end GOTV recording in %d seconds.", tvDelay);
DataPack pack = CreateDataPack();
pack.WriteString(g_MatchID);
pack.WriteCell(g_MapNumber);
pack.WriteString(g_DemoFileName);
CreateTimer(float(tvDelay), Timer_StopGoTVRecording, pack, TIMER_FLAG_NO_MAPCHANGE);
} else {
LogDebug("Ending GOTV recording immediately as tv_delay is 0.");
StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName);
}
// We need to restore cvars on a timer if GOTV is recording, as it might otherwise set mp_match_restart_delay
// "back" to something that's shorter than the GOTV delay, which is a problem.
int goTvDelay = GetTvDelay();
if (goTvDelay > 0) {
CreateTimer(float(goTvDelay) + MATCH_END_DELAY_AFTER_TV, Timer_RestoreCvars);
} else {
LogDebug("Cannot stop GOTV as tv_enable is 0.");
RestoreCvars(g_MatchConfigChangedCvars);
}
}

static void StopRecordingCallback(char[] matchId, int mapNumber, char[] demoFileName) {
ServerCommand("tv_stoprecord");
if (StrEqual("", g_DemoFileName)) {
LogDebug("Demo was not recorded by Get5; not firing Get5_OnDemoFinished()");
return;
public Action Timer_RestoreCvars(Handle timer) {
if (g_GameState == Get5State_None) {
// Only reset if no game is running, otherwise a game started before the GOTV for another ends will mess this up.
RestoreCvars(g_MatchConfigChangedCvars);
}

// We delay this by 3 seconds to allow the server to flush to the file before firing the event.
// This requires a pack with the data, as the map might change and stuff might happen after the
// tv_delay has expired. This would also allow us to extend this delay later without breaking anything.
DataPack pack = CreateDataPack();
pack.WriteString(matchId);
pack.WriteCell(mapNumber);
pack.WriteString(demoFileName);

CreateTimer(3.0, Timer_FireStopRecordingEvent, pack);
}

public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) {

char matchId[MATCH_ID_LENGTH];
char demoFileName[PLATFORM_MAX_PATH];
pack.Reset();
pack.ReadString(matchId, sizeof(matchId));
int mapNumber = pack.ReadCell();
pack.ReadString(demoFileName, sizeof(demoFileName));
delete pack;

StopRecordingCallback(matchId, mapNumber, demoFileName);
return Plugin_Handled;
}

public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) {

char matchId[MATCH_ID_LENGTH];
char demoFileName[PLATFORM_MAX_PATH];
pack.Reset();
pack.ReadString(matchId, sizeof(matchId));
int mapNumber = pack.ReadCell();
pack.ReadString(demoFileName, sizeof(demoFileName));
delete pack;

Get5DemoFinishedEvent event = new Get5DemoFinishedEvent(matchId, mapNumber, demoFileName);
LogDebug("Calling Get5_OnDemoFinished()");
Call_StartForward(g_OnDemoFinished);
Call_PushCell(event);
Call_Finish();
EventLogger_LogAndDeleteEvent(event);
return Plugin_Handled;
}

Expand Down Expand Up @@ -1640,20 +1597,9 @@ public Action Event_CvarChanged(Event event, const char[] name, bool dontBroadca

public void StartGame(bool knifeRound) {
LogDebug("StartGame");
if (!IsTVEnabled()) {
LogMessage("GOTV demo could not be recorded since tv_enable is not set to 1");
g_DemoFileName = "";
} else {
char demoName[PLATFORM_MAX_PATH + 1];
if (FormatCvarString(g_DemoNameFormatCvar, demoName, sizeof(demoName)) && Record(demoName)) {
Format(g_DemoFileName, sizeof(g_DemoFileName), "%s.dem", demoName);
LogMessage("Recording to %s", g_DemoFileName);
} else {
g_DemoFileName = "";
}
}

ExecCfg(g_LiveCfgCvar);
StartRecording();

if (knifeRound) {
LogDebug("StartGame: about to begin knife round");
Expand Down
1 change: 0 additions & 1 deletion scripting/get5/backups.sp
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,6 @@ public void RestoreGet5Backup() {
ChangeState(Get5State_Live);
SetMatchTeamCvars();
ExecuteMatchConfigCvars();
SetMatchRestartDelay();

// There are some timing issues leading to incorrect score when restoring matches in second
// half. Doing the restore on a timer
Expand Down
20 changes: 0 additions & 20 deletions scripting/get5/goinglive.sp
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,7 @@ public Action MatchLive(Handle timer) {
// to be sure.
SetMatchTeamCvars();
ExecuteMatchConfigCvars();

// We force the match end-delay to extend for the duration of the GOTV broadcast here.
g_PendingSideSwap = false;
SetMatchRestartDelay();

for (int i = 0; i < 5; i++) {
Get5_MessageToAll("%t", "MatchIsLiveInfoMessage");
Expand All @@ -74,20 +71,3 @@ public Action MatchLive(Handle timer) {

return Plugin_Handled;
}

public void SetMatchRestartDelay() {
// We set this on a timer to make sure that all match configs and cvars etc. have been set when it runs.
CreateTimer(3.0, Timer_SetMatchRestartDelay, _, TIMER_FLAG_NO_MAPCHANGE);
}

public Action Timer_SetMatchRestartDelay(Handle timer) {
// This ensures that the mp_match_restart_delay is not shorter than what
// is required for the GOTV recording to finish.
ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay");
int requiredDelay = GetTvDelay() + MATCH_END_DELAY_AFTER_TV;
if (requiredDelay > mp_match_restart_delay.IntValue) {
LogDebug("Extended mp_match_restart_delay from %d to %d to ensure GOTV can finish recording.", mp_match_restart_delay.IntValue, requiredDelay);
mp_match_restart_delay.IntValue = requiredDelay;
}
return Plugin_Handled;
}
124 changes: 124 additions & 0 deletions scripting/get5/recording.sp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
stock bool StartRecording() {
if (!IsTVEnabled()) {
LogError("Demo recording will not work with \"tv_enable 0\". Set \"tv_enable 1\" and restart the map to fix this.");
g_DemoFileName = "";
return false;
}

char demoName[PLATFORM_MAX_PATH + 1];
if (!FormatCvarString(g_DemoNameFormatCvar, demoName, sizeof(demoName))) {
LogError("Failed to format demo filename. Please check your demo file format convar.");
g_DemoFileName = "";
return false;
}

Format(g_DemoFileName, sizeof(g_DemoFileName), "%s.dem", demoName);
LogMessage("Recording to %s", g_DemoFileName);

// Escape unsafe characters and start recording.
char szDemoName[PLATFORM_MAX_PATH + 1];
strcopy(szDemoName, sizeof(szDemoName), demoName);
ReplaceString(szDemoName, sizeof(szDemoName), "\"", "\\\"");
ServerCommand("tv_record \"%s\"", szDemoName);
return true;
}

stock void StopRecording(bool forceStop = false) {
if (!IsTVEnabled()) {
LogDebug("Cannot stop recording as GOTV is not enabled.");
return;
}
if (forceStop) {
LogDebug("Ending GOTV recording immediately by force.");
StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName);
return;
}
int tvDelay = GetTvDelay();
if (tvDelay > 0) {
LogDebug("Starting timer that will end GOTV recording in %d seconds.", tvDelay);
DataPack pack = CreateDataPack();
pack.WriteString(g_MatchID);
pack.WriteCell(g_MapNumber);
pack.WriteString(g_DemoFileName);
CreateTimer(float(tvDelay), Timer_StopGoTVRecording, pack, TIMER_FLAG_NO_MAPCHANGE); // changemap ends recording, so the timer cannot carry over.
} else {
LogDebug("Ending GOTV recording immediately as tv_delay is 0.");
StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName);
}
}

static void StopRecordingCallback(char[] matchId, int mapNumber, char[] demoFileName) {
ServerCommand("tv_stoprecord");
if (StrEqual("", demoFileName)) {
LogDebug("Demo was not recorded by Get5; not firing Get5_OnDemoFinished()");
return;
}

// We delay this by 3 seconds to allow the server to flush to the file before firing the event.
// This requires a pack with the data, as the map might change and stuff might happen after the
// tv_delay has expired. This would also allow us to extend this delay later without breaking anything.
DataPack pack = CreateDataPack();
pack.WriteString(matchId);
pack.WriteCell(mapNumber);
pack.WriteString(demoFileName);

CreateTimer(3.0, Timer_FireStopRecordingEvent, pack);
}

public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) {

char matchId[MATCH_ID_LENGTH];
char demoFileName[PLATFORM_MAX_PATH];
pack.Reset();
pack.ReadString(matchId, sizeof(matchId));
int mapNumber = pack.ReadCell();
pack.ReadString(demoFileName, sizeof(demoFileName));
delete pack;

StopRecordingCallback(matchId, mapNumber, demoFileName);
return Plugin_Handled;
}

public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) {

char matchId[MATCH_ID_LENGTH];
char demoFileName[PLATFORM_MAX_PATH];
pack.Reset();
pack.ReadString(matchId, sizeof(matchId));
int mapNumber = pack.ReadCell();
pack.ReadString(demoFileName, sizeof(demoFileName));
delete pack;

Get5DemoFinishedEvent event = new Get5DemoFinishedEvent(matchId, mapNumber, demoFileName);
LogDebug("Calling Get5_OnDemoFinished()");
Call_StartForward(g_OnDemoFinished);
Call_PushCell(event);
Call_Finish();
EventLogger_LogAndDeleteEvent(event);
return Plugin_Handled;
}

stock bool IsTVEnabled() {
ConVar tvEnabledCvar = FindConVar("tv_enable");
if (tvEnabledCvar == null) {
LogError("Failed to get tv_enable cvar");
return false;
}
if (tvEnabledCvar.BoolValue) {
// GOTV can be enabled without the bot actually running; map restart is
// required, so it might be disabled in edge-cases.
LOOP_CLIENTS(i) {
if (IsClientSourceTV(i)) {
return true;
}
}
}
return false;
}

stock int GetTvDelay() {
if (IsTVEnabled()) {
return GetCvarIntSafe("tv_delay");
}
return 0;
}
31 changes: 0 additions & 31 deletions scripting/get5/util.sp
Original file line number Diff line number Diff line change
Expand Up @@ -196,37 +196,6 @@ stock void ReplaceStringWithInt(char[] buffer, int len, const char[] replace, in
ReplaceString(buffer, len, replace, intString, caseSensitive);
}

stock bool IsTVEnabled() {
ConVar tvEnabledCvar = FindConVar("tv_enable");
if (tvEnabledCvar == null) {
LogError("Failed to get tv_enable cvar");
return false;
}
return tvEnabledCvar.BoolValue;
}

stock int GetTvDelay() {
if (IsTVEnabled()) {
return GetCvarIntSafe("tv_delay");
}
return 0;
}

stock bool Record(const char[] demoName) {
char szDemoName[256];
strcopy(szDemoName, sizeof(szDemoName), demoName);
ReplaceString(szDemoName, sizeof(szDemoName), "\"", "\\\"");
ServerCommand("tv_record \"%s\"", szDemoName);

if (!IsTVEnabled()) {
LogError(
"Autorecording will not work with current cvar \"tv_enable\"=0. Set \"tv_enable 1\" in server.cfg (or another config file) to fix this.");
return false;
}

return true;
}

stock bool InWarmup() {
return GameRules_GetProp("m_bWarmupPeriod") != 0;
}
Expand Down
4 changes: 4 additions & 0 deletions scripting/include/restorecvars.inc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ stock Handle SaveCvars(ArrayList cvarNames) {

// Restores cvars to their previous value using a return value of SaveCvars.
stock void RestoreCvars(Handle& cvarStorage, bool close = true) {
LogDebug("Restoring match cvars.");
if (cvarStorage == INVALID_HANDLE) {
return;
}
ArrayList cvarNameList = view_as<ArrayList>(GetArrayCell(cvarStorage, 0));
ArrayList cvarValueList = view_as<ArrayList>(GetArrayCell(cvarStorage, 1));

Expand Down

0 comments on commit b046f02

Please sign in to comment.