]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/teamplay.qc
Remove deprecated CTF code in cl_client.qc and teamplay.qc
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / teamplay.qc
1 string cache_mutatormsg;
2 string cache_lastmutatormsg;
3
4 // client counts for each team
5 float c1, c2, c3, c4;
6 // # of bots on those teams
7 float cb1, cb2, cb3, cb4;
8
9 float audit_teams_time;
10
11 float IsTeamBalanceForced()
12 {
13         if(intermission_running)
14                 return 0; // no rebalancing whatsoever please
15         if(!teamplay)
16                 return 0;
17         if(autocvar_g_campaign)
18                 return 0;
19         if(autocvar_bot_vs_human && (c3==-1 && c4==-1))
20                 return 0;
21         if(!autocvar_g_balance_teams_force)
22                 return -1;
23         return 1;
24 }
25
26 void TeamchangeFrags(entity e)
27 {
28         PlayerScore_Clear(e);
29 }
30
31 vector TeamColor(float teem)
32 {
33         switch(teem)
34         {
35                 case COLOR_TEAM1:
36                         return '1 0.0625 0.0625';
37                 case COLOR_TEAM2:
38                         return '0.0625 0.0625 1';
39                 case COLOR_TEAM3:
40                         return '1 1 0.0625';
41                 case COLOR_TEAM4:
42                         return '1 0.0625 1';
43                 default:
44                         return '1 1 1';
45         }
46 }
47
48 string TeamName(float t)
49 {
50         return strcat(Team_ColorName(t), " Team");
51 }
52 string ColoredTeamName(float t)
53 {
54         return strcat(Team_ColorCode(t), Team_ColorName(t), " Team^7");
55 }
56 string TeamNoName(float t)
57 {
58         // fixme: Search for team entities and get their .netname's!
59         if(t == 1)
60                 return "Red Team";
61         if(t == 2)
62                 return "Blue Team";
63         if(t == 3)
64                 return "Yellow Team";
65         if(t == 4)
66                 return "Pink Team";
67         return "Neutral Team";
68 }
69
70 void dom_init();
71 void runematch_init();
72 void tdm_init();
73 void entcs_init();
74
75 void LogTeamchange(float player_id, float team_number, float type)
76 {
77         if(!autocvar_sv_eventlog)
78                 return;
79
80         if(player_id < 1)
81                 return;
82
83         GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
84 }
85
86 void default_delayedinit()
87 {
88         if(!scores_initialized)
89                 ScoreRules_generic();
90 }
91
92 void ActivateTeamplay()
93 {
94         serverflags |= SERVERFLAG_TEAMPLAY;
95         teamplay = 1;
96 }
97
98 void InitGameplayMode()
99 {
100         float fraglimit_override, timelimit_override, leadlimit_override, qualifying_override;
101
102         qualifying_override = -1;
103
104         VoteReset();
105
106         // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
107         get_mi_min_max(1);
108         world.mins = mi_min;
109         world.maxs = mi_max;
110
111         MapInfo_LoadMapSettings(mapname);
112         teamplay = 0;
113         serverflags &~= SERVERFLAG_TEAMPLAY;
114
115         if not(cvar_value_issafe(world.fog))
116         {
117                 print("The current map contains a potentially harmful fog setting, ignored\n");
118                 world.fog = string_null;
119         }
120         if(MapInfo_Map_fog != "")
121                 if(MapInfo_Map_fog == "none")
122                         world.fog = string_null;
123                 else
124                         world.fog = strzone(MapInfo_Map_fog);
125         clientstuff = strzone(MapInfo_Map_clientstuff);
126
127         MapInfo_ClearTemps();
128
129         // set both here, gamemode can override it later
130         timelimit_override = autocvar_timelimit_override;
131         fraglimit_override = autocvar_fraglimit_override;
132         leadlimit_override = autocvar_leadlimit_override;
133         gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
134
135         if(g_dm)
136         {
137         }
138
139         if(g_tdm)
140         {
141                 ActivateTeamplay();
142                 tdm_init();
143                 if(autocvar_g_tdm_team_spawns)
144                         have_team_spawns = -1; // request team spawns
145         }
146
147         if(g_domination)
148         {
149                 ActivateTeamplay();
150                 fraglimit_override = autocvar_g_domination_point_limit;
151                 leadlimit_override = autocvar_g_domination_point_leadlimit;
152                 dom_init();
153                 have_team_spawns = -1; // request team spawns
154         }
155
156         if(g_ctf)
157         {
158                 ActivateTeamplay();
159                 fraglimit_override = autocvar_capturelimit_override;
160                 leadlimit_override = autocvar_captureleadlimit_override;
161                 MUTATOR_ADD(gamemode_ctf);
162                 have_team_spawns = -1; // request team spawns
163         }
164
165         if(g_runematch)
166         {
167                 // ActivateTeamplay();
168                 fraglimit_override = autocvar_g_runematch_point_limit;
169                 leadlimit_override = autocvar_g_runematch_point_leadlimit;
170                 runematch_init();
171         }
172
173         if(g_lms)
174         {
175                 fraglimit_override = autocvar_g_lms_lives_override;
176                 leadlimit_override = 0; // not supported by LMS
177                 if(fraglimit_override == 0)
178                         fraglimit_override = -1;
179                 lms_lowest_lives = 9999;
180                 lms_next_place = 0;
181                 ScoreRules_lms();
182         }
183
184         if(g_arena)
185         {
186                 fraglimit_override = autocvar_g_arena_point_limit;
187                 leadlimit_override = autocvar_g_arena_point_leadlimit;
188                 maxspawned = autocvar_g_arena_maxspawned;
189                 if(maxspawned < 2)
190                         maxspawned = 2;
191                 arena_roundbased = autocvar_g_arena_roundbased;
192         }
193
194         if(g_ca)
195         {
196                 ActivateTeamplay();
197                 fraglimit_override = autocvar_g_ca_point_limit;
198                 leadlimit_override = autocvar_g_ca_point_leadlimit;
199                 precache_sound("ctf/red_capture.wav");
200                 precache_sound("ctf/blue_capture.wav");
201         }
202         if(g_keyhunt)
203         {
204                 ActivateTeamplay();
205                 fraglimit_override = autocvar_g_keyhunt_point_limit;
206                 leadlimit_override = autocvar_g_keyhunt_point_leadlimit;
207                 MUTATOR_ADD(gamemode_keyhunt);
208         }
209
210         if(g_freezetag)
211         {
212                 ActivateTeamplay();
213                 fraglimit_override = autocvar_g_freezetag_point_limit;
214                 leadlimit_override = autocvar_g_freezetag_point_leadlimit;
215                 MUTATOR_ADD(gamemode_freezetag);
216         }
217
218         if(g_assault)
219         {
220                 ActivateTeamplay();
221                 ScoreRules_assault();
222                 have_team_spawns = -1; // request team spawns
223         }
224
225         if(g_onslaught)
226         {
227                 ActivateTeamplay();
228                 have_team_spawns = -1; // request team spawns
229         }
230
231         if(g_race)
232         {
233
234                 if(autocvar_g_race_teams)
235                 {
236                         ActivateTeamplay();
237                         race_teams = bound(2, autocvar_g_race_teams, 4);
238                         have_team_spawns = -1; // request team spawns
239                 }
240                 else
241                         race_teams = 0;
242
243                 qualifying_override = autocvar_g_race_qualifying_timelimit_override;
244                 fraglimit_override = autocvar_g_race_laps_limit;
245                 leadlimit_override = 0; // currently not supported by race
246         }
247
248         if(g_cts)
249         {
250                 g_race_qualifying = 1;
251                 fraglimit_override = 0;
252                 leadlimit_override = 0;
253         }
254
255         if(g_nexball)
256         {
257         fraglimit_override = autocvar_g_nexball_goallimit;
258         leadlimit_override = autocvar_g_nexball_goalleadlimit;
259         ActivateTeamplay();
260         have_team_spawns = -1; // request team spawns
261             MUTATOR_ADD(gamemode_nexball);
262         }
263         
264         if(g_keepaway)
265         {
266                 MUTATOR_ADD(gamemode_keepaway);
267         }
268
269         if(teamplay)
270                 entcs_init();
271
272         cache_mutatormsg = strzone("");
273         cache_lastmutatormsg = strzone("");
274
275         // enforce the server's universal frag/time limits
276         if(!autocvar_g_campaign)
277         {
278                 if(fraglimit_override >= 0)
279                         cvar_set("fraglimit", ftos(fraglimit_override));
280                 if(timelimit_override >= 0)
281                         cvar_set("timelimit", ftos(timelimit_override));
282                 if(leadlimit_override >= 0)
283                         cvar_set("leadlimit", ftos(leadlimit_override));
284                 if(qualifying_override >= 0)
285                         cvar_set("g_race_qualifying_timelimit", ftos(qualifying_override));
286         }
287
288         if(g_race)
289         {
290                 // we need to find out the correct value for g_race_qualifying
291                 if(autocvar_g_campaign)
292                 {
293                         g_race_qualifying = 1;
294                 }
295                 else if(!autocvar_g_campaign && autocvar_g_race_qualifying_timelimit > 0)
296                 {
297                         g_race_qualifying = 2;
298                         race_fraglimit = autocvar_fraglimit;
299                         race_leadlimit = autocvar_leadlimit;
300                         race_timelimit = autocvar_timelimit;
301                         cvar_set("fraglimit", "0");
302                         cvar_set("leadlimit", "0");
303                         cvar_set("timelimit", ftos(autocvar_g_race_qualifying_timelimit));
304                 }
305                 else
306                         g_race_qualifying = 0;
307         }
308
309         if(g_race || g_cts)
310         {
311                 if(g_race_qualifying)
312                         independent_players = 1;
313
314                 ScoreRules_race();
315         }
316
317         InitializeEntity(world, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
318 }
319
320 string GetClientVersionMessage() {
321         string versionmsg;
322         if (self.version_mismatch) {
323                 if(self.version < autocvar_gameversion) {
324                         versionmsg = "^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8";
325                 } else {
326                         versionmsg = "^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8";
327                 }
328         } else {
329                 versionmsg = "^2client version and server version are compatible.^8";
330         }
331         return versionmsg;
332 }
333
334 string getwelcomemessage(void)
335 {
336         string s, modifications, motd;
337
338         ret_string = "";
339         MUTATOR_CALLHOOK(BuildMutatorsPrettyString);
340         modifications = ret_string;
341         
342         if(g_minstagib)
343                 modifications = strcat(modifications, ", MinstaGib");
344         if(g_weaponarena)
345         {
346                 if(g_weaponarena_random)
347                         modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
348                 else
349                         modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
350         }
351         if(autocvar_g_start_weapon_laser == 0)
352                 modifications = strcat(modifications, ", No start weapons");
353         if(autocvar_sv_gravity < 800)
354                 modifications = strcat(modifications, ", Low gravity");
355         if(g_cloaked && !g_cts)
356                 modifications = strcat(modifications, ", Cloaked");
357         if(g_grappling_hook)
358                 modifications = strcat(modifications, ", Hook");
359         if(g_midair)
360                 modifications = strcat(modifications, ", Midair");
361         if(g_pinata)
362                 modifications = strcat(modifications, ", Piñata");
363         if(g_weapon_stay && !g_cts)
364                 modifications = strcat(modifications, ", Weapons stay");
365         if(g_bloodloss > 0)
366                 modifications = strcat(modifications, ", Blood loss");
367         if(g_jetpack)
368                 modifications = strcat(modifications, ", Jet pack");
369         if(autocvar_g_powerups == 0)
370                 modifications = strcat(modifications, ", No powerups");
371         if(autocvar_g_powerups > 0)
372                 modifications = strcat(modifications, ", Powerups");
373         modifications = substring(modifications, 2, strlen(modifications) - 2);
374
375         string versionmessage;
376         versionmessage = GetClientVersionMessage();
377
378         s = strcat("This is Xonotic ", autocvar_g_xonoticversion, "\n", versionmessage);
379         s = strcat(s, "^8\n\nmatch type is ^1", gamemode_name, "^8\n");
380
381         if(modifications != "")
382                 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
383
384         if (g_grappling_hook)
385                 s = strcat(s, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n");
386
387         if(cache_lastmutatormsg != autocvar_g_mutatormsg)
388         {
389                 if(cache_lastmutatormsg)
390                         strunzone(cache_lastmutatormsg);
391                 if(cache_mutatormsg)
392                         strunzone(cache_mutatormsg);
393                 cache_lastmutatormsg = strzone(autocvar_g_mutatormsg);
394                 cache_mutatormsg = strzone(cache_lastmutatormsg);
395         }
396
397         if (cache_mutatormsg != "") {
398                 s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
399         }
400
401         motd = autocvar_sv_motd;
402         if (motd != "") {
403                 s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
404         }
405         return s;
406 }
407
408 void SetPlayerColors(entity pl, float _color)
409 {
410         /*string s;
411         s = ftos(cl);
412         stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
413         pl.team = cl + 1;
414         //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
415         pl.clientcolors = 16*cl + cl;*/
416
417         float pants, shirt;
418         pants = _color & 0x0F;
419         shirt = _color & 0xF0;
420
421
422         if(teamplay) {
423                 setcolor(pl, 16*pants + pants);
424         } else {
425                 setcolor(pl, shirt + pants);
426         }
427 }
428
429 void SetPlayerTeam(entity pl, float t, float s, float noprint)
430 {
431         float _color;
432
433         if(t == 4)
434                 _color = COLOR_TEAM4 - 1;
435         else if(t == 3)
436                 _color = COLOR_TEAM3 - 1;
437         else if(t == 2)
438                 _color = COLOR_TEAM2 - 1;
439         else
440                 _color = COLOR_TEAM1 - 1;
441
442         SetPlayerColors(pl,_color);
443
444         if(t != s) {
445                 LogTeamchange(pl.playerid, pl.team, 3);  // log manual team join
446
447                 if(!noprint)
448                 bprint(pl.netname, "^7 has changed from ", TeamNoName(s), " to ", TeamNoName(t), "\n");
449         }
450
451 }
452
453 // set c1...c4 to show what teams are allowed
454 void CheckAllowedTeams (entity for_whom)
455 {
456         float dm;
457         entity head;
458         string teament_name;
459
460         c1 = c2 = c3 = c4 = -1;
461         cb1 = cb2 = cb3 = cb4 = 0;
462
463         if(g_onslaught)
464         {
465                 // onslaught is special
466                 head = findchain(classname, "onslaught_generator");
467                 while (head)
468                 {
469                         if (head.team == COLOR_TEAM1) c1 = 0;
470                         if (head.team == COLOR_TEAM2) c2 = 0;
471                         if (head.team == COLOR_TEAM3) c3 = 0;
472                         if (head.team == COLOR_TEAM4) c4 = 0;
473                         head = head.chain;
474                 }
475         }
476         else if(g_domination)
477                 teament_name = "dom_team";
478         else if(g_ctf)
479                 teament_name = "ctf_team";
480         else if(g_tdm)
481                 teament_name = "tdm_team";
482         else if(g_nexball)
483                 teament_name = "nexball_team";
484         else if(g_assault)
485                 c1 = c2 = 0; // Assault always has 2 teams
486         else
487         {
488                 // cover anything else by treating it like tdm with no teams spawned
489                 if(g_race)
490                         dm = race_teams;
491                 else
492                         dm = 2;
493
494                 ret_float = dm;
495                 MUTATOR_CALLHOOK(GetTeamCount);
496                 dm = ret_float;
497
498                 if(dm >= 4)
499                         c1 = c2 = c3 = c4 = 0;
500                 else if(dm >= 3)
501                         c1 = c2 = c3 = 0;
502                 else
503                         c1 = c2 = 0;
504         }
505
506         // find out what teams are allowed if necessary
507         if(teament_name)
508         {
509                 head = find(world, classname, teament_name);
510                 while(head)
511                 {
512                         if(!(g_domination && head.netname == ""))
513                         {
514                                 if(head.team == COLOR_TEAM1)
515                                         c1 = 0;
516                                 else if(head.team == COLOR_TEAM2)
517                                         c2 = 0;
518                                 else if(head.team == COLOR_TEAM3)
519                                         c3 = 0;
520                                 else if(head.team == COLOR_TEAM4)
521                                         c4 = 0;
522                         }
523                         head = find(head, classname, teament_name);
524                 }
525         }
526
527         // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
528         if(c3==-1 && c4==-1)
529         if(autocvar_bot_vs_human && for_whom)
530         {
531                 if(autocvar_bot_vs_human > 0)
532                 {
533                         // bots are all blue
534                         if(clienttype(for_whom) == CLIENTTYPE_BOT)
535                                 c1 = c3 = c4 = -1;
536                         else
537                                 c2 = -1;
538                 }
539                 else
540                 {
541                         // bots are all red
542                         if(clienttype(for_whom) == CLIENTTYPE_BOT)
543                                 c2 = c3 = c4 = -1;
544                         else
545                                 c1 = -1;
546                 }
547         }
548
549         // if player has a forced team, ONLY allow that one
550         if(self.team_forced == COLOR_TEAM1 && c1 >= 0)
551                 c2 = c3 = c4 = -1;
552         else if(self.team_forced == COLOR_TEAM2 && c2 >= 0)
553                 c1 = c3 = c4 = -1;
554         else if(self.team_forced == COLOR_TEAM3 && c3 >= 0)
555                 c1 = c2 = c4 = -1;
556         else if(self.team_forced == COLOR_TEAM4 && c4 >= 0)
557                 c1 = c2 = c3 = -1;
558 }
559
560 float PlayerValue(entity p)
561 {
562         if(IsTeamBalanceForced() == 1)
563                 return 1;
564         return 1;
565         // FIXME: it always returns 1...
566 }
567
568 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
569 // teams that are allowed will now have their player counts stored in c1...c4
570 void GetTeamCounts(entity ignore)
571 {
572         entity head;
573         float value, bvalue;
574         // now count how many players are on each team already
575
576         // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
577         // also remember the lowest-scoring player
578
579         FOR_EACH_CLIENT(head)
580         {
581                 float t;
582                 if(head.classname == "player")
583                         t = head.team;
584                 else if(head.team_forced > 0)
585                         t = head.team_forced; // reserve the spot
586                 else
587                         continue;
588                 if(head != ignore)// && head.netname != "")
589                 {
590                         value = PlayerValue(head);
591                         if(clienttype(head) == CLIENTTYPE_BOT)
592                                 bvalue = value;
593                         else
594                                 bvalue = 0;
595                         if(t == COLOR_TEAM1)
596                         {
597                                 if(c1 >= 0)
598                                 {
599                                         c1 = c1 + value;
600                                         cb1 = cb1 + bvalue;
601                                 }
602                         }
603                         if(t == COLOR_TEAM2)
604                         {
605                                 if(c2 >= 0)
606                                 {
607                                         c2 = c2 + value;
608                                         cb2 = cb2 + bvalue;
609                                 }
610                         }
611                         if(t == COLOR_TEAM3)
612                         {
613                                 if(c3 >= 0)
614                                 {
615                                         c3 = c3 + value;
616                                         cb3 = cb3 + bvalue;
617                                 }
618                         }
619                         if(t == COLOR_TEAM4)
620                         {
621                                 if(c4 >= 0)
622                                 {
623                                         c4 = c4 + value;
624                                         cb4 = cb4 + bvalue;
625                                 }
626                         }
627                 }
628         }
629
630         // if the player who has a forced team has not joined yet, reserve the spot
631         if(autocvar_g_campaign)
632         {
633                 switch(autocvar_g_campaign_forceteam)
634                 {
635                         case 1: if(c1 == cb1) ++c1; break;
636                         case 2: if(c2 == cb2) ++c2; break;
637                         case 3: if(c3 == cb3) ++c3; break;
638                         case 4: if(c4 == cb4) ++c4; break;
639                 }
640         }
641 }
642
643 // returns # of smallest team (1, 2, 3, 4)
644 // NOTE: Assumes CheckAllowedTeams has already been called!
645 float FindSmallestTeam(entity pl, float ignore_pl)
646 {
647         float totalteams, balance_type, maxc;
648         totalteams = 0;
649
650         // find out what teams are available
651         //CheckAllowedTeams();
652
653         // make sure there are at least 2 teams to join
654         if(c1 >= 0)
655                 totalteams = totalteams + 1;
656         if(c2 >= 0)
657                 totalteams = totalteams + 1;
658         if(c3 >= 0)
659                 totalteams = totalteams + 1;
660         if(c4 >= 0)
661                 totalteams = totalteams + 1;
662
663         if((autocvar_bot_vs_human || pl.team_forced > 0) && totalteams == 1)
664                 totalteams += 1;
665
666         if(totalteams <= 1)
667         {
668                 if(autocvar_g_campaign && pl && clienttype(pl) == CLIENTTYPE_REAL)
669                         return 1; // special case for campaign and player joining
670                 else if(g_domination)
671                         error("Too few teams available for domination\n");
672                 else if(g_ctf)
673                         error("Too few teams available for ctf\n");
674                 else if(g_keyhunt)
675                         error("Too few teams available for key hunt\n");
676                 else if(g_freezetag)
677                         error("Too few teams available for freeze tag\n");
678                 else
679                         error("Too few teams available for team deathmatch\n");
680         }
681
682         // count how many players are in each team
683         if(ignore_pl)
684                 GetTeamCounts(pl);
685         else
686                 GetTeamCounts(world);
687
688         // c1...c4 now have counts of each team
689         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
690
691         // 2 gives priority to what team you're already on, 1 goes in order
692         // 2 doesn't seem to work though...
693         balance_type = 1;
694
695         if(bots_would_leave)
696         //if(pl.classname != "player")
697         if(clienttype(pl) != CLIENTTYPE_BOT)
698         {
699                 c1 -= cb1 * 255.0/256.0;
700                 c2 -= cb2 * 255.0/256.0;
701                 c3 -= cb3 * 255.0/256.0;
702                 c4 -= cb4 * 255.0/256.0;
703         }
704         maxc = max4(c1, c2, c3, c4);
705
706         RandomSelection_Init();
707         if(balance_type == 1)
708         {
709                 // 1: use team count, then score (note: can only use 8 significant bits of score)
710                 if(c1 >= 0) RandomSelection_Add(world, 1, string_null, 1, (maxc - c1) + float2range01(-team1_score) / 256.0);
711                 if(c2 >= 0) RandomSelection_Add(world, 2, string_null, 1, (maxc - c2) + float2range01(-team2_score) / 256.0);
712                 if(c3 >= 0) RandomSelection_Add(world, 3, string_null, 1, (maxc - c3) + float2range01(-team3_score) / 256.0);
713                 if(c4 >= 0) RandomSelection_Add(world, 4, string_null, 1, (maxc - c4) + float2range01(-team4_score) / 256.0);
714         }
715         else if(balance_type == 2)
716         {
717                 // 1: use team count, if equal prefer own team
718                 if(c1 >= 0) RandomSelection_Add(world, 1, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM1) / 512.0);
719                 if(c2 >= 0) RandomSelection_Add(world, 2, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM2) / 512.0);
720                 if(c3 >= 0) RandomSelection_Add(world, 3, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM3) / 512.0);
721                 if(c4 >= 0) RandomSelection_Add(world, 4, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM4) / 512.0);
722         }
723         else if(balance_type == 3)
724         {
725                 // 1: use team count, then score, if equal prefer own team (probably fails due to float accuracy problems)
726                 if(c1 >= 0) RandomSelection_Add(world, 1, string_null, 1, (maxc - c1) + float2range01(-team1_score + 0.5 * (self.team == COLOR_TEAM1)) / 256.0);
727                 if(c2 >= 0) RandomSelection_Add(world, 2, string_null, 1, (maxc - c2) + float2range01(-team2_score + 0.5 * (self.team == COLOR_TEAM2)) / 256.0);
728                 if(c3 >= 0) RandomSelection_Add(world, 3, string_null, 1, (maxc - c3) + float2range01(-team3_score + 0.5 * (self.team == COLOR_TEAM3)) / 256.0);
729                 if(c4 >= 0) RandomSelection_Add(world, 4, string_null, 1, (maxc - c4) + float2range01(-team4_score + 0.5 * (self.team == COLOR_TEAM4)) / 256.0);
730         }
731         return RandomSelection_chosen_float;
732 }
733
734 float JoinBestTeam(entity pl, float only_return_best, float forcebestteam)
735 {
736         float smallest, selectedteam;
737
738         // don't join a team if we're not playing a team game
739         if(!teamplay)
740                 return 0;
741
742         // find out what teams are available
743         CheckAllowedTeams(pl);
744
745         // if we don't care what team he ends up on, put him on whatever team he entered as.
746         // if he's not on a valid team, then let other code put him on the smallest team
747         if(!forcebestteam)
748         {
749                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
750                         selectedteam = pl.team;
751                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
752                         selectedteam = pl.team;
753                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
754                         selectedteam = pl.team;
755                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
756                         selectedteam = pl.team;
757                 else
758                         selectedteam = -1;
759
760                 if(selectedteam > 0)
761                 {
762                         if(!only_return_best)
763                         {
764                                 SetPlayerColors(pl, selectedteam - 1);
765
766                                 // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
767                                 // when JoinBestTeam is called by cl_client.qc/ClientConnect the player_id is 0 the log attempt is rejected
768                                 LogTeamchange(pl.playerid, pl.team, 99);
769                         }
770                         return selectedteam;
771                 }
772                 // otherwise end up on the smallest team (handled below)
773         }
774
775         smallest = FindSmallestTeam(pl, TRUE);
776
777         if(!only_return_best && !pl.bot_forced_team)
778         {
779                 TeamchangeFrags(self);
780                 if(smallest == 1)
781                 {
782                         SetPlayerColors(pl, COLOR_TEAM1 - 1);
783                 }
784                 else if(smallest == 2)
785                 {
786                         SetPlayerColors(pl, COLOR_TEAM2 - 1);
787                 }
788                 else if(smallest == 3)
789                 {
790                         SetPlayerColors(pl, COLOR_TEAM3 - 1);
791                 }
792                 else if(smallest == 4)
793                 {
794                         SetPlayerColors(pl, COLOR_TEAM4 - 1);
795                 }
796                 else
797                 {
798                         error("smallest team: invalid team\n");
799                 }
800
801                 LogTeamchange(pl.playerid, pl.team, 2); // log auto join
802
803                 if(pl.deadflag == DEAD_NO)
804                         Damage(pl, pl, pl, 100000, DEATH_TEAMCHANGE, pl.origin, '0 0 0');
805         }
806
807         return smallest;
808 }
809
810 //void() ctf_playerchanged;
811 void SV_ChangeTeam(float _color)
812 {
813         float scolor, dcolor, steam, dteam, dbotcount, scount, dcount;
814
815         // in normal deathmatch we can just apply the color and we're done
816         if(!teamplay) {
817                 SetPlayerColors(self, _color);
818                 return;
819         }
820
821         scolor = self.clientcolors & 0x0F;
822         dcolor = _color & 0x0F;
823
824         if(scolor == COLOR_TEAM1 - 1)
825                 steam = 1;
826         else if(scolor == COLOR_TEAM2 - 1)
827                 steam = 2;
828         else if(scolor == COLOR_TEAM3 - 1)
829                 steam = 3;
830         else // if(scolor == COLOR_TEAM4 - 1)
831                 steam = 4;
832         if(dcolor == COLOR_TEAM1 - 1)
833                 dteam = 1;
834         else if(dcolor == COLOR_TEAM2 - 1)
835                 dteam = 2;
836         else if(dcolor == COLOR_TEAM3 - 1)
837                 dteam = 3;
838         else // if(dcolor == COLOR_TEAM4 - 1)
839                 dteam = 4;
840
841         CheckAllowedTeams(self);
842
843         if(dteam == 1 && c1 < 0) dteam = 4;
844         if(dteam == 4 && c4 < 0) dteam = 3;
845         if(dteam == 3 && c3 < 0) dteam = 2;
846         if(dteam == 2 && c2 < 0) dteam = 1;
847
848         // not changing teams
849         if(scolor == dcolor)
850         {
851                 //bprint("same team change\n");
852                 SetPlayerTeam(self, dteam, steam, TRUE);
853                 return;
854         }
855
856         if((autocvar_g_campaign) || (autocvar_g_changeteam_banned && self.wasplayer)) {
857                 sprint(self, "Team changes not allowed\n");
858                 return; // changing teams is not allowed
859         }
860
861         if(autocvar_g_balance_teams_prevent_imbalance)
862         {
863                 // only allow changing to a smaller or equal size team
864
865                 // find out what teams are available
866                 //CheckAllowedTeams();
867                 // count how many players on each team
868                 GetTeamCounts(world);
869
870                 // get desired team
871                 if(dteam == 1 && c1 >= 0)//dcolor == COLOR_TEAM1 - 1)
872                 {
873                         dcount = c1;
874                         dbotcount = cb1;
875                 }
876                 else if(dteam == 2 && c2 >= 0)//dcolor == COLOR_TEAM2 - 1)
877                 {
878                         dcount = c2;
879                         dbotcount = cb2;
880                 }
881                 else if(dteam == 3 && c3 >= 0)//dcolor == COLOR_TEAM3 - 1)
882                 {
883                         dcount = c3;
884                         dbotcount = cb3;
885                 }
886                 else if(dteam == 4 && c4 >= 0)//dcolor == COLOR_TEAM4 - 1)
887                 {
888                         dcount = c4;
889                         dbotcount = cb4;
890                 }
891                 else
892                 {
893                         sprint(self, "Cannot change to an invalid team\n");
894
895                         return;
896                 }
897
898                 // get starting team
899                 if(steam == 1)//scolor == COLOR_TEAM1 - 1)
900                         scount = c1;
901                 else if(steam == 2)//scolor == COLOR_TEAM2 - 1)
902                         scount = c2;
903                 else if(steam == 3)//scolor == COLOR_TEAM3 - 1)
904                         scount = c3;
905                 else if(steam == 4)//scolor == COLOR_TEAM4 - 1)
906                         scount = c4;
907
908                 if(scount) // started at a valid, nonempty team
909                 {
910                         // check if we're trying to change to a larger team that doens't have bots to swap with
911                         if(dcount >= scount && dbotcount <= 0)
912                         {
913                                 sprint(self, "Cannot change to a larger team\n");
914                                 return; // can't change to a larger team
915                         }
916                 }
917         }
918
919 //      bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
920
921         if(self.classname == "player" && steam != dteam)
922         {
923                 // reduce frags during a team change
924                 TeamchangeFrags(self);
925         }
926
927         SetPlayerTeam(self, dteam, steam, FALSE);
928
929         if(self.classname == "player" && steam != dteam)
930         {
931                 // kill player when changing teams
932                 if(self.deadflag == DEAD_NO)
933                         Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
934         }
935 }
936
937 void ShufflePlayerOutOfTeam (float source_team)
938 {
939         float smallestteam, smallestteam_count, steam;
940         float lowest_bot_score, lowest_player_score;
941         entity head, lowest_bot, lowest_player, selected;
942
943         smallestteam = 0;
944         smallestteam_count = 999999999;
945
946         if(c1 >= 0 && c1 < smallestteam_count)
947         {
948                 smallestteam = 1;
949                 smallestteam_count = c1;
950         }
951         if(c2 >= 0 && c2 < smallestteam_count)
952         {
953                 smallestteam = 2;
954                 smallestteam_count = c2;
955         }
956         if(c3 >= 0 && c3 < smallestteam_count)
957         {
958                 smallestteam = 3;
959                 smallestteam_count = c3;
960         }
961         if(c4 >= 0 && c4 < smallestteam_count)
962         {
963                 smallestteam = 4;
964                 smallestteam_count = c4;
965         }
966
967         if(!smallestteam)
968         {
969                 bprint("warning: no smallest team\n");
970                 return;
971         }
972
973         if(source_team == 1)
974                 steam = COLOR_TEAM1;
975         else if(source_team == 2)
976                 steam = COLOR_TEAM2;
977         else if(source_team == 3)
978                 steam = COLOR_TEAM3;
979         else if(source_team == 4)
980                 steam = COLOR_TEAM4;
981
982         lowest_bot = world;
983         lowest_bot_score = 999999999;
984         lowest_player = world;
985         lowest_player_score = 999999999;
986
987         // find the lowest-scoring player & bot of that team
988         FOR_EACH_PLAYER(head)
989         {
990                 if(head.team == steam)
991                 {
992                         if(head.isbot)
993                         {
994                                 if(head.totalfrags < lowest_bot_score)
995                                 {
996                                         lowest_bot = head;
997                                         lowest_bot_score = head.totalfrags;
998                                 }
999                         }
1000                         else
1001                         {
1002                                 if(head.totalfrags < lowest_player_score)
1003                                 {
1004                                         lowest_player = head;
1005                                         lowest_player_score = head.totalfrags;
1006                                 }
1007                         }
1008                 }
1009         }
1010
1011         // prefers to move a bot...
1012         if(lowest_bot != world)
1013                 selected = lowest_bot;
1014         // but it will move a player if it has to
1015         else
1016                 selected = lowest_player;
1017         // don't do anything if it couldn't find anyone
1018         if(!selected)
1019         {
1020                 bprint("warning: couldn't find a player to move from team\n");
1021                 return;
1022         }
1023
1024         // smallest team gains a member
1025         if(smallestteam == 1)
1026         {
1027                 c1 = c1 + 1;
1028         }
1029         else if(smallestteam == 2)
1030         {
1031                 c2 = c2 + 1;
1032         }
1033         else if(smallestteam == 3)
1034         {
1035                 c3 = c3 + 1;
1036         }
1037         else if(smallestteam == 4)
1038         {
1039                 c4 = c4 + 1;
1040         }
1041         else
1042         {
1043                 bprint("warning: destination team invalid\n");
1044                 return;
1045         }
1046         // source team loses a member
1047         if(source_team == 1)
1048         {
1049                 c1 = c1 + 1;
1050         }
1051         else if(source_team == 2)
1052         {
1053                 c2 = c2 + 2;
1054         }
1055         else if(source_team == 3)
1056         {
1057                 c3 = c3 + 3;
1058         }
1059         else if(source_team == 4)
1060         {
1061                 c4 = c4 + 4;
1062         }
1063         else
1064         {
1065                 bprint("warning: source team invalid\n");
1066                 return;
1067         }
1068
1069         // move the player to the new team
1070         TeamchangeFrags(selected);
1071         SetPlayerTeam(selected, smallestteam, source_team, FALSE);
1072
1073         if(selected.deadflag == DEAD_NO)
1074                 Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE, selected.origin, '0 0 0');
1075         centerprint(selected, strcat("You have been moved into a different team to improve team balance\nYou are now on: ", ColoredTeamName(selected.team)));
1076 }
1077
1078 void CauseRebalance(float source_team, float howmany_toomany)
1079 {
1080         if(IsTeamBalanceForced() == 1)
1081         {
1082                 bprint("Rebalancing Teams\n");
1083                 ShufflePlayerOutOfTeam(source_team);
1084         }
1085 }
1086
1087 // part of g_balance_teams_force
1088 // occasionally perform an audit of the teams to make
1089 // sure they're more or less balanced in player count.
1090 void AuditTeams()
1091 {
1092         float numplayers, numteams, smallest, toomany;
1093         float balance;
1094         balance = IsTeamBalanceForced();
1095         if(balance == 0)
1096                 return;
1097
1098         if(audit_teams_time > time)
1099                 return;
1100
1101         audit_teams_time = time + 4 + random();
1102
1103 //      bprint("Auditing teams\n");
1104
1105         CheckAllowedTeams(world);
1106         GetTeamCounts(world);
1107
1108
1109         numteams = numplayers = smallest = 0;
1110         if(c1 >= 0)
1111         {
1112                 numteams = numteams + 1;
1113                 numplayers = numplayers + c1;
1114                 smallest = c1;
1115         }
1116         if(c2 >= 0)
1117         {
1118                 numteams = numteams + 1;
1119                 numplayers = numplayers + c2;
1120                 if(c2 < smallest)
1121                         smallest = c2;
1122         }
1123         if(c3 >= 0)
1124         {
1125                 numteams = numteams + 1;
1126                 numplayers = numplayers + c3;
1127                 if(c3 < smallest)
1128                         smallest = c3;
1129         }
1130         if(c4 >= 0)
1131         {
1132                 numteams = numteams + 1;
1133                 numplayers = numplayers + c4;
1134                 if(c4 < smallest)
1135                         smallest = c4;
1136         }
1137
1138         if(numplayers <= 0)
1139                 return; // no players to move around
1140         if(numteams < 2)
1141                 return; // don't bother shuffling if for some reason there aren't any teams
1142
1143         toomany = smallest + 1;
1144
1145         if(c1 && c1 > toomany)
1146                 CauseRebalance(1, c1 - toomany);
1147         if(c2 && c2 > toomany)
1148                 CauseRebalance(2, c2 - toomany);
1149         if(c3 && c3 > toomany)
1150                 CauseRebalance(3, c3 - toomany);
1151         if(c4 && c4 > toomany)
1152                 CauseRebalance(4, c4 - toomany);
1153
1154         // if teams are still unbalanced, balance them further in the next audit,
1155         // which will happen sooner (keep doing rapid audits until things are in order)
1156         audit_teams_time = time + 0.7 + random()*0.3;
1157 }
1158
1159 // code from here on is just to support maps that don't have team entities
1160 void tdm_spawnteam (string teamname, float teamcolor)
1161 {
1162         entity e;
1163         e = spawn();
1164         e.classname = "tdm_team";
1165         e.netname = teamname;
1166         e.cnt = teamcolor;
1167         e.team = e.cnt + 1;
1168 }
1169
1170 // spawn some default teams if the map is not set up for tdm
1171 void tdm_spawnteams()
1172 {
1173         float numteams;
1174
1175         numteams = autocvar_g_tdm_teams_override;
1176         if(numteams < 2)
1177                 numteams = autocvar_g_tdm_teams;
1178         numteams = bound(2, numteams, 4);
1179
1180         tdm_spawnteam("Red", COLOR_TEAM1-1);
1181         tdm_spawnteam("Blue", COLOR_TEAM2-1);
1182         if(numteams >= 3)
1183                 tdm_spawnteam("Yellow", COLOR_TEAM3-1);
1184         if(numteams >= 4)
1185                 tdm_spawnteam("Pink", COLOR_TEAM4-1);
1186 }
1187
1188 void tdm_delayedinit()
1189 {
1190         // if no teams are found, spawn defaults
1191         if (find(world, classname, "tdm_team") == world)
1192                 tdm_spawnteams();
1193 }
1194
1195 void tdm_init()
1196 {
1197         InitializeEntity(world, tdm_delayedinit, INITPRIO_GAMETYPE);
1198 }