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