+/// \brief Indicates that the player is not allowed to join a team.
+const int TEAM_NOT_ALLOWED = -1;
+
+.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.
+.entity m_lowest_human; ///< Human with the lowest score in a team.
+.entity m_lowest_bot; ///< Bot with the lowest score in a team.
+
+entity g_team_entities[4]; ///< Holds global team entities.
+
+STATIC_INIT(g_team_entities)
+{
+ g_team_entities[0] = spawn();
+ g_team_entities[1] = spawn();
+ g_team_entities[2] = spawn();
+ g_team_entities[3] = spawn();
+}
+
+entity Team_GetTeamFromIndex(int index)
+{
+ if (!Team_IsValidIndex(index))
+ {
+ LOG_FATALF("Team_GetTeamFromIndex: Index is invalid: %f", index);
+ }
+ return g_team_entities[index - 1];
+}
+
+entity Team_GetTeam(int team_num)
+{
+ if (!Team_IsValidTeam(team_num))
+ {
+ LOG_FATALF("Team_GetTeam: Value is invalid: %f", team_num);
+ }
+ return g_team_entities[Team_TeamToNumber(team_num) - 1];
+}
+
+float Team_GetTeamScore(entity team_)
+{
+ return team_.m_team_score;
+}
+
+void Team_SetTeamScore(entity team_, float score)
+{
+ team_.m_team_score = score;
+}
+
+void CheckAllowedTeams(entity for_whom)
+{
+ for (int i = 0; i < 4; ++i)
+ {
+ g_team_entities[i].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[i].m_num_bots = 0;
+ g_team_entities[i].m_lowest_human = NULL;
+ g_team_entities[i].m_lowest_bot = NULL;
+ }
+
+ int teams_mask = 0;
+ string teament_name = string_null;
+ bool mutator_returnvalue = MUTATOR_CALLHOOK(CheckAllowedTeams, teams_mask,
+ teament_name, for_whom);
+ teams_mask = M_ARGV(0, float);
+ teament_name = M_ARGV(1, string);
+ if (mutator_returnvalue)
+ {
+ for (int i = 0; i < 4; ++i)
+ {
+ if (teams_mask & BIT(i))
+ {
+ g_team_entities[i].m_num_players = 0;
+ }
+ }
+ }
+
+ // find out what teams are allowed if necessary
+ if (teament_name)
+ {
+ entity head = find(NULL, classname, teament_name);
+ while (head)
+ {
+ if (Team_IsValidTeam(head.team))
+ {
+ Team_GetTeam(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 (autocvar_bot_vs_human > 0)
+ {
+ // find last team available
+ if (IS_BOT_CLIENT(for_whom))
+ {
+ if (Team_IsAllowed(g_team_entities[3]))
+ {
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else if (Team_IsAllowed(g_team_entities[2]))
+ {
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else
+ {
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ // no further cases, we know at least 2 teams exist
+ }
+ else
+ {
+ if (Team_IsAllowed(g_team_entities[0]))
+ {
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else if (Team_IsAllowed(g_team_entities[1]))
+ {
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else
+ {
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ // no further cases, bots have one of the teams
+ }
+ }
+ else
+ {
+ // find first team available
+ if (IS_BOT_CLIENT(for_whom))
+ {
+ if (Team_IsAllowed(g_team_entities[0]))
+ {
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else if (Team_IsAllowed(g_team_entities[1]))
+ {
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else
+ {
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ // no further cases, we know at least 2 teams exist
+ }
+ else
+ {
+ if (Team_IsAllowed(g_team_entities[3]))
+ {
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else if (Team_IsAllowed(g_team_entities[2]))
+ {
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else
+ {
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ // no further cases, bots have one of the teams
+ }
+ }
+ }
+
+ if (!for_whom)
+ {
+ return;
+ }
+
+ // if player has a forced team, ONLY allow that one
+ if (for_whom.team_forced == NUM_TEAM_1 && Team_IsAllowed(
+ g_team_entities[0]))
+ {
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else if (for_whom.team_forced == NUM_TEAM_2 && Team_IsAllowed(
+ g_team_entities[1]))
+ {
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else if (for_whom.team_forced == NUM_TEAM_3 && Team_IsAllowed(
+ g_team_entities[2]))
+ {
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
+ }
+ else if (for_whom.team_forced == NUM_TEAM_4 && Team_IsAllowed(
+ g_team_entities[3]))
+ {
+ g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
+ g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
+ }
+}
+
+int GetAllowedTeams()
+{
+ int result = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ if (Team_IsAllowed(g_team_entities[i]))
+ {
+ result |= BIT(i);
+ }
+ }
+ return result;
+}
+
+bool Team_IsAllowed(entity team_)
+{
+ return team_.m_num_players != TEAM_NOT_ALLOWED;
+}
+
+void GetTeamCounts(entity ignore)
+{
+ if (MUTATOR_CALLHOOK(GetTeamCounts) == true)
+ {
+ // Mutator has overriden the configuration.
+ for (int i = 0; i < 4; ++i)
+ {
+ entity team_ = g_team_entities[i];
+ if (Team_IsAllowed(team_))
+ {
+ MUTATOR_CALLHOOK(GetTeamCount, Team_NumberToTeam(i + 1), ignore,
+ team_.m_num_players, team_.m_num_bots, team_.m_lowest_human,
+ team_.m_lowest_bot);
+ team_.m_num_players = M_ARGV(2, float);
+ team_.m_num_bots = M_ARGV(3, float);
+ team_.m_lowest_human = M_ARGV(4, entity);
+ team_.m_lowest_bot = M_ARGV(5, entity);
+ }
+ }
+ }
+ else
+ {
+ // Manually count all players.
+ FOREACH_CLIENT(true,
+ {
+ if (it == ignore)
+ {
+ continue;
+ }
+ int team_num;
+ if (IS_PLAYER(it) || it.caplayer)
+ {
+ team_num = it.team;
+ }
+ else if (it.team_forced > 0)
+ {
+ team_num = it.team_forced; // reserve the spot
+ }
+ else
+ {
+ continue;
+ }
+ if (!Team_IsValidTeam(team_num))
+ {
+ continue;
+ }
+ entity team_ = Team_GetTeam(team_num);
+ if (!Team_IsAllowed(team_))
+ {
+ continue;
+ }
+ ++team_.m_num_players;
+ if (IS_BOT_CLIENT(it))
+ {
+ ++team_.m_num_bots;
+ }
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (!IS_BOT_CLIENT(it))
+ {
+ if (team_.m_lowest_human == NULL)
+ {
+ team_.m_lowest_human = it;
+ continue;
+ }
+ if (temp_score < PlayerScore_Get(team_.m_lowest_human,
+ SP_SCORE))
+ {
+ team_.m_lowest_human = it;
+ }
+ continue;
+ }
+ if (team_.m_lowest_bot == NULL)
+ {
+ team_.m_lowest_bot = it;
+ continue;
+ }
+ if (temp_score < PlayerScore_Get(team_.m_lowest_bot, SP_SCORE))
+ {
+ team_.m_lowest_bot = it;
+ }
+ });
+ }
+
+ // if the player who has a forced team has not joined yet, reserve the spot
+ if (autocvar_g_campaign)
+ {
+ if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
+ {
+ entity team_ = Team_GetTeamFromIndex(autocvar_g_campaign_forceteam);
+ if (team_.m_num_players == team_.m_num_bots)
+ {
+ ++team_.m_num_players;
+ }
+ }
+ }
+}
+
+int Team_GetNumberOfPlayers(entity team_)
+{
+ return team_.m_num_players;
+}
+
+int Team_GetNumberOfBots(entity team_)
+{
+ return team_.m_num_bots;
+}
+
+entity Team_GetLowestHuman(entity team_)
+{
+ return team_.m_lowest_human;
+}
+
+entity Team_GetLowestBot(entity team_)
+{
+ return team_.m_lowest_bot;
+}
+