]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/teamplay.qc
Merge branch 'AriosJentu/SpectatorsScoreboard' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / teamplay.qc
1 #include "teamplay.qh"
2
3 #include <common/deathtypes/all.qh>
4 #include <common/gamemodes/_mod.qh>
5 #include <common/teams.qh>
6 #include <server/bot/api.qh>
7 #include <server/bot/default/cvars.qh>
8 #include <server/campaign.qh>
9 #include <server/client.qh>
10 #include <server/command/vote.qh>
11 #include <server/damage.qh>
12 #include <server/gamelog.qh>
13 #include <server/mutators/_mod.qh>
14 #include <server/race.qh>
15 #include <server/scores.qh>
16 #include <server/scores_rules.qh>
17
18 /// \brief Describes a state of team balance entity.
19 enum
20 {
21         TEAM_BALANCE_UNINITIALIZED, ///< The team balance has not been initialized.
22         /// \brief TeamBalance_CheckAllowedTeams has been called.
23         TEAM_BALANCE_TEAMS_CHECKED,
24         /// \brief TeamBalance_GetTeamCounts has been called.
25         TEAM_BALANCE_TEAM_COUNTS_FILLED
26 };
27
28 /// \brief Indicates that the player is not allowed to join a team.
29 const int TEAM_NOT_ALLOWED = -1;
30
31 .float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
32
33 .int m_team_balance_state; ///< Holds the state of the team balance entity.
34 .entity m_team_balance_team[NUM_TEAMS]; ///< ???
35
36 .float m_team_score; ///< The score of the team.
37 .int m_num_players; ///< Number of players (both humans and bots) in a team.
38 .int m_num_bots; ///< Number of bots in a team.
39 .int m_num_players_alive; ///< Number of alive players in a team.
40 .int m_num_control_points; ///< Number of control points owned by a team.
41
42 string autocvar_g_forced_team_red;
43 string autocvar_g_forced_team_blue;
44 string autocvar_g_forced_team_yellow;
45 string autocvar_g_forced_team_pink;
46
47 entity g_team_entities[NUM_TEAMS]; ///< Holds global team entities.
48
49 void Team_InitTeams()
50 {
51         if (g_team_entities[0])
52                 return;
53         for (int i = 0; i < NUM_TEAMS; ++i)
54         {
55                 g_team_entities[i] = new_pure(team_entity);
56         }
57 }
58
59 entity Team_GetTeamFromIndex(int index)
60 {
61         if (!Team_IsValidIndex(index))
62         {
63                 LOG_FATALF("Team_GetTeamFromIndex: Index is invalid: %f", index);
64         }
65         return g_team_entities[index - 1];
66 }
67
68 entity Team_GetTeam(int team_num)
69 {
70         if (!Team_IsValidTeam(team_num))
71         {
72                 LOG_FATALF("Team_GetTeam: Value is invalid: %f", team_num);
73         }
74         return g_team_entities[Team_TeamToIndex(team_num) - 1];
75 }
76
77 float Team_GetTeamScore(entity team_ent)
78 {
79         return team_ent.m_team_score;
80 }
81
82 void Team_SetTeamScore(entity team_ent, float score)
83 {
84         team_ent.m_team_score = score;
85 }
86
87 int Team_GetNumberOfAlivePlayers(entity team_ent)
88 {
89         return team_ent.m_num_players_alive;
90 }
91
92 void Team_SetNumberOfAlivePlayers(entity team_ent, int number)
93 {
94         team_ent.m_num_players_alive = number;
95 }
96
97 int Team_GetNumberOfAliveTeams()
98 {
99         int result = 0;
100         for (int i = 0; i < NUM_TEAMS; ++i)
101         {
102                 if (g_team_entities[i].m_num_players_alive > 0)
103                 {
104                         ++result;
105                 }
106         }
107         return result;
108 }
109
110 int Team_GetNumberOfControlPoints(entity team_ent)
111 {
112         return team_ent.m_num_control_points;
113 }
114
115 void Team_SetNumberOfControlPoints(entity team_ent, int number)
116 {
117         team_ent.m_num_control_points = number;
118 }
119
120 int Team_GetNumberOfTeamsWithControlPoints()
121 {
122         int result = 0;
123         for (int i = 0; i < NUM_TEAMS; ++i)
124         {
125                 if (g_team_entities[i].m_num_control_points > 0)
126                 {
127                         ++result;
128                 }
129         }
130         return result;
131 }
132
133 void setcolor(entity this, int clr)
134 {
135 #if 0
136         this.clientcolors = clr;
137         this.team = (clr & 15) + 1;
138 #else
139         builtin_setcolor(this, clr);
140 #endif
141 }
142
143 bool Entity_HasValidTeam(entity this)
144 {
145         return Team_IsValidTeam(this.team);
146 }
147
148 int Entity_GetTeamIndex(entity this)
149 {
150         return Team_TeamToIndex(this.team);
151 }
152
153 entity Entity_GetTeam(entity this)
154 {
155         int index = Entity_GetTeamIndex(this);
156         if (!Team_IsValidIndex(index))
157         {
158                 return NULL;
159         }
160         return Team_GetTeamFromIndex(index);
161 }
162
163 void SetPlayerColors(entity player, float _color)
164 {
165         float pants = _color & 0x0F;
166         float shirt = _color & 0xF0;
167         if (teamplay)
168         {
169                 setcolor(player, 16 * pants + pants);
170         }
171         else
172         {
173                 setcolor(player, shirt + pants);
174         }
175 }
176
177 bool Player_SetTeamIndex(entity player, int index)
178 {
179         int new_team = Team_IndexToTeam(index);
180         if (player.team == new_team)
181         {
182                 if (new_team != -1)
183                 {
184                         // This is important when players join the game and one of their
185                         // color matches the team color while other doesn't. For example
186                         // [BOT]Lion: color 0 4.
187                         SetPlayerColors(player, new_team - 1);
188                 }
189                 return true;
190         }
191         int old_index = Team_TeamToIndex(player.team);
192         if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, old_index, index) == true)
193         {
194                 // Mutator has blocked team change.
195                 return false;
196         }
197         if (new_team == -1)
198         {
199                 player.team = -1;
200         }
201         else
202         {
203                 SetPlayerColors(player, new_team - 1);
204         }
205         MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_index, index);
206         return true;
207 }
208
209 bool SetPlayerTeam(entity player, int team_index, int type)
210 {
211         int old_team_index = Entity_GetTeamIndex(player);
212
213         if (!Player_SetTeamIndex(player, team_index))
214                 return false;
215
216         LogTeamChange(player.playerid, player.team, type);
217
218         if (team_index != old_team_index)
219         {
220                 KillPlayerForTeamChange(player);
221                 PlayerScore_Clear(player);
222                 CS(player).parm_idlesince = time;
223
224                 if (!IS_BOT_CLIENT(player))
225                         TeamBalance_AutoBalanceBots();
226
227                 if (team_index != -1)
228                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, INFO_JOIN_PLAY_TEAM), player.netname);
229         }
230
231         if (team_index == -1)
232         {
233                 if (autocvar_sv_maxidle_playertospectator > 0 && CS(player).idlekick_lasttimeleft)
234                 {
235                         // this done here so it happens even when manually speccing during the countdown
236                         Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_IDLING);
237                         CS(player).idlekick_lasttimeleft = 0;
238                 }
239                 else if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
240                 {
241                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
242                 }
243         }
244
245         return true;
246 }
247
248 void Player_SetTeamIndexChecked(entity player, int team_index)
249 {
250         if (!teamplay)
251         {
252                 return;
253         }
254         if (!Team_IsValidIndex(team_index))
255         {
256                 return;
257         }
258         if ((autocvar_g_campaign) || (autocvar_g_changeteam_banned &&
259                 CS(player).wasplayer))
260         {
261                 Send_Notification(NOTIF_ONE, player, MSG_INFO,
262                         INFO_TEAMCHANGE_NOTALLOWED);
263                 return;
264         }
265         entity balance = TeamBalance_CheckAllowedTeams(player);
266         if (team_index == 1 && !TeamBalance_IsTeamAllowedInternal(balance, 1))
267         {
268                 team_index = 4;
269         }
270         if (team_index == 4 && !TeamBalance_IsTeamAllowedInternal(balance, 4))
271         {
272                 team_index = 3;
273         }
274         if (team_index == 3 && !TeamBalance_IsTeamAllowedInternal(balance, 3))
275         {
276                 team_index = 2;
277         }
278         if (team_index == 2 && !TeamBalance_IsTeamAllowedInternal(balance, 2))
279         {
280                 team_index = 1;
281         }
282         // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
283         if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
284         {
285                 TeamBalance_GetTeamCounts(balance, player);
286                 if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance,
287                         player, false)) == 0)
288                 {
289                         Send_Notification(NOTIF_ONE, player, MSG_INFO,
290                                 INFO_TEAMCHANGE_LARGERTEAM);
291                         TeamBalance_Destroy(balance);
292                         return;
293                 }
294         }
295         TeamBalance_Destroy(balance);
296         SetPlayerTeam(player, team_index, TEAM_CHANGE_MANUAL);
297 }
298
299 bool MoveToTeam(entity client, int team_index, int type)
300 {
301         //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index));
302         int lockteams_backup = lockteams;  // backup any team lock
303         lockteams = 0;  // disable locked teams
304         if (!SetPlayerTeam(client, team_index, type))
305         {
306                 lockteams = lockteams_backup;  // restore the team lock
307                 return false;
308         }
309         lockteams = lockteams_backup;  // restore the team lock
310         return true;
311 }
312
313 bool Player_HasRealForcedTeam(entity player)
314 {
315         return player.team_forced > TEAM_FORCE_DEFAULT;
316 }
317
318 int Player_GetForcedTeamIndex(entity player)
319 {
320         return player.team_forced;
321 }
322
323 void Player_SetForcedTeamIndex(entity player, int team_index)
324 {
325         switch (team_index)
326         {
327                 case TEAM_FORCE_SPECTATOR:
328                 case TEAM_FORCE_DEFAULT:
329                 {
330                         player.team_forced = team_index;
331                         break;
332                 }
333                 default:
334                 {
335                         if (!Team_IsValidIndex(team_index))
336                         {
337                                 LOG_FATAL("Player_SetForcedTeamIndex: Invalid team index.");
338                         }
339                         else
340                         {
341                                 player.team_forced = team_index;
342                                 break;
343                         }
344                 }
345         }
346 }
347
348 void Player_DetermineForcedTeam(entity player)
349 {
350         if (autocvar_g_campaign)
351         {
352                 if (IS_REAL_CLIENT(player)) // only players, not bots
353                 {
354                         if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
355                         {
356                                 player.team_forced = autocvar_g_campaign_forceteam;
357                         }
358                         else
359                         {
360                                 player.team_forced = TEAM_FORCE_DEFAULT;
361                         }
362                 }
363         }
364         else if (PlayerInList(player, autocvar_g_forced_team_red))
365         {
366                 player.team_forced = 1;
367         }
368         else if (PlayerInList(player, autocvar_g_forced_team_blue))
369         {
370                 player.team_forced = 2;
371         }
372         else if (PlayerInList(player, autocvar_g_forced_team_yellow))
373         {
374                 player.team_forced = 3;
375         }
376         else if (PlayerInList(player, autocvar_g_forced_team_pink))
377         {
378                 player.team_forced = 4;
379         }
380         else
381         {
382                 switch (autocvar_g_forced_team_otherwise)
383                 {
384                         case "red":
385                         {
386                                 player.team_forced = 1;
387                                 break;
388                         }
389                         case "blue":
390                         {
391                                 player.team_forced = 2;
392                                 break;
393                         }
394                         case "yellow":
395                         {
396                                 player.team_forced = 3;
397                                 break;
398                         }
399                         case "pink":
400                         {
401                                 player.team_forced = 4;
402                                 break;
403                         }
404                         case "spectate":
405                         case "spectator":
406                         {
407                                 player.team_forced = TEAM_FORCE_SPECTATOR;
408                                 break;
409                         }
410                         default:
411                         {
412                                 player.team_forced = TEAM_FORCE_DEFAULT;
413                                 break;
414                         }
415                 }
416         }
417         if (!teamplay && Player_HasRealForcedTeam(player))
418         {
419                 player.team_forced = TEAM_FORCE_DEFAULT;
420         }
421 }
422
423 void TeamBalance_JoinBestTeam(entity player)
424 {
425         //PrintToChatAll(sprintf("TeamBalance_JoinBestTeam: %s", player.netname));
426         if (!teamplay)
427         {
428                 return;
429         }
430         if (player.bot_forced_team)
431         {
432                 return;
433         }
434         entity balance = TeamBalance_CheckAllowedTeams(player);
435         if (Player_HasRealForcedTeam(player))
436         {
437                 int forced_team_index = player.team_forced;
438                 bool is_team_allowed = TeamBalance_IsTeamAllowedInternal(balance,
439                         forced_team_index);
440                 TeamBalance_Destroy(balance);
441                 if (!is_team_allowed)
442                 {
443                         return;
444                 }
445                 if (!SetPlayerTeam(player, forced_team_index, TEAM_CHANGE_AUTO))
446                 {
447                         return;
448                 }
449                 return;
450         }
451         int best_team_index = TeamBalance_FindBestTeam(balance, player, true);
452         TeamBalance_Destroy(balance);
453         if (!SetPlayerTeam(player, best_team_index, TEAM_CHANGE_AUTO))
454         {
455                 return;
456         }
457 }
458
459 entity TeamBalance_CheckAllowedTeams(entity for_whom)
460 {
461         entity balance = spawn();
462         for (int i = 0; i < NUM_TEAMS; ++i)
463         {
464                 entity team_ent = balance.m_team_balance_team[i] = spawn();
465                 team_ent.m_team_score = g_team_entities[i].m_team_score;
466                 team_ent.m_num_players = TEAM_NOT_ALLOWED;
467                 team_ent.m_num_bots = 0;
468         }
469         setthink(balance, TeamBalance_Destroy);
470         balance.nextthink = time;
471
472         int teams_mask = 0;
473         string teament_name = string_null;
474         bool mutator_returnvalue = MUTATOR_CALLHOOK(TeamBalance_CheckAllowedTeams,
475                 teams_mask, teament_name, for_whom);
476         teams_mask = M_ARGV(0, float);
477         teament_name = M_ARGV(1, string);
478         if (mutator_returnvalue)
479         {
480                 for (int i = 0; i < NUM_TEAMS; ++i)
481                 {
482                         if (teams_mask & BIT(i))
483                         {
484                                 balance.m_team_balance_team[i].m_num_players = 0;
485                         }
486                 }
487         }
488
489         if (teament_name)
490         {
491                 entity head = find(NULL, classname, teament_name);
492                 while (head)
493                 {
494                         if (Team_IsValidTeam(head.team))
495                         {
496                                 TeamBalance_GetTeam(balance, head.team).m_num_players = 0;
497                         }
498                         head = find(head, classname, teament_name);
499                 }
500         }
501
502         // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
503         if (autocvar_bot_vs_human && AvailableTeams() == 2 && for_whom)
504         {
505                 if (autocvar_bot_vs_human > 0)
506                 {
507                         // find last team available
508                         if (IS_BOT_CLIENT(for_whom))
509                         {
510                                 if (TeamBalance_IsTeamAllowedInternal(balance, 4))
511                                 {
512                                         TeamBalance_BanTeamsExcept(balance, 4);
513                                 }
514                                 else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
515                                 {
516                                         TeamBalance_BanTeamsExcept(balance, 3);
517                                 }
518                                 else
519                                 {
520                                         TeamBalance_BanTeamsExcept(balance, 2);
521                                 }
522                                 // no further cases, we know at least 2 teams exist
523                         }
524                         else
525                         {
526                                 if (TeamBalance_IsTeamAllowedInternal(balance, 1))
527                                 {
528                                         TeamBalance_BanTeamsExcept(balance, 1);
529                                 }
530                                 else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
531                                 {
532                                         TeamBalance_BanTeamsExcept(balance, 2);
533                                 }
534                                 else
535                                 {
536                                         TeamBalance_BanTeamsExcept(balance, 3);
537                                 }
538                                 // no further cases, bots have one of the teams
539                         }
540                 }
541                 else
542                 {
543                         // find first team available
544                         if (IS_BOT_CLIENT(for_whom))
545                         {
546                                 if (TeamBalance_IsTeamAllowedInternal(balance, 1))
547                                 {
548                                         TeamBalance_BanTeamsExcept(balance, 1);
549                                 }
550                                 else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
551                                 {
552                                         TeamBalance_BanTeamsExcept(balance, 2);
553                                 }
554                                 else
555                                 {
556                                         TeamBalance_BanTeamsExcept(balance, 3);
557                                 }
558                                 // no further cases, we know at least 2 teams exist
559                         }
560                         else
561                         {
562                                 if (TeamBalance_IsTeamAllowedInternal(balance, 4))
563                                 {
564                                         TeamBalance_BanTeamsExcept(balance, 4);
565                                 }
566                                 else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
567                                 {
568                                         TeamBalance_BanTeamsExcept(balance, 3);
569                                 }
570                                 else
571                                 {
572                                         TeamBalance_BanTeamsExcept(balance, 2);
573                                 }
574                                 // no further cases, bots have one of the teams
575                         }
576                 }
577         }
578
579         if (!for_whom)
580         {
581                 balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
582                 return balance;
583         }
584
585         // if player has a forced team, ONLY allow that one
586         for (int i = 1; i <= NUM_TEAMS; ++i)
587         {
588                 if (for_whom.team_forced == i &&
589                         TeamBalance_IsTeamAllowedInternal(balance, i))
590                 {
591                         TeamBalance_BanTeamsExcept(balance, i);
592                         break;
593                 }
594         }
595         balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
596         return balance;
597 }
598
599 void TeamBalance_Destroy(entity balance)
600 {
601         if (balance == NULL)
602         {
603                 return;
604         }
605         for (int i = 0; i < NUM_TEAMS; ++i)
606         {
607                 delete(balance.(m_team_balance_team[i]));
608         }
609         delete(balance);
610 }
611
612 int TeamBalance_GetAllowedTeams(entity balance)
613 {
614         if (balance == NULL)
615         {
616                 LOG_FATAL("TeamBalance_GetAllowedTeams: Team balance entity is NULL.");
617         }
618         if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
619         {
620                 LOG_FATAL("TeamBalance_GetAllowedTeams: "
621                         "Team balance entity is not initialized.");
622         }
623         int result = 0;
624         for (int i = 1; i <= NUM_TEAMS; ++i)
625         {
626                 if (TeamBalance_IsTeamAllowedInternal(balance, i))
627                 {
628                         result |= Team_IndexToBit(i);
629                 }
630         }
631         return result;
632 }
633
634 bool TeamBalance_IsTeamAllowed(entity balance, int index)
635 {
636         if (balance == NULL)
637         {
638                 LOG_FATAL("TeamBalance_IsTeamAllowed: Team balance entity is NULL.");
639         }
640         if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
641         {
642                 LOG_FATAL("TeamBalance_IsTeamAllowed: "
643                         "Team balance entity is not initialized.");
644         }
645         if (!Team_IsValidIndex(index))
646         {
647                 LOG_FATALF("TeamBalance_IsTeamAllowed: Team index is invalid: %f",
648                         index);
649         }
650         return TeamBalance_IsTeamAllowedInternal(balance, index);
651 }
652
653 void TeamBalance_GetTeamCounts(entity balance, entity ignore)
654 {
655         if (balance == NULL)
656         {
657                 LOG_FATAL("TeamBalance_GetTeamCounts: Team balance entity is NULL.");
658         }
659         if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
660         {
661                 LOG_FATAL("TeamBalance_GetTeamCounts: "
662                         "Team balance entity is not initialized.");
663         }
664         if (MUTATOR_CALLHOOK(TeamBalance_GetTeamCounts) == true)
665         {
666                 // Mutator has overriden the configuration.
667                 for (int i = 1; i <= NUM_TEAMS; ++i)
668                 {
669                         entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
670                         if (TeamBalanceTeam_IsAllowed(team_ent))
671                         {
672                                 MUTATOR_CALLHOOK(TeamBalance_GetTeamCount, i, ignore);
673                                 team_ent.m_num_players = M_ARGV(2, float);
674                                 team_ent.m_num_bots = M_ARGV(3, float);
675                         }
676                 }
677         }
678         else
679         {
680                 // Manually count all players.
681                 FOREACH_CLIENT(true,
682                 {
683                         if (it == ignore)
684                         {
685                                 continue;
686                         }
687                         int team_num;
688                         // TODO: Reconsider when the player is truly on the team.
689                         if (IS_CLIENT(it) || (it.caplayer))
690                         {
691                                 team_num = it.team;
692                         }
693                         else if (Player_HasRealForcedTeam(it))
694                         {
695                                 // Do we really need this? Probably not.
696                                 team_num = Team_IndexToTeam(it.team_forced); // reserve the spot
697                         }
698                         else
699                         {
700                                 continue;
701                         }
702                         if (!Team_IsValidTeam(team_num))
703                         {
704                                 continue;
705                         }
706                         entity team_ent = TeamBalance_GetTeam(balance, team_num);
707                         if (!TeamBalanceTeam_IsAllowed(team_ent))
708                         {
709                                 continue;
710                         }
711                         ++team_ent.m_num_players;
712                         if (IS_BOT_CLIENT(it))
713                         {
714                                 ++team_ent.m_num_bots;
715                         }
716                 });
717         }
718
719         // if the player who has a forced team has not joined yet, reserve the spot
720         if (autocvar_g_campaign)
721         {
722                 if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
723                 {
724                         entity team_ent = TeamBalance_GetTeamFromIndex(balance,
725                                 autocvar_g_campaign_forceteam);
726                         if (team_ent.m_num_players == team_ent.m_num_bots)
727                         {
728                                 ++team_ent.m_num_players;
729                         }
730                 }
731         }
732         balance.m_team_balance_state = TEAM_BALANCE_TEAM_COUNTS_FILLED;
733 }
734
735 int TeamBalance_GetNumberOfPlayers(entity balance, int index)
736 {
737         if (balance == NULL)
738         {
739                 LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
740                         "Team balance entity is NULL.");
741         }
742         if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
743         {
744                 LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
745                         "TeamBalance_GetTeamCounts has not been called.");
746         }
747         if (!Team_IsValidIndex(index))
748         {
749                 LOG_FATALF("TeamBalance_GetNumberOfPlayers: Team index is invalid: %f",
750                         index);
751         }
752         return balance.m_team_balance_team[index - 1].m_num_players;
753 }
754
755 int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player)
756 {
757         if (balance == NULL)
758         {
759                 LOG_FATAL("TeamBalance_FindBestTeam: Team balance entity is NULL.");
760         }
761         if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
762         {
763                 LOG_FATAL("TeamBalance_FindBestTeam: "
764                         "Team balance entity is not initialized.");
765         }
766         // count how many players are in each team
767         if (ignore_player)
768         {
769                 TeamBalance_GetTeamCounts(balance, player);
770         }
771         else
772         {
773                 TeamBalance_GetTeamCounts(balance, NULL);
774         }
775         int team_bits = TeamBalance_FindBestTeams(balance, player, true);
776         if (team_bits == 0)
777         {
778                 LOG_FATALF("TeamBalance_FindBestTeam: No teams available for %s\n",
779                         MapInfo_Type_ToString(MapInfo_CurrentGametype()));
780         }
781         RandomSelection_Init();
782         for (int i = 1; i <= NUM_TEAMS; ++i)
783         {
784                 if (team_bits & Team_IndexToBit(i))
785                 {
786                         RandomSelection_AddFloat(i, 1, 1);
787                 }
788         }
789         return RandomSelection_chosen_float;
790 }
791
792 int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
793 {
794         if (balance == NULL)
795         {
796                 LOG_FATAL("TeamBalance_FindBestTeams: Team balance entity is NULL.");
797         }
798         if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
799         {
800                 LOG_FATAL("TeamBalance_FindBestTeams: "
801                         "TeamBalance_GetTeamCounts has not been called.");
802         }
803         if (MUTATOR_CALLHOOK(TeamBalance_FindBestTeams, player) == true)
804         {
805                 return M_ARGV(1, float);
806         }
807         int team_bits = 0;
808         int previous_team = 0;
809         for (int i = 1; i <= NUM_TEAMS; ++i)
810         {
811                 if (!TeamBalance_IsTeamAllowedInternal(balance, i))
812                 {
813                         continue;
814                 }
815                 if (previous_team == 0)
816                 {
817                         team_bits = Team_IndexToBit(i);
818                         previous_team = i;
819                         continue;
820                 }
821                 int compare = TeamBalance_CompareTeams(balance, i, previous_team,
822                         player, use_score);
823                 if (compare == TEAMS_COMPARE_LESS)
824                 {
825                         team_bits = Team_IndexToBit(i);
826                         previous_team = i;
827                         continue;
828                 }
829                 if (compare == TEAMS_COMPARE_EQUAL)
830                 {
831                         team_bits |= Team_IndexToBit(i);
832                         previous_team = i;
833                 }
834         }
835         return team_bits;
836 }
837
838 int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
839         entity player, bool use_score)
840 {
841         if (balance == NULL)
842         {
843                 LOG_FATAL("TeamBalance_CompareTeams: Team balance entity is NULL.");
844         }
845         if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
846         {
847                 LOG_FATAL("TeamBalance_CompareTeams: "
848                         "TeamBalance_GetTeamCounts has not been called.");
849         }
850         if (!Team_IsValidIndex(team_index_a))
851         {
852                 LOG_FATALF("TeamBalance_CompareTeams: team_index_a is invalid: %f",
853                         team_index_a);
854         }
855         if (!Team_IsValidIndex(team_index_b))
856         {
857                 LOG_FATALF("TeamBalance_CompareTeams: team_index_b is invalid: %f",
858                         team_index_b);
859         }
860         if (team_index_a == team_index_b)
861         {
862                 return TEAMS_COMPARE_EQUAL;
863         }
864         entity team_a = TeamBalance_GetTeamFromIndex(balance, team_index_a);
865         entity team_b = TeamBalance_GetTeamFromIndex(balance, team_index_b);
866         return TeamBalance_CompareTeamsInternal(team_a, team_b, player, use_score);
867 }
868
869 void TeamBalance_AutoBalanceBots()
870 {
871         // checks disabled because we always want auto-balanced bots
872         //if (!(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance))
873         //      return;
874
875         entity balance = TeamBalance_CheckAllowedTeams(NULL);
876         TeamBalance_GetTeamCounts(balance, NULL);
877         int smallest_team_index = 0;
878         int smallest_team_player_count = 0;
879         for (int i = 1; i <= NUM_TEAMS; ++i)
880         {
881                 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
882                 if (!TeamBalanceTeam_IsAllowed(team_))
883                 {
884                         continue;
885                 }
886                 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
887                 if (smallest_team_index == 0)
888                 {
889                         smallest_team_index = i;
890                         smallest_team_player_count = playercount;
891                 }
892                 else if (playercount < smallest_team_player_count)
893                 {
894                         smallest_team_index = i;
895                         smallest_team_player_count = playercount;
896                 }
897         }
898         //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
899         //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
900         entity switchable_bot = NULL;
901         int teams = BITS(NUM_TEAMS);
902         while (teams != 0)
903         {
904                 int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
905                         teams);
906                 if (smallest_team_index == largest_team_index)
907                 {
908                         TeamBalance_Destroy(balance);
909                         return;
910                 }
911                 entity largest_team = TeamBalance_GetTeamFromIndex(balance,
912                         largest_team_index);
913                 int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
914                         largest_team);
915                 if (largest_team_player_count - smallest_team_player_count < 2)
916                 {
917                         TeamBalance_Destroy(balance);
918                         return;
919                 }
920                 //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
921                 //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
922                 switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
923                         smallest_team_index, true);
924                 if (switchable_bot != NULL)
925                 {
926                         break;
927                 }
928                 teams &= ~Team_IndexToBit(largest_team_index);
929         }
930         TeamBalance_Destroy(balance);
931         if (switchable_bot == NULL)
932         {
933                 //PrintToChatAll("No bot found after searching through all the teams");
934                 return;
935         }
936         SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
937 }
938
939 int TeamBalance_GetLargestTeamIndex(entity balance, int teams)
940 {
941         int largest_team_index = 0;
942         int largest_team_player_count = 0;
943         for (int i = 1; i <= NUM_TEAMS; ++i)
944         {
945                 if (!(Team_IndexToBit(i) & teams))
946                 {
947                         continue;
948                 }
949                 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
950                 if (!TeamBalanceTeam_IsAllowed(team_))
951                 {
952                         continue;
953                 }
954                 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
955                 if (largest_team_index == 0)
956                 {
957                         largest_team_index = i;
958                         largest_team_player_count = playercount;
959                 }
960                 else if (playercount > largest_team_player_count)
961                 {
962                         largest_team_index = i;
963                         largest_team_player_count = playercount;
964                 }
965         }
966         return largest_team_index;
967 }
968
969 entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index,
970         int destination_team_index, bool is_bot)
971 {
972         if (MUTATOR_CALLHOOK(TeamBalance_GetPlayerForTeamSwitch, source_team_index,
973                 destination_team_index, is_bot))
974         {
975                 return M_ARGV(3, entity);
976         }
977         entity lowest_player = NULL;
978         float lowest_score = FLOAT_MAX;
979         FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
980         {
981                 if (IS_BOT_CLIENT(it) != is_bot)
982                 {
983                         continue;
984                 }
985                 float temp_score = PlayerScore_Get(it, SP_SCORE);
986                 if (temp_score >= lowest_score)
987                 {
988                         continue;
989                 }
990                 //PrintToChatAll(sprintf(
991                 //      "Found %s with lowest score, checking allowed teams", it.netname));
992                 entity balance = TeamBalance_CheckAllowedTeams(it);
993                 if (TeamBalance_IsTeamAllowed(balance, source_team_index))
994                 {
995                         //PrintToChatAll("Allowed");
996                         lowest_player = it;
997                         lowest_score = temp_score;
998                 }
999                 else
1000                 {
1001                         //PrintToChatAll("Not allowed");
1002                 }
1003                 TeamBalance_Destroy(balance);
1004         });
1005         return lowest_player;
1006 }
1007
1008 void LogTeamChange(float player_id, float team_number, int type)
1009 {
1010         if (!autocvar_sv_eventlog)
1011         {
1012                 return;
1013         }
1014         if (player_id < 1)
1015         {
1016                 return;
1017         }
1018         GameLogEcho(sprintf(":team:%d:%d:%d", player_id, team_number, type));
1019 }
1020
1021 void KillPlayerForTeamChange(entity player)
1022 {
1023         if (IS_DEAD(player))
1024         {
1025                 return;
1026         }
1027         if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
1028         {
1029                 return;
1030         }
1031         Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
1032                 player.origin, '0 0 0');
1033 }
1034
1035 bool TeamBalance_IsTeamAllowedInternal(entity balance, int index)
1036 {
1037         return balance.m_team_balance_team[index - 1].m_num_players !=
1038                 TEAM_NOT_ALLOWED;
1039 }
1040
1041 void TeamBalance_BanTeamsExcept(entity balance, int index)
1042 {
1043         for (int i = 1; i <= NUM_TEAMS; ++i)
1044         {
1045                 if (i != index)
1046                 {
1047                         balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED;
1048                 }
1049         }
1050 }
1051
1052 entity TeamBalance_GetTeamFromIndex(entity balance, int index)
1053 {
1054         if (!Team_IsValidIndex(index))
1055         {
1056                 LOG_FATALF("TeamBalance_GetTeamFromIndex: Index is invalid: %f", index);
1057         }
1058         return balance.m_team_balance_team[index - 1];
1059 }
1060
1061 entity TeamBalance_GetTeam(entity balance, int team_num)
1062 {
1063         return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num));
1064 }
1065
1066 bool TeamBalanceTeam_IsAllowed(entity team_ent)
1067 {
1068         return team_ent.m_num_players != TEAM_NOT_ALLOWED;
1069 }
1070
1071 int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent)
1072 {
1073         return team_ent.m_num_players;
1074 }
1075
1076 int TeamBalanceTeam_GetNumberOfBots(entity team_ent)
1077 {
1078         return team_ent.m_num_bots;
1079 }
1080
1081 int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b,
1082         entity player, bool use_score)
1083 {
1084         if (team_a == team_b)
1085         {
1086                 return TEAMS_COMPARE_EQUAL;
1087         }
1088         if (!TeamBalanceTeam_IsAllowed(team_a) ||
1089                 !TeamBalanceTeam_IsAllowed(team_b))
1090         {
1091                 return TEAMS_COMPARE_INVALID;
1092         }
1093         int num_players_team_a = team_a.m_num_players;
1094         int num_players_team_b = team_b.m_num_players;
1095         if (IS_REAL_CLIENT(player) && bots_would_leave)
1096         {
1097                 num_players_team_a -= team_a.m_num_bots;
1098                 num_players_team_b -= team_b.m_num_bots;
1099         }
1100         if (num_players_team_a < num_players_team_b)
1101         {
1102                 return TEAMS_COMPARE_LESS;
1103         }
1104         if (num_players_team_a > num_players_team_b)
1105         {
1106                 return TEAMS_COMPARE_GREATER;
1107         }
1108         if (!use_score)
1109         {
1110                 return TEAMS_COMPARE_EQUAL;
1111         }
1112         if (team_a.m_team_score < team_b.m_team_score)
1113         {
1114                 return TEAMS_COMPARE_LESS;
1115         }
1116         if (team_a.m_team_score > team_b.m_team_score)
1117         {
1118                 return TEAMS_COMPARE_GREATER;
1119         }
1120         return TEAMS_COMPARE_EQUAL;
1121 }
1122
1123 void SV_ChangeTeam(entity player, int new_color)
1124 {
1125         if (!teamplay)
1126         {
1127                 SetPlayerColors(player, new_color);
1128         }
1129         if(!IS_CLIENT(player))
1130         {
1131                 return;
1132         }
1133         if (!teamplay)
1134         {
1135                 return;
1136         }
1137         Player_SetTeamIndexChecked(player, Team_TeamToIndex((new_color & 0x0F) + 1));
1138 }