From 6b01f68884d8994a5b34ee10a4e929ceef28d5df Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 28 Sep 2022 02:44:08 +0200 Subject: [PATCH] Disable !swap/!stay during after-round time in the knife round as it causes odd race conditions when called at the wrong time Reduce info message interval from 29 to 20 but restart it on each round start to avoid double-printing messages Default mp_round_restart_delay 3 in knife config Deduplicate knife decision prompt text Remove MVP reason hack; it was buggy and could print for someone from wrong team Restore knife cvars when knife round has ended, not on round win panel Adjust knife round winner callout due to changes Properly reset knife round var Move cvar save logic to start-knife callback to prevent exec clashes Move knife timer to round end instead of win panel Do not allow less than 10 seconds on knife timer as that does not really make any sense. --- cfg/get5/knife.cfg | 1 + documentation/docs/configuration.md | 3 +- scripting/get5.sp | 107 ++++++++++------------------ scripting/get5/kniferounds.sp | 35 +++++++-- scripting/get5/matchconfig.sp | 1 + 5 files changed, 74 insertions(+), 73 deletions(-) diff --git a/cfg/get5/knife.cfg b/cfg/get5/knife.cfg index 2125eb366..a987f2192 100644 --- a/cfg/get5/knife.cfg +++ b/cfg/get5/knife.cfg @@ -10,3 +10,4 @@ mp_roundtime 1.92 mp_roundtime_defuse 1.92 mp_roundtime_hostage 1.92 mp_t_default_secondary "" +mp_round_restart_delay 3 diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 0e6bb164c..08d70bafe 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -117,7 +117,8 @@ in [scrim mode](../getting_started/#scrims), put on `team2`. **`Default: 1`** ####`get5_time_to_make_knife_decision` : Time (in seconds) a team has to make a [`!stay`](../commands/#stay) or [`!swap`](../commands/#swap) -decision after winning knife round, 0 = unlimited. **`Default: 60`** +decision after winning knife round. Cannot be set lower than 10 if non-zero. Set to zero to remove +limit.
**`Default: 60`** ####`get5_veto_countdown` : Time (in seconds) to countdown before veto process commences. **`Default: 5`** diff --git a/scripting/get5.sp b/scripting/get5.sp index 091a69d6c..f8f532145 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -33,7 +33,7 @@ #include #define CHECK_READY_TIMER_INTERVAL 1.0 -#define INFO_MESSAGE_TIMER_INTERVAL 29.0 +#define INFO_MESSAGE_TIMER_INTERVAL 20.0 #define DEBUG_CVAR "get5_debug" #define MATCH_ID_LENGTH 64 @@ -175,6 +175,7 @@ ArrayList g_MapSides = null; ArrayList g_MapsLeftInVetoPool = null; Get5Team g_LastVetoTeam; Menu g_ActiveVetoMenu = null; +Handle g_InfoTimer = INVALID_HANDLE; /** Backup data **/ bool g_WaitingForRoundBackup = false; @@ -623,8 +624,7 @@ public void OnPluginStart() { /** Start any repeating timers **/ CreateTimer(CHECK_READY_TIMER_INTERVAL, Timer_CheckReady, _, TIMER_REPEAT); - CreateTimer(INFO_MESSAGE_TIMER_INTERVAL, Timer_InfoMessages, _, TIMER_REPEAT); - + RestartInfoTimer(); CheckForLatestVersion(); } @@ -669,18 +669,7 @@ static Action Timer_InfoMessages(Handle timer) { g_MapChangePending && GetTvDelay() > 0) { Get5_MessageToAll("%t", "WaitingForGOTVVetoInfoMessage"); } else if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { - if (g_KnifeWinnerTeam == Get5Team_None) { - return Plugin_Continue; - } - // Handle waiting for knife decision. Also check g_KnifeWinnerTeam as there is a small delay between - // selecting a side and the game state changing, during which this message should not be printed. - char formattedStayCommand[64]; - FormatChatCommand(formattedStayCommand, sizeof(formattedStayCommand), "!stay"); - char formattedSwapCommand[64]; - FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); - Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", - g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, - formattedSwapCommand); + PromptForKnifeDecision(); } else if (g_GameState == Get5State_PostGame && GetTvDelay() > 0) { // Handle postgame Get5_MessageToAll("%t", "WaitingForGOTVBrodcastEndingInfoMessage"); @@ -826,6 +815,7 @@ static Action Timer_ConfigsExecutedCallback(Handle timer) { g_MapChangePending = false; g_DoingBackupRestoreNow = false; g_ReadyTimeWaitingUsed = 0; + g_KnifeWinnerTeam = Get5Team_None; g_HasKnifeRoundStarted = false; // Recording is always automatically stopped on map change, and // since there are no hooks to detect tv_stoprecord, we reset @@ -1450,6 +1440,15 @@ static Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast } } +static void RestartInfoTimer() { + // We restart this on each round start to make sure we don't double-print info messages + // right on top of manually printed messages, such as "waiting for knife decision". + if (g_InfoTimer != INVALID_HANDLE) { + delete g_InfoTimer; + } + g_InfoTimer = CreateTimer(INFO_MESSAGE_TIMER_INTERVAL, Timer_InfoMessages, _, TIMER_REPEAT); +} + static Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundStart"); @@ -1458,6 +1457,7 @@ static Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas g_RoundStartedTime = 0.0; g_BombPlantedTime = 0.0; g_BombSiteLastPlanted = Get5BombSite_Unknown; + RestartInfoTimer(); if (g_GameState == Get5State_None || IsDoingRestoreOrMapChange()) { // Get5_OnRoundStart() is fired from within the backup event when loading the valve backup. @@ -1467,13 +1467,26 @@ static Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas // We cannot do this during warmup, as sending users into warmup post-knife triggers a round start // event. if (!InWarmup()) { - if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { + if (g_GameState == Get5State_KnifeRound && g_HasKnifeRoundStarted) { + // Knife-round decision cannot be made until the round has completely ended and a new round starts. + // Letting players choose a side in after-round-time sometimes causes weird problems, such as going + // directly to live and missing the countdown if done at the *exact* wrong time. + g_HasKnifeRoundStarted = false; + // Ensures that round end after knife sends players directly into warmup. // This immediately triggers another Event_RoundStart, so we can return here and avoid // writing backup twice. LogDebug("Changed to warmup post knife."); + if (g_KnifeChangedCvars != INVALID_HANDLE) { + RestoreCvars(g_KnifeChangedCvars, true); + } ExecCfg(g_WarmupCfgCvar); StartWarmup(); + + // Change state *after* starting the warmup just to reduce !swap/!stay race condition windows. + ChangeState(Get5State_WaitingForKnifeRoundDecision); + PromptForKnifeDecision(); + StartKnifeTimer(); return; } if (g_GameState == Get5State_GoingLive) { @@ -1519,13 +1532,6 @@ static Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas static Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundWinPanel"); if (g_GameState == Get5State_KnifeRound && g_HasKnifeRoundStarted) { - g_HasKnifeRoundStarted = false; - - ChangeState(Get5State_WaitingForKnifeRoundDecision); - if (g_KnifeChangedCvars != INVALID_HANDLE) { - RestoreCvars(g_KnifeChangedCvars, true); - } - int ctAlive = CountAlivePlayersOnTeam(CS_TEAM_CT); int tAlive = CountAlivePlayersOnTeam(CS_TEAM_T); int winningCSTeam; @@ -1545,44 +1551,15 @@ static Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad LogDebug("Randomized knife winner to side %d", winningCSTeam); } } - g_KnifeWinnerTeam = CSTeamToGet5Team(winningCSTeam); - char formattedStayCommand[64]; - FormatChatCommand(formattedStayCommand, sizeof(formattedStayCommand), "!stay"); - char formattedSwapCommand[64]; - FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); - Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", - g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, - formattedSwapCommand); - - if (g_TeamTimeToKnifeDecisionCvar.FloatValue > 0) { - g_KnifeDecisionTimer = - CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision); - } - // This ensures that the correct graphic is displayed in-game for the winning team, as CTs will - // always win if the clock runs out. It also ensures that the fun fact displayed is correct; - // overriding to number of players killed by knife and no "CT won by running down the clock". - // MVP can still be on the losing team though. ran down". - int maxFrags = 0; - int topFragClient = 0; - int frags; - LOOP_CLIENTS(i) { - if (IsValidClient(i)) { - frags = GetClientFrags(i); - if (frags >= maxFrags) { - maxFrags = frags; - topFragClient = i; - } - } - } - if (topFragClient > 0) { - // Found here: - // https://github.com/SteamDatabase/GameTracking-CSGO/blob/master/csgo/bin/server_client_strings.txt - event.SetString("funfact_token", "#funfact_knife_kills"); - event.SetInt("funfact_player", topFragClient); - event.SetInt("funfact_data1", maxFrags); - } + // Adjust fun-fact to nothing and make sure the correct team is announced as winner. + // This prevents things like "CTs won by running down the clock" + event.SetString("funfact_token", ""); + event.SetInt("funfact_player", 0); + event.SetInt("funfact_data1", 0); + event.SetInt("funfact_data2", 0); + event.SetInt("funfact_data3", 0); event.SetInt("final_event", ConvertCSTeamToDefaultWinReason(winningCSTeam)); } } @@ -1593,7 +1570,7 @@ static Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) return; } - if (g_GameState == Get5State_WaitingForKnifeRoundDecision && g_KnifeWinnerTeam != Get5Team_None) { + if (g_GameState == Get5State_KnifeRound && g_KnifeWinnerTeam != Get5Team_None) { int winningCSTeam = Get5TeamToCSTeam(g_KnifeWinnerTeam); // Event_RoundWinPanel is called before Event_RoundEnd, so that event handles knife winner. // We override this event only to have the correct audio callout in the game. @@ -1718,16 +1695,10 @@ static void StartGame(bool knifeRound) { LogDebug("StartGame"); if (knifeRound) { - ExecCfg(g_LiveCfgCvar); // live first, then apply and save knife cvars below + ExecCfg(g_LiveCfgCvar); // live first, then apply and save knife cvars in callback LogDebug("StartGame: about to begin knife round"); ChangeState(Get5State_KnifeRound); - if (g_KnifeChangedCvars != INVALID_HANDLE) { - CloseCvarStorage(g_KnifeChangedCvars); - } - char knifeConfig[PLATFORM_MAX_PATH]; - g_KnifeCfgCvar.GetString(knifeConfig, sizeof(knifeConfig)); - g_KnifeChangedCvars = ExecuteAndSaveCvars(knifeConfig); - CreateTimer(1.0, StartKnifeRound); + CreateTimer(0.5, StartKnifeRound); } else { // If there is no knife round, we go directly to live, which loads the live config etc. on its // own. diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index dfc028d1e..dca64c341 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -1,8 +1,11 @@ Action StartKnifeRound(Handle timer) { g_HasKnifeRoundStarted = false; - - // Removes ready tags - SetMatchTeamCvars(); + if (g_KnifeChangedCvars != INVALID_HANDLE) { + CloseCvarStorage(g_KnifeChangedCvars); + } + char knifeConfig[PLATFORM_MAX_PATH]; + g_KnifeCfgCvar.GetString(knifeConfig, sizeof(knifeConfig)); + g_KnifeChangedCvars = ExecuteAndSaveCvars(knifeConfig); Get5_MessageToAll("%t", "KnifeIn5SecInfoMessage"); if (InWarmup()) { @@ -33,6 +36,30 @@ static Action Timer_AnnounceKnife(Handle timer) { return Plugin_Handled; } +void StartKnifeTimer() { + float knifeDecisionTime = g_TeamTimeToKnifeDecisionCvar.FloatValue; + if (knifeDecisionTime > 0.0) { + if (knifeDecisionTime < 10.0) { + knifeDecisionTime = 10.0; + } + g_KnifeDecisionTimer = CreateTimer(knifeDecisionTime, Timer_ForceKnifeDecision); + } +} + +void PromptForKnifeDecision() { + if (g_KnifeWinnerTeam == Get5Team_None) { + // Handle waiting for knife decision. Also check g_KnifeWinnerTeam as there is a small delay between + // selecting a side and the game state changing, during which this message should not be printed. + return; + } + char formattedStayCommand[64]; + FormatChatCommand(formattedStayCommand, sizeof(formattedStayCommand), "!stay"); + char formattedSwapCommand[64]; + FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); + Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", + g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, formattedSwapCommand); +} + static void PerformSideSwap(bool swap) { if (swap) { int tmp = g_TeamSide[Get5Team_2]; @@ -151,7 +178,7 @@ Action Command_T(int client, int args) { return Plugin_Handled; } -Action Timer_ForceKnifeDecision(Handle timer) { +static Action Timer_ForceKnifeDecision(Handle timer) { g_KnifeDecisionTimer = INVALID_HANDLE; if (g_GameState == Get5State_WaitingForKnifeRoundDecision && g_KnifeWinnerTeam != Get5Team_None) { Get5_MessageToAll("%t", "TeamLostTimeToDecideInfoMessage", diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 705548eb6..584a078b7 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -39,6 +39,7 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { g_MatchID = ""; g_ReadyTimeWaitingUsed = 0; g_HasKnifeRoundStarted = false; + g_KnifeWinnerTeam = Get5Team_None; g_MapChangePending = false; g_MapNumber = 0; g_NumberOfMapsInSeries = 0;