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