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