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