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