MSG_INFO_NOTIF(QUIT_DISCONNECT, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 disconnected"), "")
MSG_INFO_NOTIF(QUIT_KICK_IDLING, N_CHATCON, 1, 1, "s1 f1", "", "", _("^BG%s^F3 was kicked after idling for %s seconds"), "")
MSG_INFO_NOTIF(MOVETOSPEC_IDLING, N_CHATCON, 1, 1, "s1 f1", "", "", _("^BG%s^F3 was moved to^BG spectators^F3 after idling for %s seconds"), "")
+ MSG_INFO_NOTIF(MOVETOSPEC_REMOVE, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 was moved to^BG spectators^F3 for balance reasons"), "")
MSG_INFO_NOTIF(QUIT_KICK_SPECTATING, N_CONSOLE, 0, 0, "", "", "", _("^F2You were kicked from the server because you are a spectator and spectators aren't allowed at the moment."), "")
MSG_INFO_NOTIF(QUIT_KICK_TEAMKILL, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 was kicked for excessive teamkilling"), "")
MSG_INFO_NOTIF(QUIT_SPECTATE, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 is now^BG spectating"), "")
+ MSG_INFO_NOTIF(QUIT_QUEUE, N_CHATCON, 1, 0, "s1", "", "", _("^BG%s^F3 has left the queue"), "")
MSG_INFO_NOTIF(RACE_ABANDONED, N_CONSOLE, 1, 0, "s1", "", "", _("^BG%s^BG has abandoned the race"), "")
MSG_INFO_NOTIF(RACE_FAIL_RANKED, N_CONSOLE, 1, 3, "s1 race_col f1ord race_col f3race_time race_diff", "s1 f3race_time", "race_newfail", _("^BG%s^BG couldn't break their %s%s^BG place record of %s%s %s"), "")
GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this.netname, this.team, false)));
CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
- this.wants_join = false;
+ this.wants_join = 0;
stuffcmd(this, clientstuff, "\n");
stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
if (player_count == 0)
localcmd("\nsv_hook_lastleave\n");
+
+ if (teamplay && autocvar_g_balance_teams_remove)
+ TeamBalance_RemoveExcessPlayers(NULL);
}
void ChatBubbleThink(entity this)
else
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
this.team_selected = false;
-
- if(queued_join)
- this.wants_join = 0;
+ this.wants_join = 0;
}
int GetPlayerLimit()
bool IsQueueNeeded(entity ignore)
{
- return (teamplay && TeamBalance_AreEqual(ignore));
+ return (teamplay && autocvar_g_balance_teams_queue && TeamBalance_AreEqual(ignore));
}
entity SpectatorWantsJoin(entity this)
if (IsQueueNeeded(player) && !SpectatorWantsJoin(player))
{
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, INFO_JOIN_WANTS_TEAM), player.netname);
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_JOIN_PREVENT_QUEUE);
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_JOIN_PREVENT_QUEUE);
player.wants_join = team_index; // Player queued to join
}
else
Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_IDLING);
CS(player).idlekick_lasttimeleft = 0;
}
- else if (player.wants_join)
+ else if (player.wants_join > 0)
{
+ LOG_INFOF("Triggered by %s (%d)", player.netname, player.wants_join);
player.wants_join = 0;
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_QUEUE, player.netname);
}
else if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
{
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
+ if(autocvar_g_balance_teams_remove)
+ TeamBalance_RemoveExcessPlayers(player);
}
}
int total;
int prev_total = 0;
- for(int i = 0; i < AVAILABLE_TEAMS; i++)
+ for(int i = 1; i <= AVAILABLE_TEAMS; i++)
{
- total = TeamBalance_GetTeamFromIndex(balance, i+1).m_num_players;
- if(i > 0 && total != prev_total)
+ total = TeamBalance_GetTeamFromIndex(balance, i).m_num_players;
+ if(i > 1 && total != prev_total)
{
TeamBalance_Destroy(balance);
return false;
return true;
}
+void TeamBalance_RemoveExcessPlayers(entity ignore)
+{
+ entity balance = TeamBalance_CheckAllowedTeams(ignore);
+ TeamBalance_GetTeamCounts(balance, ignore);
+
+ int min = 0;
+
+ for(int i = 1; i <= AVAILABLE_TEAMS; i++)
+ {
+ int cur = TeamBalance_GetTeamFromIndex(balance, i).m_num_players;
+ if(cur < min)
+ min = cur;
+ }
+
+ for(int tmi = 1; tmi <= AVAILABLE_TEAMS; tmi++)
+ {
+ int cur = TeamBalance_GetTeamFromIndex(balance, tmi).m_num_players;
+ if(cur > 0 && cur > min)
+ {
+ // Get newest player
+ int latest_join = 0;
+ entity latest_join_pl = NULL;
+
+ FOREACH_CLIENT(IS_CLIENT(it) || INGAME(it), {
+ if(it.team == Team_IndexToTeam(tmi) && CS(it).startplaytime > latest_join)
+ {
+ latest_join = CS(it).startplaytime;
+ latest_join_pl = it;
+ }
+ });
+
+ // Force player to spectate
+ if(latest_join_pl)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_REMOVE, latest_join_pl.netname);
+ PutObserverInServer(latest_join_pl, true, true);
+ }
+ }
+ }
+
+ TeamBalance_Destroy(balance);
+}
+
bool TeamBalance_IsTeamAllowed(entity balance, int index)
{
if (balance == NULL)
bool autocvar_g_balance_teams;
bool autocvar_g_balance_teams_prevent_imbalance;
+bool autocvar_g_balance_teams_queue;
+bool autocvar_g_balance_teams_remove;
string autocvar_g_forced_team_otherwise;
int TeamBalance_GetAllowedTeams(entity balance);
bool TeamBalance_AreEqual(entity ignore);
+void TeamBalance_RemoveExcessPlayers(entity ignore);
/// \brief Returns whether the team change to the specified team is allowed.
/// \param[in] balance Team balance entity.
set g_balance_teams 1 "automatically balance out players entering instead of asking them for their preferred team"
set g_balance_teams_prevent_imbalance 1 "prevent players from changing to larger teams"
+set g_balance_teams_queue 1 "queue players before joining"
+set g_balance_teams_remove 1 "remove excess players from teams"
set g_changeteam_banned 0 "not allowed to change team"
set sv_teamnagger 1 "enable a nag message when the teams are unbalanced"