#include "teamplay.qh"
-#include "client.qh"
-#include "race.qh"
-#include "scores.qh"
-#include "scores_rules.qh"
-
-#include "bot/api.qh"
-
-#include "command/vote.qh"
-
-#include <server/mutators/_mod.qh>
-
-#include "../common/deathtypes/all.qh"
+#include <common/deathtypes/all.qh>
#include <common/gamemodes/_mod.qh>
-#include "../common/teams.qh"
+#include <common/teams.qh>
+#include <server/bot/api.qh>
+#include <server/bot/default/cvars.qh>
+#include <server/campaign.qh>
+#include <server/client.qh>
+#include <server/command/vote.qh>
+#include <server/damage.qh>
+#include <server/gamelog.qh>
+#include <server/mutators/_mod.qh>
+#include <server/race.qh>
+#include <server/scores.qh>
+#include <server/scores_rules.qh>
/// \brief Describes a state of team balance entity.
enum
/// \brief Indicates that the player is not allowed to join a team.
const int TEAM_NOT_ALLOWED = -1;
-.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
-
.int m_team_balance_state; ///< Holds the state of the team balance entity.
.entity m_team_balance_team[NUM_TEAMS]; ///< ???
entity g_team_entities[NUM_TEAMS]; ///< Holds global team entities.
-STATIC_INIT(g_team_entities)
+void Team_InitTeams()
{
+ if (g_team_entities[0])
+ return;
for (int i = 0; i < NUM_TEAMS; ++i)
{
- g_team_entities[i] = spawn();
+ g_team_entities[i] = new_pure(team_entity);
}
}
{
// 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.
+ // [BOT]Lion: color 0 4.
SetPlayerColors(player, new_team - 1);
}
return true;
bool SetPlayerTeam(entity player, int team_index, int type)
{
int old_team_index = Entity_GetTeamIndex(player);
+
if (!Player_SetTeamIndex(player, team_index))
- {
return false;
- }
+
LogTeamChange(player.playerid, player.team, type);
+
if (team_index != old_team_index)
{
+ KillPlayerForTeamChange(player);
PlayerScore_Clear(player);
+ CS(player).parm_idlesince = time;
+
+ if (!IS_BOT_CLIENT(player))
+ TeamBalance_AutoBalanceBots();
+
if (team_index != -1)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, INFO_JOIN_PLAY_TEAM), player.netname);
+ }
+
+ if (team_index == -1)
+ {
+ if (autocvar_sv_maxidle_playertospectator > 0 && CS(player).idlekick_lasttimeleft)
{
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(
- player.team, INFO_JOIN_PLAY_TEAM), player.netname);
+ // this done here so it happens even when manually speccing during the countdown
+ Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_IDLING);
+ CS(player).idlekick_lasttimeleft = 0;
}
- else
+ else if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
{
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE,
- player.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
}
- KillPlayerForTeamChange(player);
- if (!IS_BOT_CLIENT(player))
+ }
+
+ return true;
+}
+
+void Player_SetTeamIndexChecked(entity player, int team_index)
+{
+ if (!teamplay)
+ {
+ return;
+ }
+ if (!Team_IsValidIndex(team_index))
+ {
+ return;
+ }
+ if ((autocvar_g_campaign) || (autocvar_g_changeteam_banned &&
+ CS(player).wasplayer))
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_INFO,
+ INFO_TEAMCHANGE_NOTALLOWED);
+ return;
+ }
+ entity balance = TeamBalance_CheckAllowedTeams(player);
+ if (team_index == 1 && !TeamBalance_IsTeamAllowedInternal(balance, 1))
+ {
+ team_index = 4;
+ }
+ if (team_index == 4 && !TeamBalance_IsTeamAllowedInternal(balance, 4))
+ {
+ team_index = 3;
+ }
+ if (team_index == 3 && !TeamBalance_IsTeamAllowedInternal(balance, 3))
+ {
+ team_index = 2;
+ }
+ if (team_index == 2 && !TeamBalance_IsTeamAllowedInternal(balance, 2))
+ {
+ team_index = 1;
+ }
+ // 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)
+ {
+ TeamBalance_GetTeamCounts(balance, player);
+ if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance,
+ player, false)) == 0)
{
- TeamBalance_AutoBalanceBots();
+ Send_Notification(NOTIF_ONE, player, MSG_INFO,
+ INFO_TEAMCHANGE_LARGERTEAM);
+ TeamBalance_Destroy(balance);
+ return;
}
}
- return true;
+ TeamBalance_Destroy(balance);
+ SetPlayerTeam(player, team_index, TEAM_CHANGE_MANUAL);
}
bool MoveToTeam(entity client, int team_index, int type)
team_ent.m_num_bots = 0;
}
setthink(balance, TeamBalance_Destroy);
-
- int teams_mask = 0;
+ balance.nextthink = time;
+
+ int teams_mask = 0;
string teament_name = string_null;
bool mutator_returnvalue = MUTATOR_CALLHOOK(TeamBalance_CheckAllowedTeams,
teams_mask, teament_name, for_whom);
}
// TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
- if (AvailableTeams() == 2)
- if (autocvar_bot_vs_human && for_whom)
+ if (autocvar_bot_vs_human && AvailableTeams() == 2 && for_whom)
{
if (autocvar_bot_vs_human > 0)
{
TeamBalance_IsTeamAllowedInternal(balance, i))
{
TeamBalance_BanTeamsExcept(balance, i);
+ break;
}
- break;
}
balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
return balance;
void TeamBalance_AutoBalanceBots()
{
- if (!autocvar_g_balance_teams ||
- !autocvar_g_balance_teams_prevent_imbalance)
- {
- return;
- }
- //PrintToChatAll("TeamBalance_AutoBalanceBots");
+ // checks disabled because we always want auto-balanced bots
+ //if (!(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance))
+ // return;
+
entity balance = TeamBalance_CheckAllowedTeams(NULL);
TeamBalance_GetTeamCounts(balance, NULL);
int smallest_team_index = 0;
{
continue;
}
- int player_count = TeamBalanceTeam_GetNumberOfPlayers(team_);
+ int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
if (smallest_team_index == 0)
{
smallest_team_index = i;
- smallest_team_player_count = player_count;
+ smallest_team_player_count = playercount;
}
- else if (player_count < smallest_team_player_count)
+ else if (playercount < smallest_team_player_count)
{
smallest_team_index = i;
- smallest_team_player_count = player_count;
+ smallest_team_player_count = playercount;
}
}
//PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
{
continue;
}
- int player_count = TeamBalanceTeam_GetNumberOfPlayers(team_);
+ int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
if (largest_team_index == 0)
{
largest_team_index = i;
- largest_team_player_count = player_count;
+ largest_team_player_count = playercount;
}
- else if (player_count > largest_team_player_count)
+ else if (playercount > largest_team_player_count)
{
largest_team_index = i;
- largest_team_player_count = player_count;
+ largest_team_player_count = playercount;
}
}
return largest_team_index;
{
return;
}
- GameLogEcho(sprintf(":team:%f:%f:%f", player_id, team_number, type));
+ GameLogEcho(sprintf(":team:%d:%d:%d", player_id, team_number, type));
}
void KillPlayerForTeamChange(entity player)
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)
+void SV_ChangeTeam(entity player, int new_color)
{
- //PrintToChatAll(sprintf("SV_ChangeTeam: %s, %f", this.netname, _color));
-
- // in normal deathmatch we can just apply the color and we're done
- if(!teamplay)
- SetPlayerColors(this, _color);
-
- if(!IS_CLIENT(this))
+ if (!teamplay)
{
- //PrintToChatAll("Not a client yet");
- // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING, this.netname);
- return;
+ SetPlayerColors(player, new_color);
}
-
- if(!teamplay)
- return;
-
- int source_color, destination_color;
- int source_team_index, destination_team_index;
-
- source_color = this.clientcolors & 0x0F;
- destination_color = _color & 0x0F;
-
- source_team_index = Team_TeamToIndex(source_color + 1);
- destination_team_index = Team_TeamToIndex(destination_color + 1);
-
- if (destination_team_index == -1)
+ if(!IS_CLIENT(player))
{
return;
}
-
- entity balance = TeamBalance_CheckAllowedTeams(this);
-
- 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)
+ if (!teamplay)
{
- SetPlayerTeam(this, destination_team_index, TEAM_CHANGE_MANUAL);
- TeamBalance_Destroy(balance);
return;
}
-
- if((autocvar_g_campaign) || (autocvar_g_changeteam_banned && CS(this).wasplayer)) {
- Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_NOTALLOWED);
- return; // changing teams is not allowed
- }
-
- // 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)
- {
- 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;
- }
- }
- TeamBalance_Destroy(balance);
- SetPlayerTeam(this, destination_team_index, TEAM_CHANGE_MANUAL);
+ Player_SetTeamIndexChecked(player, Team_TeamToIndex((new_color & 0x0F) + 1));
}