Skip to content

Commit

Permalink
Disable !swap/!stay during after-round time in the knife round as it …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
nickdnk committed Sep 30, 2022
1 parent bde6f41 commit 6b01f68
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 73 deletions.
1 change: 1 addition & 0 deletions cfg/get5/knife.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion documentation/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br>**`Default: 60`**

####`get5_veto_countdown`
: Time (in seconds) to countdown before veto process commences. **`Default: 5`**
Expand Down
107 changes: 39 additions & 68 deletions scripting/get5.sp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#include <SteamWorks>

#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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");

Expand All @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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));
}
}
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
35 changes: 31 additions & 4 deletions scripting/get5/kniferounds.sp
Original file line number Diff line number Diff line change
@@ -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()) {
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions scripting/get5/matchconfig.sp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 6b01f68

Please sign in to comment.