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