]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/teamplay.qc
Better team balance function names.
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / teamplay.qc
1 #include "teamplay.qh"
2
3 #include "client.qh"
4 #include "race.qh"
5 #include "scores.qh"
6 #include "scores_rules.qh"
7
8 #include "bot/api.qh"
9
10 #include "command/vote.qh"
11
12 #include "mutators/_mod.qh"
13
14 #include "../common/deathtypes/all.qh"
15 #include "../common/gamemodes/_mod.qh"
16 #include "../common/teams.qh"
17
18 /// \brief Indicates that the player is not allowed to join a team.
19 const int TEAM_NOT_ALLOWED = -1;
20
21 .float m_team_score; ///< The score of the team.
22 .int m_num_players; ///< Number of players (both humans and bots) in a team.
23 .int m_num_bots; ///< Number of bots in a team.
24 .entity m_lowest_human; ///< Human with the lowest score in a team.
25 .entity m_lowest_bot; ///< Bot with the lowest score in a team.
26
27 entity g_team_entities[4]; ///< Holds global team entities.
28
29 STATIC_INIT(g_team_entities)
30 {
31         g_team_entities[0] = spawn();
32         g_team_entities[1] = spawn();
33         g_team_entities[2] = spawn();
34         g_team_entities[3] = spawn();
35 }
36
37 entity Team_GetTeamFromIndex(int index)
38 {
39         if (!Team_IsValidIndex(index))
40         {
41                 LOG_FATALF("Team_GetTeamFromIndex: Index is invalid: %f", index);
42         }
43         return g_team_entities[index - 1];
44 }
45
46 entity Team_GetTeam(int team_num)
47 {
48         if (!Team_IsValidTeam(team_num))
49         {
50                 LOG_FATALF("Team_GetTeam: Value is invalid: %f", team_num);
51         }
52         return g_team_entities[Team_TeamToNumber(team_num) - 1];
53 }
54
55 float Team_GetTeamScore(entity team_)
56 {
57         return team_.m_team_score;
58 }
59
60 void Team_SetTeamScore(entity team_, float score)
61 {
62         team_.m_team_score = score;
63 }
64
65 void CheckAllowedTeams(entity for_whom)
66 {
67         for (int i = 0; i < 4; ++i)
68         {
69                 g_team_entities[i].m_num_players = TEAM_NOT_ALLOWED;
70                 g_team_entities[i].m_num_bots = 0;
71                 g_team_entities[i].m_lowest_human = NULL;
72                 g_team_entities[i].m_lowest_bot = NULL;
73         }
74         
75         int teams_mask = 0;     
76         string teament_name = string_null;
77         bool mutator_returnvalue = MUTATOR_CALLHOOK(CheckAllowedTeams, teams_mask,
78                 teament_name, for_whom);
79         teams_mask = M_ARGV(0, float);
80         teament_name = M_ARGV(1, string);
81         if (mutator_returnvalue)
82         {
83                 for (int i = 0; i < 4; ++i)
84                 {
85                         if (teams_mask & BIT(i))
86                         {
87                                 g_team_entities[i].m_num_players = 0;
88                         }
89                 }
90         }
91
92         // find out what teams are allowed if necessary
93         if (teament_name)
94         {
95                 entity head = find(NULL, classname, teament_name);
96                 while (head)
97                 {
98                         if (Team_IsValidTeam(head.team))
99                         {
100                                 Team_GetTeam(head.team).m_num_players = 0;
101                         }
102                         head = find(head, classname, teament_name);
103                 }
104         }
105
106         // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
107         if (AvailableTeams() == 2)
108         if (autocvar_bot_vs_human && for_whom)
109         {
110                 if (autocvar_bot_vs_human > 0)
111                 {
112                         // find last team available
113                         if (IS_BOT_CLIENT(for_whom))
114                         {
115                                 if (Team_IsAllowed(g_team_entities[3]))
116                                 {
117                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
118                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
119                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
120                                 }
121                                 else if (Team_IsAllowed(g_team_entities[2]))
122                                 {
123                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
124                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
125                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
126                                 }
127                                 else
128                                 {
129                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
130                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
131                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
132                                 }
133                                 // no further cases, we know at least 2 teams exist
134                         }
135                         else
136                         {
137                                 if (Team_IsAllowed(g_team_entities[0]))
138                                 {
139                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
140                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
141                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
142                                 }
143                                 else if (Team_IsAllowed(g_team_entities[1]))
144                                 {
145                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
146                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
147                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
148                                 }
149                                 else
150                                 {
151                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
152                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
153                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
154                                 }
155                                 // no further cases, bots have one of the teams
156                         }
157                 }
158                 else
159                 {
160                         // find first team available
161                         if (IS_BOT_CLIENT(for_whom))
162                         {
163                                 if (Team_IsAllowed(g_team_entities[0]))
164                                 {
165                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
166                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
167                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
168                                 }
169                                 else if (Team_IsAllowed(g_team_entities[1]))
170                                 {
171                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
172                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
173                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
174                                 }
175                                 else
176                                 {
177                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
178                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
179                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
180                                 }
181                                 // no further cases, we know at least 2 teams exist
182                         }
183                         else
184                         {
185                                 if (Team_IsAllowed(g_team_entities[3]))
186                                 {
187                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
188                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
189                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
190                                 }
191                                 else if (Team_IsAllowed(g_team_entities[2]))
192                                 {
193                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
194                                         g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
195                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
196                                 }
197                                 else
198                                 {
199                                         g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
200                                         g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
201                                         g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
202                                 }
203                                 // no further cases, bots have one of the teams
204                         }
205                 }
206         }
207
208         if (!for_whom)
209         {
210                 return;
211         }
212
213         // if player has a forced team, ONLY allow that one
214         if (for_whom.team_forced == NUM_TEAM_1 && Team_IsAllowed(
215                 g_team_entities[0]))
216         {
217                 g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
218                 g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
219                 g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
220         }
221         else if (for_whom.team_forced == NUM_TEAM_2 && Team_IsAllowed(
222                 g_team_entities[1]))
223         {
224                 g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
225                 g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
226                 g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
227         }
228         else if (for_whom.team_forced == NUM_TEAM_3 && Team_IsAllowed(
229                 g_team_entities[2]))
230         {
231                 g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
232                 g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
233                 g_team_entities[3].m_num_players = TEAM_NOT_ALLOWED;
234         }
235         else if (for_whom.team_forced == NUM_TEAM_4 && Team_IsAllowed(
236                 g_team_entities[3]))
237         {
238                 g_team_entities[0].m_num_players = TEAM_NOT_ALLOWED;
239                 g_team_entities[1].m_num_players = TEAM_NOT_ALLOWED;
240                 g_team_entities[2].m_num_players = TEAM_NOT_ALLOWED;
241         }
242 }
243
244 int GetAllowedTeams()
245 {
246         int result = 0;
247         for (int i = 0; i < 4; ++i)
248         {
249                 if (Team_IsAllowed(g_team_entities[i]))
250                 {
251                         result |= BIT(i);
252                 }
253         }
254         return result;
255 }
256
257 bool Team_IsAllowed(entity team_)
258 {
259         return team_.m_num_players != TEAM_NOT_ALLOWED;
260 }
261
262 void GetTeamCounts(entity ignore)
263 {
264         if (MUTATOR_CALLHOOK(GetTeamCounts) == true)
265         {
266                 // Mutator has overriden the configuration.
267                 for (int i = 0; i < 4; ++i)
268                 {
269                         entity team_ = g_team_entities[i];
270                         if (Team_IsAllowed(team_))
271                         {
272                                 MUTATOR_CALLHOOK(GetTeamCount, Team_NumberToTeam(i + 1), ignore,
273                                         team_.m_num_players, team_.m_num_bots, team_.m_lowest_human,
274                                         team_.m_lowest_bot);
275                                 team_.m_num_players = M_ARGV(2, float);
276                                 team_.m_num_bots = M_ARGV(3, float);
277                                 team_.m_lowest_human = M_ARGV(4, entity);
278                                 team_.m_lowest_bot = M_ARGV(5, entity);
279                         }
280                 }
281         }
282         else
283         {
284                 // Manually count all players.
285                 FOREACH_CLIENT(true,
286                 {
287                         if (it == ignore)
288                         {
289                                 continue;
290                         }
291                         int team_num;
292                         if (IS_PLAYER(it) || it.caplayer)
293                         {
294                                 team_num = it.team;
295                         }
296                         else if (it.team_forced > 0)
297                         {
298                                 team_num = it.team_forced; // reserve the spot
299                         }
300                         else
301                         {
302                                 continue;
303                         }
304                         if (!Team_IsValidTeam(team_num))
305                         {
306                                 continue;
307                         }
308                         entity team_ = Team_GetTeam(team_num);
309                         if (!Team_IsAllowed(team_))
310                         {
311                                 continue;
312                         }
313                         ++team_.m_num_players;
314                         if (IS_BOT_CLIENT(it))
315                         {
316                                 ++team_.m_num_bots;
317                         }
318                         float temp_score = PlayerScore_Get(it, SP_SCORE);
319                         if (!IS_BOT_CLIENT(it))
320                         {
321                                 if (team_.m_lowest_human == NULL)
322                                 {
323                                         team_.m_lowest_human = it;
324                                         continue;
325                                 }
326                                 if (temp_score < PlayerScore_Get(team_.m_lowest_human,
327                                         SP_SCORE))
328                                 {
329                                         team_.m_lowest_human = it;
330                                 }
331                                 continue;
332                         }
333                         if (team_.m_lowest_bot == NULL)
334                         {
335                                 team_.m_lowest_bot = it;
336                                 continue;
337                         }
338                         if (temp_score < PlayerScore_Get(team_.m_lowest_bot, SP_SCORE))
339                         {
340                                 team_.m_lowest_bot = it;
341                         }
342                 });
343         }
344
345         // if the player who has a forced team has not joined yet, reserve the spot
346         if (autocvar_g_campaign)
347         {
348                 if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
349                 {
350                         entity team_ = Team_GetTeamFromIndex(autocvar_g_campaign_forceteam);
351                         if (team_.m_num_players == team_.m_num_bots)
352                         {
353                                 ++team_.m_num_players;
354                         }
355                 }
356         }
357 }
358
359 int Team_GetNumberOfPlayers(entity team_)
360 {
361         return team_.m_num_players;
362 }
363
364 int Team_GetNumberOfBots(entity team_)
365 {
366         return team_.m_num_bots;
367 }
368
369 entity Team_GetLowestHuman(entity team_)
370 {
371         return team_.m_lowest_human;
372 }
373
374 entity Team_GetLowestBot(entity team_)
375 {
376         return team_.m_lowest_bot;
377 }
378
379 void TeamchangeFrags(entity e)
380 {
381         PlayerScore_Clear(e);
382 }
383
384 void LogTeamchange(float player_id, float team_number, float type)
385 {
386         if(!autocvar_sv_eventlog)
387                 return;
388
389         if(player_id < 1)
390                 return;
391
392         GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
393 }
394
395 void default_delayedinit(entity this)
396 {
397         if(!scores_initialized)
398                 ScoreRules_generic();
399 }
400
401 void InitGameplayMode()
402 {
403         VoteReset();
404
405         // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
406         get_mi_min_max(1);
407         // assign reflectively to avoid "assignment to world" warning
408         int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
409             string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
410             if (v) {
411             putentityfieldstring(i, world, sprintf("%v", v));
412             if (++done == 2) break;
413         }
414         }
415         // currently, NetRadiant's limit is 131072 qu for each side
416         // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
417         // set the distance according to map size but don't go over the limit to avoid issues with float precision
418         // in case somebody makes extremely large maps
419         max_shot_distance = min(230000, vlen(world.maxs - world.mins));
420
421         MapInfo_LoadMapSettings(mapname);
422         GameRules_teams(false);
423
424         if (!cvar_value_issafe(world.fog))
425         {
426                 LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
427                 world.fog = string_null;
428         }
429         if(MapInfo_Map_fog != "")
430                 if(MapInfo_Map_fog == "none")
431                         world.fog = string_null;
432                 else
433                         world.fog = strzone(MapInfo_Map_fog);
434         clientstuff = strzone(MapInfo_Map_clientstuff);
435
436         MapInfo_ClearTemps();
437
438         gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
439
440         cache_mutatormsg = strzone("");
441         cache_lastmutatormsg = strzone("");
442
443         InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
444 }
445
446 string GetClientVersionMessage(entity this)
447 {
448         if (CS(this).version_mismatch) {
449                 if(CS(this).version < autocvar_gameversion) {
450                         return strcat("This is Xonotic ", autocvar_g_xonoticversion,
451                                 "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
452                 } else {
453                         return strcat("This is Xonotic ", autocvar_g_xonoticversion,
454                                 "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
455                 }
456         } else {
457                 return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
458         }
459 }
460
461 string getwelcomemessage(entity this)
462 {
463         MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
464         string modifications = M_ARGV(0, string);
465
466         if(g_weaponarena)
467         {
468                 if(g_weaponarena_random)
469                         modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
470                 else
471                         modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
472         }
473         else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
474                 modifications = strcat(modifications, ", No start weapons");
475         if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
476                 modifications = strcat(modifications, ", Low gravity");
477         if(g_weapon_stay && !g_cts)
478                 modifications = strcat(modifications, ", Weapons stay");
479         if(g_jetpack)
480                 modifications = strcat(modifications, ", Jet pack");
481         if(autocvar_g_powerups == 0)
482                 modifications = strcat(modifications, ", No powerups");
483         if(autocvar_g_powerups > 0)
484                 modifications = strcat(modifications, ", Powerups");
485         modifications = substring(modifications, 2, strlen(modifications) - 2);
486
487         string versionmessage = GetClientVersionMessage(this);
488         string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
489
490         if(modifications != "")
491                 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
492
493         if(cache_lastmutatormsg != autocvar_g_mutatormsg)
494         {
495                 if(cache_lastmutatormsg)
496                         strunzone(cache_lastmutatormsg);
497                 if(cache_mutatormsg)
498                         strunzone(cache_mutatormsg);
499                 cache_lastmutatormsg = strzone(autocvar_g_mutatormsg);
500                 cache_mutatormsg = strzone(cache_lastmutatormsg);
501         }
502
503         if (cache_mutatormsg != "") {
504                 s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
505         }
506
507         string mutator_msg = "";
508         MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
509         mutator_msg = M_ARGV(0, string);
510
511         s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
512
513         string motd = autocvar_sv_motd;
514         if (motd != "") {
515                 s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
516         }
517         return s;
518 }
519
520 void setcolor(entity this, int clr)
521 {
522 #if 0
523         this.clientcolors = clr;
524         this.team = (clr & 15) + 1;
525 #else
526         builtin_setcolor(this, clr);
527 #endif
528 }
529
530 void SetPlayerColors(entity player, float _color)
531 {
532         float pants = _color & 0x0F;
533         float shirt = _color & 0xF0;
534         if (teamplay)
535         {
536                 setcolor(player, 16 * pants + pants);
537         }
538         else
539         {
540                 setcolor(player, shirt + pants);
541         }
542 }
543
544 void KillPlayerForTeamChange(entity player)
545 {
546         if (IS_DEAD(player))
547         {
548                 return;
549         }
550         if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
551         {
552                 return;
553         }
554         Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
555                 player.origin, '0 0 0');
556 }
557
558 bool SetPlayerTeamSimple(entity player, int team_num)
559 {
560         if (player.team == team_num)
561         {
562                 // This is important when players join the game and one of their color
563                 // matches the team color while other doesn't. For example [BOT]Lion.
564                 SetPlayerColors(player, team_num - 1);
565                 return true;
566         }
567         if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber(
568                 player.team), Team_TeamToNumber(team_num)) == true)
569         {
570                 // Mutator has blocked team change.
571                 return false;
572         }
573         int old_team_num = player.team;
574         SetPlayerColors(player, team_num - 1);
575         MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_team_num, player.team);
576         return true;
577 }
578
579 bool SetPlayerTeam(entity player, int destination_team_index,
580         int source_team_index, bool no_print)
581 {
582         int team_num = Team_NumberToTeam(destination_team_index);
583         if (!SetPlayerTeamSimple(player, team_num))
584         {
585                 return false;
586         }
587         LogTeamchange(player.playerid, player.team, 3);  // log manual team join
588         if (no_print)
589         {
590                 return true;
591         }
592         bprint(playername(player, false), "^7 has changed from ",
593                 Team_NumberToColoredFullName(source_team_index), "^7 to ",
594                 Team_NumberToColoredFullName(destination_team_index), "\n");
595         return true;
596 }
597
598 int FindBestTeamsForBalance(entity player, bool use_score)
599 {
600         if (MUTATOR_CALLHOOK(FindBestTeams, player) == true)
601         {
602                 return M_ARGV(1, float);
603         }
604         int team_bits = 0;
605         int previous_team = 0;
606         if (Team_IsAllowed(g_team_entities[0]))
607         {
608                 team_bits = BIT(0);
609                 previous_team = 1;
610         }
611         if (Team_IsAllowed(g_team_entities[1]))
612         {
613                 if (previous_team == 0)
614                 {
615                         team_bits = BIT(1);
616                         previous_team = 2;
617                 }
618                 else if (IsTeamSmallerThanTeam(2, previous_team, player, use_score))
619                 {
620                         team_bits = BIT(1);
621                         previous_team = 2;
622                 }
623                 else if (IsTeamEqualToTeam(2, previous_team, player, use_score))
624                 {
625                         team_bits |= BIT(1);
626                         previous_team = 2;
627                 }
628         }
629         if (Team_IsAllowed(g_team_entities[2]))
630         {
631                 if (previous_team == 0)
632                 {
633                         team_bits = BIT(2);
634                         previous_team = 3;
635                 }
636                 else if (IsTeamSmallerThanTeam(3, previous_team, player, use_score))
637                 {
638                         team_bits = BIT(2);
639                         previous_team = 3;
640                 }
641                 else if (IsTeamEqualToTeam(3, previous_team, player, use_score))
642                 {
643                         team_bits |= BIT(2);
644                         previous_team = 3;
645                 }
646         }
647         if (Team_IsAllowed(g_team_entities[3]))
648         {
649                 if (previous_team == 0)
650                 {
651                         team_bits = BIT(3);
652                 }
653                 else if (IsTeamSmallerThanTeam(4, previous_team, player, use_score))
654                 {
655                         team_bits = BIT(3);
656                 }
657                 else if (IsTeamEqualToTeam(4, previous_team, player, use_score))
658                 {
659                         team_bits |= BIT(3);
660                 }
661         }
662         return team_bits;
663 }
664
665 int FindBestTeamForBalance(entity player, float ignore_player)
666 {
667         // count how many players are in each team
668         if (ignore_player)
669         {
670                 GetTeamCounts(player);
671         }
672         else
673         {
674                 GetTeamCounts(NULL);
675         }
676         int team_bits = FindBestTeamsForBalance(player, true);
677         if (team_bits == 0)
678         {
679                 LOG_FATALF("FindBestTeam: No teams available for %s\n",
680                         MapInfo_Type_ToString(MapInfo_CurrentGametype()));
681         }
682         RandomSelection_Init();
683         if ((team_bits & BIT(0)) != 0)
684         {
685                 RandomSelection_AddFloat(1, 1, 1);
686         }
687         if ((team_bits & BIT(1)) != 0)
688         {
689                 RandomSelection_AddFloat(2, 1, 1);
690         }
691         if ((team_bits & BIT(2)) != 0)
692         {
693                 RandomSelection_AddFloat(3, 1, 1);
694         }
695         if ((team_bits & BIT(3)) != 0)
696         {
697                 RandomSelection_AddFloat(4, 1, 1);
698         }
699         return RandomSelection_chosen_float;
700 }
701
702 void JoinBestTeamForBalance(entity this, bool force_best_team)
703 {
704         // don't join a team if we're not playing a team game
705         if (!teamplay)
706         {
707                 return;
708         }
709
710         // find out what teams are available
711         CheckAllowedTeams(this);
712
713         // if we don't care what team they end up on, put them on whatever team they entered as.
714         // if they're not on a valid team, then let other code put them on the smallest team
715         if (!force_best_team)
716         {
717                 int selected_team_num = -1;
718                 for (int i = 0; i < 4; ++i)
719                 {
720                         if (Team_IsAllowed(g_team_entities[i]) && (this.team ==
721                                 Team_NumberToTeam(i)))
722                         {
723                                 selected_team_num = this.team;
724                                 break;
725                         }
726                 }
727                 
728                 if (Team_IsValidTeam(selected_team_num))
729                 {
730                         SetPlayerTeamSimple(this, selected_team_num);
731                         LogTeamchange(this.playerid, this.team, 99);
732                         return;
733                 }
734         }
735         // otherwise end up on the smallest team (handled below)
736         if (this.bot_forced_team)
737         {
738                 return;
739         }
740         int best_team_index = FindBestTeamForBalance(this, true);
741         int best_team_num = Team_NumberToTeam(best_team_index);
742         int old_team_index = Team_TeamToNumber(this.team);
743         TeamchangeFrags(this);
744         SetPlayerTeamSimple(this, best_team_num);
745         LogTeamchange(this.playerid, this.team, 2); // log auto join
746         if ((old_team_index != -1) && !IS_BOT_CLIENT(this))
747         {
748                 AutoBalanceBots(old_team_index, best_team_index);
749         }
750         KillPlayerForTeamChange(this);
751 }
752
753 bool IsTeamSmallerThanTeam(int team_index_a, int team_index_b, entity player,
754         bool use_score)
755 {
756         if (!Team_IsValidIndex(team_index_a))
757         {
758                 LOG_FATALF("IsTeamSmallerThanTeam: team_index_a is invalid: %f",
759                         team_index_a);
760         }
761         if (!Team_IsValidIndex(team_index_b))
762         {
763                 LOG_FATALF("IsTeamSmallerThanTeam: team_index_b is invalid: %f",
764                         team_index_b);
765         }
766         if (team_index_a == team_index_b)
767         {
768                 return false;
769         }
770         entity team_a = Team_GetTeamFromIndex(team_index_a);
771         entity team_b = Team_GetTeamFromIndex(team_index_b);
772         if (!Team_IsAllowed(team_a) || !Team_IsAllowed(team_b))
773         {
774                 return false;
775         }
776         int num_players_team_a = team_a.m_num_players;
777         int num_players_team_b = team_b.m_num_players;
778         if (IS_REAL_CLIENT(player) && bots_would_leave)
779         {
780                 num_players_team_a -= team_a.m_num_bots;
781                 num_players_team_b -= team_b.m_num_bots;
782         }
783         if (!use_score)
784         {
785                 return num_players_team_a < num_players_team_b;
786         }
787         if (num_players_team_a < num_players_team_b)
788         {
789                 return true;
790         }
791         if (num_players_team_a > num_players_team_b)
792         {
793                 return false;
794         }
795         return team_a.m_team_score < team_b.m_team_score;
796 }
797
798 bool IsTeamEqualToTeam(int team_index_a, int team_index_b, entity player,
799         bool use_score)
800 {
801         if (!Team_IsValidIndex(team_index_a))
802         {
803                 LOG_FATALF("IsTeamEqualToTeam: team_index_a is invalid: %f",
804                         team_index_a);
805         }
806         if (!Team_IsValidIndex(team_index_b))
807         {
808                 LOG_FATALF("IsTeamEqualToTeam: team_index_b is invalid: %f",
809                         team_index_b);
810         }
811         if (team_index_a == team_index_b)
812         {
813                 return true;
814         }
815         entity team_a = Team_GetTeamFromIndex(team_index_a);
816         entity team_b = Team_GetTeamFromIndex(team_index_b);
817         if (!Team_IsAllowed(team_a) || !Team_IsAllowed(team_b))
818         {
819                 return false;
820         }
821         int num_players_team_a = team_a.m_num_players;
822         int num_players_team_b = team_b.m_num_players;
823         if (IS_REAL_CLIENT(player) && bots_would_leave)
824         {
825                 num_players_team_a -= team_a.m_num_bots;
826                 num_players_team_b -= team_b.m_num_bots;
827         }
828         if (!use_score)
829         {
830                 return num_players_team_a == num_players_team_b;
831         }
832         if (num_players_team_a != num_players_team_b)
833         {
834                 return false;
835         }
836         return team_a.m_team_score == team_b.m_team_score;
837 }
838
839 void SV_ChangeTeam(entity this, float _color)
840 {
841         int source_color, destination_color;
842         int source_team_index, destination_team_index;
843
844         // in normal deathmatch we can just apply the color and we're done
845         if(!teamplay)
846                 SetPlayerColors(this, _color);
847
848         if(!IS_CLIENT(this))
849         {
850                 // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
851                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING, this.netname);
852                 return;
853         }
854
855         if(!teamplay)
856                 return;
857
858         source_color = this.clientcolors & 0x0F;
859         destination_color = _color & 0x0F;
860
861         source_team_index = Team_TeamToNumber(source_color + 1);
862         destination_team_index = Team_TeamToNumber(destination_color + 1);
863
864         if (destination_team_index == -1)
865         {
866                 return;
867         }
868
869         CheckAllowedTeams(this);
870
871         if (destination_team_index == 1 && !Team_IsAllowed(g_team_entities[0])) destination_team_index = 4;
872         if (destination_team_index == 4 && !Team_IsAllowed(g_team_entities[3])) destination_team_index = 3;
873         if (destination_team_index == 3 && !Team_IsAllowed(g_team_entities[2])) destination_team_index = 2;
874         if (destination_team_index == 2 && !Team_IsAllowed(g_team_entities[1])) destination_team_index = 1;
875
876         // not changing teams
877         if (source_color == destination_color)
878         {
879                 SetPlayerTeam(this, destination_team_index, source_team_index, true);
880                 return;
881         }
882
883         if((autocvar_g_campaign) || (autocvar_g_changeteam_banned && CS(this).wasplayer)) {
884                 Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_NOTALLOWED);
885                 return; // changing teams is not allowed
886         }
887
888         // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
889         if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
890         {
891                 GetTeamCounts(this);
892                 if ((BIT(destination_team_index - 1) & FindBestTeamsForBalance(this, false)) == 0)
893                 {
894                         Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
895                         return;
896                 }
897         }
898         if (IS_PLAYER(this) && source_team_index != destination_team_index)
899         {
900                 // reduce frags during a team change
901                 TeamchangeFrags(this);
902         }
903         if (!SetPlayerTeam(this, destination_team_index, source_team_index,
904                 !IS_CLIENT(this)))
905         {
906                 return;
907         }
908         AutoBalanceBots(source_team_index, destination_team_index);
909         if (!IS_PLAYER(this) || (source_team_index == destination_team_index))
910         {
911                 return;
912         }
913         KillPlayerForTeamChange(this);
914 }
915
916 void AutoBalanceBots(int source_team_index, int destination_team_index)
917 {
918         if (!Team_IsValidIndex(source_team_index))
919         {
920                 LOG_WARNF("AutoBalanceBots: Source team index is invalid: %f",
921                         source_team_index);
922                 return;
923         }
924         if (!Team_IsValidIndex(destination_team_index))
925         {
926                 LOG_WARNF("AutoBalanceBots: Destination team index is invalid: %f",
927                         destination_team_index);
928                 return;
929         }
930         if (!autocvar_g_balance_teams ||
931                 !autocvar_g_balance_teams_prevent_imbalance)
932         {
933                 return;
934         }
935         entity source_team = Team_GetTeamFromIndex(source_team_index);
936         if (!Team_IsAllowed(source_team))
937         {
938                 return;
939         }
940         entity destination_team = Team_GetTeamFromIndex(destination_team_index);
941         if ((destination_team.m_num_players <= source_team.m_num_players) ||
942                 (destination_team.m_lowest_bot == NULL))
943         {
944                 return;
945         }
946         SetPlayerTeamSimple(destination_team.m_lowest_bot,
947                 Team_NumberToTeam(source_team_index));
948         KillPlayerForTeamChange(destination_team.m_lowest_bot);
949 }