Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix problems with knife swap/stay command timing #888

Merged
merged 1 commit into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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