]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/teamplay.qc
#include this
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / teamplay.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4         #include "../dpdefs/progsdefs.qc"
5     #include "../dpdefs/dpextensions.qc"
6     #include "sys-post.qh"
7     #include "../common/constants.qh"
8     #include "../common/teams.qh"
9     #include "../common/util.qh"
10     #include "autocvars.qh"
11     #include "defs.qh"
12     #include "../common/notifications.qh"
13     #include "../common/deathtypes.qh"
14     #include "mutators/mutators_include.qh"
15     #include "../common/mapinfo.qh"
16     #include "command/vote.qh"
17     #include "scores.qh"
18     #include "race.qh"
19 #endif
20
21 string cache_mutatormsg;
22 string cache_lastmutatormsg;
23
24 // client counts for each team
25 //float c1, c2, c3, c4;
26 // # of bots on those teams
27 float cb1, cb2, cb3, cb4;
28
29 //float audit_teams_time;
30
31 void TeamchangeFrags(entity e)
32 {
33         PlayerScore_Clear(e);
34 }
35
36 void entcs_init();
37
38 void LogTeamchange(float player_id, float team_number, float type)
39 {
40         if(!autocvar_sv_eventlog)
41                 return;
42
43         if(player_id < 1)
44                 return;
45
46         GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
47 }
48
49 void default_delayedinit()
50 {
51         if(!scores_initialized)
52                 ScoreRules_generic();
53 }
54
55 void ActivateTeamplay()
56 {
57         serverflags |= SERVERFLAG_TEAMPLAY;
58         teamplay = 1;
59 }
60
61 void InitGameplayMode()
62 {
63         float fraglimit_override, timelimit_override, leadlimit_override, qualifying_override;
64
65         qualifying_override = -1;
66
67         VoteReset();
68
69         // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
70         get_mi_min_max(1);
71         world.mins = mi_min;
72         world.maxs = mi_max;
73
74         MapInfo_LoadMapSettings(mapname);
75         teamplay = 0;
76         serverflags &= ~SERVERFLAG_TEAMPLAY;
77
78         if (!cvar_value_issafe(world.fog))
79         {
80                 print("The current map contains a potentially harmful fog setting, ignored\n");
81                 world.fog = string_null;
82         }
83         if(MapInfo_Map_fog != "")
84                 if(MapInfo_Map_fog == "none")
85                         world.fog = string_null;
86                 else
87                         world.fog = strzone(MapInfo_Map_fog);
88         clientstuff = strzone(MapInfo_Map_clientstuff);
89
90         MapInfo_ClearTemps();
91
92         // set both here, gamemode can override it later
93         timelimit_override = autocvar_timelimit_override;
94         fraglimit_override = autocvar_fraglimit_override;
95         leadlimit_override = autocvar_leadlimit_override;
96         gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
97
98         if(g_dm)
99         {
100         }
101
102         if(g_tdm)
103         {
104                 ActivateTeamplay();
105                 fraglimit_override = autocvar_g_tdm_point_limit;
106                 leadlimit_override = autocvar_g_tdm_point_leadlimit;
107                 if(autocvar_g_tdm_team_spawns)
108                         have_team_spawns = -1; // request team spawns
109                 MUTATOR_ADD(gamemode_tdm);
110         }
111
112         if(g_domination)
113         {
114                 ActivateTeamplay();
115                 fraglimit_override = autocvar_g_domination_point_limit;
116                 leadlimit_override = autocvar_g_domination_point_leadlimit;
117                 if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
118                         fraglimit_override = autocvar_g_domination_roundbased_point_limit;
119                 have_team_spawns = -1; // request team spawns
120                 MUTATOR_ADD(gamemode_domination);
121         }
122
123         if(g_ctf)
124         {
125                 ActivateTeamplay();
126                 fraglimit_override = autocvar_capturelimit_override;
127                 leadlimit_override = autocvar_captureleadlimit_override;
128                 have_team_spawns = -1; // request team spawns
129                 MUTATOR_ADD(gamemode_ctf);
130         }
131
132         if(g_lms)
133         {
134                 fraglimit_override = autocvar_g_lms_lives_override;
135                 leadlimit_override = 0; // not supported by LMS
136                 if(fraglimit_override == 0)
137                         fraglimit_override = -1;
138                 MUTATOR_ADD(gamemode_lms);
139         }
140
141         if(g_ca)
142         {
143                 ActivateTeamplay();
144                 fraglimit_override = autocvar_g_ca_point_limit;
145                 leadlimit_override = autocvar_g_ca_point_leadlimit;
146                 if(autocvar_g_ca_team_spawns)
147                         have_team_spawns = -1; // request team spawns
148                 MUTATOR_ADD(gamemode_ca);
149         }
150
151         if(g_keyhunt)
152         {
153                 ActivateTeamplay();
154                 fraglimit_override = autocvar_g_keyhunt_point_limit;
155                 leadlimit_override = autocvar_g_keyhunt_point_leadlimit;
156                 MUTATOR_ADD(gamemode_keyhunt);
157         }
158
159         if(g_freezetag)
160         {
161                 ActivateTeamplay();
162                 fraglimit_override = autocvar_g_freezetag_point_limit;
163                 leadlimit_override = autocvar_g_freezetag_point_leadlimit;
164                 if(autocvar_g_freezetag_team_spawns)
165                         have_team_spawns = -1; // request team spawns
166                 MUTATOR_ADD(gamemode_freezetag);
167         }
168
169         if(g_assault)
170         {
171                 ActivateTeamplay();
172                 have_team_spawns = -1; // request team spawns
173                 MUTATOR_ADD(gamemode_assault);
174         }
175
176         if(g_onslaught)
177         {
178                 ActivateTeamplay();
179                 have_team_spawns = -1; // request team spawns
180                 MUTATOR_ADD(gamemode_onslaught);
181         }
182
183         if(g_race)
184         {
185                 if(autocvar_g_race_teams)
186                 {
187                         ActivateTeamplay();
188                         race_teams = bound(2, autocvar_g_race_teams, 4);
189                         have_team_spawns = -1; // request team spawns
190                 }
191                 else
192                         race_teams = 0;
193                 qualifying_override = autocvar_g_race_qualifying_timelimit_override;
194                 fraglimit_override = autocvar_g_race_laps_limit;
195                 leadlimit_override = 0; // currently not supported by race
196
197                 // we need to find out the correct value for g_race_qualifying
198                 float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
199
200                 if(autocvar_g_campaign)
201                 {
202                         g_race_qualifying = 1;
203                         independent_players = 1;
204                 }
205                 else if(!autocvar_g_campaign && want_qualifying)
206                 {
207                         g_race_qualifying = 2;
208                         independent_players = 1;
209                         race_fraglimit = (race_fraglimit >= 0) ? fraglimit_override : autocvar_fraglimit;
210                         race_leadlimit = (race_leadlimit >= 0) ? leadlimit_override : autocvar_leadlimit;
211                         race_timelimit = (race_timelimit >= 0) ? timelimit_override : autocvar_timelimit;
212                         fraglimit_override = 0;
213                         leadlimit_override = 0;
214                         timelimit_override = autocvar_g_race_qualifying_timelimit;
215                 }
216                 else
217                 {
218                         g_race_qualifying = 0;
219                 }
220
221                 MUTATOR_ADD(gamemode_race);
222         }
223
224         if(g_cts)
225         {
226                 g_race_qualifying = 1;
227                 fraglimit_override = 0;
228                 leadlimit_override = 0;
229                 independent_players = 1;
230                 MUTATOR_ADD(gamemode_cts);
231         }
232
233         if(g_nexball)
234         {
235                 fraglimit_override = autocvar_g_nexball_goallimit;
236                 leadlimit_override = autocvar_g_nexball_goalleadlimit;
237                 ActivateTeamplay();
238                 have_team_spawns = -1; // request team spawns
239                 MUTATOR_ADD(gamemode_nexball);
240         }
241
242         if(g_keepaway)
243         {
244                 MUTATOR_ADD(gamemode_keepaway);
245         }
246
247         if(g_invasion)
248         {
249                 fraglimit_override = autocvar_g_invasion_point_limit;
250                 if(autocvar_g_invasion_teams >= 2)
251                 {
252                         ActivateTeamplay();
253                         if(autocvar_g_invasion_team_spawns)
254                                 have_team_spawns = -1; // request team spawns
255                 }
256                 MUTATOR_ADD(gamemode_invasion);
257         }
258
259         if(teamplay)
260                 entcs_init();
261
262         cache_mutatormsg = strzone("");
263         cache_lastmutatormsg = strzone("");
264
265         // enforce the server's universal frag/time limits
266         if(!autocvar_g_campaign)
267         {
268                 if(fraglimit_override >= 0)
269                         cvar_set("fraglimit", ftos(fraglimit_override));
270                 if(timelimit_override >= 0)
271                         cvar_set("timelimit", ftos(timelimit_override));
272                 if(leadlimit_override >= 0)
273                         cvar_set("leadlimit", ftos(leadlimit_override));
274                 if(qualifying_override >= 0)
275                         cvar_set("g_race_qualifying_timelimit", ftos(qualifying_override));
276         }
277
278         InitializeEntity(world, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
279 }
280
281 string GetClientVersionMessage() {
282         string versionmsg;
283         if (self.version_mismatch) {
284                 if(self.version < autocvar_gameversion) {
285                         versionmsg = "^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8";
286                 } else {
287                         versionmsg = "^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8";
288                 }
289         } else {
290                 versionmsg = "^2client version and server version are compatible.^8";
291         }
292         return versionmsg;
293 }
294
295 string getwelcomemessage(void)
296 {
297         string s, modifications, motd;
298
299         ret_string = "";
300         MUTATOR_CALLHOOK(BuildMutatorsPrettyString);
301         modifications = ret_string;
302
303         if(g_weaponarena)
304         {
305                 if(g_weaponarena_random)
306                         modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
307                 else
308                         modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
309         }
310         if(cvar("g_balance_blaster_weaponstart") == 0)
311                 modifications = strcat(modifications, ", No start weapons");
312         if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
313                 modifications = strcat(modifications, ", Low gravity");
314         if(g_cloaked && !g_cts)
315                 modifications = strcat(modifications, ", Cloaked");
316         if(g_grappling_hook)
317                 modifications = strcat(modifications, ", Hook");
318         if(g_weapon_stay && !g_cts)
319                 modifications = strcat(modifications, ", Weapons stay");
320         if(g_jetpack)
321                 modifications = strcat(modifications, ", Jet pack");
322         if(autocvar_g_powerups == 0)
323                 modifications = strcat(modifications, ", No powerups");
324         if(autocvar_g_powerups > 0)
325                 modifications = strcat(modifications, ", Powerups");
326         modifications = substring(modifications, 2, strlen(modifications) - 2);
327
328         string versionmessage;
329         versionmessage = GetClientVersionMessage();
330
331         s = strcat("This is Xonotic ", autocvar_g_xonoticversion, "\n", versionmessage);
332         s = strcat(s, "^8\n\nmatch type is ^1", gamemode_name, "^8\n");
333
334         if(modifications != "")
335                 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
336
337         if (g_grappling_hook)
338                 s = strcat(s, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n");
339
340         if (cvar("g_nades"))
341                 s = strcat(s, "\n\n^3nades^8 are enabled, press 'g' to use them\n");
342
343         if(cache_lastmutatormsg != autocvar_g_mutatormsg)
344         {
345                 if(cache_lastmutatormsg)
346                         strunzone(cache_lastmutatormsg);
347                 if(cache_mutatormsg)
348                         strunzone(cache_mutatormsg);
349                 cache_lastmutatormsg = strzone(autocvar_g_mutatormsg);
350                 cache_mutatormsg = strzone(cache_lastmutatormsg);
351         }
352
353         if (cache_mutatormsg != "") {
354                 s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
355         }
356
357         motd = autocvar_sv_motd;
358         if (motd != "") {
359                 s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
360         }
361         return s;
362 }
363
364 void SetPlayerColors(entity pl, float _color)
365 {
366         /*string s;
367         s = ftos(cl);
368         stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
369         pl.team = cl + 1;
370         //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
371         pl.clientcolors = 16*cl + cl;*/
372
373         float pants, shirt;
374         pants = _color & 0x0F;
375         shirt = _color & 0xF0;
376
377
378         if(teamplay) {
379                 setcolor(pl, 16*pants + pants);
380         } else {
381                 setcolor(pl, shirt + pants);
382         }
383 }
384
385 void SetPlayerTeam(entity pl, float t, float s, float noprint)
386 {
387         float _color;
388
389         if(t == 4)
390                 _color = NUM_TEAM_4 - 1;
391         else if(t == 3)
392                 _color = NUM_TEAM_3 - 1;
393         else if(t == 2)
394                 _color = NUM_TEAM_2 - 1;
395         else
396                 _color = NUM_TEAM_1 - 1;
397
398         SetPlayerColors(pl,_color);
399
400         if(t != s) {
401                 LogTeamchange(pl.playerid, pl.team, 3);  // log manual team join
402
403                 if(!noprint)
404                 bprint(pl.netname, "^7 has changed from ", Team_NumberToColoredFullName(s), "^7 to ", Team_NumberToColoredFullName(t), "\n");
405         }
406
407 }
408
409 // set c1...c4 to show what teams are allowed
410 void CheckAllowedTeams (entity for_whom)
411 {
412         float dm;
413         entity head;
414         string teament_name;
415
416         c1 = c2 = c3 = c4 = -1;
417         cb1 = cb2 = cb3 = cb4 = 0;
418
419         teament_name = string_null;
420         if(g_onslaught)
421         {
422                 // onslaught is special
423                 head = findchain(classname, "onslaught_generator");
424                 while (head)
425                 {
426                         if (head.team == NUM_TEAM_1) c1 = 0;
427                         if (head.team == NUM_TEAM_2) c2 = 0;
428                         if (head.team == NUM_TEAM_3) c3 = 0;
429                         if (head.team == NUM_TEAM_4) c4 = 0;
430                         head = head.chain;
431                 }
432         }
433         else if(g_domination)
434                 teament_name = "dom_team";
435         else if(g_ctf)
436                 teament_name = "ctf_team";
437         else if(g_tdm)
438                 teament_name = "tdm_team";
439         else if(g_nexball)
440                 teament_name = "nexball_team";
441         else if(g_assault)
442                 c1 = c2 = 0; // Assault always has 2 teams
443         else
444         {
445                 // cover anything else by treating it like tdm with no teams spawned
446                 dm = 2;
447
448                 ret_float = dm;
449                 MUTATOR_CALLHOOK(GetTeamCount);
450                 dm = ret_float;
451
452                 if(dm >= 4)
453                         c1 = c2 = c3 = c4 = 0;
454                 else if(dm >= 3)
455                         c1 = c2 = c3 = 0;
456                 else
457                         c1 = c2 = 0;
458         }
459
460         // find out what teams are allowed if necessary
461         if(teament_name)
462         {
463                 head = find(world, classname, teament_name);
464                 while(head)
465                 {
466                         if(!(g_domination && head.netname == ""))
467                         {
468                                 if(head.team == NUM_TEAM_1)
469                                         c1 = 0;
470                                 else if(head.team == NUM_TEAM_2)
471                                         c2 = 0;
472                                 else if(head.team == NUM_TEAM_3)
473                                         c3 = 0;
474                                 else if(head.team == NUM_TEAM_4)
475                                         c4 = 0;
476                         }
477                         head = find(head, classname, teament_name);
478                 }
479         }
480
481         // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
482         if(c3==-1 && c4==-1)
483         if(autocvar_bot_vs_human && for_whom)
484         {
485                 if(autocvar_bot_vs_human > 0)
486                 {
487                         // bots are all blue
488                         if(IS_BOT_CLIENT(for_whom))
489                                 c1 = c3 = c4 = -1;
490                         else
491                                 c2 = -1;
492                 }
493                 else
494                 {
495                         // bots are all red
496                         if(IS_BOT_CLIENT(for_whom))
497                                 c2 = c3 = c4 = -1;
498                         else
499                                 c1 = -1;
500                 }
501         }
502
503         // if player has a forced team, ONLY allow that one
504         if(self.team_forced == NUM_TEAM_1 && c1 >= 0)
505                 c2 = c3 = c4 = -1;
506         else if(self.team_forced == NUM_TEAM_2 && c2 >= 0)
507                 c1 = c3 = c4 = -1;
508         else if(self.team_forced == NUM_TEAM_3 && c3 >= 0)
509                 c1 = c2 = c4 = -1;
510         else if(self.team_forced == NUM_TEAM_4 && c4 >= 0)
511                 c1 = c2 = c3 = -1;
512 }
513
514 float PlayerValue(entity p)
515 {
516         return 1;
517         // FIXME: it always returns 1...
518 }
519
520 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
521 // teams that are allowed will now have their player counts stored in c1...c4
522 void GetTeamCounts(entity ignore)
523 {
524         entity head;
525         float value, bvalue;
526         // now count how many players are on each team already
527
528         // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
529         // also remember the lowest-scoring player
530
531         FOR_EACH_CLIENT(head)
532         {
533                 float t;
534                 if(IS_PLAYER(head) || head.caplayer)
535                         t = head.team;
536                 else if(head.team_forced > 0)
537                         t = head.team_forced; // reserve the spot
538                 else
539                         continue;
540                 if(head != ignore)// && head.netname != "")
541                 {
542                         value = PlayerValue(head);
543                         if(IS_BOT_CLIENT(head))
544                                 bvalue = value;
545                         else
546                                 bvalue = 0;
547                         if(t == NUM_TEAM_1)
548                         {
549                                 if(c1 >= 0)
550                                 {
551                                         c1 = c1 + value;
552                                         cb1 = cb1 + bvalue;
553                                 }
554                         }
555                         if(t == NUM_TEAM_2)
556                         {
557                                 if(c2 >= 0)
558                                 {
559                                         c2 = c2 + value;
560                                         cb2 = cb2 + bvalue;
561                                 }
562                         }
563                         if(t == NUM_TEAM_3)
564                         {
565                                 if(c3 >= 0)
566                                 {
567                                         c3 = c3 + value;
568                                         cb3 = cb3 + bvalue;
569                                 }
570                         }
571                         if(t == NUM_TEAM_4)
572                         {
573                                 if(c4 >= 0)
574                                 {
575                                         c4 = c4 + value;
576                                         cb4 = cb4 + bvalue;
577                                 }
578                         }
579                 }
580         }
581
582         // if the player who has a forced team has not joined yet, reserve the spot
583         if(autocvar_g_campaign)
584         {
585                 switch(autocvar_g_campaign_forceteam)
586                 {
587                         case 1: if(c1 == cb1) ++c1; break;
588                         case 2: if(c2 == cb2) ++c2; break;
589                         case 3: if(c3 == cb3) ++c3; break;
590                         case 4: if(c4 == cb4) ++c4; break;
591                 }
592         }
593 }
594
595 float TeamSmallerEqThanTeam(float ta, float tb, entity e)
596 {
597         // we assume that CheckAllowedTeams and GetTeamCounts have already been called
598         float f;
599         float ca = -1, cb = -1, cba = 0, cbb = 0, sa = 0, sb = 0;
600
601         switch(ta)
602         {
603                 case 1: ca = c1; cba = cb1; sa = team1_score; break;
604                 case 2: ca = c2; cba = cb2; sa = team2_score; break;
605                 case 3: ca = c3; cba = cb3; sa = team3_score; break;
606                 case 4: ca = c4; cba = cb4; sa = team4_score; break;
607         }
608         switch(tb)
609         {
610                 case 1: cb = c1; cbb = cb1; sb = team1_score; break;
611                 case 2: cb = c2; cbb = cb2; sb = team2_score; break;
612                 case 3: cb = c3; cbb = cb3; sb = team3_score; break;
613                 case 4: cb = c4; cbb = cb4; sb = team4_score; break;
614         }
615
616         // invalid
617         if(ca < 0 || cb < 0)
618                 return false;
619
620         // equal
621         if(ta == tb)
622                 return true;
623
624         if(IS_REAL_CLIENT(e))
625         {
626                 if(bots_would_leave)
627                 {
628                         ca -= cba * 0.999;
629                         cb -= cbb * 0.999;
630                 }
631         }
632
633         // keep teams alive (teams of size 0 always count as smaller, ignoring score)
634         if(ca < 1)
635                 if(cb >= 1)
636                         return true;
637         if(ca >= 1)
638                 if(cb < 1)
639                         return false;
640
641         // first, normalize
642         f = max(ca, cb, 1);
643         ca /= f;
644         cb /= f;
645         f = max(sa, sb, 1);
646         sa /= f;
647         sb /= f;
648
649         // the more we're at the end of the match, the more take scores into account
650         f = bound(0, game_completion_ratio * autocvar_g_balance_teams_scorefactor, 1);
651         ca += (sa - ca) * f;
652         cb += (sb - cb) * f;
653
654         return ca <= cb;
655 }
656
657 // returns # of smallest team (1, 2, 3, 4)
658 // NOTE: Assumes CheckAllowedTeams has already been called!
659 float FindSmallestTeam(entity pl, float ignore_pl)
660 {
661         float totalteams, t;
662         totalteams = 0;
663
664         // find out what teams are available
665         //CheckAllowedTeams();
666
667         // make sure there are at least 2 teams to join
668         if(c1 >= 0)
669                 totalteams = totalteams + 1;
670         if(c2 >= 0)
671                 totalteams = totalteams + 1;
672         if(c3 >= 0)
673                 totalteams = totalteams + 1;
674         if(c4 >= 0)
675                 totalteams = totalteams + 1;
676
677         if((autocvar_bot_vs_human || pl.team_forced > 0) && totalteams == 1)
678                 totalteams += 1;
679
680         if(totalteams <= 1)
681         {
682                 if(autocvar_g_campaign && pl && IS_REAL_CLIENT(pl))
683                         return 1; // special case for campaign and player joining
684                 else
685                         error(sprintf("Too few teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
686         }
687
688         // count how many players are in each team
689         if(ignore_pl)
690                 GetTeamCounts(pl);
691         else
692                 GetTeamCounts(world);
693
694         RandomSelection_Init();
695
696         t = 1;
697         if(TeamSmallerEqThanTeam(2, t, pl))
698                 t = 2;
699         if(TeamSmallerEqThanTeam(3, t, pl))
700                 t = 3;
701         if(TeamSmallerEqThanTeam(4, t, pl))
702                 t = 4;
703
704         // now t is the minimum, or A minimum!
705         if(t == 1 || TeamSmallerEqThanTeam(1, t, pl))
706                 RandomSelection_Add(world, 1, string_null, 1, 1);
707         if(t == 2 || TeamSmallerEqThanTeam(2, t, pl))
708                 RandomSelection_Add(world, 2, string_null, 1, 1);
709         if(t == 3 || TeamSmallerEqThanTeam(3, t, pl))
710                 RandomSelection_Add(world, 3, string_null, 1, 1);
711         if(t == 4 || TeamSmallerEqThanTeam(4, t, pl))
712                 RandomSelection_Add(world, 4, string_null, 1, 1);
713
714         return RandomSelection_chosen_float;
715 }
716
717 float JoinBestTeam(entity pl, float only_return_best, float forcebestteam)
718 {
719         float smallest, selectedteam;
720
721         // don't join a team if we're not playing a team game
722         if(!teamplay)
723                 return 0;
724
725         // find out what teams are available
726         CheckAllowedTeams(pl);
727
728         // if we don't care what team he ends up on, put him on whatever team he entered as.
729         // if he's not on a valid team, then let other code put him on the smallest team
730         if(!forcebestteam)
731         {
732                 if(     c1 >= 0 && pl.team == NUM_TEAM_1)
733                         selectedteam = pl.team;
734                 else if(c2 >= 0 && pl.team == NUM_TEAM_2)
735                         selectedteam = pl.team;
736                 else if(c3 >= 0 && pl.team == NUM_TEAM_3)
737                         selectedteam = pl.team;
738                 else if(c4 >= 0 && pl.team == NUM_TEAM_4)
739                         selectedteam = pl.team;
740                 else
741                         selectedteam = -1;
742
743                 if(selectedteam > 0)
744                 {
745                         if(!only_return_best)
746                         {
747                                 SetPlayerColors(pl, selectedteam - 1);
748
749                                 // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
750                                 // when JoinBestTeam is called by cl_client.qc/ClientConnect the player_id is 0 the log attempt is rejected
751                                 LogTeamchange(pl.playerid, pl.team, 99);
752                         }
753                         return selectedteam;
754                 }
755                 // otherwise end up on the smallest team (handled below)
756         }
757
758         smallest = FindSmallestTeam(pl, true);
759
760         if(!only_return_best && !pl.bot_forced_team)
761         {
762                 TeamchangeFrags(self);
763                 if(smallest == 1)
764                 {
765                         SetPlayerColors(pl, NUM_TEAM_1 - 1);
766                 }
767                 else if(smallest == 2)
768                 {
769                         SetPlayerColors(pl, NUM_TEAM_2 - 1);
770                 }
771                 else if(smallest == 3)
772                 {
773                         SetPlayerColors(pl, NUM_TEAM_3 - 1);
774                 }
775                 else if(smallest == 4)
776                 {
777                         SetPlayerColors(pl, NUM_TEAM_4 - 1);
778                 }
779                 else
780                 {
781                         error("smallest team: invalid team\n");
782                 }
783
784                 LogTeamchange(pl.playerid, pl.team, 2); // log auto join
785
786                 if(pl.deadflag == DEAD_NO)
787                         Damage(pl, pl, pl, 100000, DEATH_TEAMCHANGE, pl.origin, '0 0 0');
788         }
789
790         return smallest;
791 }
792
793 //void() ctf_playerchanged;
794 void SV_ChangeTeam(float _color)
795 {
796         float scolor, dcolor, steam, dteam; //, dbotcount, scount, dcount;
797
798         // in normal deathmatch we can just apply the color and we're done
799         if(!teamplay) {
800                 SetPlayerColors(self, _color);
801                 return;
802         }
803
804         scolor = self.clientcolors & 0x0F;
805         dcolor = _color & 0x0F;
806
807         if(scolor == NUM_TEAM_1 - 1)
808                 steam = 1;
809         else if(scolor == NUM_TEAM_2 - 1)
810                 steam = 2;
811         else if(scolor == NUM_TEAM_3 - 1)
812                 steam = 3;
813         else // if(scolor == NUM_TEAM_4 - 1)
814                 steam = 4;
815         if(dcolor == NUM_TEAM_1 - 1)
816                 dteam = 1;
817         else if(dcolor == NUM_TEAM_2 - 1)
818                 dteam = 2;
819         else if(dcolor == NUM_TEAM_3 - 1)
820                 dteam = 3;
821         else // if(dcolor == NUM_TEAM_4 - 1)
822                 dteam = 4;
823
824         CheckAllowedTeams(self);
825
826         if(dteam == 1 && c1 < 0) dteam = 4;
827         if(dteam == 4 && c4 < 0) dteam = 3;
828         if(dteam == 3 && c3 < 0) dteam = 2;
829         if(dteam == 2 && c2 < 0) dteam = 1;
830
831         // not changing teams
832         if(scolor == dcolor)
833         {
834                 //bprint("same team change\n");
835                 SetPlayerTeam(self, dteam, steam, true);
836                 return;
837         }
838
839         if((autocvar_g_campaign) || (autocvar_g_changeteam_banned && self.wasplayer)) {
840                 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TEAMCHANGE_NOTALLOWED);
841                 return; // changing teams is not allowed
842         }
843
844         // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
845         if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
846         {
847                 GetTeamCounts(self);
848                 if(!TeamSmallerEqThanTeam(dteam, steam, self))
849                 {
850                         Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
851                         return;
852                 }
853         }
854
855 //      bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
856
857         if(IS_PLAYER(self) && steam != dteam)
858         {
859                 // reduce frags during a team change
860                 TeamchangeFrags(self);
861         }
862
863         SetPlayerTeam(self, dteam, steam, false);
864
865         if(IS_PLAYER(self) && steam != dteam)
866         {
867                 // kill player when changing teams
868                 if(self.deadflag == DEAD_NO)
869                         Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
870         }
871 }
872
873 void ShufflePlayerOutOfTeam (float source_team)
874 {
875         float smallestteam, smallestteam_count, steam;
876         float lowest_bot_score, lowest_player_score;
877         entity head, lowest_bot, lowest_player, selected;
878
879         smallestteam = 0;
880         smallestteam_count = 999999999;
881
882         if(c1 >= 0 && c1 < smallestteam_count)
883         {
884                 smallestteam = 1;
885                 smallestteam_count = c1;
886         }
887         if(c2 >= 0 && c2 < smallestteam_count)
888         {
889                 smallestteam = 2;
890                 smallestteam_count = c2;
891         }
892         if(c3 >= 0 && c3 < smallestteam_count)
893         {
894                 smallestteam = 3;
895                 smallestteam_count = c3;
896         }
897         if(c4 >= 0 && c4 < smallestteam_count)
898         {
899                 smallestteam = 4;
900                 smallestteam_count = c4;
901         }
902
903         if(!smallestteam)
904         {
905                 bprint("warning: no smallest team\n");
906                 return;
907         }
908
909         if(source_team == 1)
910                 steam = NUM_TEAM_1;
911         else if(source_team == 2)
912                 steam = NUM_TEAM_2;
913         else if(source_team == 3)
914                 steam = NUM_TEAM_3;
915         else // if(source_team == 4)
916                 steam = NUM_TEAM_4;
917
918         lowest_bot = world;
919         lowest_bot_score = 999999999;
920         lowest_player = world;
921         lowest_player_score = 999999999;
922
923         // find the lowest-scoring player & bot of that team
924         FOR_EACH_PLAYER(head)
925         {
926                 if(head.team == steam)
927                 {
928                         if(head.isbot)
929                         {
930                                 if(head.totalfrags < lowest_bot_score)
931                                 {
932                                         lowest_bot = head;
933                                         lowest_bot_score = head.totalfrags;
934                                 }
935                         }
936                         else
937                         {
938                                 if(head.totalfrags < lowest_player_score)
939                                 {
940                                         lowest_player = head;
941                                         lowest_player_score = head.totalfrags;
942                                 }
943                         }
944                 }
945         }
946
947         // prefers to move a bot...
948         if(lowest_bot != world)
949                 selected = lowest_bot;
950         // but it will move a player if it has to
951         else
952                 selected = lowest_player;
953         // don't do anything if it couldn't find anyone
954         if(!selected)
955         {
956                 bprint("warning: couldn't find a player to move from team\n");
957                 return;
958         }
959
960         // smallest team gains a member
961         if(smallestteam == 1)
962         {
963                 c1 = c1 + 1;
964         }
965         else if(smallestteam == 2)
966         {
967                 c2 = c2 + 1;
968         }
969         else if(smallestteam == 3)
970         {
971                 c3 = c3 + 1;
972         }
973         else if(smallestteam == 4)
974         {
975                 c4 = c4 + 1;
976         }
977         else
978         {
979                 bprint("warning: destination team invalid\n");
980                 return;
981         }
982         // source team loses a member
983         if(source_team == 1)
984         {
985                 c1 = c1 + 1;
986         }
987         else if(source_team == 2)
988         {
989                 c2 = c2 + 2;
990         }
991         else if(source_team == 3)
992         {
993                 c3 = c3 + 3;
994         }
995         else if(source_team == 4)
996         {
997                 c4 = c4 + 4;
998         }
999         else
1000         {
1001                 bprint("warning: source team invalid\n");
1002                 return;
1003         }
1004
1005         // move the player to the new team
1006         TeamchangeFrags(selected);
1007         SetPlayerTeam(selected, smallestteam, source_team, false);
1008
1009         if(selected.deadflag == DEAD_NO)
1010                 Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE, selected.origin, '0 0 0');
1011         Send_Notification(NOTIF_ONE, selected, MSG_CENTER, CENTER_DEATH_SELF_AUTOTEAMCHANGE, selected.team);
1012 }