Skip to content

Commit

Permalink
Adjust GOTV and cvar restore logic (#841)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickdnk committed Aug 20, 2022
1 parent 43a7db7 commit aecac87
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 122 deletions.
2 changes: 0 additions & 2 deletions cfg/get5/live.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,4 @@ sv_holiday_mode 0
sv_talk_enemy_dead 0
sv_talk_enemy_living 0
sv_voiceenable 1
tv_delay 105
tv_delaymapchange 1
tv_relayvoice 0
3 changes: 0 additions & 3 deletions cfg/get5/warmup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,5 @@ sv_hibernate_when_empty 0
sv_infinite_ammo 0
sv_showimpacts 0
sv_voiceenable 1
tv_delay 105
tv_delaymapchange 1
tv_relayvoice 0

sv_cheats 0
7 changes: 5 additions & 2 deletions documentation/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ cfg/get5/live.cfg # (3)
: Whether the [`!stop`](../commands/#stop) command is enabled. **`Default: 1`**

####`get5_kick_when_no_match_loaded`
: Whether to kick all clients if no match is loaded. **`Default: 0`**
: Whether to kick all clients if no match is loaded. Players will not be kicked if a match is forcefully ended
using [`get5_endmatch`](../commands/#get5_endmatch). **`Default: 0`**

####`get5_end_match_on_empty_server`
: Whether the match is ended with no winner if all players leave (note: this will happen even if all players
Expand Down Expand Up @@ -188,7 +189,9 @@ must confirm. **`Default: 0`**
doing! Avoid using spaces or colons.** **`Default: %Y-%m-%d_%H`**

####`get5_demo_name_format`
: Format to name demo files. Set to empty string to disable. **`Default: {MATCHID}_map{MAPNUMBER}_{MAPNAME}`**
: Format to use for demo files when [recording matches](gotv.md). Do not include a file extension (`.dem` is added
automatically). Set to empty string to disable.<br>Note that the [`{MAPNUMBER}`](#tag-mapnumber) variable is not
zero-indexed!<br>**`Default: {MATCHID}_map{MAPNUMBER}_{MAPNAME}`**

####`get5_event_log_format`
: Format to write event logs to. Set to empty string to disable. **`Default: ""`**
Expand Down
22 changes: 22 additions & 0 deletions documentation/docs/gotv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# :material-filmstrip: GOTV & Demos {: #gotv }

Get5 can be configured to automatically record matches. This is enabled by default based on the state
of [`get5_demo_name_format`](../configuration/#get5_demo_name_format) and can be disabled by setting that parameter to
an empty string.

!!! warning "Don't mess too much with the delay!"

Changing the `tv_delay` or `tv_enable` in `warmup.cfg`, `live.cfg` etc. is going to cause problems with your demos.
We recommend you set this variable either on your server in general or only once in the `cvar` section of your
[match configuration](../match_schema). You should also not set `tv_delaymapchange` as Get5 handles this
automatically.

Demo recording starts once all teams have readied up and ends shortly following a map result. When a demo file is
written to disk, the [`Get5_OnDemoFinished`](events_and_forwards.md) forward is called, which you can use to move the
file or upload it somewhere. The filename can also be found in the map-section of the
[KeyValue stats system](../stats_system/#keyvalue).

Get5 will automatically adjust the [`mp_match_restart_delay`](https://totalcsgo.com/command/mpmatchrestartdelay) when a
map ends if GOTV is enabled to assure that it won't be shorter than what is required for the GOTV broadcast to finish.
Players will also not be [kicked from the server](../configuration/#get5_kick_when_no_match_loaded) before this delay
has passed.
1 change: 1 addition & 0 deletions documentation/docs/stats_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Partial Example:
"map0"
{
"mapname" "de_mirage"
"demo_filename" "304_map1_de_mirage.dem"
"winner" "team1"
"team1"
{
Expand Down
3 changes: 2 additions & 1 deletion documentation/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ nav:
- Configuration: configuration.md
- Basics:
- Getting Started: getting_started.md
- Match Schema: match_schema.md
- Commands: commands.md
- Pausing: pausing.md
- Backup System: backup.md
- Match Schema: match_schema.md
- GOTV & Demos: gotv.md
- Advanced:
- Developer API: developer_api.md
- Events & Forwards: events_and_forwards.md
Expand Down
124 changes: 66 additions & 58 deletions scripting/get5.sp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
#define DEBUG_CVAR "get5_debug"
#define MATCH_ID_LENGTH 64
#define MAX_CVAR_LENGTH 128
#define MATCH_END_DELAY_AFTER_TV 10
#define MATCH_END_DELAY_AFTER_TV 15

#define TEAM1_COLOR "{LIGHT_GREEN}"
#define TEAM2_COLOR "{PINK}"
Expand Down Expand Up @@ -797,8 +797,8 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr
g_GameState < Get5State_PostGame && GetRealClientCount() == 0 && !g_MapChangePending) {
g_TeamSeriesScores[Get5Team_1] = 0;
g_TeamSeriesScores[Get5Team_2] = 0;
StopRecording(true);
EndSeries(Get5Team_None, false, false);
StopRecording();
EndSeries(Get5Team_None, false, 0.0, false);
}
}

Expand Down Expand Up @@ -911,16 +911,19 @@ static void CheckReadyWaitingTimes() {

if (team1Forfeited || team2Forfeited) {
Stats_Forfeit();
StopRecording(true);
}

// False for printing in all these cases, as CheckReadyWaitingTime prints forfeit messages.
if (team1Forfeited && team2Forfeited) {
EndSeries(Get5Team_None, false, false);
} else if (team1Forfeited) {
EndSeries(Get5Team_2, false, false);
} else if (team2Forfeited) {
EndSeries(Get5Team_1, false, false);
float minDelay = 5.0;
StopRecording(minDelay);
float endDelay = float(GetTvDelay());
if (endDelay < minDelay) {
endDelay = minDelay;
}
if (team1Forfeited && team2Forfeited) {
EndSeries(Get5Team_None, false, endDelay);
} else if (team1Forfeited) {
EndSeries(Get5Team_2, false, endDelay);
} else {
EndSeries(Get5Team_1, false, endDelay);
}
}
}
}
Expand Down Expand Up @@ -1003,10 +1006,8 @@ public Action Command_EndMatch(int client, int args) {
Call_Finish();
EventLogger_LogAndDeleteEvent(mapResultEvent);

// Don't print series result here as admin force-end is printed below.
// We force-end the recording as the actual game is not ended, so there is no round restart to match the recording time to.
StopRecording(true);
EndSeries(winningTeam, false, false);
// No delay required when not kicking players.
EndSeries(winningTeam, false, 0.0, false);

UpdateClanTags();

Expand All @@ -1030,6 +1031,7 @@ public Action Command_EndMatch(int client, int args) {
delete g_KnifeDecisionTimer;
}

StopRecording(1.0);
ServerCommand("mp_restartgame 1");

return Plugin_Handled;
Expand Down Expand Up @@ -1186,15 +1188,14 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast

// 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;
}
float restartDelay = GetCurrentMatchRestartDelay();
float requiredDelay = float(GetTvDelay() + MATCH_END_DELAY_AFTER_TV);
if (requiredDelay > restartDelay) {
LogDebug("Extended mp_match_restart_delay from %f to %f to ensure GOTV broadcast can finish.", restartDelay, requiredDelay);
SetCurrentMatchRestartDelay(requiredDelay);
restartDelay = requiredDelay; // reassigned because we reuse the variable below.
}
StopRecording(float(MATCH_END_DELAY_AFTER_TV));

if (g_GameState == Get5State_Live) {

Expand Down Expand Up @@ -1245,32 +1246,26 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast
int tiedMaps = g_TeamSeriesScores[Get5Team_None];
int remainingMaps = g_MapsToPlay.Length - t1maps - t2maps - tiedMaps;

// Stops recording after GOTV has ended.
// mp_match_restart_delay is always of a longer duration than GOTV delay, so the recording
// **will** finish before the map changes, regardless if a next map is pending or if the
// series ends here.
StopRecording();

if (t1maps == t2maps) {
// As long as team scores are equal, we play until there are no maps left, regardless of clinch config.
if (remainingMaps <= 0) {
EndSeries(Get5Team_None, true, true);
EndSeries(Get5Team_None, true, restartDelay);
return Plugin_Continue;
}
} else if (g_SeriesCanClinch) {
// This adjusts for ties!
int actualMapsToWin = ((g_MapsToPlay.Length - tiedMaps) / 2) + 1;
if (t1maps == actualMapsToWin) {
// Team 1 won
EndSeries(Get5Team_1, true, true);
EndSeries(Get5Team_1, true, restartDelay);
return Plugin_Continue;
} else if (t2maps == actualMapsToWin) {
// Team 2 won
EndSeries(Get5Team_2, true, true);
EndSeries(Get5Team_2, true, restartDelay);
return Plugin_Continue;
}
} else if (remainingMaps <= 0) {
EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, true); // Tie handled in first if-block
EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, restartDelay); // Tie handled in first if-block
return Plugin_Continue;
}

Expand All @@ -1289,8 +1284,6 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast
char nextMap[PLATFORM_MAX_PATH];
g_MapsToPlay.GetString(Get5_GetMapNumber(), nextMap, sizeof(nextMap));

float restartDelay = FindConVar("mp_match_restart_delay").FloatValue;

char timeToMapChangeFormatted[8];
convertSecondsToMinutesAndSeconds(RoundToFloor(restartDelay), timeToMapChangeFormatted, sizeof(timeToMapChangeFormatted));

Expand All @@ -1313,19 +1306,7 @@ public Action Timer_NextMatchMap(Handle timer) {
ChangeMap(map, 3.0);
}

public void KickClientsOnEnd() {
LOOP_CLIENTS(i) {
if (IsPlayer(i) && !(g_KickClientImmunityCvar.BoolValue && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) {
KickClient(i, "%t", "MatchFinishedInfoMessage");
}
}
}

public void EndSeries(Get5Team winningTeam, bool printWinnerMessage, bool waitForGoTv) {
if (g_KickClientsWithNoMatchCvar.BoolValue) {
DelayFunction(10.0, KickClientsOnEnd);
}

void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay, bool kickPlayers = true) {
Stats_SeriesEnd(winningTeam);

if (printWinnerMessage) {
Expand Down Expand Up @@ -1356,19 +1337,46 @@ public void EndSeries(Get5Team winningTeam, bool printWinnerMessage, bool waitFo
EventLogger_LogAndDeleteEvent(event);
ChangeState(Get5State_None);

// 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 && waitForGoTv) {
CreateTimer(float(goTvDelay) + MATCH_END_DELAY_AFTER_TV, Timer_RestoreCvars);
} else {
// We don't want to kick players until after the specified delay, as it will kick casters potentially before GOTV ends.
if (kickPlayers && g_KickClientsWithNoMatchCvar.BoolValue) {
if (restoreDelay < 0.1) {
KickPlayers();
} else {
CreateTimer(restoreDelay, Timer_KickOnEnd, _, TIMER_FLAG_NO_MAPCHANGE);
}
}

if (restoreDelay < 0.1) {
// When force-ending the match there is no delay.
RestoreCvars(g_MatchConfigChangedCvars);
} else {
// If we restore cvars immediately, it might change the tv_ params or set the mp_match_restart_delay to something
// lower, which is noticed by the game and may trigger a map change before GOTV broadcast ends, so we don't do this
// until the current match restart delay has passed.
CreateTimer(restoreDelay, Timer_RestoreMatchCvars, _, TIMER_FLAG_NO_MAPCHANGE);
}
}

public Action Timer_KickOnEnd(Handle timer) {
if (g_GameState == Get5State_None) {
// If a match was started before this event is triggered, don't do anything.
KickPlayers();
}
return Plugin_Handled;
}

static void KickPlayers() {
bool kickImmunity = g_KickClientImmunityCvar.BoolValue;
LOOP_CLIENTS(i) {
if (IsPlayer(i) && !(kickImmunity && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) {
KickClient(i, "%t", "MatchFinishedInfoMessage");
}
}
}

public Action Timer_RestoreCvars(Handle timer) {
public Action Timer_RestoreMatchCvars(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.
// Only reset if no game is running, otherwise a game started before the restart delay for another ends will mess this up.
RestoreCvars(g_MatchConfigChangedCvars);
}
return Plugin_Handled;
Expand Down
10 changes: 6 additions & 4 deletions scripting/get5/mapveto.sp
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,16 @@ public void VetoFinished() {
Get5_MessageToAll("%t", "MapIsInfoMessage", i + 1 - seriesScore, map);
}

float delay = 10.0;
g_MapChangePending = true;
if (!g_SkipVeto && g_DisplayGotvVetoCvar.BoolValue) {
float minDelay = float(GetTvDelay()) + MATCH_END_DELAY_AFTER_TV;
StopRecording(); // stops recording after GetTvDelay() seconds.
CreateTimer(minDelay, Timer_NextMatchMap);
// Players must wait for GOTV to end before we can change map, but we don't need to record that.
CreateTimer(float(GetTvDelay()) + delay, Timer_NextMatchMap);
} else {
CreateTimer(10.0, Timer_NextMatchMap);
CreateTimer(delay, Timer_NextMatchMap);
}
// Always end recording here; ensures that we can successfully start one after veto.
StopRecording(delay);
}

// Main Veto Controller
Expand Down
Loading

0 comments on commit aecac87

Please sign in to comment.