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