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