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