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