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

Match Restore Coach Fix #754

Merged
merged 7 commits into from
Jul 15, 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 configs/get5/example_match.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
}

"players_per_team" "5"
"coaches_per_team" "2"
"min_players_to_ready" "1" // Minimum # of players a team must have to ready
"min_spectators_to_ready" "0" // How many spectators must be ready to begin.

Expand Down
3 changes: 2 additions & 1 deletion configs/get5/example_match.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"matchid": "example_match",
"num_maps": 3,
"players_per_team": 1,
"players_per_team": 5,
"coaches_per_team": 2,
"min_players_to_ready": 1,
"min_spectators_to_ready": 0,
"skip_veto": false,
Expand Down
1 change: 1 addition & 0 deletions configs/get5/scrim_template.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"scrim" "1"
"side_type" "never_knife"
"players_per_team" "5"
"coaches_per_team" "2"
"num_maps" "1"
"skip_veto" "1"

Expand Down
3 changes: 3 additions & 0 deletions documentation/docs/match_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ match.
backup method, defaults to `0`.
- `matchtext`: Wraps `mp_teammatchstat_1`, you probably don't want to set this, in BoX series `mp_teamscore` cvars are
automatically set and take the place of the `mp_teammatchstat` cvars.
- `coaches`: Identical to the `players` tag, it's an optional list of Steam ID's for users who wish to coach a team.
You may also force player names here. This field is optional.

## Optional Values

