case NUM_TEAM_4: e = EFFECT_ROCKETMINSTA_LASER_PINK; break;
default: e = EFFECT_ROCKETMINSTA_LASER_NEUTRAL; break;
}
- if (particleeffectnum(e) < 0 || Team_TeamToNumber(teamid) == -1) { e = EFFECT_TR_NEXUIZPLASMA; }
+ if (particleeffectnum(e) < 0 || !Team_IsValidTeam(teamid)) { e = EFFECT_TR_NEXUIZPLASMA; }
return e;
}
return (frag_victim.classname == "func_assault_destructible");
}
-MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(as, TeamBalance_CheckAllowedTeams)
{
// assault always has 2 teams
- c1 = c2 = 0;
+ M_ARGV(0, float) = BIT(0) | BIT(1);
return true;
}
void CA_count_alive_players()
{
- total_players = redalive = bluealive = yellowalive = pinkalive = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- switch(it.team)
+ total_players = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+ }
+ FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+ {
+ ++total_players;
+ if (IS_DEAD(it))
{
- case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break;
- case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break;
- case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break;
- case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break;
+ continue;
}
+ entity team_ = Entity_GetTeam(it);
+ int num_alive = Team_GetNumberOfAlivePlayers(team_);
+ ++num_alive;
+ Team_SetNumberOfAlivePlayers(team_, num_alive);
});
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- STAT(REDALIVE, it) = redalive;
- STAT(BLUEALIVE, it) = bluealive;
- STAT(YELLOWALIVE, it) = yellowalive;
- STAT(PINKALIVE, it) = pinkalive;
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
+ 1));
+ STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(2));
+ STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(3));
+ STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(4));
});
}
-float CA_GetWinnerTeam()
+int CA_GetWinnerTeam()
{
- float winner_team = 0;
- if(redalive >= 1)
- winner_team = NUM_TEAM_1;
- if(bluealive >= 1)
+ int winner_team = 0;
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
+ winner_team = NUM_TEAM_1;
}
- if(yellowalive >= 1)
+ for (int i = 2; i <= NUM_TEAMS; ++i)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
}
- if(pinkalive >= 1)
+ if (winner_team)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
return winner_team;
+ }
return -1; // no player left
}
void nades_Clear(entity player);
-#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams))
+#define CA_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(ca_teams))
float CA_CheckWinner()
{
if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
}
CA_count_alive_players();
- if(CA_ALIVE_TEAMS() > 1)
+ if (Team_GetNumberOfAliveTeams() > 1)
+ {
return 0;
+ }
int winner_team = CA_GetWinnerTeam();
if(winner_team > 0)
return false;
}
int missing_teams_mask = 0;
- if(ca_teams & BIT(0))
- missing_teams_mask += (!redalive) * 1;
- if(ca_teams & BIT(1))
- missing_teams_mask += (!bluealive) * 2;
- if(ca_teams & BIT(2))
- missing_teams_mask += (!yellowalive) * 4;
- if(ca_teams & BIT(3))
- missing_teams_mask += (!pinkalive) * 8;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if ((ca_teams & Team_IndexToBit(i)) &&
+ (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+ {
+ missing_teams_mask |= Team_IndexToBit(i);
+ }
+ }
if(prev_missing_teams_mask != missing_teams_mask)
{
Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
return true;
}
-MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+MUTATOR_HOOKFUNCTION(ca, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
{
M_ARGV(0, float) = ca_teams;
+ return true;
}
entity ca_LastPlayerForTeam(entity this)
return true;
}
-MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(ctf, TeamBalance_CheckAllowedTeams)
{
- //M_ARGV(0, float) = ctf_teams;
M_ARGV(1, string) = "ctf_team";
- return true;
}
MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
// scoreboard setup
void ctf_ScoreRules(int teams)
{
- CheckAllowedTeams(NULL);
+ //CheckAllowedTeams(NULL); // Bug? Need to get allowed teams?
GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
}
-float total_controlpoints;
+int total_control_points;
void Domination_count_controlpoints()
{
- total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
+ total_control_points = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+ }
IL_EACH(g_dompoints, true,
{
- ++total_controlpoints;
- redowned += (it.goalentity.team == NUM_TEAM_1);
- blueowned += (it.goalentity.team == NUM_TEAM_2);
- yellowowned += (it.goalentity.team == NUM_TEAM_3);
- pinkowned += (it.goalentity.team == NUM_TEAM_4);
+ ++total_control_points;
+ entity team_ = Entity_GetTeam(it.goalentity);
+ int num_control_points = Team_GetNumberOfControlPoints(team_);
+ ++num_control_points;
+ Team_SetNumberOfControlPoints(team_, num_control_points);
});
}
-float Domination_GetWinnerTeam()
+int Domination_GetWinnerTeam()
{
- float winner_team = 0;
- if(redowned == total_controlpoints)
- winner_team = NUM_TEAM_1;
- if(blueowned == total_controlpoints)
+ int winner_team = 0;
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
+ total_control_points)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
+ winner_team = NUM_TEAM_1;
}
- if(yellowowned == total_controlpoints)
+ for (int i = 2; i <= NUM_TEAMS; ++i)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
+ total_control_points)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
}
- if(pinkowned == total_controlpoints)
+ if (winner_team)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
return winner_team;
+ }
return -1; // no control points left?
}
-#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
float Domination_CheckWinner()
{
if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
}
}
-MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
{
// fallback?
M_ARGV(0, float) = domination_teams;
{
if(head.netname != "")
{
- 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;
+ M_ARGV(0, float) |= Team_TeamToBit(head.team);
}
}
dom_spawnteams(domination_teams);
}
- CheckAllowedTeams(NULL);
- //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
-
- int teams = 0;
- if(c1 >= 0) teams |= BIT(0);
- if(c2 >= 0) teams |= BIT(1);
- if(c3 >= 0) teams |= BIT(2);
- if(c4 >= 0) teams |= BIT(3);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ int teams = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
domination_teams = teams;
domination_roundbased = autocvar_g_domination_roundbased;
// TODO: sv_freezetag
#ifdef SVQC
+
+#include <server/resources.qh>
+
float autocvar_g_freezetag_frozen_maxtime;
float autocvar_g_freezetag_revive_clearspeed;
float autocvar_g_freezetag_round_timelimit;
void freezetag_count_alive_players()
{
- total_players = redalive = bluealive = yellowalive = pinkalive = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- switch(it.team)
+ total_players = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+ }
+ FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+ {
+ ++total_players;
+ if ((GetResourceAmount(it, RESOURCE_HEALTH) < 1) ||
+ (STAT(FROZEN, it) == 1))
{
- case NUM_TEAM_1: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++redalive; break;
- case NUM_TEAM_2: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break;
- case NUM_TEAM_3: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break;
- case NUM_TEAM_4: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++pinkalive; break;
+ continue;
}
+ entity team_ = Entity_GetTeam(it);
+ int num_alive = Team_GetNumberOfAlivePlayers(team_);
+ ++num_alive;
+ Team_SetNumberOfAlivePlayers(team_, num_alive);
});
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- STAT(REDALIVE, it) = redalive;
- STAT(BLUEALIVE, it) = bluealive;
- STAT(YELLOWALIVE, it) = yellowalive;
- STAT(PINKALIVE, it) = pinkalive;
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
+ 1));
+ STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(2));
+ STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(3));
+ STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(4));
});
eliminatedPlayers.SendFlags |= 1;
}
-#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(freezetag_teams))
+
+#define FREEZETAG_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(freezetag_teams))
float freezetag_CheckTeams()
{
return 0;
}
int missing_teams_mask = 0;
- if(freezetag_teams & BIT(0))
- missing_teams_mask += (!redalive) * 1;
- if(freezetag_teams & BIT(1))
- missing_teams_mask += (!bluealive) * 2;
- if(freezetag_teams & BIT(2))
- missing_teams_mask += (!yellowalive) * 4;
- if(freezetag_teams & BIT(3))
- missing_teams_mask += (!pinkalive) * 8;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if ((freezetag_teams & Team_IndexToBit(i)) &&
+ (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+ {
+ missing_teams_mask |= Team_IndexToBit(i);
+ }
+ }
if(prev_missing_teams_mask != missing_teams_mask)
{
Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
return 0;
}
-float freezetag_getWinnerTeam()
+int freezetag_getWinnerTeam()
{
- float winner_team = 0;
- if(redalive >= 1)
- winner_team = NUM_TEAM_1;
- if(bluealive >= 1)
+ int winner_team = 0;
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
+ winner_team = NUM_TEAM_1;
}
- if(yellowalive >= 1)
+ for (int i = 2; i <= NUM_TEAMS; ++i)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
+ if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
}
- if(pinkalive >= 1)
+ if (winner_team)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
return winner_team;
+ }
return -1; // no player left
}
return 1;
}
- if(FREEZETAG_ALIVE_TEAMS() > 1)
+ if (Team_GetNumberOfAliveTeams() > 1)
+ {
return 0;
+ }
int winner_team = freezetag_getWinnerTeam();
if(winner_team > 0)
return true;
}
-MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+MUTATOR_HOOKFUNCTION(ft, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
{
M_ARGV(0, float) = freezetag_teams;
+ return true;
}
MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
return true;
}
-MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+MUTATOR_HOOKFUNCTION(inv, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
{
M_ARGV(0, float) = invasion_teams;
+ return true;
}
MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
void invasion_ScoreRules(int inv_teams)
{
- if(inv_teams) { CheckAllowedTeams(NULL); }
+ //if(inv_teams) { CheckAllowedTeams(NULL); } // Another bug?
GameRules_score_enabled(false);
GameRules_scoring(inv_teams, 0, 0, {
if (inv_teams) {
kh_finalize();
}
-MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+MUTATOR_HOOKFUNCTION(kh, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
{
M_ARGV(0, float) = kh_teams;
+ return true;
}
MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
EXACTTRIGGER_INIT;
- if(this.team != GOAL_OUT && Team_TeamToNumber(this.team) != -1)
+ if(this.team != GOAL_OUT && Team_IsValidTeam(this.team))
{
entity wp = WaypointSprite_SpawnFixed(WP_NbGoal, (this.absmin + this.absmax) * 0.5, this, sprite, RADARICON_NONE);
wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0.5 0');
return MUT_ITEMTOUCH_CONTINUE;
}
-MUTATOR_HOOKFUNCTION(nb, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(nb, TeamBalance_CheckAllowedTeams)
{
M_ARGV(1, string) = "nexball_team";
return true;
void Onslaught_count_generators()
{
entity e;
- total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
+ total_generators = 0;
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+ }
for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
{
++total_generators;
- redowned += (e.team == NUM_TEAM_1 && e.health > 0);
- blueowned += (e.team == NUM_TEAM_2 && e.health > 0);
- yellowowned += (e.team == NUM_TEAM_3 && e.health > 0);
- pinkowned += (e.team == NUM_TEAM_4 && e.health > 0);
+ if (GetResourceAmount(e, RESOURCE_HEALTH) < 1)
+ {
+ continue;
+ }
+ entity team_ = Entity_GetTeam(e);
+ int num_control_points = Team_GetNumberOfControlPoints(team_);
+ ++num_control_points;
+ Team_SetNumberOfControlPoints(team_, num_control_points);
}
}
int Onslaught_GetWinnerTeam()
{
int winner_team = 0;
- if(redowned > 0)
- winner_team = NUM_TEAM_1;
- if(blueowned > 0)
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) >= 1)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_2;
+ winner_team = NUM_TEAM_1;
}
- if(yellowowned > 0)
+ for (int i = 2; i <= NUM_TEAMS; ++i)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_3;
+ if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) >= 1)
+ {
+ if (winner_team != 0)
+ {
+ return 0;
+ }
+ winner_team = Team_IndexToTeam(i);
+ }
}
- if(pinkowned > 0)
+ if (winner_team)
{
- if(winner_team) return 0;
- winner_team = NUM_TEAM_4;
- }
- if(winner_team)
return winner_team;
+ }
return -1; // no generators left?
}
void nades_Clear(entity e);
-#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
bool Onslaught_CheckWinner()
{
if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
Onslaught_count_generators();
- if(ONS_OWNED_GENERATORS_OK())
+ if (Team_GetNumberOfTeamsWithControlPoints() > 1)
+ {
return 0;
+ }
int winner_team = Onslaught_GetWinnerTeam();
return true;
}
-MUTATOR_HOOKFUNCTION(ons, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(ons, TeamBalance_CheckAllowedTeams)
{
// onslaught is special
for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
{
- switch(tmp_entity.team)
+ if (Team_IsValidTeam(tmp_entity.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;
+ M_ARGV(0, float) |= Team_TeamToBit(tmp_entity.team);
}
}
// scoreboard setup
void ons_ScoreRules()
{
- CheckAllowedTeams(NULL);
- int teams = 0;
- if(c1 >= 0) teams |= BIT(0);
- if(c2 >= 0) teams |= BIT(1);
- if(c3 >= 0) teams |= BIT(2);
- if(c4 >= 0) teams |= BIT(3);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ int teams = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
field_team(ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY);
field(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
return true; // in qualifying, you don't lose score by observing
}
-MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+MUTATOR_HOOKFUNCTION(rc, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
{
M_ARGV(0, float) = race_teams;
+ return true;
}
MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
}
}
-MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+MUTATOR_HOOKFUNCTION(tdm, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
{
M_ARGV(1, string) = "tdm_team";
- return true;
}
MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
// TODO: find a better location for these?
float total_players;
-float redalive, bluealive, yellowalive, pinkalive;
// todo: accept the number of teams as a parameter
void GameRules_teams(bool value);
return normal_respawntime;
}
- CheckAllowedTeams(NULL);
- GetTeamCounts(NULL);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ TeamBalance_GetTeamCounts(balance, NULL);
int players = 0;
- if (c1 != -1) players += c1;
- if (c2 != -1) players += c2;
- if (c3 != -1) players += c3;
- if (c4 != -1) players += c4;
-
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (TeamBalance_IsTeamAllowed(balance, i))
+ {
+ players += TeamBalance_GetNumberOfPlayers(balance, i);
+ }
+ }
+ TeamBalance_Destroy(balance);
+
if (players >= 2) {
return normal_respawntime * (r / (players + o) + l);
} else {
#pragma once
+const int NUM_TEAMS = 4; ///< Number of teams in the game.
+
#ifdef TEAMNUMBERS_THAT_ARENT_STUPID
const int NUM_TEAM_1 = 1; // red
const int NUM_TEAM_2 = 2; // blue
return -1;
}
-/// \brief Returns whether team is valid.
-/// \param[in] team_ Team to check.
+/// \brief Returns whether team value is valid.
+/// \param[in] team_num Team to check.
/// \return True if team is valid, false otherwise.
-bool Team_IsValidTeam(int team_)
+bool Team_IsValidTeam(int team_num)
{
- switch (team_)
+ switch (team_num)
{
case NUM_TEAM_1:
case NUM_TEAM_2:
return false;
}
-/// \brief Returns whether team number is valid.
-/// \param[in] number Team number to check.
-/// \return True if team number is valid, false otherwise.
-bool Team_IsValidNumber(int number)
+/// \brief Returns whether the team index is valid.
+/// \param[in] index Team index to check.
+/// \return True if team index is valid, false otherwise.
+bool Team_IsValidIndex(int index)
{
- switch (number)
+ switch (index)
{
case 1:
case 2:
return false;
}
-float Team_NumberToTeam(float number)
+/// \brief Converts team index into team value.
+/// \param[in] index Team index to convert.
+/// \return Team value.
+int Team_IndexToTeam(int index)
{
- switch(number)
+ switch (index)
{
case 1: return NUM_TEAM_1;
case 2: return NUM_TEAM_2;
case 3: return NUM_TEAM_3;
case 4: return NUM_TEAM_4;
}
-
return -1;
}
-float Team_TeamToNumber(float teamid)
+/// \brief Converts team value into team index.
+/// \param[in] team_num Team value to convert.
+/// \return Team index.
+int Team_TeamToIndex(int team_num)
{
- switch(teamid)
+ switch (team_num)
{
case NUM_TEAM_1: return 1;
case NUM_TEAM_2: return 2;
case NUM_TEAM_3: return 3;
case NUM_TEAM_4: return 4;
}
-
return -1;
}
+/// \brief Converts team value into bit value that is used in team bitmasks.
+/// \param[in] team_num Team value to convert.
+/// \return Team bit.
+int Team_TeamToBit(int team_num)
+{
+ if (!Team_IsValidTeam(team_num))
+ {
+ return 0;
+ }
+ return BIT(Team_TeamToIndex(team_num) - 1);
+}
+
+/// \brief Converts team index into bit value that is used in team bitmasks.
+/// \param[in] index Team index to convert.
+/// \return Team bit.
+int Team_IndexToBit(int index)
+{
+ return BIT(index - 1);
+}
+
// legacy aliases for shitty code
-#define TeamByColor(teamid) (Team_TeamToNumber(teamid) - 1)
-#define ColorByTeam(number) Team_NumberToTeam(number + 1)
+#define TeamByColor(teamid) (Team_TeamToIndex(teamid) - 1)
+#define ColorByTeam(number) Team_IndexToTeam(number + 1)
// useful aliases
#define Team_ColorName_Lower(teamid) strtolower(Team_ColorName(teamid))
#define Team_FullName(teamid) strcat(Team_ColorName(teamid), " ", NAME_TEAM, "^7")
#define Team_ColoredFullName(teamid) strcat(Team_ColorCode(teamid), Team_ColorName(teamid), " ", NAME_TEAM, "^7")
-#define Team_NumberToFullName(number) Team_FullName(Team_NumberToTeam(number))
-#define Team_NumberToColoredFullName(number) Team_ColoredFullName(Team_NumberToTeam(number))
+#define Team_IndexToFullName(index) Team_FullName(Team_IndexToTeam(index))
+#define Team_IndexToColoredFullName(index) Team_ColoredFullName(Team_IndexToTeam(index))
// replace these flags in a string with the strings provided
#define TCR(input,type,team) strreplace("^TC", COL_TEAM_##team, strreplace("^TT", strtoupper(type##_TEAM_##team), input))
else if(this.bot_forced_team==4)
this.team = NUM_TEAM_4;
else
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this, true);
havocbot_setupbot(this);
}
void bot_removefromlargestteam()
{
- CheckAllowedTeams(NULL);
- GetTeamCounts(NULL);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ TeamBalance_GetTeamCounts(balance, NULL);
entity best = NULL;
float besttime = 0;
int thiscount = 0;
- switch(it.team)
+ if (Team_IsValidTeam(it.team))
{
- case NUM_TEAM_1: thiscount = c1; break;
- case NUM_TEAM_2: thiscount = c2; break;
- case NUM_TEAM_3: thiscount = c3; break;
- case NUM_TEAM_4: thiscount = c4; break;
+ thiscount = TeamBalance_GetNumberOfPlayers(balance,
+ Team_TeamToIndex(it.team));
}
if(thiscount > bestcount)
best = it;
}
});
+ TeamBalance_Destroy(balance);
if(!bcount)
return; // no bots to remove
currentbots = currentbots - 1;
if (mutator_returnvalue) {
// mutator prevents resetting teams+score
} else {
- int oldteam = this.team;
- this.team = -1; // move this as it is needed to log the player spectating in eventlog
- MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team);
- this.frags = FRAGS_SPECTATOR;
+ Player_SetTeamIndex(this, -1);
+ this.frags = FRAGS_SPECTATOR;
PlayerScore_Clear(this); // clear scores when needed
}
Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
if(!CS(this).just_joined)
- LogTeamchange(this.playerid, -1, 4);
+ LogTeamchange(this.playerid, -1, TEAM_CHANGE_SPECTATOR);
else
CS(this).just_joined = false;
}
accuracy_resend(this);
if (this.team < 0)
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this, true);
entity spot = SelectSpawnPoint(this, false);
if (!spot) {
{
if(this.killindicator_teamchange == -1)
{
- JoinBestTeam( this, true );
+ TeamBalance_JoinBestTeam(this, true);
}
else if(this.killindicator_teamchange == -2)
{
}
#endif
+string GetClientVersionMessage(entity this)
+{
+ 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);
+ }
+}
+
+string getwelcomemessage(entity this)
+{
+ MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
+ string modifications = M_ARGV(0, string);
+
+ if(g_weaponarena)
+ {
+ if(g_weaponarena_random)
+ modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
+ 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);
+
+ string versionmessage = GetClientVersionMessage(this);
+ string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
+
+ if(modifications != "")
+ s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+
+ if(cache_lastmutatormsg != autocvar_g_mutatormsg)
+ {
+ 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);
+ }
+
+ string mutator_msg = "";
+ MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
+ mutator_msg = M_ARGV(0, string);
+
+ s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
+
+ string motd = autocvar_sv_motd;
+ if (motd != "") {
+ s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
+ }
+ return s;
+}
+
/**
=============
ClientConnect
int playerid_save = this.playerid;
this.playerid = 0; // silent
- JoinBestTeam(this, false); // if the team number is valid, keep it
+ TeamBalance_JoinBestTeam(this, false); // if the team number is valid, keep it
this.playerid = playerid_save;
if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) {
if (autocvar_sv_eventlog)
GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", playername(this, false)));
- LogTeamchange(this.playerid, this.team, 1);
+ LogTeamchange(this.playerid, this.team, TEAM_CHANGE_CONNECT);
CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
// notify about available teams
if (teamplay)
{
- CheckAllowedTeams(this);
- int t = 0;
- if (c1 >= 0) t |= BIT(0);
- if (c2 >= 0) t |= BIT(1);
- if (c3 >= 0) t |= BIT(2);
- if (c4 >= 0) t |= BIT(3);
+ entity balance = TeamBalance_CheckAllowedTeams(this);
+ int t = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
stuffcmd(this, sprintf("set _teams_available %d\n", t));
}
else
if(!this.team_selected)
if(autocvar_g_campaign || autocvar_g_balance_teams)
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this, true);
if(autocvar_g_campaign)
campaign_bots_may_start = true;
if(IS_PLAYER(this))
if(teamplay && this.team != -1)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
+ {
+ //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
+ }
else
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
this.team_selected = false;
if ((selection != -1) && autocvar_g_balance_teams &&
autocvar_g_balance_teams_prevent_imbalance)
{
- CheckAllowedTeams(caller);
- GetTeamCounts(caller);
- if ((BIT(Team_TeamToNumber(selection) - 1) & FindBestTeams(caller, false)) == 0)
+ entity balance = TeamBalance_CheckAllowedTeams(caller);
+ TeamBalance_GetTeamCounts(balance, caller);
+ if ((Team_IndexToBit(Team_TeamToIndex(selection)) &
+ TeamBalance_FindBestTeams(balance, caller, false)) == 0)
{
Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
+ TeamBalance_Destroy(balance);
return;
}
+ TeamBalance_Destroy(balance);
}
ClientKill_TeamChange(caller, selection);
if (!IS_PLAYER(caller))
// find the team to move the player to
team_id = Team_ColorToTeam(destination);
+ entity balance;
if (team_id == client.team) // already on the destination team
{
// keep the forcing undone
}
else if (team_id == 0) // auto team
{
- CheckAllowedTeams(client);
- team_id = Team_NumberToTeam(FindSmallestTeam(client, false));
+ balance = TeamBalance_CheckAllowedTeams(client);
+ team_id = Team_IndexToTeam(TeamBalance_FindBestTeam(balance, client, false));
}
else
{
- CheckAllowedTeams(client);
+ balance = TeamBalance_CheckAllowedTeams(client);
}
client.team_forced = save;
// Check to see if the destination team is even available
switch (team_id)
{
- case NUM_TEAM_1: if (c1 == -1) { LOG_INFO("Sorry, can't move player to red team if it doesn't exist."); return; } break;
- case NUM_TEAM_2: if (c2 == -1) { LOG_INFO("Sorry, can't move player to blue team if it doesn't exist."); return; } break;
- case NUM_TEAM_3: if (c3 == -1) { LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist."); return; } break;
- case NUM_TEAM_4: if (c4 == -1) { LOG_INFO("Sorry, can't move player to pink team if it doesn't exist."); return; } break;
-
- default: LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.");
+ case NUM_TEAM_1:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 1))
+ {
+ LOG_INFO("Sorry, can't move player to red team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ case NUM_TEAM_2:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 2))
+ {
+ LOG_INFO("Sorry, can't move player to blue team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ case NUM_TEAM_3:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 3))
+ {
+ LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ case NUM_TEAM_4:
+ {
+ if (!TeamBalance_IsTeamAllowed(balance, 4))
+ {
+ LOG_INFO("Sorry, can't move player to pink team if it doesn't exist.");
+ TeamBalance_Destroy(balance);
+ return;
+ }
+ TeamBalance_Destroy(balance);
+ break;
+ }
+ default:
+ {
+ LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.");
return;
+ }
}
// If so, lets continue and finally move the player
client.team_forced = 0;
- if (MoveToTeam(client, team_id, 6))
+ if (MoveToTeam(client, Team_TeamToIndex(team_id), 6))
{
successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.");
});
int number_of_teams = 0;
- CheckAllowedTeams(NULL);
- if (c1 >= 0) number_of_teams = max(1, number_of_teams);
- if (c2 >= 0) number_of_teams = max(2, number_of_teams);
- if (c3 >= 0) number_of_teams = max(3, number_of_teams);
- if (c4 >= 0) number_of_teams = max(4, number_of_teams);
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ for (int i = 1; i <= NUM_TEAMS; ++i)
+ {
+ if (TeamBalance_IsTeamAllowed(balance, i))
+ {
+ number_of_teams = max(i, number_of_teams);
+ }
+ }
+ TeamBalance_Destroy(balance);
int team_index = 0;
FOREACH_CLIENT_RANDOM(IS_PLAYER(it) || it.caplayer, {
- int target_team_number = Team_NumberToTeam(team_index + 1);
- if (it.team != target_team_number) MoveToTeam(it, target_team_number, 6);
+ int target_team_index = team_index + 1;
+ if (Entity_GetTeamIndex(it) != target_team_index)
+ {
+ MoveToTeam(it, target_team_index, 6);
+ }
team_index = (team_index + 1) % number_of_teams;
});
void UpdateFrags(entity player, int f);
.float totalfrags;
-float team1_score, team2_score, team3_score, team4_score;
-
// flag set on worldspawn so that the code knows if it is dedicated or not
float server_is_dedicated;
// WEAPONTODO
#define DMG_NOWEP (weaponentities[0])
-float lockteams;
-
float sv_maxidle;
float sv_maxidle_spectatorsareidle;
int sv_maxidle_slots;
}
}
+void default_delayedinit(entity this)
+{
+ if(!scores_initialized)
+ ScoreRules_generic();
+}
+
+void InitGameplayMode()
+{
+ 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);
+}
+
void Map_MarkAsRecent(string m);
float world_already_spawned;
void Nagger_Init();
if(teamplay)
{
- team1_score = TeamScore_GetCompareValue(NUM_TEAM_1);
- team2_score = TeamScore_GetCompareValue(NUM_TEAM_2);
- team3_score = TeamScore_GetCompareValue(NUM_TEAM_3);
- team4_score = TeamScore_GetCompareValue(NUM_TEAM_4);
+ for (int i = 1; i < 5; ++i)
+ {
+ Team_SetTeamScore(Team_GetTeamFromIndex(i),
+ TeamScore_GetCompareValue(Team_IndexToTeam(i)));
+ }
}
ClearWinners();
if(!some_spawn_has_been_used)
return WINNING_NO;
- team1_score = team2_score = team3_score = team4_score = 0;
-
- FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
- switch(it.team)
+ for (int i = 1; i < 5; ++i)
+ {
+ Team_SetTeamScore(Team_GetTeamFromIndex(i), 0);
+ }
+
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+ {
+ if (Team_IsValidTeam(it.team))
{
- case NUM_TEAM_1: team1_score = 1; break;
- case NUM_TEAM_2: team2_score = 1; break;
- case NUM_TEAM_3: team3_score = 1; break;
- case NUM_TEAM_4: team4_score = 1; break;
+ Team_SetTeamScore(Team_GetTeam(it.team), 1);
}
});
IL_EACH(g_spawnpoints, true,
{
- switch(it.team)
+ if (Team_IsValidTeam(it.team))
{
- case NUM_TEAM_1: team1_score = 1; break;
- case NUM_TEAM_2: team2_score = 1; break;
- case NUM_TEAM_3: team3_score = 1; break;
- case NUM_TEAM_4: team4_score = 1; break;
+ Team_SetTeamScore(Team_GetTeam(it.team), 1);
}
});
ClearWinners();
+ float team1_score = Team_GetTeamScore(Team_GetTeamFromIndex(1));
+ float team2_score = Team_GetTeamScore(Team_GetTeamFromIndex(2));
+ float team3_score = Team_GetTeamScore(Team_GetTeamFromIndex(3));
+ float team4_score = Team_GetTeamScore(Team_GetTeamFromIndex(4));
if(team1_score + team2_score + team3_score + team4_score == 0)
{
checkrules_equality = true;
{
float t, i;
if(team1_score)
- t = NUM_TEAM_1;
+ t = 1;
else if(team2_score)
- t = NUM_TEAM_2;
+ t = 2;
else if(team3_score)
- t = NUM_TEAM_3;
+ t = 3;
else // if(team4_score)
- t = NUM_TEAM_4;
- CheckAllowedTeams(NULL);
+ t = 4;
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
for(i = 0; i < MAX_TEAMSCORE; ++i)
{
- if(t != NUM_TEAM_1) if(c1 >= 0) TeamScore_AddToTeam(NUM_TEAM_1, i, -1000);
- if(t != NUM_TEAM_2) if(c2 >= 0) TeamScore_AddToTeam(NUM_TEAM_2, i, -1000);
- if(t != NUM_TEAM_3) if(c3 >= 0) TeamScore_AddToTeam(NUM_TEAM_3, i, -1000);
- if(t != NUM_TEAM_4) if(c4 >= 0) TeamScore_AddToTeam(NUM_TEAM_4, i, -1000);
+ for (int j = 1; j <= NUM_TEAMS; ++j)
+ {
+ if (t == j)
+ {
+ continue;
+ }
+ if (!TeamBalance_IsTeamAllowed(balance, j))
+ {
+ continue;
+ }
+ TeamScore_AddToTeam(Team_IndexToTeam(j), i, -1000);
+ }
}
AddWinners(team, t);
float checkrules_suddendeathend;
float checkrules_overtimesadded; //how many overtimes have been already added
+string cache_mutatormsg;
+string cache_lastmutatormsg;
+
const int WINNING_NO = 0; // no winner, but time limits may terminate the game
const int WINNING_YES = 1; // winner found
const int WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached
/** called when the match ends */
MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
-/** allows adjusting allowed teams */
-#define EV_CheckAllowedTeams(i, o) \
+/** Allows adjusting allowed teams. Return true to use the bitmask value and set
+ * non-empty string to use team entity name. Both behaviors can be active at the
+ * same time and will stack allowed teams.
+ */
+#define EV_TeamBalance_CheckAllowedTeams(i, o) \
/** mask of teams */ i(float, MUTATOR_ARGV_0_float) \
/**/ o(float, MUTATOR_ARGV_0_float) \
/** team entity name */ i(string, MUTATOR_ARGV_1_string) \
/**/ o(string, MUTATOR_ARGV_1_string) \
/** player checked */ i(entity, MUTATOR_ARGV_2_entity) \
/**/
-MUTATOR_HOOKABLE(CheckAllowedTeams, EV_CheckAllowedTeams);
+MUTATOR_HOOKABLE(TeamBalance_CheckAllowedTeams,
+ EV_TeamBalance_CheckAllowedTeams);
/** return true to manually override team counts */
-MUTATOR_HOOKABLE(GetTeamCounts, EV_NO_ARGS);
+MUTATOR_HOOKABLE(TeamBalance_GetTeamCounts, EV_NO_ARGS);
-/** allow overriding of team counts */
-#define EV_GetTeamCount(i, o) \
- /** team to count */ i(float, MUTATOR_ARGV_0_float) \
+/** allows overriding of team counts */
+#define EV_TeamBalance_GetTeamCount(i, o) \
+ /** team index to count */ i(float, MUTATOR_ARGV_0_float) \
/** player to ignore */ i(entity, MUTATOR_ARGV_1_entity) \
- /** number of players in a team */ i(float, MUTATOR_ARGV_2_float) \
- /**/ o(float, MUTATOR_ARGV_2_float) \
- /** number of bots in a team */ i(float, MUTATOR_ARGV_3_float) \
- /**/ o(float, MUTATOR_ARGV_3_float) \
- /** lowest scoring human in a team */ i(entity, MUTATOR_ARGV_4_entity) \
- /**/ o(entity, MUTATOR_ARGV_4_entity) \
- /** lowest scoring bot in a team */ i(entity, MUTATOR_ARGV_5_entity) \
- /**/ o(entity, MUTATOR_ARGV_5_entity) \
- /**/
-MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount);
-
-/** allows overriding best teams */
-#define EV_FindBestTeams(i, o) \
+ /** number of players in a team */ o(float, MUTATOR_ARGV_2_float) \
+ /** number of bots in a team */ o(float, MUTATOR_ARGV_3_float) \
+ /**/
+MUTATOR_HOOKABLE(TeamBalance_GetTeamCount, EV_TeamBalance_GetTeamCount);
+
+/** allows overriding the teams that will make the game most balanced if the
+ * player joins any of them.
+ */
+#define EV_TeamBalance_FindBestTeams(i, o) \
/** player checked */ i(entity, MUTATOR_ARGV_0_entity) \
/** bitmask of teams */ o(float, MUTATOR_ARGV_1_float) \
/**/
-MUTATOR_HOOKABLE(FindBestTeams, EV_FindBestTeams);
+MUTATOR_HOOKABLE(TeamBalance_FindBestTeams, EV_TeamBalance_FindBestTeams);
+
+/** Called during autobalance. Return true to override the player that will be
+switched. */
+#define EV_TeamBalance_GetPlayerForTeamSwitch(i, o) \
+ /** source team index */ i(int, MUTATOR_ARGV_0_int) \
+ /** destination team index */ i(int, MUTATOR_ARGV_1_int) \
+ /** is looking for bot */ i(bool, MUTATOR_ARGV_2_bool) \
+ /** player to switch */ o(entity, MUTATOR_ARGV_3_entity) \
+ /**/
+MUTATOR_HOOKABLE(TeamBalance_GetPlayerForTeamSwitch,
+ EV_TeamBalance_GetPlayerForTeamSwitch);
/** copies variables for spectating "spectatee" to "this" */
#define EV_SpectateCopy(i, o) \
* Called before player changes their team. Return true to block team change.
*/
#define EV_Player_ChangeTeam(i, o) \
- /** player */ i(entity, MUTATOR_ARGV_0_entity) \
- /** current team */ i(float, MUTATOR_ARGV_1_float) \
- /** new team */ i(float, MUTATOR_ARGV_2_float) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** current team index */ i(float, MUTATOR_ARGV_1_float) \
+ /** new team index */ i(float, MUTATOR_ARGV_2_float) \
/**/
MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam);
* Called after player has changed their team.
*/
#define EV_Player_ChangedTeam(i, o) \
- /** player */ i(entity, MUTATOR_ARGV_0_entity) \
- /** old team */ i(float, MUTATOR_ARGV_1_float) \
- /** current team */ i(float, MUTATOR_ARGV_2_float) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** old team index */ i(float, MUTATOR_ARGV_1_float) \
+ /** current team index */ i(float, MUTATOR_ARGV_2_float) \
/**/
MUTATOR_HOOKABLE(Player_ChangedTeam, EV_Player_ChangedTeam);
}
}
-bool MoveToTeam(entity client, int team_colour, int type)
-{
- int lockteams_backup = lockteams; // backup any team lock
- lockteams = 0; // disable locked teams
- TeamchangeFrags(client); // move the players frags
- if (!SetPlayerTeamSimple(client, team_colour))
- {
- return false;
- }
- Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, DMG_NOWEP, client.origin, '0 0 0'); // kill the player
- lockteams = lockteams_backup; // restore the team lock
- LogTeamchange(client.playerid, client.team, type);
- return true;
-}
-
/**
* message "": do not say, just test flood control
* return value:
void ClientKill_Now_TeamChange(entity this);
-/// \brief Moves player to the specified team.
-/// \param[in,out] client Client to move.
-/// \param[in] team_colour Color of the team.
-/// \param[in] type ???
-/// \return True on success, false otherwise.
-bool MoveToTeam(entity client, float team_colour, float type);
-
void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
int ScoreRules_teams;
-void CheckAllowedTeams (entity for_whom);
-
int NumTeams(int teams)
{
return boolean(teams & BIT(0)) + boolean(teams & BIT(1)) + boolean(teams & BIT(2)) + boolean(teams & BIT(3));
int AvailableTeams()
{
return NumTeams(ScoreRules_teams);
- // NOTE: this method is unreliable, as forced teams set the c* globals to weird values
- //return boolean(c1 >= 0) + boolean(c2 >= 0) + boolean(c3 >= 0) + boolean(c4 >= 0);
}
// NOTE: ST_constants may not be >= MAX_TEAMSCORE
void ScoreRules_generic()
{
int teams = 0;
- if (teamplay) {
- CheckAllowedTeams(NULL);
- if (c1 >= 0) teams |= BIT(0);
- if (c2 >= 0) teams |= BIT(1);
- if (c3 >= 0) teams |= BIT(2);
- if (c4 >= 0) teams |= BIT(3);
+ if (teamplay)
+ {
+ entity balance = TeamBalance_CheckAllowedTeams(NULL);
+ teams = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
}
GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {});
}
#include <common/gamemodes/_mod.qh>
#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)
#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;
}
}
-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)
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;
}
// 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);
}
#pragma once
-string cache_mutatormsg;
-string cache_lastmutatormsg;
+bool lockteams;
-// The following variables are used for balancing. They are not updated
-// automatically. You need to call CheckAllowedTeams and GetTeamCounts to get
-// proper values.
+// ========================== Global teams API ================================
-// These four have 2 different states. If they are equal to -1, it means that
-// the player can't join the team. Zero or positive value means that player can
-// join the team and means the number of players on that team.
-float c1;
-float c2;
-float c3;
-float c4;
-float num_bots_team1; ///< Number of bots in the first team.
-float num_bots_team2; ///< Number of bots in the second team.
-float num_bots_team3; ///< Number of bots in the third team.
-float num_bots_team4; ///< Number of bots in the fourth team.
-entity lowest_human_team1; ///< Human with the lowest score in the first team.
-entity lowest_human_team2; ///< Human with the lowest score in the second team.
-entity lowest_human_team3; ///< Human with the lowest score in the third team.
-entity lowest_human_team4; ///< Human with the lowest score in the fourth team.
-entity lowest_bot_team1; ///< Bot with the lowest score in the first team.
-entity lowest_bot_team2; ///< Bot with the lowest score in the second team.
-entity lowest_bot_team3; ///< Bot with the lowest score in the third team.
-entity lowest_bot_team4; ///< Bot with the lowest score in the fourth team.
+/// \brief Returns the global team entity at the given index.
+/// \param[in] index Index of the team.
+/// \return Global team entity at the given index.
+entity Team_GetTeamFromIndex(int index);
-int redowned, blueowned, yellowowned, pinkowned;
+/// \brief Returns the global team entity that corresponds to the given TEAM_NUM
+/// value.
+/// \param[in] team_num Team value. See TEAM_NUM constants.
+/// \return Global team entity that corresponds to the given TEAM_NUM value.
+entity Team_GetTeam(int team_num);
-//float audit_teams_time;
+// ========================= Team specific API ================================
-void TeamchangeFrags(entity e);
+/// \brief Returns the score of the team.
+/// \param[in] team_ent Team entity.
+/// \return Score of the team.
+float Team_GetTeamScore(entity team_ent);
-void LogTeamchange(float player_id, float team_number, float type);
+/// \brief Sets the score of the team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] score Score to set.
+void Team_SetTeamScore(entity team_ent, float score);
-void default_delayedinit(entity this);
+/// \brief Returns the number of alive players in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of alive players in a team.
+int Team_GetNumberOfAlivePlayers(entity team_ent);
-void InitGameplayMode();
+/// \brief Sets the number of alive players in a team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] number Number of players to set.
+void Team_SetNumberOfAlivePlayers(entity team_ent, int number);
-string GetClientVersionMessage(entity this);
+/// \brief Returns the number of alive teams.
+/// \return Number of alive teams.
+int Team_GetNumberOfAliveTeams();
-string getwelcomemessage(entity this);
+/// \brief Returns the number of control points owned by a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of control points owned by a team.
+int Team_GetNumberOfControlPoints(entity team_ent);
-void SetPlayerColors(entity player, float _color);
+/// \brief Sets the number of control points owned by a team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] number Number of control points to set.
+void Team_SetNumberOfControlPoints(entity team_ent, int number);
-/// \brief Kills player as a result of team change.
-/// \param[in,out] player Player to kill.
-/// \return No return.
-void KillPlayerForTeamChange(entity player);
+/// \brief Returns the number of teams that own control points.
+/// \return Number of teams that own control points.
+int Team_GetNumberOfTeamsWithControlPoints();
-/// \brief Sets the team of the player.
+// ======================= Entity specific API ================================
+
+void setcolor(entity this, int clr);
+
+/// \brief Returns whether the given entity belongs to a valid team.
+/// \param[in] this Entity to check.
+/// \return True if entity belongs to a valid team, false otherwise.
+bool Entity_HasValidTeam(entity this);
+
+/// \brief Returns the team index of the given entity.
+/// \param[in] this Entity to check.
+/// \return Team index of the entity.
+int Entity_GetTeamIndex(entity this);
+
+/// \brief Returns the team entity of the given entity.
+/// \param[in] this Entity to check.
+/// \return Team entity of the given entity.
+entity Entity_GetTeam(entity this);
+
+void SetPlayerColors(entity player, float _color);
+
+/// \brief Sets the team of the player using its index.
/// \param[in,out] player Player to adjust.
-/// \param[in] team_num Team number to set. See TEAM_NUM constants.
+/// \param[in] index Index of the team to set.
/// \return True if team switch was successful, false otherwise.
-bool SetPlayerTeamSimple(entity player, int team_num);
+bool Player_SetTeamIndex(entity player, int index);
/// \brief Sets the team of the player.
/// \param[in,out] player Player to adjust.
-/// \param[in] destination_team Team to set.
-/// \param[in] source_team Previous team of the player.
-/// \param[in] no_print Whether to print this event to players' console.
+/// \param[in] team_index Index of the team to set.
+/// \param[in] type ???
/// \return True if team switch was successful, false otherwise.
-bool SetPlayerTeam(entity player, int destination_team, int source_team,
- bool no_print);
+bool SetPlayerTeam(entity player, int team_index, int type);
+
+/// \brief Moves player to the specified team.
+/// \param[in,out] client Client to move.
+/// \param[in] team_index Index of the team.
+/// \param[in] type ???
+/// \return True on success, false otherwise.
+bool MoveToTeam(entity client, int team_index, int type);
+
+/// \brief Kills player as a result of team change.
+/// \param[in,out] player Player to kill.
+void KillPlayerForTeamChange(entity player);
-// set c1...c4 to show what teams are allowed
-void CheckAllowedTeams(entity for_whom);
+enum
+{
+ TEAM_CHANGE_CONNECT = 1,
+ TEAM_CHANGE_AUTO = 2,
+ TEAM_CHANGE_MANUAL = 3,
+ TEAM_CHANGE_SPECTATOR = 4,
+ TEAM_CHANGE_AUTO_RELAXED = 99
+};
-float PlayerValue(entity p);
+void LogTeamchange(float player_id, float team_number, int type);
-// 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);
+// ========================= Team balance API =================================
-/// \brief Returns whether one team is smaller than the other.
-/// \param[in] team_a First team.
-/// \param[in] team_b Second team.
+/// \brief Checks whether the player can join teams according to global
+/// configuration and mutator settings.
+/// \param[in] for_whom Player to check for. Pass NULL for global rules.
+/// \return Team balance entity that holds information about teams. This entity
+/// must be manually destroyed by calling TeamBalance_Destroy.
+entity TeamBalance_CheckAllowedTeams(entity for_whom);
+
+/// \brief Destroy the team balance entity.
+/// \param[in,out] balance Team balance entity to destroy.
+/// \note Team balance entity is allowed to be NULL.
+void TeamBalance_Destroy(entity balance);
+
+/// \brief Returns the bitmask of allowed teams.
+/// \param[in] balance Team balance entity.
+/// \return Bitmask of allowed teams.
+int TeamBalance_GetAllowedTeams(entity balance);
+
+/// \brief Returns whether the team change to the specified team is allowed.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return True if team change to the specified team is allowed, false
+/// otherwise.
+bool TeamBalance_IsTeamAllowed(entity balance, int index);
+
+/// \brief Counts the number of players and various other information about
+/// each team.
+/// \param[in,out] balance Team balance entity.
+/// \param[in] ignore Player to ignore. This is useful if you plan to switch the
+/// player's team. Pass NULL for global information.
+/// \note This function updates the internal state of the team balance entity.
+void TeamBalance_GetTeamCounts(entity balance, entity ignore);
+
+/// \brief Returns the number of players (both humans and bots) in a team.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return Number of player (both humans and bots) in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_GetNumberOfPlayers(entity balance, int index);
+
+/// \brief Finds the team that will make the game most balanced if the player
+/// joins it.
+/// \param[in] balance Team balance entity.
/// \param[in] player Player to check.
-/// \param[in] use_score 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(int team_a, int team_b, entity player,
- bool use_score);
+/// \param[in] ignore_player ???
+/// \return Index of the team that will make the game most balanced if the
+/// player joins it. If there are several equally good teams available, the
+/// function will pick a random one.
+int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player);
-/// \brief Returns whether one team is equal to the other.
-/// \param[in] team_a First team.
-/// \param[in] team_b Second team.
+/// \brief Returns the bitmask of the teams that will make the game most
+/// balanced if the player joins any of them.
+/// \param[in] balance Team balance entity.
/// \param[in] player Player to check.
/// \param[in] use_score 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(int team_a, int team_b, entity player, bool use_score);
+/// \return Bitmask of the teams that will make the game most balanced if the
+/// player joins any of them.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score);
+
+void TeamBalance_JoinBestTeam(entity this, bool force_best_team);
-/// \brief Returns the bitmask of the best teams for the player to join.
+/// \brief Describes the result of comparing teams.
+enum
+{
+ TEAMS_COMPARE_INVALID, ///< One or both teams are invalid.
+ TEAMS_COMPARE_LESS, ///< First team is less than the second one.
+ TEAMS_COMPARE_EQUAL, ///< Both teams are equal.
+ TEAMS_COMPARE_GREATER ///< First team the greater than the second one.
+};
+
+/// \brief Compares two teams for the purposes of game balance.
+/// \param[in] balance Team balance entity.
+/// \param[in] team_index_a Index of the first team.
+/// \param[in] team_index_b Index of the second team.
/// \param[in] player Player to check.
/// \param[in] use_score 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 use_score);
+/// \return TEAMS_COMPARE value. See above.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
+ entity player, bool use_score);
-// returns # of smallest team (1, 2, 3, 4)
-// NOTE: Assumes CheckAllowedTeams has already been called!
-int FindSmallestTeam(entity player, float ignore_player);
+/// \brief Switches a bot from one team to another if teams are not balanced.
+/// \param[in] source_team_index Index of the team to switch from.
+/// \param[in] destination_team_index Index of the team to switch to.
+void TeamBalance_AutoBalanceBots(int source_team_index,
+ int destination_team_index);
-void JoinBestTeam(entity this, bool force_best_team);
+// ============================ Internal API ==================================
-/// \brief Auto balances bots in teams after the player has changed team.
-/// \param[in] source_team Previous team of the player (1, 2, 3, 4).
-/// \param[in] destination_team Current team of the player (1, 2, 3, 4).
-/// \return No return.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-void AutoBalanceBots(int source_team, int destination_team);
+/// \brief Returns whether the team change to the specified team is allowed.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return True if team change to the specified team is allowed, false
+/// otherwise.
+/// \note This function bypasses all the sanity checks.
+bool TeamBalance_IsTeamAllowedInternal(entity balance, int index);
-void setcolor(entity this, int clr);
+/// \brief Bans team change to all teams except the given one.
+/// \param[in,out] balance Team balance entity.
+/// \param[in] index Index of the team.
+void TeamBalance_BanTeamsExcept(entity balance, int index);
+
+/// \brief Returns the team entity of the team balance entity at the given
+/// index.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return Team entity of the team balance entity at the given index.
+entity TeamBalance_GetTeamFromIndex(entity balance, int index);
+
+/// \brief Returns the team entity of the team balance entity that corresponds
+/// to the given TEAM_NUM value.
+/// \param[in] balance Team balance entity.
+/// \param[in] team_num Team value. See TEAM_NUM constants.
+/// \return Team entity of the team balance entity that corresponds to the given
+/// TEAM_NUM value.
+entity TeamBalance_GetTeam(entity balance, int team_num);
+
+/// \brief Returns whether the team is allowed.
+/// \param[in] team_ent Team entity.
+/// \return True if team is allowed, false otherwise.
+bool TeamBalanceTeam_IsAllowed(entity team_ent);
+
+/// \brief Returns the number of players (both humans and bots) in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of player (both humans and bots) in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent);
+
+/// \brief Returns the number of bots in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of bots in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalanceTeam_GetNumberOfBots(entity team_ent);
+
+/// \brief Compares two teams for the purposes of game balance.
+/// \param[in] team_a First team.
+/// \param[in] team_b Second team.
+/// \param[in] player Player to check.
+/// \param[in] use_score Whether to take into account team scores.
+/// \return TEAMS_COMPARE value. See above.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_CompareTeamsInternal(entity team_a, entity team_index_b,
+ entity player, bool use_score);