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