X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fteamplay.qc;h=22ea235463b949c92ce7cb109abc2afd437bd2d2;hb=76de60deecae125795551ba1a4576a265d7ae7de;hp=a30e4696dd555d6010ad912afff04092991d245d;hpb=94f293de630d6173c5303adf65e45d4f4a83863a;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index a30e4696d..22ea23546 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -15,141 +15,110 @@ #include #include "../common/teams.qh" -void TeamchangeFrags(entity e) +/// \brief Describes a state of team balance entity. +enum { - PlayerScore_Clear(e); -} + TEAM_BALANCE_UNINITIALIZED, ///< The team balance has not been initialized. + /// \brief TeamBalance_CheckAllowedTeams has been called. + TEAM_BALANCE_TEAMS_CHECKED, + /// \brief TeamBalance_GetTeamCounts has been called. + TEAM_BALANCE_TEAM_COUNTS_FILLED +}; -void LogTeamchange(float player_id, float team_number, float type) -{ - if(!autocvar_sv_eventlog) - return; +/// \brief Indicates that the player is not allowed to join a team. +const int TEAM_NOT_ALLOWED = -1; - if(player_id < 1) - return; +.int m_team_balance_state; ///< Holds the state of the team balance entity. +.entity m_team_balance_team[NUM_TEAMS]; ///< ??? - GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type))); -} +.float m_team_score; ///< The score of the team. +.int m_num_players; ///< Number of players (both humans and bots) in a team. +.int m_num_bots; ///< Number of bots in a team. +.int m_num_players_alive; ///< Number of alive players in a team. +.int m_num_control_points; ///< Number of control points owned by a team. + +entity g_team_entities[NUM_TEAMS]; ///< Holds global team entities. -void default_delayedinit(entity this) +STATIC_INIT(g_team_entities) { - if(!scores_initialized) - ScoreRules_generic(); + for (int i = 0; i < NUM_TEAMS; ++i) + { + g_team_entities[i] = spawn(); + } } -void InitGameplayMode() +entity Team_GetTeamFromIndex(int index) { - VoteReset(); - - // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds - get_mi_min_max(1); - // assign reflectively to avoid "assignment to world" warning - int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) { - string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0'; - if (v) { - putentityfieldstring(i, world, sprintf("%v", v)); - if (++done == 2) break; - } - } - // currently, NetRadiant's limit is 131072 qu for each side - // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu - // set the distance according to map size but don't go over the limit to avoid issues with float precision - // in case somebody makes extremely large maps - max_shot_distance = min(230000, vlen(world.maxs - world.mins)); - - MapInfo_LoadMapSettings(mapname); - GameRules_teams(false); - - if (!cvar_value_issafe(world.fog)) - { - LOG_INFO("The current map contains a potentially harmful fog setting, ignored"); - world.fog = string_null; - } - if(MapInfo_Map_fog != "") - if(MapInfo_Map_fog == "none") - world.fog = string_null; - else - world.fog = strzone(MapInfo_Map_fog); - clientstuff = strzone(MapInfo_Map_clientstuff); - - MapInfo_ClearTemps(); - - gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype); - - cache_mutatormsg = strzone(""); - cache_lastmutatormsg = strzone(""); - - InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK); + if (!Team_IsValidIndex(index)) + { + LOG_FATALF("Team_GetTeamFromIndex: Index is invalid: %f", index); + } + return g_team_entities[index - 1]; } -string GetClientVersionMessage(entity this) +entity Team_GetTeam(int team_num) { - if (CS(this).version_mismatch) { - if(CS(this).version < autocvar_gameversion) { - return strcat("This is Xonotic ", autocvar_g_xonoticversion, - "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8"); - } else { - return strcat("This is Xonotic ", autocvar_g_xonoticversion, - "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8"); - } - } else { - return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion); + if (!Team_IsValidTeam(team_num)) + { + LOG_FATALF("Team_GetTeam: Value is invalid: %f", team_num); } + return g_team_entities[Team_TeamToIndex(team_num) - 1]; } -string getwelcomemessage(entity this) +float Team_GetTeamScore(entity team_ent) { - MUTATOR_CALLHOOK(BuildMutatorsPrettyString, ""); - string modifications = M_ARGV(0, string); + return team_ent.m_team_score; +} - if(g_weaponarena) - { - if(g_weaponarena_random) - modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena"); // TODO: somehow get this into the mutator - else - modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena"); - } - else if(cvar("g_balance_blaster_weaponstartoverride") == 0) - modifications = strcat(modifications, ", No start weapons"); - if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity"))) - modifications = strcat(modifications, ", Low gravity"); - if(g_weapon_stay && !g_cts) - modifications = strcat(modifications, ", Weapons stay"); - if(g_jetpack) - modifications = strcat(modifications, ", Jet pack"); - if(autocvar_g_powerups == 0) - modifications = strcat(modifications, ", No powerups"); - if(autocvar_g_powerups > 0) - modifications = strcat(modifications, ", Powerups"); - modifications = substring(modifications, 2, strlen(modifications) - 2); +void Team_SetTeamScore(entity team_ent, float score) +{ + team_ent.m_team_score = score; +} - string versionmessage = GetClientVersionMessage(this); - string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n"); +int Team_GetNumberOfAlivePlayers(entity team_ent) +{ + return team_ent.m_num_players_alive; +} - if(modifications != "") - s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n"); +void Team_SetNumberOfAlivePlayers(entity team_ent, int number) +{ + team_ent.m_num_players_alive = number; +} - if(cache_lastmutatormsg != autocvar_g_mutatormsg) +int Team_GetNumberOfAliveTeams() +{ + int result = 0; + for (int i = 0; i < NUM_TEAMS; ++i) { - strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg); - strcpy(cache_mutatormsg, cache_lastmutatormsg); - } - - if (cache_mutatormsg != "") { - s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg); + if (g_team_entities[i].m_num_players_alive > 0) + { + ++result; + } } + return result; +} - string mutator_msg = ""; - MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg); - mutator_msg = M_ARGV(0, string); +int Team_GetNumberOfControlPoints(entity team_ent) +{ + return team_ent.m_num_control_points; +} - s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting +void Team_SetNumberOfControlPoints(entity team_ent, int number) +{ + team_ent.m_num_control_points = number; +} - string motd = autocvar_sv_motd; - if (motd != "") { - s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd)); +int Team_GetNumberOfTeamsWithControlPoints() +{ + int result = 0; + for (int i = 0; i < NUM_TEAMS; ++i) + { + if (g_team_entities[i].m_num_control_points > 0) + { + ++result; + } } - return s; + return result; } void setcolor(entity this, int clr) @@ -162,6 +131,26 @@ void setcolor(entity this, int clr) #endif } +bool Entity_HasValidTeam(entity this) +{ + return Team_IsValidTeam(this.team); +} + +int Entity_GetTeamIndex(entity this) +{ + return Team_TeamToIndex(this.team); +} + +entity Entity_GetTeam(entity this) +{ + int index = Entity_GetTeamIndex(this); + if (!Team_IsValidIndex(index)) + { + return NULL; + } + return Team_GetTeamFromIndex(index); +} + void SetPlayerColors(entity player, float _color) { float pants = _color & 0x0F; @@ -176,748 +165,715 @@ void SetPlayerColors(entity player, float _color) } } -void KillPlayerForTeamChange(entity player) +bool Player_SetTeamIndex(entity player, int index) { - if (IS_DEAD(player)) + int new_team = Team_IndexToTeam(index); + if (player.team == new_team) { - return; + if (new_team != -1) + { + // This is important when players join the game and one of their + // color matches the team color while other doesn't. For example + // [BOT]Lion. + SetPlayerColors(player, new_team - 1); + } + return true; } - if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true) + int old_index = Team_TeamToIndex(player.team); + if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, old_index, index) == true) { - return; + // Mutator has blocked team change. + return false; } - Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP, player.origin, - '0 0 0'); + if (new_team != -1) + { + SetPlayerColors(player, new_team - 1); + } + MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_index, index); + return true; } -bool SetPlayerTeamSimple(entity player, int team_num) +bool SetPlayerTeam(entity player, int team_index, int type) { - if (player.team == team_num) + int old_team_index = Entity_GetTeamIndex(player); + if (!Player_SetTeamIndex(player, team_index)) { - // This is important when players join the game and one of their color - // matches the team color while other doesn't. For example [BOT]Lion. - SetPlayerColors(player, team_num - 1); - return true; + return false; } - if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber( - player.team), Team_TeamToNumber(team_num)) == true) + LogTeamchange(player.playerid, player.team, type); + if (team_index != old_team_index) { - // Mutator has blocked team change. - return false; + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, + INFO_JOIN_PLAY_TEAM), player.netname); + KillPlayerForTeamChange(player); } - int old_team = player.team; - SetPlayerColors(player, team_num - 1); - MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_team, player.team); return true; } -bool SetPlayerTeam(entity player, int destination_team, int source_team, - bool no_print) +bool MoveToTeam(entity client, int team_index, int type) { - int team_num = Team_NumberToTeam(destination_team); - if (!SetPlayerTeamSimple(player, team_num)) + //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index)); + int lockteams_backup = lockteams; // backup any team lock + lockteams = 0; // disable locked teams + PlayerScore_Clear(client); + if (!SetPlayerTeam(client, team_index, type)) { + lockteams = lockteams_backup; // restore the team lock return false; } - LogTeamchange(player.playerid, player.team, 3); // log manual team join - if (no_print) + lockteams = lockteams_backup; // restore the team lock + return true; +} + +void KillPlayerForTeamChange(entity player) +{ + if (IS_DEAD(player)) { - return true; + return; } - bprint(playername(player, false), "^7 has changed from ", Team_NumberToColoredFullName(source_team), "^7 to ", Team_NumberToColoredFullName(destination_team), "\n"); - return true; + if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true) + { + return; + } + Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP, + player.origin, '0 0 0'); } -// set c1...c4 to show what teams are allowed -void CheckAllowedTeams(entity for_whom) +void LogTeamchange(float player_id, float team_number, int type) { - int teams_mask = 0; + if(!autocvar_sv_eventlog) + return; - c1 = c2 = c3 = c4 = -1; - num_bots_team1 = num_bots_team2 = num_bots_team3 = num_bots_team4 = 0; + if(player_id < 1) + return; - string teament_name = string_null; + GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type))); +} - bool mutator_returnvalue = MUTATOR_CALLHOOK(CheckAllowedTeams, teams_mask, teament_name, for_whom); +entity TeamBalance_CheckAllowedTeams(entity for_whom) +{ + entity balance = spawn(); + for (int i = 0; i < NUM_TEAMS; ++i) + { + entity team_ent = balance.m_team_balance_team[i] = spawn(); + team_ent.m_team_score = g_team_entities[i].m_team_score; + team_ent.m_num_players = TEAM_NOT_ALLOWED; + team_ent.m_num_bots = 0; + } + + int teams_mask = 0; + string teament_name = string_null; + bool mutator_returnvalue = MUTATOR_CALLHOOK(TeamBalance_CheckAllowedTeams, + teams_mask, teament_name, for_whom); teams_mask = M_ARGV(0, float); teament_name = M_ARGV(1, string); - - if(!mutator_returnvalue) + if (mutator_returnvalue) { - if(teams_mask & BIT(0)) c1 = 0; - if(teams_mask & BIT(1)) c2 = 0; - if(teams_mask & BIT(2)) c3 = 0; - if(teams_mask & BIT(3)) c4 = 0; + for (int i = 0; i < NUM_TEAMS; ++i) + { + if (teams_mask & BIT(i)) + { + balance.m_team_balance_team[i].m_num_players = 0; + } + } } - // find out what teams are allowed if necessary - if(teament_name) + if (teament_name) { entity head = find(NULL, classname, teament_name); - while(head) + while (head) { - switch(head.team) + if (Team_IsValidTeam(head.team)) { - case NUM_TEAM_1: c1 = 0; break; - case NUM_TEAM_2: c2 = 0; break; - case NUM_TEAM_3: c3 = 0; break; - case NUM_TEAM_4: c4 = 0; break; + TeamBalance_GetTeam(balance, head.team).m_num_players = 0; } - head = find(head, classname, teament_name); } } // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line) - if(AvailableTeams() == 2) - if(autocvar_bot_vs_human && for_whom) + if (AvailableTeams() == 2) + if (autocvar_bot_vs_human && for_whom) { - if(autocvar_bot_vs_human > 0) + if (autocvar_bot_vs_human > 0) { // find last team available - - if(IS_BOT_CLIENT(for_whom)) + if (IS_BOT_CLIENT(for_whom)) { - if(c4 >= 0) { c3 = c2 = c1 = -1; } - else if(c3 >= 0) { c4 = c2 = c1 = -1; } - else { c4 = c3 = c1 = -1; } + if (TeamBalance_IsTeamAllowedInternal(balance, 4)) + { + TeamBalance_BanTeamsExcept(balance, 4); + } + else if (TeamBalance_IsTeamAllowedInternal(balance, 3)) + { + TeamBalance_BanTeamsExcept(balance, 3); + } + else + { + TeamBalance_BanTeamsExcept(balance, 2); + } // no further cases, we know at least 2 teams exist } else { - if(c1 >= 0) { c2 = c3 = c4 = -1; } - else if(c2 >= 0) { c1 = c3 = c4 = -1; } - else { c1 = c2 = c4 = -1; } + if (TeamBalance_IsTeamAllowedInternal(balance, 1)) + { + TeamBalance_BanTeamsExcept(balance, 1); + } + else if (TeamBalance_IsTeamAllowedInternal(balance, 2)) + { + TeamBalance_BanTeamsExcept(balance, 2); + } + else + { + TeamBalance_BanTeamsExcept(balance, 3); + } // no further cases, bots have one of the teams } } else { // find first team available - - if(IS_BOT_CLIENT(for_whom)) + if (IS_BOT_CLIENT(for_whom)) { - if(c1 >= 0) { c2 = c3 = c4 = -1; } - else if(c2 >= 0) { c1 = c3 = c4 = -1; } - else { c1 = c2 = c4 = -1; } + if (TeamBalance_IsTeamAllowedInternal(balance, 1)) + { + TeamBalance_BanTeamsExcept(balance, 1); + } + else if (TeamBalance_IsTeamAllowedInternal(balance, 2)) + { + TeamBalance_BanTeamsExcept(balance, 2); + } + else + { + TeamBalance_BanTeamsExcept(balance, 3); + } // no further cases, we know at least 2 teams exist } else { - if(c4 >= 0) { c3 = c2 = c1 = -1; } - else if(c3 >= 0) { c4 = c2 = c1 = -1; } - else { c4 = c3 = c1 = -1; } + if (TeamBalance_IsTeamAllowedInternal(balance, 4)) + { + TeamBalance_BanTeamsExcept(balance, 4); + } + else if (TeamBalance_IsTeamAllowedInternal(balance, 3)) + { + TeamBalance_BanTeamsExcept(balance, 3); + } + else + { + TeamBalance_BanTeamsExcept(balance, 2); + } // no further cases, bots have one of the teams } } } - if(!for_whom) - return; + if (!for_whom) + { + balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED; + return balance; + } // if player has a forced team, ONLY allow that one - if(for_whom.team_forced == NUM_TEAM_1 && c1 >= 0) - c2 = c3 = c4 = -1; - else if(for_whom.team_forced == NUM_TEAM_2 && c2 >= 0) - c1 = c3 = c4 = -1; - else if(for_whom.team_forced == NUM_TEAM_3 && c3 >= 0) - c1 = c2 = c4 = -1; - else if(for_whom.team_forced == NUM_TEAM_4 && c4 >= 0) - c1 = c2 = c3 = -1; + for (int i = 1; i <= NUM_TEAMS; ++i) + { + if (for_whom.team_forced == Team_IndexToTeam(i) && + TeamBalance_IsTeamAllowedInternal(balance, i)) + { + TeamBalance_BanTeamsExcept(balance, i); + } + break; + } + balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED; + return balance; } -float PlayerValue(entity p) +void TeamBalance_Destroy(entity balance) { - return 1; - // FIXME: it always returns 1... + if (balance == NULL) + { + return; + } + for (int i = 0; i < NUM_TEAMS; ++i) + { + delete(balance.(m_team_balance_team[i])); + } + delete(balance); } -// c1...c4 should be set to -1 (not allowed) or 0 (allowed). -// teams that are allowed will now have their player counts stored in c1...c4 -void GetTeamCounts(entity ignore) +int TeamBalance_GetAllowedTeams(entity balance) { - if (MUTATOR_CALLHOOK(GetTeamCounts) == true) + if (balance == NULL) { - if (c1 >= 0) - { - MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_1, ignore, c1, - num_bots_team1, lowest_human_team1, lowest_bot_team1); - c1 = M_ARGV(2, float); - num_bots_team1 = M_ARGV(3, float); - lowest_human_team1 = M_ARGV(4, entity); - lowest_bot_team1 = M_ARGV(5, entity); - } - if (c2 >= 0) - { - MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_2, ignore, c2, - num_bots_team2, lowest_human_team2, lowest_bot_team2); - c2 = M_ARGV(2, float); - num_bots_team2 = M_ARGV(3, float); - lowest_human_team2 = M_ARGV(4, entity); - lowest_bot_team2 = M_ARGV(5, entity); - } - if (c3 >= 0) + LOG_FATAL("TeamBalance_GetAllowedTeams: Team balance entity is NULL."); + } + if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED) + { + LOG_FATAL("TeamBalance_GetAllowedTeams: " + "Team balance entity is not initialized."); + } + int result = 0; + for (int i = 1; i <= NUM_TEAMS; ++i) + { + if (TeamBalance_IsTeamAllowedInternal(balance, i)) { - MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_3, ignore, c3, - num_bots_team3, lowest_human_team3, lowest_bot_team3); - c3 = M_ARGV(2, float); - num_bots_team3 = M_ARGV(3, float); - lowest_human_team3 = M_ARGV(4, entity); - lowest_bot_team3 = M_ARGV(5, entity); + result |= Team_IndexToBit(i); } - if (c4 >= 0) + } + return result; +} + +bool TeamBalance_IsTeamAllowed(entity balance, int index) +{ + if (balance == NULL) + { + LOG_FATAL("TeamBalance_IsTeamAllowed: Team balance entity is NULL."); + } + if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED) + { + LOG_FATAL("TeamBalance_IsTeamAllowed: " + "Team balance entity is not initialized."); + } + if (!Team_IsValidIndex(index)) + { + LOG_FATALF("TeamBalance_IsTeamAllowed: Team index is invalid: %f", + index); + } + return TeamBalance_IsTeamAllowedInternal(balance, index); +} + +void TeamBalance_GetTeamCounts(entity balance, entity ignore) +{ + if (balance == NULL) + { + LOG_FATAL("TeamBalance_GetTeamCounts: Team balance entity is NULL."); + } + if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED) + { + LOG_FATAL("TeamBalance_GetTeamCounts: " + "Team balance entity is not initialized."); + } + if (MUTATOR_CALLHOOK(TeamBalance_GetTeamCounts) == true) + { + // Mutator has overriden the configuration. + for (int i = 1; i <= NUM_TEAMS; ++i) { - MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_4, ignore, - c4, num_bots_team4, lowest_human_team4, lowest_bot_team4); - c4 = M_ARGV(2, float); - num_bots_team4 = M_ARGV(3, float); - lowest_human_team4 = M_ARGV(4, entity); - lowest_bot_team4 = M_ARGV(5, entity); + entity team_ent = TeamBalance_GetTeamFromIndex(balance, i); + if (TeamBalanceTeam_IsAllowed(team_ent)) + { + MUTATOR_CALLHOOK(TeamBalance_GetTeamCount, i, ignore); + team_ent.m_num_players = M_ARGV(2, float); + team_ent.m_num_bots = M_ARGV(3, float); + } } } else { - float value, bvalue; - // now count how many players are on each team already - float lowest_human_score1 = FLOAT_MAX; - float lowest_bot_score1 = FLOAT_MAX; - float lowest_human_score2 = FLOAT_MAX; - float lowest_bot_score2 = FLOAT_MAX; - float lowest_human_score3 = FLOAT_MAX; - float lowest_bot_score3 = FLOAT_MAX; - float lowest_human_score4 = FLOAT_MAX; - float lowest_bot_score4 = FLOAT_MAX; + // Manually count all players. FOREACH_CLIENT(true, { - float t; + if (it == ignore) + { + continue; + } + int team_num; if (IS_PLAYER(it) || it.caplayer) { - t = it.team; + team_num = it.team; } else if (it.team_forced > 0) { - t = it.team_forced; // reserve the spot + team_num = it.team_forced; // reserve the spot } else { continue; } - if (it == ignore) + if (!Team_IsValidTeam(team_num)) { continue; } - value = PlayerValue(it); - if (IS_BOT_CLIENT(it)) - { - bvalue = value; - } - else - { - bvalue = 0; - } - if (value == 0) + entity team_ent = TeamBalance_GetTeam(balance, team_num); + if (!TeamBalanceTeam_IsAllowed(team_ent)) { continue; } - switch (t) + ++team_ent.m_num_players; + if (IS_BOT_CLIENT(it)) { - case NUM_TEAM_1: - { - if (c1 < 0) - { - break; - } - c1 += value; - num_bots_team1 += bvalue; - float temp_score = PlayerScore_Get(it, SP_SCORE); - if (!bvalue) - { - if (temp_score < lowest_human_score1) - { - lowest_human_team1 = it; - lowest_human_score1 = temp_score; - } - break; - } - if (temp_score < lowest_bot_score1) - { - lowest_bot_team1 = it; - lowest_bot_score1 = temp_score; - } - break; - } - case NUM_TEAM_2: - { - if (c2 < 0) - { - break; - } - c2 += value; - num_bots_team2 += bvalue; - float temp_score = PlayerScore_Get(it, SP_SCORE); - if (!bvalue) - { - if (temp_score < lowest_human_score2) - { - lowest_human_team2 = it; - lowest_human_score2 = temp_score; - } - break; - } - if (temp_score < lowest_bot_score2) - { - lowest_bot_team2 = it; - lowest_bot_score2 = temp_score; - } - break; - } - case NUM_TEAM_3: - { - if (c3 < 0) - { - break; - } - c3 += value; - num_bots_team3 += bvalue; - float temp_score = PlayerScore_Get(it, SP_SCORE); - if (!bvalue) - { - if (temp_score < lowest_human_score3) - { - lowest_human_team3 = it; - lowest_human_score3 = temp_score; - } - break; - } - if (temp_score < lowest_bot_score3) - { - lowest_bot_team3 = it; - lowest_bot_score3 = temp_score; - } - break; - } - case NUM_TEAM_4: - { - if (c4 < 0) - { - break; - } - c4 += value; - num_bots_team4 += bvalue; - float temp_score = PlayerScore_Get(it, SP_SCORE); - if (!bvalue) - { - if (temp_score < lowest_human_score4) - { - lowest_human_team4 = it; - lowest_human_score4 = temp_score; - } - break; - } - if (temp_score < lowest_bot_score4) - { - lowest_bot_team4 = it; - lowest_bot_score4 = temp_score; - } - break; - } + ++team_ent.m_num_bots; } }); } // if the player who has a forced team has not joined yet, reserve the spot - if(autocvar_g_campaign) + if (autocvar_g_campaign) { - switch(autocvar_g_campaign_forceteam) + if (Team_IsValidIndex(autocvar_g_campaign_forceteam)) { - case 1: if(c1 == num_bots_team1) ++c1; break; - case 2: if(c2 == num_bots_team2) ++c2; break; - case 3: if(c3 == num_bots_team3) ++c3; break; - case 4: if(c4 == num_bots_team4) ++c4; break; + entity team_ent = TeamBalance_GetTeamFromIndex(balance, + autocvar_g_campaign_forceteam); + if (team_ent.m_num_players == team_ent.m_num_bots) + { + ++team_ent.m_num_players; + } } } + balance.m_team_balance_state = TEAM_BALANCE_TEAM_COUNTS_FILLED; } -bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player, - bool use_score) +int TeamBalance_GetNumberOfPlayers(entity balance, int index) { - if (!Team_IsValidNumber(team_a)) + if (balance == NULL) { - LOG_FATALF("IsTeamSmallerThanTeam: team_a is invalid: %f", team_a); + LOG_FATAL("TeamBalance_GetNumberOfPlayers: " + "Team balance entity is NULL."); } - if (!Team_IsValidNumber(team_b)) + if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED) { - LOG_FATALF("IsTeamSmallerThanTeam: team_b is invalid: %f", team_b); + LOG_FATAL("TeamBalance_GetNumberOfPlayers: " + "TeamBalance_GetTeamCounts has not been called."); } - if (team_a == team_b) + if (!Team_IsValidIndex(index)) { - return false; + LOG_FATALF("TeamBalance_GetNumberOfPlayers: Team index is invalid: %f", + index); } - // we assume that CheckAllowedTeams and GetTeamCounts have already been called - int num_players_team_a = -1, num_players_team_b = -1; - int num_bots_team_a = 0, num_bots_team_b = 0; - float score_team_a = 0, score_team_b = 0; - switch (team_a) + return balance.m_team_balance_team[index - 1].m_num_players; +} + +int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player) +{ + if (balance == NULL) { - case 1: - { - num_players_team_a = c1; - num_bots_team_a = num_bots_team1; - score_team_a = team1_score; - break; - } - case 2: - { - num_players_team_a = c2; - num_bots_team_a = num_bots_team2; - score_team_a = team2_score; - break; - } - case 3: - { - num_players_team_a = c3; - num_bots_team_a = num_bots_team3; - score_team_a = team3_score; - break; - } - case 4: - { - num_players_team_a = c4; - num_bots_team_a = num_bots_team4; - score_team_a = team4_score; - break; - } + LOG_FATAL("TeamBalance_FindBestTeam: Team balance entity is NULL."); } - switch (team_b) + if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED) { - case 1: - { - num_players_team_b = c1; - num_bots_team_b = num_bots_team1; - score_team_b = team1_score; - break; - } - case 2: - { - num_players_team_b = c2; - num_bots_team_b = num_bots_team2; - score_team_b = team2_score; - break; - } - case 3: - { - num_players_team_b = c3; - num_bots_team_b = num_bots_team3; - score_team_b = team3_score; - break; - } - case 4: - { - num_players_team_b = c4; - num_bots_team_b = num_bots_team4; - score_team_b = team4_score; - break; - } + LOG_FATAL("TeamBalance_FindBestTeam: " + "Team balance entity is not initialized."); } - // invalid - if (num_players_team_a < 0 || num_players_team_b < 0) - { - return false; - } - if (IS_REAL_CLIENT(player) && bots_would_leave) + // count how many players are in each team + if (ignore_player) { - num_players_team_a -= num_bots_team_a; - num_players_team_b -= num_bots_team_b; + TeamBalance_GetTeamCounts(balance, player); } - if (!use_score) + else { - return num_players_team_a < num_players_team_b; + TeamBalance_GetTeamCounts(balance, NULL); } - if (num_players_team_a < num_players_team_b) + int team_bits = TeamBalance_FindBestTeams(balance, player, true); + if (team_bits == 0) { - return true; + LOG_FATALF("TeamBalance_FindBestTeam: No teams available for %s\n", + MapInfo_Type_ToString(MapInfo_CurrentGametype())); } - if (num_players_team_a > num_players_team_b) + RandomSelection_Init(); + for (int i = 1; i <= NUM_TEAMS; ++i) { - return false; + if (team_bits & Team_IndexToBit(i)) + { + RandomSelection_AddFloat(i, 1, 1); + } } - return score_team_a < score_team_b; + return RandomSelection_chosen_float; } -bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score) +int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score) { - if (!Team_IsValidNumber(team_a)) + if (balance == NULL) { - LOG_FATALF("IsTeamEqualToTeam: team_a is invalid: %f", team_a); + LOG_FATAL("TeamBalance_FindBestTeams: Team balance entity is NULL."); } - if (!Team_IsValidNumber(team_b)) + if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED) { - LOG_FATALF("IsTeamEqualToTeam: team_b is invalid: %f", team_b); + LOG_FATAL("TeamBalance_FindBestTeams: " + "TeamBalance_GetTeamCounts has not been called."); } - if (team_a == team_b) + if (MUTATOR_CALLHOOK(TeamBalance_FindBestTeams, player) == true) { - return true; + return M_ARGV(1, float); } - // we assume that CheckAllowedTeams and GetTeamCounts have already been called - int num_players_team_a = -1, num_players_team_b = -1; - int num_bots_team_a = 0, num_bots_team_b = 0; - float score_team_a = 0, score_team_b = 0; - switch (team_a) + int team_bits = 0; + int previous_team = 0; + for (int i = 1; i <= NUM_TEAMS; ++i) { - case 1: + if (!TeamBalance_IsTeamAllowedInternal(balance, i)) { - num_players_team_a = c1; - num_bots_team_a = num_bots_team1; - score_team_a = team1_score; - break; + continue; } - case 2: + if (previous_team == 0) { - num_players_team_a = c2; - num_bots_team_a = num_bots_team2; - score_team_a = team2_score; - break; + team_bits = Team_IndexToBit(i); + previous_team = i; + continue; } - case 3: + int compare = TeamBalance_CompareTeams(balance, i, previous_team, + player, use_score); + if (compare == TEAMS_COMPARE_LESS) { - num_players_team_a = c3; - num_bots_team_a = num_bots_team3; - score_team_a = team3_score; - break; + team_bits = Team_IndexToBit(i); + previous_team = i; + continue; } - case 4: + if (compare == TEAMS_COMPARE_EQUAL) { - num_players_team_a = c4; - num_bots_team_a = num_bots_team4; - score_team_a = team4_score; - break; + team_bits |= Team_IndexToBit(i); + previous_team = i; } } - switch (team_b) + return team_bits; +} + +void TeamBalance_JoinBestTeam(entity this, bool force_best_team) +{ + //PrintToChatAll(sprintf("JoinBestTeam: %s, %f", this.netname, force_best_team)); + // don't join a team if we're not playing a team game + if (!teamplay) { - case 1: - { - num_players_team_b = c1; - num_bots_team_b = num_bots_team1; - score_team_b = team1_score; - break; - } - case 2: - { - num_players_team_b = c2; - num_bots_team_b = num_bots_team2; - score_team_b = team2_score; - break; - } - case 3: + return; + } + + // find out what teams are available + entity balance = TeamBalance_CheckAllowedTeams(this); + + // if we don't care what team they end up on, put them on whatever team they entered as. + // if they're not on a valid team, then let other code put them on the smallest team + if (!force_best_team) + { + int selected_team_index = -1; + for (int i = 1; i <= NUM_TEAMS; ++i) { - num_players_team_b = c3; - num_bots_team_b = num_bots_team3; - score_team_b = team3_score; - break; + if (TeamBalance_IsTeamAllowedInternal(balance, i) && + (Team_TeamToIndex(this.team) == i)) + { + selected_team_index = i; + break; + } } - case 4: + + if (Team_IsValidIndex(selected_team_index)) { - num_players_team_b = c4; - num_bots_team_b = num_bots_team4; - score_team_b = team4_score; - break; + SetPlayerTeam(this, selected_team_index, TEAM_CHANGE_AUTO_RELAXED); + TeamBalance_Destroy(balance); + return; } } - // invalid - if (num_players_team_a < 0 || num_players_team_b < 0) - return false; - - if (IS_REAL_CLIENT(player) && bots_would_leave) + // otherwise end up on the smallest team (handled below) + if (this.bot_forced_team) { - num_players_team_a -= num_bots_team_a; - num_players_team_b -= num_bots_team_b; + TeamBalance_Destroy(balance); + return; } - if (!use_score) + int best_team_index = TeamBalance_FindBestTeam(balance, this, true); + int old_team_index = Team_TeamToIndex(this.team); + TeamBalance_Destroy(balance); + PlayerScore_Clear(this); + if (!SetPlayerTeam(this, best_team_index, TEAM_CHANGE_AUTO)) { - return num_players_team_a == num_players_team_b; + return; } - if (num_players_team_a != num_players_team_b) + if ((old_team_index != -1) && !IS_BOT_CLIENT(this)) { - return false; + TeamBalance_AutoBalanceBots(best_team_index, old_team_index); } - return score_team_a == score_team_b; } -int FindBestTeams(entity player, bool use_score) +int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b, + entity player, bool use_score) { - if (MUTATOR_CALLHOOK(FindBestTeams, player) == true) + if (balance == NULL) { - return M_ARGV(1, float); + LOG_FATAL("TeamBalance_CompareTeams: Team balance entity is NULL."); } - int team_bits = 0; - int previous_team = 0; - if (c1 >= 0) + if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED) { - team_bits = BIT(0); - previous_team = 1; + LOG_FATAL("TeamBalance_CompareTeams: " + "TeamBalance_GetTeamCounts has not been called."); } - if (c2 >= 0) + if (!Team_IsValidIndex(team_index_a)) { - if (previous_team == 0) - { - team_bits = BIT(1); - previous_team = 2; - } - else if (IsTeamSmallerThanTeam(2, previous_team, player, use_score)) - { - team_bits = BIT(1); - previous_team = 2; - } - else if (IsTeamEqualToTeam(2, previous_team, player, use_score)) - { - team_bits |= BIT(1); - previous_team = 2; - } + LOG_FATALF("TeamBalance_CompareTeams: team_index_a is invalid: %f", + team_index_a); } - if (c3 >= 0) + if (!Team_IsValidIndex(team_index_b)) { - if (previous_team == 0) - { - team_bits = BIT(2); - previous_team = 3; - } - else if (IsTeamSmallerThanTeam(3, previous_team, player, use_score)) - { - team_bits = BIT(2); - previous_team = 3; - } - else if (IsTeamEqualToTeam(3, previous_team, player, use_score)) - { - team_bits |= BIT(2); - previous_team = 3; - } + LOG_FATALF("TeamBalance_CompareTeams: team_index_b is invalid: %f", + team_index_b); } - if (c4 >= 0) + if (team_index_a == team_index_b) { - if (previous_team == 0) - { - team_bits = BIT(3); - } - else if (IsTeamSmallerThanTeam(4, previous_team, player, use_score)) - { - team_bits = BIT(3); - } - else if (IsTeamEqualToTeam(4, previous_team, player, use_score)) - { - team_bits |= BIT(3); - } + return TEAMS_COMPARE_EQUAL; } - return team_bits; + entity team_a = TeamBalance_GetTeamFromIndex(balance, team_index_a); + entity team_b = TeamBalance_GetTeamFromIndex(balance, team_index_b); + return TeamBalance_CompareTeamsInternal(team_a, team_b, player, use_score); } -// returns # of smallest team (1, 2, 3, 4) -// NOTE: Assumes CheckAllowedTeams has already been called! -int FindSmallestTeam(entity player, float ignore_player) +void TeamBalance_AutoBalanceBots(int source_team_index, + int destination_team_index) { - // count how many players are in each team - if (ignore_player) + if (!Team_IsValidIndex(source_team_index)) { - GetTeamCounts(player); + LOG_WARNF("TeamBalance_AutoBalanceBots: " + "Source team index is invalid: %f", source_team_index); + return; } - else + if (!Team_IsValidIndex(destination_team_index)) { - GetTeamCounts(NULL); + LOG_WARNF("TeamBalance_AutoBalanceBots: " + "Destination team index is invalid: %f", destination_team_index); + return; } - int team_bits = FindBestTeams(player, true); - if (team_bits == 0) + if (!autocvar_g_balance_teams || + !autocvar_g_balance_teams_prevent_imbalance) { - error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype()))); + return; } - RandomSelection_Init(); - if ((team_bits & BIT(0)) != 0) + entity balance = TeamBalance_CheckAllowedTeams(NULL); + TeamBalance_GetTeamCounts(balance, NULL); + entity source_team = TeamBalance_GetTeamFromIndex(balance, + source_team_index); + entity destination_team = TeamBalance_GetTeamFromIndex(balance, + destination_team_index); + if ((source_team.m_num_bots == 0) || (source_team.m_num_players <= + destination_team.m_num_players)) { - RandomSelection_AddFloat(1, 1, 1); + TeamBalance_Destroy(balance); + return; } - if ((team_bits & BIT(1)) != 0) + TeamBalance_Destroy(balance); + entity lowest_bot = NULL; + if (MUTATOR_CALLHOOK(TeamBalance_GetPlayerForTeamSwitch, source_team_index, + destination_team_index, true)) { - RandomSelection_AddFloat(2, 1, 1); + lowest_bot = M_ARGV(3, entity); } - if ((team_bits & BIT(2)) != 0) + else { - RandomSelection_AddFloat(3, 1, 1); + float lowest_score = FLOAT_MAX; + FOREACH_CLIENT(IS_BOT_CLIENT(it) && (Entity_GetTeamIndex(it) == + source_team_index), + { + float temp_score = PlayerScore_Get(it, SP_SCORE); + if (temp_score >= lowest_score) + { + continue; + } + balance = TeamBalance_CheckAllowedTeams(it); + if (TeamBalance_IsTeamAllowed(balance, destination_team_index)) + { + lowest_bot = it; + lowest_score = temp_score; + } + TeamBalance_Destroy(balance); + }); } - if ((team_bits & BIT(3)) != 0) + if (lowest_bot == NULL) { - RandomSelection_AddFloat(4, 1, 1); + return; } - return RandomSelection_chosen_float; -} - -void JoinBestTeam(entity this, bool force_best_team) -{ - // don't join a team if we're not playing a team game - if (!teamplay) + if (!Player_SetTeamIndex(lowest_bot, destination_team_index)) { return; } + KillPlayerForTeamChange(lowest_bot); +} - // find out what teams are available - CheckAllowedTeams(this); +bool TeamBalance_IsTeamAllowedInternal(entity balance, int index) +{ + return balance.m_team_balance_team[index - 1].m_num_players != + TEAM_NOT_ALLOWED; +} - // if we don't care what team they end up on, put them on whatever team they entered as. - // if they're not on a valid team, then let other code put them on the smallest team - if (!force_best_team) +void TeamBalance_BanTeamsExcept(entity balance, int index) +{ + for (int i = 1; i <= NUM_TEAMS; ++i) { - int selected_team; - if ((c1 >= 0) && (this.team == NUM_TEAM_1)) - { - selected_team = this.team; - } - else if ((c2 >= 0) && (this.team == NUM_TEAM_2)) - { - selected_team = this.team; - } - else if ((c3 >= 0) && (this.team == NUM_TEAM_3)) - { - selected_team = this.team; - } - else if ((c4 >= 0) && (this.team == NUM_TEAM_4)) - { - selected_team = this.team; - } - else + if (i != index) { - selected_team = -1; + balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED; } + } +} - if (selected_team > 0) - { - SetPlayerTeamSimple(this, selected_team); - LogTeamchange(this.playerid, this.team, 99); - return; - } +entity TeamBalance_GetTeamFromIndex(entity balance, int index) +{ + if (!Team_IsValidIndex(index)) + { + LOG_FATALF("TeamBalance_GetTeamFromIndex: Index is invalid: %f", index); } - // otherwise end up on the smallest team (handled below) - if (this.bot_forced_team) + return balance.m_team_balance_team[index - 1]; +} + +entity TeamBalance_GetTeam(entity balance, int team_num) +{ + return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num)); +} + +bool TeamBalanceTeam_IsAllowed(entity team_ent) +{ + return team_ent.m_num_players != TEAM_NOT_ALLOWED; +} + +int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent) +{ + return team_ent.m_num_players; +} + +int TeamBalanceTeam_GetNumberOfBots(entity team_ent) +{ + return team_ent.m_num_bots; +} + +int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b, + entity player, bool use_score) +{ + if (team_a == team_b) { - return; + return TEAMS_COMPARE_EQUAL; + } + if (!TeamBalanceTeam_IsAllowed(team_a) || + !TeamBalanceTeam_IsAllowed(team_b)) + { + return TEAMS_COMPARE_INVALID; + } + int num_players_team_a = team_a.m_num_players; + int num_players_team_b = team_b.m_num_players; + if (IS_REAL_CLIENT(player) && bots_would_leave) + { + num_players_team_a -= team_a.m_num_bots; + num_players_team_b -= team_b.m_num_bots; + } + if (num_players_team_a < num_players_team_b) + { + return TEAMS_COMPARE_LESS; + } + if (num_players_team_a > num_players_team_b) + { + return TEAMS_COMPARE_GREATER; + } + if (!use_score) + { + return TEAMS_COMPARE_EQUAL; } - int best_team = FindSmallestTeam(this, true); - best_team = Team_NumberToTeam(best_team); - if (best_team == -1) + if (team_a.m_team_score < team_b.m_team_score) { - error("JoinBestTeam: invalid team\n"); + return TEAMS_COMPARE_LESS; } - int old_team = Team_TeamToNumber(this.team); - TeamchangeFrags(this); - SetPlayerTeamSimple(this, best_team); - LogTeamchange(this.playerid, this.team, 2); // log auto join - if ((old_team != -1) && !IS_BOT_CLIENT(this)) + if (team_a.m_team_score > team_b.m_team_score) { - AutoBalanceBots(old_team, Team_TeamToNumber(best_team)); + return TEAMS_COMPARE_GREATER; } - KillPlayerForTeamChange(this); + return TEAMS_COMPARE_EQUAL; } +// Called when the player connects or when they change their color with "color" +// command. void SV_ChangeTeam(entity this, float _color) { - float source_color, destination_color, source_team, destination_team; + //PrintToChatAll(sprintf("SV_ChangeTeam: %s, %f", this.netname, _color)); // in normal deathmatch we can just apply the color and we're done if(!teamplay) @@ -933,28 +889,48 @@ void SV_ChangeTeam(entity this, float _color) if(!teamplay) return; + int source_color, destination_color; + int source_team_index, destination_team_index; + source_color = this.clientcolors & 0x0F; destination_color = _color & 0x0F; - source_team = Team_TeamToNumber(source_color + 1); - destination_team = Team_TeamToNumber(destination_color + 1); + source_team_index = Team_TeamToIndex(source_color + 1); + destination_team_index = Team_TeamToIndex(destination_color + 1); - if (destination_team == -1) + if (destination_team_index == -1) { return; } - CheckAllowedTeams(this); + entity balance = TeamBalance_CheckAllowedTeams(this); - if (destination_team == 1 && c1 < 0) destination_team = 4; - if (destination_team == 4 && c4 < 0) destination_team = 3; - if (destination_team == 3 && c3 < 0) destination_team = 2; - if (destination_team == 2 && c2 < 0) destination_team = 1; + if (destination_team_index == 1 && !TeamBalance_IsTeamAllowedInternal( + balance, 1)) + { + destination_team_index = 4; + } + if (destination_team_index == 4 && !TeamBalance_IsTeamAllowedInternal( + balance, 4)) + { + destination_team_index = 3; + } + if (destination_team_index == 3 && !TeamBalance_IsTeamAllowedInternal( + balance, 3)) + { + destination_team_index = 2; + } + if (destination_team_index == 2 && !TeamBalance_IsTeamAllowedInternal( + balance, 2)) + { + destination_team_index = 1; + } // not changing teams if (source_color == destination_color) { - SetPlayerTeam(this, destination_team, source_team, true); + SetPlayerTeam(this, destination_team_index, TEAM_CHANGE_MANUAL); + TeamBalance_Destroy(balance); return; } @@ -966,111 +942,24 @@ void SV_ChangeTeam(entity this, float _color) // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance) { - GetTeamCounts(this); - if ((BIT(destination_team - 1) & FindBestTeams(this, false)) == 0) + TeamBalance_GetTeamCounts(balance, this); + if ((Team_IndexToBit(destination_team_index) & + TeamBalance_FindBestTeams(balance, this, false)) == 0) { Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM); + TeamBalance_Destroy(balance); return; } } - if(IS_PLAYER(this) && source_team != destination_team) + TeamBalance_Destroy(balance); + if (IS_PLAYER(this) && source_team_index != destination_team_index) { // reduce frags during a team change - TeamchangeFrags(this); - } - if (!SetPlayerTeam(this, destination_team, source_team, !IS_CLIENT(this))) - { - return; - } - AutoBalanceBots(source_team, destination_team); - if (!IS_PLAYER(this) || (source_team == destination_team)) - { - return; - } - KillPlayerForTeamChange(this); -} - -void AutoBalanceBots(int source_team, int destination_team) -{ - if (!Team_IsValidNumber(source_team)) - { - LOG_WARNF("AutoBalanceBots: Source team is invalid: %f", source_team); - return; - } - if (!Team_IsValidNumber(destination_team)) - { - LOG_WARNF("AutoBalanceBots: Destination team is invalid: %f", - destination_team); - return; - } - if (!autocvar_g_balance_teams || - !autocvar_g_balance_teams_prevent_imbalance) - { - return; - } - int num_players_source_team = 0; - int num_players_destination_team = 0; - entity lowest_bot_destination_team = NULL; - switch (source_team) - { - case 1: - { - num_players_source_team = c1; - break; - } - case 2: - { - num_players_source_team = c2; - break; - } - case 3: - { - num_players_source_team = c3; - break; - } - case 4: - { - num_players_source_team = c4; - break; - } - } - if (num_players_source_team < 0) - { - return; - } - switch (destination_team) - { - case 1: - { - num_players_destination_team = c1; - lowest_bot_destination_team = lowest_bot_team1; - break; - } - case 2: - { - num_players_destination_team = c2; - lowest_bot_destination_team = lowest_bot_team2; - break; - } - case 3: - { - num_players_destination_team = c3; - lowest_bot_destination_team = lowest_bot_team3; - break; - } - case 4: - { - num_players_destination_team = c4; - lowest_bot_destination_team = lowest_bot_team4; - break; - } + PlayerScore_Clear(this); } - if ((num_players_destination_team <= num_players_source_team) || - (lowest_bot_destination_team == NULL)) + if (!SetPlayerTeam(this, destination_team_index, TEAM_CHANGE_MANUAL)) { return; } - SetPlayerTeamSimple(lowest_bot_destination_team, - Team_NumberToTeam(source_team)); - KillPlayerForTeamChange(lowest_bot_destination_team); + TeamBalance_AutoBalanceBots(destination_team_index, source_team_index); }