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