From 54fe7b2f9fd295914cdf0430d94fa9904e923a15 Mon Sep 17 00:00:00 2001 From: Lyberta Date: Thu, 15 Jun 2017 04:03:16 +0300 Subject: [PATCH] Major rewrite of team balance code. New bitmask system. --- qcsrc/server/command/cmd.qc | 7 +- qcsrc/server/mutators/events.qh | 11 +- qcsrc/server/teamplay.qc | 197 ++++++++++++++++++++------------ qcsrc/server/teamplay.qh | 30 ++++- 4 files changed, 161 insertions(+), 84 deletions(-) diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 686eeae9a..51c2fa4f3 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -398,13 +398,8 @@ void ClientCommand_selectteam(entity caller, float request, float argc) { CheckAllowedTeams(caller); GetTeamCounts(caller); - if (caller.team == -1) + if ((BIT(Team_TeamToNumber(selection) - 1) & FindBestTeams(caller, false)) == 0) { - - } - else if (!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(caller.team), caller, false)) - { - PrintToChatAll("TeamSmallerEqThanTeam prevented team switch."); Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM); return; } diff --git a/qcsrc/server/mutators/events.qh b/qcsrc/server/mutators/events.qh index 0eb6b40b3..c8879918c 100644 --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@ -148,13 +148,12 @@ MUTATOR_HOOKABLE(GetTeamCounts, EV_NO_ARGS); /**/ MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount); -/** allows overriding best team */ -#define EV_JoinBestTeam(i, o) \ - /** player checked */ i(entity, MUTATOR_ARGV_0_entity) \ - /** team number */ i(float, MUTATOR_ARGV_1_float) \ - /**/ o(float, MUTATOR_ARGV_1_float) \ +/** allows overriding best teams */ +#define EV_FindBestTeams(i, o) \ + /** player checked */ i(entity, MUTATOR_ARGV_0_entity) \ + /** bitmask of teams */ o(float, MUTATOR_ARGV_1_float) \ /**/ -MUTATOR_HOOKABLE(JoinBestTeam, EV_JoinBestTeam); +MUTATOR_HOOKABLE(FindBestTeams, EV_FindBestTeams); /** copies variables for spectating "spectatee" to "this" */ #define EV_SpectateCopy(i, o) \ diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 8206ceff2..c991f72ec 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -528,12 +528,12 @@ void GetTeamCounts(entity ignore) } } -float TeamSmallerEqThanTeam(int teama, int teamb, entity e, bool usescore) +bool IsTeamSmallerThanTeam(int teama, int teamb, entity e, bool usescore) { // equal if (teama == teamb) { - return true; + return false; } // we assume that CheckAllowedTeams and GetTeamCounts have already been called float numplayersteama = -1, numplayersteamb = -1; @@ -566,7 +566,7 @@ float TeamSmallerEqThanTeam(int teama, int teamb, entity e, bool usescore) } if (!usescore) { - return numplayersteama <= numplayersteamb; + return numplayersteama < numplayersteamb; } if (numplayersteama < numplayersteamb) { @@ -576,87 +576,148 @@ float TeamSmallerEqThanTeam(int teama, int teamb, entity e, bool usescore) { return false; } - return scoreteama <= scoreteamb; - - // first, normalize - //f = max(numplayersteama, numplayersteamb, 1); - //numplayersteama /= f; - //numplayersteamb /= f; - - //float f = max(scoreteama, scoreteamb, 1); - //scoreteama /= f; - //scoreteamb /= f; - - // the more we're at the end of the match, the more take scores into account - //f = bound(0, game_completion_ratio * autocvar_g_balance_teams_scorefactor, 1); - //numplayersteama += (scoreteama - numplayersteama) * f; - //numplayersteamb += (scoreteamb - numplayersteamb) * f; + return scoreteama < scoreteamb; } -// returns # of smallest team (1, 2, 3, 4) -// NOTE: Assumes CheckAllowedTeams has already been called! -float FindSmallestTeam(entity pl, float ignore_pl) +bool IsTeamEqualToTeam(int teama, int teamb, entity e, bool usescore) { - int totalteams = 0; - int t = 1; // initialize with a random team? - if(c4 >= 0) t = 4; - if(c3 >= 0) t = 3; - if(c2 >= 0) t = 2; - if(c1 >= 0) t = 1; + // equal + if (teama == teamb) + { + return true; + } + // we assume that CheckAllowedTeams and GetTeamCounts have already been called + float numplayersteama = -1, numplayersteamb = -1; + float numbotsteama = 0, numbotsteamb = 0; + float scoreteama = 0, scoreteamb = 0; - // find out what teams are available - //CheckAllowedTeams(); + switch (teama) + { + case 1: numplayersteama = c1; numbotsteama = numbotsteam1; scoreteama = team1_score; break; + case 2: numplayersteama = c2; numbotsteama = numbotsteam2; scoreteama = team2_score; break; + case 3: numplayersteama = c3; numbotsteama = numbotsteam3; scoreteama = team3_score; break; + case 4: numplayersteama = c4; numbotsteama = numbotsteam4; scoreteama = team4_score; break; + } + switch (teamb) + { + case 1: numplayersteamb = c1; numbotsteamb = numbotsteam1; scoreteamb = team1_score; break; + case 2: numplayersteamb = c2; numbotsteamb = numbotsteam2; scoreteamb = team2_score; break; + case 3: numplayersteamb = c3; numbotsteamb = numbotsteam3; scoreteamb = team3_score; break; + case 4: numplayersteamb = c4; numbotsteamb = numbotsteam4; scoreteamb = team4_score; break; + } - // make sure there are at least 2 teams to join - if(c1 >= 0) - totalteams = totalteams + 1; - if(c2 >= 0) - totalteams = totalteams + 1; - if(c3 >= 0) - totalteams = totalteams + 1; - if(c4 >= 0) - totalteams = totalteams + 1; + // invalid + if (numplayersteama < 0 || numplayersteamb < 0) + return false; - if((autocvar_bot_vs_human || pl.team_forced > 0) && totalteams == 1) - totalteams += 1; + if ((IS_REAL_CLIENT(e) && bots_would_leave)) + { + numplayersteama -= numbotsteama; + numplayersteamb -= numbotsteamb; + } + if (!usescore) + { + return numplayersteama == numplayersteamb; + } + if (numplayersteama < numplayersteamb) + { + return false; + } + if (numplayersteama > numplayersteamb) + { + return false; + } + return scoreteama == scoreteamb; +} - if(totalteams <= 1) +int FindBestTeams(entity player, bool usescore) +{ + if (MUTATOR_CALLHOOK(FindBestTeams, player) == true) + { + return M_ARGV(1, float); + } + int teambits = 0; + int previousteam = 0; + if (c1 >= 0) + { + teambits = BIT(0); + previousteam = 1; + } + if (c2 >= 0) + { + if (IsTeamSmallerThanTeam(2, previousteam, player, usescore)) + { + teambits = BIT(1); + previousteam = 2; + } + else if (IsTeamEqualToTeam(2, previousteam, player, usescore)) + { + teambits |= BIT(1); + previousteam = 2; + } + } + if (c3 >= 0) + { + if (IsTeamSmallerThanTeam(3, previousteam, player, usescore)) + { + teambits = BIT(2); + previousteam = 3; + } + else if (IsTeamEqualToTeam(3, previousteam, player, usescore)) + { + teambits |= BIT(2); + previousteam = 3; + } + } + if (c4 >= 0) { - if(autocvar_g_campaign && pl && IS_REAL_CLIENT(pl)) - return 1; // special case for campaign and player joining - else if(totalteams == 1) // single team - LOG_TRACEF("Only 1 team available for %s, you may need to fix your map", MapInfo_Type_ToString(MapInfo_CurrentGametype())); - else // no teams, major no no - error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype()))); + if (IsTeamSmallerThanTeam(4, previousteam, player, usescore)) + { + teambits = BIT(3); + } + else if (IsTeamEqualToTeam(4, previousteam, player, usescore)) + { + teambits |= BIT(3); + } } + return teambits; +} +// returns # of smallest team (1, 2, 3, 4) +// NOTE: Assumes CheckAllowedTeams has already been called! +float FindSmallestTeam(entity pl, float ignore_pl) +{ // count how many players are in each team - if(ignore_pl) + if (ignore_pl) + { GetTeamCounts(pl); + } else + { GetTeamCounts(NULL); - + } + int teambits = FindBestTeams(pl, true); + if (teambits == 0) + { + error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype()))); + } RandomSelection_Init(); - - if(TeamSmallerEqThanTeam(1, t, pl, true)) - t = 1; - if(TeamSmallerEqThanTeam(2, t, pl, true)) - t = 2; - if(TeamSmallerEqThanTeam(3, t, pl, true)) - t = 3; - if(TeamSmallerEqThanTeam(4, t, pl, true)) - t = 4; - - // now t is the minimum, or A minimum! - if(t == 1 || TeamSmallerEqThanTeam(1, t, pl, true)) + if ((teambits & BIT(0)) != 0) + { RandomSelection_AddFloat(1, 1, 1); - if(t == 2 || TeamSmallerEqThanTeam(2, t, pl, true)) + } + if ((teambits & BIT(1)) != 0) + { RandomSelection_AddFloat(2, 1, 1); - if(t == 3 || TeamSmallerEqThanTeam(3, t, pl, true)) + } + if ((teambits & BIT(2)) != 0) + { RandomSelection_AddFloat(3, 1, 1); - if(t == 4 || TeamSmallerEqThanTeam(4, t, pl, true)) + } + if ((teambits & BIT(3)) != 0) + { RandomSelection_AddFloat(4, 1, 1); - + } return RandomSelection_chosen_float; } @@ -704,9 +765,6 @@ int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam) } float bestteam = FindSmallestTeam(this, true); - MUTATOR_CALLHOOK(JoinBestTeam, this, bestteam); - bestteam = M_ARGV(1, float); - if (only_return_best || this.bot_forced_team) { return bestteam; @@ -780,9 +838,8 @@ void SV_ChangeTeam(entity this, float _color) if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance) { GetTeamCounts(this); - if (!TeamSmallerEqThanTeam(destinationteam, sourceteam, this, false)) + if ((BIT(destinationteam - 1) & FindBestTeams(this, false)) == 0) { - PrintToChatAll("TeamSmallerEqThanTeam prevented team switch."); Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM); return; } diff --git a/qcsrc/server/teamplay.qh b/qcsrc/server/teamplay.qh index 078814cc6..6d6ccbe2b 100644 --- a/qcsrc/server/teamplay.qh +++ b/qcsrc/server/teamplay.qh @@ -62,7 +62,33 @@ float PlayerValue(entity p); // teams that are allowed will now have their player counts stored in c1...c4 void GetTeamCounts(entity ignore); -float TeamSmallerEqThanTeam(float teama, float teamb, entity e, bool usescore); +/// \brief Returns whether one team is smaller than the other. +/// \param[in] teama First team. +/// \param[in] teamb Second team. +/// \param[in] e Player to check. +/// \param[in] usescore Whether to take into account team scores. +/// \return True if first team is smaller than the second one, false otherwise. +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have +/// been called. +bool IsTeamSmallerThanTeam(float teama, float teamb, entity e, bool usescore); + +/// \brief Returns whether one team is equal to the other. +/// \param[in] teama First team. +/// \param[in] teamb Second team. +/// \param[in] e Player to check. +/// \param[in] usescore Whether to take into account team scores. +/// \return True if first team is equal to the second one, false otherwise. +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have +/// been called. +bool IsTeamEqualToTeam(float teama, float teamb, entity e, bool usescore); + +/// \brief Returns the bitmask of the best teams for the player to join. +/// \param[in] player Player to check. +/// \param[in] usescore Whether to take into account team scores. +/// \return Bitmask of the best teams for the player to join. +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have +/// been called. +int FindBestTeams(entity player, bool usescore); // returns # of smallest team (1, 2, 3, 4) // NOTE: Assumes CheckAllowedTeams has already been called! @@ -74,7 +100,7 @@ int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam); /// \param[in] sourceteam Previous team of the player (1, 2, 3, 4). /// \param[in] destinationteam Current team of the player (1, 2, 3, 4). /// \return No return. -/// \note This function assumes that CheckAllowedTeams and GetTeamCounts has +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have /// been called. void AutoBalanceBots(int sourceteam, int destinationteam); -- 2.39.2