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