Expand All @@ -39,6 +41,7 @@ match.
gets the side choice, "never_knife" means "team1" is always on CT first, and "always_knife" means there is always a
knife round.
- `players_per_team`: Maximum players per team (doesn't include a coach spot, default: 5).
- `coaches_per_team`: Maximum coaches per team (default: 2).
- `min_players_to_ready`: Minimum players a team needs to be able to ready up (default: 1).
- `favored_percentage_team1`: Wrapper for the servers `mp_teamprediction_pct`.
- `favored_percentage_text` Wrapper for the servers `mp_teamprediction_txt`.
Expand Down
20 changes: 15 additions & 5 deletions scripting/get5.sp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ bool g_BO2Match = false;
char g_MatchID[MATCH_ID_LENGTH];
ArrayList g_MapPoolList = null;
ArrayList g_TeamAuths[MATCHTEAM_COUNT];
ArrayList g_TeamCoaches[MATCHTEAM_COUNT];
StringMap g_PlayerNames;
char g_TeamNames[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_TeamTags[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
Expand All @@ -113,6 +114,7 @@ char g_MatchTitle[MAX_CVAR_LENGTH];
int g_FavoredTeamPercentage = 0;
char g_FavoredTeamText[MAX_CVAR_LENGTH];
int g_PlayersPerTeam = 5;
int g_CoachesPerTeam = 2;
int g_MinPlayersToReady = 1;
int g_MinSpectatorsToReady = 0;
bool g_SkipVeto = false;
Expand Down Expand Up @@ -414,6 +416,8 @@ public void OnPluginStart() {
RegAdminCmd("get5_endmatch", Command_EndMatch, ADMFLAG_CHANGEMAP, "Force ends the current match");
RegAdminCmd("get5_addplayer", Command_AddPlayer, ADMFLAG_CHANGEMAP,
"Adds a steamid to a match team");
RegAdminCmd("get5_addcoach", Command_AddCoach, ADMFLAG_CHANGEMAP,
"Adds a steamid to a match teams coach slot");
RegAdminCmd("get5_removeplayer", Command_RemovePlayer, ADMFLAG_CHANGEMAP,
"Removes a steamid from a match team");
RegAdminCmd("get5_addkickedplayer", Command_AddKickedPlayer, ADMFLAG_CHANGEMAP,
Expand Down Expand Up @@ -483,6 +487,8 @@ public void OnPluginStart() {

for (int i = 0; i < sizeof(g_TeamAuths); i++) {
g_TeamAuths[i] = new ArrayList(AUTH_LENGTH);
// Same length.
g_TeamCoaches[i] = new ArrayList(AUTH_LENGTH);
}
g_PlayerNames = new StringMap();

Expand Down Expand Up @@ -959,12 +965,16 @@ public void RestoreLastRound(int client) {

char lastBackup[PLATFORM_MAX_PATH];
g_LastGet5BackupCvar.GetString(lastBackup, sizeof(lastBackup));
if (RestoreFromBackup(lastBackup)) {
Get5_MessageToAll("%t", "BackupLoadedInfoMessage", lastBackup);
// Fix the last backup cvar since it gets reset.
g_LastGet5BackupCvar.SetString(lastBackup);
if (!StrEqual(lastBackup, "")) {
if (RestoreFromBackup(lastBackup)) {
Get5_MessageToAll("%t", "BackupLoadedInfoMessage", lastBackup);
// Fix the last backup cvar since it gets reset.
g_LastGet5BackupCvar.SetString(lastBackup);
} else {
ReplyToCommand(client, "Failed to load backup %s - check error logs", lastBackup);
}
} else {
ReplyToCommand(client, "Failed to load backup %s - check error logs", lastBackup);
ReplyToCommand(client, "Failed to load backup, as previous round backup does not exist.");
}
}

Expand Down
22 changes: 22 additions & 0 deletions scripting/get5/backups.sp
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ public void RestoreGet5Backup() {
EndWarmup();
ServerCommand("mp_restartgame 5");
Pause(PauseType_Backup);
if (g_CoachingEnabledCvar.BoolValue) {
CreateTimer(6.0, Timer_SwapCoaches);
}
} else {
EnsurePausedWarmup();
}
Expand All @@ -359,6 +362,16 @@ public void RestoreGet5Backup() {
}
}

public Action Timer_SwapCoaches(Handle timer) {
for (int i = 1; i <= MaxClients; i++) {
if (IsAuthedPlayer(i)) {
CheckIfClientCoaching(i, MatchTeam_Team1);
CheckIfClientCoaching(i, MatchTeam_Team2);
}

}
}

public Action Time_StartRestore(Handle timer) {
Pause(PauseType_Backup);

Expand All @@ -369,6 +382,15 @@ public Action Time_StartRestore(Handle timer) {
}

public Action Timer_FinishBackup(Handle timer) {
if (g_CoachingEnabledCvar.BoolValue) {
// If we are coaching we want to ensure our
// coaches get moved back onto the team.
// We cannot trust Valve's system as a disconnected
// player will count as a "player" and not be placed
// in the coach slot. So, we cannot enable warmup during
// the round restore process if using a Valve backup.
CreateTimer(0.5, Timer_SwapCoaches);
}
g_DoingBackupRestoreNow = false;
}

Expand Down
4 changes: 4 additions & 0 deletions scripting/get5/debug.sp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ static void AddGlobalStateInfo(File f) {

f.WriteLine("g_MatchTitle = %s", g_MatchTitle);
f.WriteLine("g_PlayersPerTeam = %d", g_PlayersPerTeam);
f.WriteLine("g_CoachesPerTeam = %d", g_CoachesPerTeam);
f.WriteLine("g_MinPlayersToReady = %d", g_MinPlayersToReady);
f.WriteLine("g_MinSpectatorsToReady = %d", g_MinSpectatorsToReady);
f.WriteLine("g_SkipVeto = %d", g_SkipVeto);
Expand Down Expand Up @@ -137,6 +138,8 @@ static void AddGlobalStateInfo(File f) {
f.WriteLine("g_TeamPausesUsed = %d", g_TeamPausesUsed[team]);
f.WriteLine("g_TeamTechPausesUsed = %d", g_TeamTechPausesUsed[team]);
f.WriteLine("g_TeamGivenTechPauseCommand = %d", g_TeamGivenTechPauseCommand[team]);
f.WriteLine("g_TeamGivenStopCommand = %d", g_TeamGivenStopCommand[team]);
WriteArrayList(f, "g_TeamCoaches", g_TeamCoaches[team]);
}
}

Expand All @@ -155,6 +158,7 @@ static void AddInterestingCvars(File f) {
WriteCvarString(f, "get5_pausing_enabled");
WriteCvarString(f, "get5_reset_pauses_each_half");
WriteCvarString(f, "get5_web_api_url");
WriteCvarString(f, "get5_last_backup_file");
WriteCvarString(f, "mp_freezetime");
WriteCvarString(f, "mp_halftime");
WriteCvarString(f, "mp_halftime_duration");
Expand Down
77 changes: 77 additions & 0 deletions scripting/get5/matchconfig.sp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#define CONFIG_MATCHID_DEFAULT "matchid"
#define CONFIG_MATCHTITLE_DEFAULT "Map {MAPNUMBER} of {MAXMAPS}"
#define CONFIG_PLAYERSPERTEAM_DEFAULT 5
#define CONFIG_COACHESPERTEAM_DEFAULT 2
#define CONFIG_MINPLAYERSTOREADY_DEFAULT 0
#define CONFIG_MINSPECTATORSTOREADY_DEFAULT 0
#define CONFIG_SPECTATORSNAME_DEFAULT "casters"
Expand Down Expand Up @@ -32,6 +33,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) {
}
g_TechPausedTimeOverride[team] = 0;
g_TeamGivenTechPauseCommand[team] = false;
ClearArray(GetTeamCoaches(team));
ClearArray(GetTeamAuths(team));
}

Expand Down Expand Up @@ -274,6 +276,7 @@ public void WriteMatchToKv(KeyValues kv) {
kv.SetNum("bo2_series", g_BO2Match);
kv.SetNum("skip_veto", g_SkipVeto);
kv.SetNum("players_per_team", g_PlayersPerTeam);
kv.SetNum("coaches_per_team", g_CoachesPerTeam);
kv.SetNum("min_players_to_ready", g_MinPlayersToReady);
kv.SetNum("min_spectators_to_ready", g_MinSpectatorsToReady);
kv.SetString("match_title", g_MatchTitle);
Expand Down Expand Up @@ -335,6 +338,15 @@ static void AddTeamBackupData(KeyValues kv, MatchTeam team) {
kv.SetString("flag", g_TeamFlags[team]);
kv.SetString("logo", g_TeamLogos[team]);
kv.SetString("matchtext", g_TeamMatchTexts[team]);
kv.JumpToKey("coaches", true);
for (int i = 0; i < GetTeamCoaches(team).Length; i++) {
GetTeamCoaches(team).GetString(i, auth, sizeof(auth));
if (!g_PlayerNames.GetString(auth, name, sizeof(name))) {
strcopy(name, sizeof(name), KEYVALUE_STRING_PLACEHOLDER);
}
kv.SetString(auth, KEYVALUE_STRING_PLACEHOLDER);
}
kv.GoBack();
}
}

Expand All @@ -343,6 +355,7 @@ static bool LoadMatchFromKv(KeyValues kv) {
g_InScrimMode = kv.GetNum("scrim") != 0;
kv.GetString("match_title", g_MatchTitle, sizeof(g_MatchTitle), CONFIG_MATCHTITLE_DEFAULT);
g_PlayersPerTeam = kv.GetNum("players_per_team", CONFIG_PLAYERSPERTEAM_DEFAULT);
g_CoachesPerTeam = kv.GetNum("coaches_per_team", CONFIG_COACHESPERTEAM_DEFAULT);
g_MinPlayersToReady = kv.GetNum("min_players_to_ready", CONFIG_MINPLAYERSTOREADY_DEFAULT);
g_MinSpectatorsToReady =
kv.GetNum("min_spectators_to_ready", CONFIG_MINSPECTATORSTOREADY_DEFAULT);
Expand Down Expand Up @@ -454,6 +467,8 @@ static bool LoadMatchFromJson(JSON_Object json) {

g_PlayersPerTeam =
json_object_get_int_safe(json, "players_per_team", CONFIG_PLAYERSPERTEAM_DEFAULT);
g_CoachesPerTeam =
json_object_get_int_safe(json, "coaches_per_team", CONFIG_COACHESPERTEAM_DEFAULT);
g_MinPlayersToReady =
json_object_get_int_safe(json, "min_players_to_ready", CONFIG_MINPLAYERSTOREADY_DEFAULT);
g_MinSpectatorsToReady = json_object_get_int_safe(json, "min_spectators_to_ready",
Expand Down Expand Up @@ -574,7 +589,11 @@ static void LoadTeamDataJson(JSON_Object json, MatchTeam matchTeam) {
if (StrEqual(fromfile, "")) {
// TODO: this needs to support both an array and a dictionary
// For now, it only supports an array
JSON_Object coaches = json.GetObject("coaches");
AddJsonAuthsToList(json, "players", GetTeamAuths(matchTeam), AUTH_LENGTH);
if (coaches != null) {
AddJsonAuthsToList(json, "coaches", GetTeamCoaches(matchTeam), AUTH_LENGTH);
}
json_object_get_string_safe(json, "name", g_TeamNames[matchTeam], MAX_CVAR_LENGTH);
json_object_get_string_safe(json, "tag", g_TeamTags[matchTeam], MAX_CVAR_LENGTH);
json_object_get_string_safe(json, "flag", g_TeamFlags[matchTeam], MAX_CVAR_LENGTH);
Expand Down Expand Up @@ -603,6 +622,7 @@ static void LoadTeamData(KeyValues kv, MatchTeam matchTeam) {

if (StrEqual(fromfile, "")) {
AddSubsectionAuthsToList(kv, "players", GetTeamAuths(matchTeam), AUTH_LENGTH);
AddSubsectionAuthsToList(kv, "coaches", GetTeamCoaches(matchTeam), AUTH_LENGTH);
kv.GetString("name", g_TeamNames[matchTeam], MAX_CVAR_LENGTH, "");
kv.GetString("tag", g_TeamTags[matchTeam], MAX_CVAR_LENGTH, "");
kv.GetString("flag", g_TeamFlags[matchTeam], MAX_CVAR_LENGTH, "");
Expand Down Expand Up @@ -815,6 +835,63 @@ public Action Command_AddPlayer(int client, int args) {
return Plugin_Handled;
}

public Action Command_AddCoach(int client, int args) {
if (g_GameState == Get5State_None) {
ReplyToCommand(client, "Cannot change coach targets when there is no match to modify");
return Plugin_Handled;
} else if (!g_CoachingEnabledCvar.BoolValue) {
ReplyToCommand(client, "Cannot change coach targets if coaching is disabled.");
return Plugin_Handled;
}

char auth[AUTH_LENGTH];
char steam64[AUTH_LENGTH];
char teamString[32];
char name[MAX_NAME_LENGTH];
if (args >= 2 && GetCmdArg(1, auth, sizeof(auth)) &&
GetCmdArg(2, teamString, sizeof(teamString))) {
if (args >= 3) {
GetCmdArg(3, name, sizeof(name));
}

MatchTeam team = MatchTeam_TeamNone;
if (StrEqual(teamString, "team1")) {
team = MatchTeam_Team1;
} else if (StrEqual(teamString, "team2")) {
team = MatchTeam_Team2;
} else {
ReplyToCommand(client, "Unknown team: must be one of team1 or team2");
return Plugin_Handled;
}

if (GetTeamCoaches(team).Length == g_CoachesPerTeam) {
ReplyToCommand(client, "Coach Spots are full for team %s", teamString);
return Plugin_Handled;
}

if(!ConvertAuthToSteam64(auth, steam64)) {
return Plugin_Handled;
}

if (AddCoachToTeam(auth, team, name)) {
// Check if we are in the playerlist already and remove.
int index = GetTeamAuths(team).FindString(auth);
if (index >= 0) {
GetTeamAuths(team).Erase(index);
}
// Update the backup structure as well for round restores, covers edge
// case of users joining, coaching, stopping, and getting 16k cash as player.
WriteBackup();
ReplyToCommand(client, "Successfully added player %s to coach team %s", auth, teamString);
} else {
ReplyToCommand(client, "Player %s is already in a coaching position on a team.", auth);
}
} else {
ReplyToCommand(client, "Usage: get5_addcoach <auth> <team1|team2> [name]");
}
return Plugin_Handled;
}

public Action Command_AddKickedPlayer(int client, int args) {
if (g_GameState == Get5State_None) {
ReplyToCommand(client, "Cannot change player lists when there is no match to modify");
Expand Down
2 changes: 2 additions & 0 deletions scripting/get5/pausing.sp
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ public Action Command_Unpause(int client, int args) {
return Plugin_Handled;
}

// Check to see if we have a timeout that is timed. Otherwise, we need to
// continue for unpausing. New pause type to avoid match restores failing.
if (g_FixedPauseTimeCvar.BoolValue && g_PauseType == PauseType_Tactical) {
return Plugin_Handled;
}
Expand Down
Loading