]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/teamplay.qc
now REALLY remove dead players
[divverent/nexuiz.git] / data / qcsrc / server / teamplay.qc
1 string cache_motd;
2 string cache_mutatormsg;
3 string cache_lastmotd;
4 string cache_lastmutatormsg;
5
6 // client counts for each team
7 float c1, c2, c3, c4;
8 // # of bots on those teams
9 float cb1, cb2, cb3, cb4;
10
11 float audit_teams_time;
12
13 float IsTeamBalanceForced()
14 {
15         if(intermission_running)
16                 return 0; // no rebalancing whatsoever please
17         if(!cvar("teamplay"))
18                 return 0;
19         if(cvar("g_campaign"))
20                 return 0;
21         if(!cvar("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 entcs_init();
75
76 void LogTeamchange(entity pl)
77 {
78         string str;
79         if(!cvar("sv_eventlog"))
80                 return;
81         if(pl.playerid < 1)
82                 return;
83         str = strcat(":team:", ftos(pl.playerid), ":");
84         str = strcat(str, ftos(pl.team));
85         GameLogEcho(str);
86 }
87
88 void WriteGameCvars()
89 {
90         cvar_set("g_dm", ftos(g_dm));
91         cvar_set("g_tdm", ftos(g_tdm));
92         cvar_set("g_domination", ftos(g_domination));
93         cvar_set("g_ctf", ftos(g_ctf));
94         cvar_set("g_runematch", ftos(g_runematch));
95         cvar_set("g_lms", ftos(g_lms));
96         cvar_set("g_arena", ftos(g_arena));
97         cvar_set("g_keyhunt", ftos(g_keyhunt));
98         cvar_set("g_assault", ftos(g_assault));
99         cvar_set("g_onslaught", ftos(g_onslaught));
100         cvar_set("g_race", ftos(g_race));
101 }
102
103 void ReadGameCvars()
104 {
105         float found;
106         float prev;
107         float i;
108
109         found = 0;
110         prev = cvar("gamecfg");
111         for(i = 0; i < 2; ++i)
112         {
113                 found += (g_dm = (!found && (prev != GAME_DEATHMATCH) && cvar("g_dm")));
114                 found += (g_tdm = (!found && (prev != GAME_TEAM_DEATHMATCH) && cvar("g_tdm")));
115                 found += (g_domination = (!found && (prev != GAME_DOMINATION) && cvar("g_domination")));
116                 found += (g_ctf = (!found && (prev != GAME_CTF) && cvar("g_ctf")));
117                 found += (g_runematch = (!found && (prev != GAME_RUNEMATCH) && cvar("g_runematch")));
118                 found += (g_lms = (!found && (prev != GAME_LMS) && cvar("g_lms")));
119                 found += (g_arena = (!found && (prev != GAME_ARENA) && cvar("g_arena")));
120                 found += (g_keyhunt = (!found && (prev != GAME_KEYHUNT) && cvar("g_keyhunt")));
121                 found += (g_assault = (!found && (prev != GAME_ASSAULT) && cvar("g_assault")));
122                 found += (g_onslaught = (!found && (prev != GAME_ONSLAUGHT) && cvar("g_onslaught")));
123                 found += (g_race = (!found && (prev != GAME_RACE) && cvar("g_race")));
124
125                 if(found)
126                         break;
127
128                 prev = -1; // second attempt takes place WITHOUT prev set
129         }
130
131         if(!found)
132                 g_dm = 1;
133
134         if(g_dm && cvar("deathmatch_force_teamplay"))
135         {
136                 g_dm = 0;
137                 g_tdm = 1;
138         }
139
140         teams_matter = 0;
141 }
142
143 void default_delayedinit()
144 {
145         if(!scores_initialized)
146                 ScoreRules_generic();
147 }
148
149 void ActivateTeamplay()
150 {
151         float teamplay_default;
152         teamplay_default = cvar("teamplay_default");
153
154         if(teamplay_default)
155                 cvar_set("teamplay", ftos(teamplay_default));
156         else
157                 cvar_set("teamplay", "3");
158
159         teams_matter = 1;
160 }
161
162 void InitGameplayMode()
163 {
164         float fraglimit_override, timelimit_override;
165
166         VoteReset();
167         
168         teams_matter = 0;
169         cvar_set("teamplay", "0");
170
171         // make sure only ONE type is selected
172         ReadGameCvars();
173         WriteGameCvars();
174
175         MapInfo_LoadMapSettings(mapname);
176         if not(cvar_value_issafe(world.fog))
177         {
178                 print("The current map contains a potentially harmful fog setting, ignored\n");
179                 world.fog = string_null;
180         }
181         if(MapInfo_Map_fog != "")
182                 if(MapInfo_Map_fog == "none")
183                         world.fog = string_null;
184                 else
185                         world.fog = strzone(MapInfo_Map_fog);
186         clientstuff = strzone(MapInfo_Map_clientstuff);
187         MapInfo_ClearTemps();
188
189         // in case mapinfo switched the type
190         ReadGameCvars();
191
192         // set both here, gamemode can override it later
193         timelimit_override = cvar("timelimit_override");
194         fraglimit_override = cvar("fraglimit_override");
195
196         if(g_dm)
197         {
198                 game = GAME_DEATHMATCH;
199                 gamemode_name = "Deathmatch";
200         }
201
202         if(g_tdm)
203         {
204                 game = GAME_TEAM_DEATHMATCH;
205                 gamemode_name = "Team Deathmatch";
206                 ActivateTeamplay();
207                 tdm_init();
208         }
209
210         if(g_domination)
211         {
212                 game = GAME_DOMINATION;
213                 gamemode_name = "Domination";
214                 ActivateTeamplay();
215                 fraglimit_override = cvar("g_domination_point_limit");
216                 dom_init();
217         }
218
219         if(g_ctf)
220         {
221                 game = GAME_CTF;
222                 gamemode_name = "Capture the Flag";
223                 ActivateTeamplay();
224                 g_ctf_win_mode = cvar("g_ctf_win_mode");
225                 if(g_ctf_win_mode >= 2)
226                         fraglimit_override = cvar("g_ctf_capture_limit");
227                 else
228                         fraglimit_override = cvar("capturelimit_override");
229                 ctf_init();
230         }
231
232         if(g_runematch)
233         {
234                 game = GAME_RUNEMATCH;
235                 gamemode_name = "Rune Match";
236                 if(cvar("deathmatch_force_teamplay"))
237                         ActivateTeamplay();
238                 fraglimit_override = cvar("g_runematch_point_limit");
239                 runematch_init();
240         }
241
242         if(g_lms)
243         {
244                 game = GAME_LMS;
245                 gamemode_name = "Last Man Standing";
246                 fraglimit_override = cvar("g_lms_lives_override");
247                 if(fraglimit_override == 0)
248                         fraglimit_override = -1;
249                 lms_lowest_lives = 9999;
250                 lms_next_place = 0;
251                 ScoreRules_lms();
252         }
253
254         if(g_arena)
255         {
256                 game = GAME_ARENA;
257                 gamemode_name = "Arena";
258                 fraglimit_override = cvar("g_arena_point_limit");
259                 maxspawned = cvar("g_arena_maxspawned");
260                 if(maxspawned < 2)
261                         maxspawned = 2;
262                 arena_roundbased = cvar("g_arena_roundbased");
263         }
264
265         if(g_keyhunt)
266         {
267                 game = GAME_KEYHUNT;
268                 gamemode_name = "Key Hunt";
269                 ActivateTeamplay();
270                 fraglimit_override = cvar("g_keyhunt_point_limit");
271                 kh_init();
272         }
273
274         if(g_assault)
275         {
276                 game = GAME_ASSAULT;
277                 gamemode_name = "Assault";
278                 ActivateTeamplay();
279         }
280
281         if(g_onslaught)
282         {
283                 game = GAME_ONSLAUGHT;
284                 gamemode_name = "Onslaught";
285                 ActivateTeamplay();
286         }
287
288         if(g_race)
289         {
290                 game = GAME_RACE;
291                 gamemode_name = "Race";
292                 g_race_qualifying = cvar("g_race_qualifying");
293
294                 // In campaign, only this mode makes sense...
295                 if(cvar("g_campaign"))
296                         g_race_qualifying = 1;
297
298                 if(cvar("g_race_teams"))
299                 {
300                         g_race_qualifying = 0; // not supported!
301                         ActivateTeamplay();
302                         race_teams = bound(2, cvar("g_race_teams"), 4);
303                 }
304                 else
305                         race_teams = 0;
306
307                 if(g_race_qualifying == 1)
308                         fraglimit_override = 0;
309                 else
310                         fraglimit_override = cvar("g_race_laps_limit");
311
312                 if(g_race_qualifying)
313                         independent_players = 1;
314
315                 ScoreRules_race();
316         }
317
318         if(teams_matter)
319                 entcs_init();
320
321         // save it (for the next startup)
322         cvar_set("gamecfg", ftos(game));
323
324         cache_mutatormsg = strzone("");
325         cache_motd = strzone("");
326         cache_lastmutatormsg = strzone("");
327         cache_lastmotd = strzone("");
328
329         // enforce the server's universal frag/time limits
330         if(!cvar("g_campaign"))
331         {
332                 if(fraglimit_override >= 0)
333                         cvar_set("fraglimit", ftos(fraglimit_override));
334                 if(timelimit_override >= 0)
335                         cvar_set("timelimit", ftos(timelimit_override));
336         }
337
338         if(g_race && g_race_qualifying == 2)
339         {
340                 race_fraglimit = cvar("fraglimit");
341                 cvar_set("fraglimit", "0");
342         }
343
344         InitializeEntity(world, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
345 }
346
347 string GetClientVersionMessage() {
348         local string versionmsg;
349         if (self.version_mismatch) {
350                 if(self.version < cvar("gameversion")) {
351                         versionmsg = "^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8";
352                 } else {
353                         versionmsg = "^3This server is using an outdated Nexuiz version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8";
354                 }
355         } else {
356                 versionmsg = "^2client version and server version are compatible.^8";
357         }
358         return versionmsg;
359 }
360
361
362 void PrintWelcomeMessage(entity pl)
363 {
364         string s, mutator, modifications;
365
366         /*if(self.welcomemessage_time > time)
367                 return;
368         self.welcomemessage_time = time + 0.8; */
369
370         if(self.cvar_scr_centertime == 0) return;
371         if( !(timeoutStatus >= 1 || (time < restart_countdown) ) ) { //really print the WelcomeMessage to the player every frame when timeout-seconds are shown or the game is restarted, to make sure that the shown number is accurate
372                 if(self.welcomemessage_time > time) return;
373                 self.welcomemessage_time = time + self.cvar_scr_centertime * 0.6;
374         }
375
376         if(cvar("g_campaign"))
377         {
378                 centerprint(pl, campaign_message);
379                 return;
380         }
381
382         if(!self.BUTTON_INFO)
383         {
384                 // TODO get rid of this too
385                 local string specString;
386                 specString = NEWLINES;
387                 if(time < restart_countdown) //also show the countdown when being a spectator
388                         specString = strcat(specString, "\n\n^1Game starts in ", ftos(restartAnnouncer.cnt + 1), " seconds^7");
389                 else if (timeoutStatus != 0)
390                         specString = strcat(specString, "\n\n", getTimeoutText(1));
391                 else
392                 {
393                         if(!self.BUTTON_INFO && self.classname == "player")
394                                 return;
395                         goto normal;
396                 }
397                 return centerprint_atprio(self, CENTERPRIO_SPAM, specString);
398         }
399
400 :normal
401         if(g_minstagib)
402                 mutator = "^2Minstagib ^1";
403         else if(g_rocketarena)
404                 mutator = "^2Rocketarena ^1";
405         else if(g_nixnex)
406                 mutator = "^2No Items Nexuiz ^1";
407
408         if(g_cloaked) {
409                 // to protect against unheedingly made changes
410                 if (modifications) {
411                         modifications = strcat(modifications, ", ");
412                 }
413                 modifications = "cloaked";
414         }
415         if(g_footsteps) {
416                 if (modifications) {
417                         modifications = strcat(modifications, ", ");
418                 }
419                 modifications = strcat(modifications, "footsteps");
420         }
421         if(g_midair) {
422                 if (modifications) {
423                         modifications = strcat(modifications, ", ");
424                 }
425                 modifications = strcat(modifications, "midair");
426         }
427         if(g_vampire) {
428                 if (modifications) {
429                         modifications = strcat(modifications, ", ");
430                 }
431                 modifications = strcat(modifications, "vampire");
432         }
433         if(g_laserguided_missile) {
434                 if (modifications) {
435                         modifications = strcat(modifications, ", ");
436                 }
437                 modifications = strcat(modifications, "laser guided missiles");
438         }
439         if(cvar("sv_gravity") < 800) {
440                 if (modifications) {
441                         modifications = strcat(modifications, ", ");
442                 }
443                 modifications = strcat(modifications, "low gravity");
444         }
445
446         local string versionmessage;
447         versionmessage = GetClientVersionMessage();
448
449         s = strcat(s, NEWLINES, "This is Nexuiz ", cvar_string("g_nexuizversion"), "\n", versionmessage);
450         s = strcat(s, "^8\n\nmatch type is ^1", mutator, gamemode_name, "^8\n");
451
452         if(modifications != "")
453                 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
454
455         if(time < restart_countdown)
456                 s = strcat(s, "\n^1Game starts in ", ftos(restartAnnouncer.cnt + 1), " seconds^7");
457
458         if(timeoutStatus != 0)
459                 s = strcat(s, "\n\n", getTimeoutText(1));
460
461         if (g_grappling_hook)
462                 s = strcat(s, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n");
463
464         if(cache_lastmutatormsg != cvar_string("g_mutatormsg"))
465         {
466                 if(cache_lastmutatormsg)
467                         strunzone(cache_lastmutatormsg);
468                 if(cache_mutatormsg)
469                         strunzone(cache_mutatormsg);
470                 cache_lastmutatormsg = strzone(cvar_string("g_mutatormsg"));
471                 cache_mutatormsg = strzone(wordwrap(cache_lastmutatormsg, 50));
472         }
473
474         if (cache_mutatormsg != "") {
475                 s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
476         }
477         
478         if(cache_lastmotd != cvar_string("sv_motd"))
479         {
480                 if(cache_lastmotd)
481                         strunzone(cache_lastmotd);
482                 if(cache_motd)
483                         strunzone(cache_motd);
484                 cache_lastmotd = strzone(cvar_string("sv_motd"));
485                 cache_motd = strzone(wordwrap(cache_lastmotd, 50));
486         }
487
488         if (cache_motd != "") {
489                 s = strcat(s, "\n\n^8MOTD: ^7", cache_motd);
490         }
491         s = strcat(s, "\n");
492
493         centerprint(pl, s);
494         //sprint(pl, s);
495 }
496
497
498 void SetPlayerColors(entity pl, float _color)
499 {
500         /*string s;
501         s = ftos(cl);
502         stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
503         pl.team = cl + 1;
504         //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
505         pl.clientcolors = 16*cl + cl;*/
506
507         float pants, shirt;
508         pants = _color & 0x0F;
509         shirt = _color & 0xF0;
510
511
512         if(teamplay) {
513                 setcolor(pl, 16*pants + pants);
514         } else {
515                 setcolor(pl, shirt + pants);
516         }
517 }
518
519 void SetPlayerTeam(entity pl, float t, float s, float noprint)
520 {
521         float _color;
522
523         if(t == 4)
524                 _color = COLOR_TEAM4 - 1;
525         else if(t == 3)
526                 _color = COLOR_TEAM3 - 1;
527         else if(t == 2)
528                 _color = COLOR_TEAM2 - 1;
529         else
530                 _color = COLOR_TEAM1 - 1;
531
532         SetPlayerColors(pl,_color);
533
534         if(!noprint && t != s)
535         {
536                 //bprint(pl.netname, " has changed to ", TeamNoName(t), "\n");
537                 bprint(pl.netname, "^7 has changed from ", TeamNoName(s), " to ", TeamNoName(t), "\n");
538         }
539
540         if(t != s)
541                 LogTeamchange(pl);
542 }
543
544
545
546
547
548
549 // set c1...c4 to show what teams are allowed
550 void CheckAllowedTeams (entity for_whom)
551 {
552         string teament_name;
553         float dm;
554         entity head;
555
556 //      if(!dom && !ctf)
557 //              dm = 1;
558
559         c1 = c2 = c3 = c4 = -1;
560         cb1 = cb2 = cb3 = cb4 = 0;
561
562         // onslaught is special
563         if(g_onslaught)
564         {
565                 head = findchain(classname, "onslaught_generator");
566                 while (head)
567                 {
568                         if (head.team == COLOR_TEAM1) c1 = 0;
569                         if (head.team == COLOR_TEAM2) c2 = 0;
570                         if (head.team == COLOR_TEAM3) c3 = 0;
571                         if (head.team == COLOR_TEAM4) c4 = 0;
572                         head = head.chain;
573                 }
574                 return;
575         }
576
577         if(g_domination)
578                 teament_name = "dom_team";
579         else if(g_ctf)
580                 teament_name = "ctf_team";
581         else if(g_tdm)
582                 teament_name = "tdm_team";
583         else if(g_assault)
584         {
585                 c1 = c2 = 0; // Assault always has 2 teams
586                 return;
587         }
588         else
589         {
590                 // cover anything else by treating it like tdm with no teams spawned
591                 if(g_keyhunt)
592                         dm = kh_teams;
593                 else if(g_race)
594                         dm = race_teams;
595                 else
596                         dm = cvar("g_tdm_teams");
597                 if(dm < 2)
598                         error("g_tdm_teams < 2, not enough teams to play team deathmatch\n");
599
600                 if(dm >= 4)
601                 {
602                         c1 = c2 = c3 = c4 = 0;
603                 }
604                 else if(dm >= 3)
605                 {
606                         c1 = c2 = c3 = 0;
607                 }
608                 else// if(dm >= 2)
609                 {
610                         c1 = c2 = 0;
611                 }
612                 return;
613         }
614
615         // first find out what teams are allowed
616         head = find(world, classname, teament_name);
617         while(head)
618         {
619                 if(!(g_domination && head.netname == ""))
620                 {
621                         if(head.team == COLOR_TEAM1)
622                         {
623                                 c1 = 0;
624                         }
625                         if(head.team == COLOR_TEAM2)
626                         {
627                                 c2 = 0;
628                         }
629                         if(head.team == COLOR_TEAM3)
630                         {
631                                 c3 = 0;
632                         }
633                         if(head.team == COLOR_TEAM4)
634                         {
635                                 c4 = 0;
636                         }
637                 }
638                 head = find(head, classname, teament_name);
639         }
640
641         if(for_whom)
642         {
643                 if(cvar("bot_vs_human") > 0)
644                 {
645                         // bots are all blue
646                         if(clienttype(for_whom) == CLIENTTYPE_BOT)
647                                 c1 = c3 = c4 = -1;
648                         else
649                                 c2 = -1;
650                 }
651                 else if(cvar("bot_vs_human") < 0)
652                 {
653                         // bots are all red
654                         if(clienttype(for_whom) == CLIENTTYPE_BOT)
655                                 c2 = c3 = c4 = -1;
656                         else
657                                 c1 = -1;
658                 }
659         }
660 }
661
662 float PlayerValue(entity p)
663 {
664         if(IsTeamBalanceForced() == 1)
665                 return 1;
666         return 1;
667 }
668
669 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
670 // teams that are allowed will now have their player counts stored in c1...c4
671 void GetTeamCounts(entity ignore)
672 {
673         entity head;
674         float value, bvalue;
675         // now count how many players are on each team already
676
677         // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
678         // also remember the lowest-scoring player
679
680         FOR_EACH_PLAYER(head)
681         {
682                 if(head != ignore)// && head.netname != "")
683                 {
684                         value = PlayerValue(head);
685                         if(clienttype(head) == CLIENTTYPE_BOT)
686                                 bvalue = value;
687                         else
688                                 bvalue = 0;
689                         if(head.team == COLOR_TEAM1)
690                         {
691                                 if(c1 >= 0)
692                                 {
693                                         c1 = c1 + value;
694                                         cb1 = cb1 + bvalue;
695                                 }
696                         }
697                         if(head.team == COLOR_TEAM2)
698                         {
699                                 if(c2 >= 0)
700                                 {
701                                         c2 = c2 + value;
702                                         cb2 = cb2 + bvalue;
703                                 }
704                         }
705                         if(head.team == COLOR_TEAM3)
706                         {
707                                 if(c3 >= 0)
708                                 {
709                                         c3 = c3 + value;
710                                         cb3 = cb3 + bvalue;
711                                 }
712                         }
713                         if(head.team == COLOR_TEAM4)
714                         {
715                                 if(c4 >= 0)
716                                 {
717                                         c4 = c4 + value;
718                                         cb4 = cb4 + bvalue;
719                                 }
720                         }
721                 }
722         }
723 }
724
725 // returns # of smallest team (1, 2, 3, 4)
726 // NOTE: Assumes CheckAllowedTeams has already been called!
727 float FindSmallestTeam(entity pl, float ignore_pl)
728 {
729         float totalteams, smallestteam, smallestteam_count, smallestteam_score, balance_type;
730         totalteams = 0;
731
732         // find out what teams are available
733         //CheckAllowedTeams();
734
735         // make sure there are at least 2 teams to join
736         if(c1 >= 0)
737                 totalteams = totalteams + 1;
738         if(c2 >= 0)
739                 totalteams = totalteams + 1;
740         if(c3 >= 0)
741                 totalteams = totalteams + 1;
742         if(c4 >= 0)
743                 totalteams = totalteams + 1;
744
745         if(cvar("bot_vs_human"))
746                 totalteams += 1;
747
748         if(totalteams <= 1)
749         {
750                 if(g_domination)
751                         error("Too few teams available for domination\n");
752                 else if(g_ctf)
753                         error("Too few teams available for ctf\n");
754                 else if(g_keyhunt)
755                         error("Too few teams available for key hunt\n");
756                 else
757                         error("Too few teams available for team deathmatch\n");
758         }
759
760         // count how many players are in each team
761         if(ignore_pl)
762                 GetTeamCounts(pl);
763         else
764                 GetTeamCounts(world);
765
766         // c1...c4 now have counts of each team
767         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
768
769         smallestteam = 0;
770         smallestteam_count = 999999999;
771         smallestteam_score = 999999999;
772
773         // 2 gives priority to what team you're already on, 1 goes in order
774         // 2 doesn't seem to work though...
775         balance_type = 1;
776
777         if(bots_would_leave)
778         //if(pl.classname != "player")
779         if(clienttype(pl) != CLIENTTYPE_BOT)
780         {
781                 c1 -= cb1 * 255.0/256;
782                 c2 -= cb2 * 255.0/256;
783                 c3 -= cb3 * 255.0/256;
784                 c4 -= cb4 * 255.0/256;
785         }
786
787         if(balance_type == 1)
788         {
789                 if(c1 >= 0 && (c1 < smallestteam_count || (c1 <= smallestteam_count && team1_score < smallestteam_score)))
790                 {
791                         smallestteam = 1;
792                         smallestteam_count = c1;
793                         smallestteam_score = team1_score;
794                 }
795                 if(c2 >= 0 && (c2 < smallestteam_count || (c2 <= smallestteam_count && team2_score < smallestteam_score)))
796                 {
797                         smallestteam = 2;
798                         smallestteam_count = c2;
799                         smallestteam_score = team2_score;
800                 }
801                 if(c3 >= 0 && (c3 < smallestteam_count || (c3 <= smallestteam_count && team3_score < smallestteam_score)))
802                 {
803                         smallestteam = 3;
804                         smallestteam_count = c3;
805                         smallestteam_score = team3_score;
806                 }
807                 if(c4 >= 0 && (c4 < smallestteam_count || (c4 <= smallestteam_count && team4_score < smallestteam_score)))
808                 {
809                         smallestteam = 4;
810                         smallestteam_count = c4;
811                         smallestteam_score = team4_score;
812                 }
813         }
814         else
815         {
816                 if(c1 >= 0 && (c1 < smallestteam_count ||
817                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
818                 {
819                         smallestteam = 1;
820                         smallestteam_count = c1;
821                 }
822                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
823                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
824                 {
825                         smallestteam = 2;
826                         smallestteam_count = c2;
827                 }
828                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
829                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
830                 {
831                         smallestteam = 3;
832                         smallestteam_count = c3;
833                 }
834                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
835                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
836                 {
837                         smallestteam = 4;
838                         smallestteam_count = c4;
839                 }
840         }
841
842         return smallestteam;
843 }
844
845 float JoinBestTeam(entity pl, float only_return_best, float forcebestteam)
846 {
847         float smallest, selectedteam;
848
849         // don't join a team if we're not playing a team game
850         if(!cvar("teamplay") && !g_domination && !g_ctf && !g_keyhunt)
851                 return 0;
852
853         // find out what teams are available
854         CheckAllowedTeams(pl);
855
856         if(g_domination)
857         {
858                 // <div0> WHY? TODO
859                 if(cvar("g_domination_default_teams") < 3)
860                         c3 = 999999999;
861                 if(cvar("g_domination_default_teams") < 4)
862                         c4 = 999999999;
863         }
864
865         // if we don't care what team he ends up on, put him on whatever team he entered as.
866         // if he's not on a valid team, then let other code put him on the smallest team
867         if(!forcebestteam)
868         {
869                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
870                         selectedteam = pl.team;
871                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
872                         selectedteam = pl.team;
873                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
874                         selectedteam = pl.team;
875                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
876                         selectedteam = pl.team;
877                 else
878                         selectedteam = -1;
879                 if(selectedteam > 0)
880                 {
881                         if(!only_return_best)
882                         {
883                                 SetPlayerColors(pl, selectedteam - 1);
884                                 LogTeamchange(pl);
885                         }
886                         return selectedteam;
887                 }
888                 // otherwise end up on the smallest team (handled below)
889         }
890
891         smallest = FindSmallestTeam(pl, TRUE);
892
893
894         if(!only_return_best)
895         {
896                 TeamchangeFrags(self);
897                 if(smallest == 1)
898                 {
899                         SetPlayerColors(pl, COLOR_TEAM1 - 1);
900                 }
901                 else if(smallest == 2)
902                 {
903                         SetPlayerColors(pl, COLOR_TEAM2 - 1);
904                 }
905                 else if(smallest == 3)
906                 {
907                         SetPlayerColors(pl, COLOR_TEAM3 - 1);
908                 }
909                 else if(smallest == 4)
910                 {
911                         SetPlayerColors(pl, COLOR_TEAM4 - 1);
912                 }
913                 else
914                 {
915                         error("smallest team: invalid team\n");
916                 }
917                 LogTeamchange(pl);
918                 if(pl.deadflag == DEAD_NO)
919                         Damage(pl, pl, pl, 100000, DEATH_TEAMCHANGE, pl.origin, '0 0 0');
920         }
921
922         return smallest;
923 }
924
925 //void() ctf_playerchanged;
926 void SV_ChangeTeam(float _color)
927 {
928         float scolor, dcolor, steam, dteam, dbotcount, scount, dcount;
929
930         // in normal deathmatch we can just apply the color and we're done
931         if(!cvar("teamplay")) {
932                 SetPlayerColors(self, _color);
933                 return;
934         }
935
936         scolor = self.clientcolors & 0x0F;
937         dcolor = _color & 0x0F;
938
939         if(scolor == COLOR_TEAM1 - 1)
940                 steam = 1;
941         else if(scolor == COLOR_TEAM2 - 1)
942                 steam = 2;
943         else if(scolor == COLOR_TEAM3 - 1)
944                 steam = 3;
945         else if(scolor == COLOR_TEAM4 - 1)
946                 steam = 4;
947         if(dcolor == COLOR_TEAM1 - 1)
948                 dteam = 1;
949         else if(dcolor == COLOR_TEAM2 - 1)
950                 dteam = 2;
951         else if(dcolor == COLOR_TEAM3 - 1)
952                 dteam = 3;
953         else if(dcolor == COLOR_TEAM4 - 1)
954                 dteam = 4;
955
956         CheckAllowedTeams(self);
957
958         if(dteam == 1 && c1 < 0) dteam = 4;
959         if(dteam == 4 && c4 < 0) dteam = 3;
960         if(dteam == 3 && c3 < 0) dteam = 2;
961         if(dteam == 2 && c2 < 0) dteam = 1;
962
963         // not changing teams
964         if(scolor == dcolor)
965         {
966                 //bprint("same team change\n");
967                 SetPlayerTeam(self, dteam, steam, TRUE);
968                 return;
969         }
970
971         if(cvar("teamplay"))
972         {
973                 if(cvar("g_campaign") || cvar("g_changeteam_banned"))
974                 {
975                         sprint(self, "Team changes not allowed\n");
976                         return; // changing teams is not allowed
977                 }
978
979                 if(!cvar("g_campaign") && cvar("g_balance_teams_prevent_imbalance"))
980                 {
981                         // only allow changing to a smaller or equal size team
982
983                         // find out what teams are available
984                         //CheckAllowedTeams();
985                         // count how many players on each team
986                         GetTeamCounts(world);
987
988                         // get desired team
989                         if(dteam == 1 && c1 >= 0)//dcolor == COLOR_TEAM1 - 1)
990                         {
991                                 dcount = c1;
992                                 dbotcount = cb1;
993                         }
994                         else if(dteam == 2 && c2 >= 0)//dcolor == COLOR_TEAM2 - 1)
995                         {
996                                 dcount = c2;
997                                 dbotcount = cb2;
998                         }
999                         else if(dteam == 3 && c3 >= 0)//dcolor == COLOR_TEAM3 - 1)
1000                         {
1001                                 dcount = c3;
1002                                 dbotcount = cb3;
1003                         }
1004                         else if(dteam == 4 && c4 >= 0)//dcolor == COLOR_TEAM4 - 1)
1005                         {
1006                                 dcount = c4;
1007                                 dbotcount = cb4;
1008                         }
1009                         else
1010                         {
1011                                 sprint(self, "Cannot change to an invalid team\n");
1012
1013                                 return;
1014                         }
1015
1016                         // get starting team
1017                         if(steam == 1)//scolor == COLOR_TEAM1 - 1)
1018                                 scount = c1;
1019                         else if(steam == 2)//scolor == COLOR_TEAM2 - 1)
1020                                 scount = c2;
1021                         else if(steam == 3)//scolor == COLOR_TEAM3 - 1)
1022                                 scount = c3;
1023                         else if(steam == 4)//scolor == COLOR_TEAM4 - 1)
1024                                 scount = c4;
1025
1026                         if(scount) // started at a valid, nonempty team
1027                         {
1028                                 // check if we're trying to change to a larger team that doens't have bots to swap with
1029                                 if(dcount >= scount && dbotcount <= 0)
1030                                 {
1031                                         sprint(self, "Cannot change to a larger team\n");
1032                                         return; // can't change to a larger team
1033                                 }
1034                         }
1035                 }
1036         }
1037
1038 //      bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
1039
1040         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
1041         {
1042                 // reduce frags during a team change
1043                 TeamchangeFrags(self);
1044         }
1045
1046         SetPlayerTeam(self, dteam, steam, FALSE);
1047
1048         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
1049         {
1050                 // kill player when changing teams
1051                 if(self.deadflag == DEAD_NO)
1052                         Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
1053         }
1054         //ctf_playerchanged();
1055 }
1056
1057 void ShufflePlayerOutOfTeam (float source_team)
1058 {
1059         float smallestteam, smallestteam_count, steam;
1060         float lowest_bot_score, lowest_player_score;
1061         entity head, lowest_bot, lowest_player, selected;
1062
1063         smallestteam = 0;
1064         smallestteam_count = 999999999;
1065
1066         if(c1 >= 0 && c1 < smallestteam_count)
1067         {
1068                 smallestteam = 1;
1069                 smallestteam_count = c1;
1070         }
1071         if(c2 >= 0 && c2 < smallestteam_count)
1072         {
1073                 smallestteam = 2;
1074                 smallestteam_count = c2;
1075         }
1076         if(c3 >= 0 && c3 < smallestteam_count)
1077         {
1078                 smallestteam = 3;
1079                 smallestteam_count = c3;
1080         }
1081         if(c4 >= 0 && c4 < smallestteam_count)
1082         {
1083                 smallestteam = 4;
1084                 smallestteam_count = c4;
1085         }
1086
1087         if(!smallestteam)
1088         {
1089                 bprint("warning: no smallest team\n");
1090                 return;
1091         }
1092
1093         if(source_team == 1)
1094                 steam = COLOR_TEAM1;
1095         else if(source_team == 2)
1096                 steam = COLOR_TEAM2;
1097         else if(source_team == 3)
1098                 steam = COLOR_TEAM3;
1099         else if(source_team == 4)
1100                 steam = COLOR_TEAM4;
1101
1102         lowest_bot = world;
1103         lowest_bot_score = 999999999;
1104         lowest_player = world;
1105         lowest_player_score = 999999999;
1106
1107         // find the lowest-scoring player & bot of that team
1108         FOR_EACH_PLAYER(head)
1109         {
1110                 if(head.team == steam)
1111                 {
1112                         if(head.isbot)
1113                         {
1114                                 if(head.totalfrags < lowest_bot_score)
1115                                 {
1116                                         lowest_bot = head;
1117                                         lowest_bot_score = head.totalfrags;
1118                                 }
1119                         }
1120                         else
1121                         {
1122                                 if(head.totalfrags < lowest_player_score)
1123                                 {
1124                                         lowest_player = head;
1125                                         lowest_player_score = head.totalfrags;
1126                                 }
1127                         }
1128                 }
1129         }
1130
1131         // prefers to move a bot...
1132         if(lowest_bot != world)
1133                 selected = lowest_bot;
1134         // but it will move a player if it has to
1135         else
1136                 selected = lowest_player;
1137         // don't do anything if it couldn't find anyone
1138         if(!selected)
1139         {
1140                 bprint("warning: couldn't find a player to move from team\n");
1141                 return;
1142         }
1143
1144         // smallest team gains a member
1145         if(smallestteam == 1)
1146         {
1147                 c1 = c1 + 1;
1148         }
1149         else if(smallestteam == 2)
1150         {
1151                 c2 = c2 + 1;
1152         }
1153         else if(smallestteam == 3)
1154         {
1155                 c3 = c3 + 1;
1156         }
1157         else if(smallestteam == 4)
1158         {
1159                 c4 = c4 + 1;
1160         }
1161         else
1162         {
1163                 bprint("warning: destination team invalid\n");
1164                 return;
1165         }
1166         // source team loses a member
1167         if(source_team == 1)
1168         {
1169                 c1 = c1 + 1;
1170         }
1171         else if(source_team == 2)
1172         {
1173                 c2 = c2 + 2;
1174         }
1175         else if(source_team == 3)
1176         {
1177                 c3 = c3 + 3;
1178         }
1179         else if(source_team == 4)
1180         {
1181                 c4 = c4 + 4;
1182         }
1183         else
1184         {
1185                 bprint("warning: source team invalid\n");
1186                 return;
1187         }
1188
1189         // move the player to the new team
1190         TeamchangeFrags(selected);
1191         SetPlayerTeam(selected, smallestteam, source_team, FALSE);
1192
1193         if(selected.deadflag == DEAD_NO)
1194                 Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE, selected.origin, '0 0 0');
1195         centerprint(selected, strcat("You have been moved into a different team to improve team balance\nYou are now on: ", ColoredTeamName(selected.team)));
1196 }
1197
1198 float lastRebalanceInfo;
1199 void CauseRebalance(float source_team, float howmany_toomany)
1200 {
1201         float steam;
1202         entity head;
1203
1204         if(IsTeamBalanceForced() == 1)
1205         {
1206                 bprint("Rebalancing Teams\n");
1207                 ShufflePlayerOutOfTeam(source_team);
1208         }
1209         else
1210         {
1211                 if(howmany_toomany < cvar("g_balance_teams_complain"))
1212                         return;
1213                 if(time < lastRebalanceInfo + 90)
1214                         return;
1215                 lastRebalanceInfo = time;
1216                 if(source_team == 1)
1217                         steam = COLOR_TEAM1;
1218                 else if(source_team == 2)
1219                         steam = COLOR_TEAM2;
1220                 else if(source_team == 3)
1221                         steam = COLOR_TEAM3;
1222                 else if(source_team == 4)
1223                         steam = COLOR_TEAM4;
1224                 print("Team ", ftos(source_team), " too large, complaining.\n");
1225                 FOR_EACH_REALPLAYER(head)
1226                 {
1227                         if(head.team == steam)
1228                         {
1229                                 sprint(head, "\{1}\{13}^3SERVER NOTICE:^7 One of you please change teams!\n");
1230                                 centerprint_atprio(head, CENTERPRIO_REBALANCE, "^3SERVER NOTICE:\n\n^7Someone of you please change teams!");
1231                         }
1232                 }
1233         }
1234 }
1235
1236 // part of g_balance_teams_force
1237 // occasionally perform an audit of the teams to make
1238 // sure they're more or less balanced in player count.
1239 void AuditTeams()
1240 {
1241         float numplayers, numteams, smallest, toomany;
1242         float balance;
1243         balance = IsTeamBalanceForced();
1244         if(balance == 0)
1245                 return;
1246
1247         if(audit_teams_time > time)
1248                 return;
1249
1250         audit_teams_time = time + 4 + random();
1251
1252 //      bprint("Auditing teams\n");
1253
1254         CheckAllowedTeams(world);
1255         GetTeamCounts(world);
1256
1257
1258         numteams = numplayers = smallest = 0;
1259         if(c1 >= 0)
1260         {
1261                 numteams = numteams + 1;
1262                 numplayers = numplayers + c1;
1263                 smallest = c1;
1264         }
1265         if(c2 >= 0)
1266         {
1267                 numteams = numteams + 1;
1268                 numplayers = numplayers + c2;
1269                 if(c2 < smallest)
1270                         smallest = c2;
1271         }
1272         if(c3 >= 0)
1273         {
1274                 numteams = numteams + 1;
1275                 numplayers = numplayers + c3;
1276                 if(c3 < smallest)
1277                         smallest = c3;
1278         }
1279         if(c4 >= 0)
1280         {
1281                 numteams = numteams + 1;
1282                 numplayers = numplayers + c4;
1283                 if(c4 < smallest)
1284                         smallest = c4;
1285         }
1286
1287         if(numplayers <= 0)
1288                 return; // no players to move around
1289         if(numteams < 2)
1290                 return; // don't bother shuffling if for some reason there aren't any teams
1291
1292         toomany = smallest + 1;
1293
1294         if(c1 && c1 > toomany)
1295                 CauseRebalance(1, c1 - toomany);
1296         if(c2 && c2 > toomany)
1297                 CauseRebalance(2, c2 - toomany);
1298         if(c3 && c3 > toomany)
1299                 CauseRebalance(3, c3 - toomany);
1300         if(c4 && c4 > toomany)
1301                 CauseRebalance(4, c4 - toomany);
1302
1303         // if teams are still unbalanced, balance them further in the next audit,
1304         // which will happen sooner (keep doing rapid audits until things are in order)
1305         audit_teams_time = time + 0.7 + random()*0.3;
1306 }
1307
1308
1309
1310 // code from here on is just to support maps that don't have team entities
1311 void tdm_spawnteam (string teamname, float teamcolor)
1312 {
1313         local entity e;
1314         e = spawn();
1315         e.classname = "tdm_team";
1316         e.netname = teamname;
1317         e.cnt = teamcolor;
1318         e.team = e.cnt + 1;
1319 };
1320
1321 // spawn some default teams if the map is not set up for tdm
1322 void tdm_spawnteams()
1323 {
1324         float numteams;
1325
1326         numteams = cvar("g_tdm_teams");
1327
1328         tdm_spawnteam("Red", COLOR_TEAM1-1);
1329         tdm_spawnteam("Blue", COLOR_TEAM2-1);
1330         if(numteams >= 3)
1331                 tdm_spawnteam("Yellow", COLOR_TEAM3-1);
1332         if(numteams >= 4)
1333                 tdm_spawnteam("Pink", COLOR_TEAM4-1);
1334 };
1335
1336 void tdm_delayedinit()
1337 {
1338         // if no teams are found, spawn defaults
1339         if (find(world, classname, "tdm_team") == world)
1340                 tdm_spawnteams();
1341 };
1342
1343 void tdm_init()
1344 {
1345         InitializeEntity(world, tdm_delayedinit, INITPRIO_GAMETYPE);
1346 };