]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/teamplay.qc
fix my accidental runematch breakage
[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
411         local string versionmessage;
412         versionmessage = GetClientVersionMessage();
413
414         s = strcat(s, NEWLINES, "This is Nexuiz ", cvar_string("g_nexuizversion"), "\n", versionmessage);
415         s = strcat(s, "^8\n\nmatch type is ^1", mutator, gamemode_name, "^8\n");
416
417         if(modifications != "")
418                 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
419
420         if(time < restart_countdown)
421                 s = strcat(s, "\n^1Game starts in ", ftos(restartAnnouncer.cnt + 1), " seconds^7");
422
423         if(timeoutStatus != 0)
424                 s = strcat(s, "\n\n", getTimeoutText(1));
425
426         if (g_grappling_hook)
427                 s = strcat(s, "\n\n^8grappling hook is enabled, press 'e' to use it\n");
428
429         if(cache_lastmutatormsg != cvar_string("g_mutatormsg"))
430         {
431                 if(cache_lastmutatormsg)
432                         strunzone(cache_lastmutatormsg);
433                 if(cache_mutatormsg)
434                         strunzone(cache_mutatormsg);
435                 cache_lastmutatormsg = strzone(cvar_string("g_mutatormsg"));
436                 cache_mutatormsg = strzone(wordwrap(cache_lastmutatormsg, 50));
437         }
438
439         if (cache_mutatormsg != "") {
440                 s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
441         }
442         
443         if(cache_lastmotd != cvar_string("sv_motd"))
444         {
445                 if(cache_lastmotd)
446                         strunzone(cache_lastmotd);
447                 if(cache_motd)
448                         strunzone(cache_motd);
449                 cache_lastmotd = strzone(cvar_string("sv_motd"));
450                 cache_motd = strzone(wordwrap(cache_lastmotd, 50));
451         }
452
453         if (cache_motd != "") {
454                 s = strcat(s, "\n\n^8MOTD: ^7", cache_motd);
455         }
456         s = strcat(s, "\n");
457
458         centerprint(pl, s);
459         //sprint(pl, s);
460 }
461
462
463 void SetPlayerColors(entity pl, float _color)
464 {
465         /*string s;
466         s = ftos(cl);
467         stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
468         pl.team = cl + 1;
469         //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
470         pl.clientcolors = 16*cl + cl;*/
471
472         float pants, shirt;
473         pants = _color & 0x0F;
474         shirt = _color & 0xF0;
475
476
477         if(teamplay) {
478                 setcolor(pl, 16*pants + pants);
479         } else {
480                 setcolor(pl, shirt + pants);
481         }
482 }
483
484 void SetPlayerTeam(entity pl, float t, float s, float noprint)
485 {
486         float _color;
487
488         if(t == 4)
489                 _color = COLOR_TEAM4 - 1;
490         else if(t == 3)
491                 _color = COLOR_TEAM3 - 1;
492         else if(t == 2)
493                 _color = COLOR_TEAM2 - 1;
494         else
495                 _color = COLOR_TEAM1 - 1;
496
497         SetPlayerColors(pl,_color);
498
499         if(!noprint && t != s)
500         {
501                 //bprint(pl.netname, " has changed to ", TeamNoName(t), "\n");
502                 bprint(pl.netname, "^7 has changed from ", TeamNoName(s), " to ", TeamNoName(t), "\n");
503         }
504
505         if(t != s)
506                 LogTeamchange(pl);
507 }
508
509
510
511
512
513
514 // set c1...c4 to show what teams are allowed
515 void CheckAllowedTeams (entity for_whom)
516 {
517         string teament_name;
518         float dm;
519         entity head;
520
521 //      if(!dom && !ctf)
522 //              dm = 1;
523
524         c1 = c2 = c3 = c4 = -1;
525         cb1 = cb2 = cb3 = cb4 = 0;
526
527         // onslaught is special
528         if(g_onslaught)
529         {
530                 head = findchain(classname, "onslaught_generator");
531                 while (head)
532                 {
533                         if (head.team == COLOR_TEAM1) c1 = 0;
534                         if (head.team == COLOR_TEAM2) c2 = 0;
535                         if (head.team == COLOR_TEAM3) c3 = 0;
536                         if (head.team == COLOR_TEAM4) c4 = 0;
537                         head = head.chain;
538                 }
539                 return;
540         }
541
542         if(g_domination)
543                 teament_name = "dom_team";
544         else if(g_ctf)
545                 teament_name = "ctf_team";
546         else if(g_tdm)
547                 teament_name = "tdm_team";
548         else if(g_assault)
549         {
550                 c1 = c2 = 0; // Assault always has 2 teams
551                 return;
552         }
553         else
554         {
555                 // cover anything else by treating it like tdm with no teams spawned
556                 if(g_keyhunt)
557                         dm = kh_teams;
558                 else if(g_race)
559                         dm = race_teams;
560                 else
561                         dm = cvar("g_tdm_teams");
562                 if(dm < 2)
563                         error("g_tdm_teams < 2, not enough teams to play team deathmatch\n");
564
565                 if(dm >= 4)
566                 {
567                         c1 = c2 = c3 = c4 = 0;
568                 }
569                 else if(dm >= 3)
570                 {
571                         c1 = c2 = c3 = 0;
572                 }
573                 else// if(dm >= 2)
574                 {
575                         c1 = c2 = 0;
576                 }
577                 return;
578         }
579
580         // first find out what teams are allowed
581         head = find(world, classname, teament_name);
582         while(head)
583         {
584                 if(!(g_domination && head.netname == ""))
585                 {
586                         if(head.team == COLOR_TEAM1)
587                         {
588                                 c1 = 0;
589                         }
590                         if(head.team == COLOR_TEAM2)
591                         {
592                                 c2 = 0;
593                         }
594                         if(head.team == COLOR_TEAM3)
595                         {
596                                 c3 = 0;
597                         }
598                         if(head.team == COLOR_TEAM4)
599                         {
600                                 c4 = 0;
601                         }
602                 }
603                 head = find(head, classname, teament_name);
604         }
605
606         if(for_whom)
607         {
608                 if(cvar("bot_vs_human") > 0)
609                 {
610                         // bots are all blue
611                         if(clienttype(for_whom) == CLIENTTYPE_BOT)
612                                 c1 = c3 = c4 = -1;
613                         else
614                                 c2 = -1;
615                 }
616                 else if(cvar("bot_vs_human") < 0)
617                 {
618                         // bots are all red
619                         if(clienttype(for_whom) == CLIENTTYPE_BOT)
620                                 c2 = c3 = c4 = -1;
621                         else
622                                 c1 = -1;
623                 }
624         }
625 }
626
627 float PlayerValue(entity p)
628 {
629         if(IsTeamBalanceForced() == 1)
630                 return 1;
631         return 1;
632 }
633
634 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
635 // teams that are allowed will now have their player counts stored in c1...c4
636 void GetTeamCounts(entity ignore)
637 {
638         entity head;
639         float value, bvalue;
640         // now count how many players are on each team already
641
642         // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
643         // also remember the lowest-scoring player
644
645         FOR_EACH_PLAYER(head)
646         {
647                 if(head != ignore)// && head.netname != "")
648                 {
649                         value = PlayerValue(head);
650                         if(clienttype(head) == CLIENTTYPE_BOT)
651                                 bvalue = value;
652                         else
653                                 bvalue = 0;
654                         if(head.team == COLOR_TEAM1)
655                         {
656                                 if(c1 >= 0)
657                                 {
658                                         c1 = c1 + value;
659                                         cb1 = cb1 + bvalue;
660                                 }
661                         }
662                         if(head.team == COLOR_TEAM2)
663                         {
664                                 if(c2 >= 0)
665                                 {
666                                         c2 = c2 + value;
667                                         cb2 = cb2 + bvalue;
668                                 }
669                         }
670                         if(head.team == COLOR_TEAM3)
671                         {
672                                 if(c3 >= 0)
673                                 {
674                                         c3 = c3 + value;
675                                         cb3 = cb3 + bvalue;
676                                 }
677                         }
678                         if(head.team == COLOR_TEAM4)
679                         {
680                                 if(c4 >= 0)
681                                 {
682                                         c4 = c4 + value;
683                                         cb4 = cb4 + bvalue;
684                                 }
685                         }
686                 }
687         }
688 }
689
690 // returns # of smallest team (1, 2, 3, 4)
691 // NOTE: Assumes CheckAllowedTeams has already been called!
692 float FindSmallestTeam(entity pl, float ignore_pl)
693 {
694         float totalteams, smallestteam, smallestteam_count, smallestteam_score, balance_type;
695         totalteams = 0;
696
697         // find out what teams are available
698         //CheckAllowedTeams();
699
700         // make sure there are at least 2 teams to join
701         if(c1 >= 0)
702                 totalteams = totalteams + 1;
703         if(c2 >= 0)
704                 totalteams = totalteams + 1;
705         if(c3 >= 0)
706                 totalteams = totalteams + 1;
707         if(c4 >= 0)
708                 totalteams = totalteams + 1;
709
710         if(cvar("bot_vs_human"))
711                 totalteams += 1;
712
713         if(totalteams <= 1)
714         {
715                 if(g_domination)
716                         error("Too few teams available for domination\n");
717                 else if(g_ctf)
718                         error("Too few teams available for ctf\n");
719                 else if(g_keyhunt)
720                         error("Too few teams available for key hunt\n");
721                 else
722                         error("Too few teams available for team deathmatch\n");
723         }
724
725         // count how many players are in each team
726         if(ignore_pl)
727                 GetTeamCounts(pl);
728         else
729                 GetTeamCounts(world);
730
731         // c1...c4 now have counts of each team
732         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
733
734         smallestteam = 0;
735         smallestteam_count = 999999999;
736         smallestteam_score = 999999999;
737
738         // 2 gives priority to what team you're already on, 1 goes in order
739         // 2 doesn't seem to work though...
740         balance_type = 1;
741
742         if(bots_would_leave)
743         //if(pl.classname != "player")
744         if(clienttype(pl) != CLIENTTYPE_BOT)
745         {
746                 c1 -= cb1 * 255.0/256;
747                 c2 -= cb2 * 255.0/256;
748                 c3 -= cb3 * 255.0/256;
749                 c4 -= cb4 * 255.0/256;
750         }
751
752         if(balance_type == 1)
753         {
754                 if(c1 >= 0 && (c1 < smallestteam_count || (c1 <= smallestteam_count && team1_score < smallestteam_score)))
755                 {
756                         smallestteam = 1;
757                         smallestteam_count = c1;
758                         smallestteam_score = team1_score;
759                 }
760                 if(c2 >= 0 && (c2 < smallestteam_count || (c2 <= smallestteam_count && team2_score < smallestteam_score)))
761                 {
762                         smallestteam = 2;
763                         smallestteam_count = c2;
764                         smallestteam_score = team2_score;
765                 }
766                 if(c3 >= 0 && (c3 < smallestteam_count || (c3 <= smallestteam_count && team3_score < smallestteam_score)))
767                 {
768                         smallestteam = 3;
769                         smallestteam_count = c3;
770                         smallestteam_score = team3_score;
771                 }
772                 if(c4 >= 0 && (c4 < smallestteam_count || (c4 <= smallestteam_count && team4_score < smallestteam_score)))
773                 {
774                         smallestteam = 4;
775                         smallestteam_count = c4;
776                         smallestteam_score = team4_score;
777                 }
778         }
779         else
780         {
781                 if(c1 >= 0 && (c1 < smallestteam_count ||
782                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
783                 {
784                         smallestteam = 1;
785                         smallestteam_count = c1;
786                 }
787                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
788                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
789                 {
790                         smallestteam = 2;
791                         smallestteam_count = c2;
792                 }
793                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
794                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
795                 {
796                         smallestteam = 3;
797                         smallestteam_count = c3;
798                 }
799                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
800                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
801                 {
802                         smallestteam = 4;
803                         smallestteam_count = c4;
804                 }
805         }
806
807         return smallestteam;
808 }
809
810 float JoinBestTeam(entity pl, float only_return_best, float forcebestteam)
811 {
812         float smallest, selectedteam;
813
814         // don't join a team if we're not playing a team game
815         if(!cvar("teamplay") && !g_domination && !g_ctf && !g_keyhunt)
816                 return 0;
817
818         // find out what teams are available
819         CheckAllowedTeams(pl);
820
821         if(g_domination)
822         {
823                 // <div0> WHY? TODO
824                 if(cvar("g_domination_default_teams") < 3)
825                         c3 = 999999999;
826                 if(cvar("g_domination_default_teams") < 4)
827                         c4 = 999999999;
828         }
829
830         // if we don't care what team he ends up on, put him on whatever team he entered as.
831         // if he's not on a valid team, then let other code put him on the smallest team
832         if(!forcebestteam)
833         {
834                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
835                         selectedteam = pl.team;
836                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
837                         selectedteam = pl.team;
838                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
839                         selectedteam = pl.team;
840                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
841                         selectedteam = pl.team;
842                 else
843                         selectedteam = -1;
844                 if(selectedteam > 0)
845                 {
846                         if(!only_return_best)
847                         {
848                                 SetPlayerColors(pl, selectedteam - 1);
849                                 LogTeamchange(pl);
850                         }
851                         return selectedteam;
852                 }
853                 // otherwise end up on the smallest team (handled below)
854         }
855
856         smallest = FindSmallestTeam(pl, TRUE);
857
858
859         if(!only_return_best)
860         {
861                 TeamchangeFrags(self);
862                 if(smallest == 1)
863                 {
864                         SetPlayerColors(pl, COLOR_TEAM1 - 1);
865                 }
866                 else if(smallest == 2)
867                 {
868                         SetPlayerColors(pl, COLOR_TEAM2 - 1);
869                 }
870                 else if(smallest == 3)
871                 {
872                         SetPlayerColors(pl, COLOR_TEAM3 - 1);
873                 }
874                 else if(smallest == 4)
875                 {
876                         SetPlayerColors(pl, COLOR_TEAM4 - 1);
877                 }
878                 else
879                 {
880                         error("smallest team: invalid team\n");
881                 }
882                 LogTeamchange(pl);
883                 if(pl.deadflag == DEAD_NO)
884                         Damage(pl, pl, pl, 100000, DEATH_TEAMCHANGE, pl.origin, '0 0 0');
885         }
886
887         return smallest;
888 }
889
890 //void() ctf_playerchanged;
891 void SV_ChangeTeam(float _color)
892 {
893         float scolor, dcolor, steam, dteam, dbotcount, scount, dcount;
894
895         // in normal deathmatch we can just apply the color and we're done
896         if(!cvar("teamplay")) {
897                 SetPlayerColors(self, _color);
898                 return;
899         }
900
901         scolor = self.clientcolors & 0x0F;
902         dcolor = _color & 0x0F;
903
904         if(scolor == COLOR_TEAM1 - 1)
905                 steam = 1;
906         else if(scolor == COLOR_TEAM2 - 1)
907                 steam = 2;
908         else if(scolor == COLOR_TEAM3 - 1)
909                 steam = 3;
910         else if(scolor == COLOR_TEAM4 - 1)
911                 steam = 4;
912         if(dcolor == COLOR_TEAM1 - 1)
913                 dteam = 1;
914         else if(dcolor == COLOR_TEAM2 - 1)
915                 dteam = 2;
916         else if(dcolor == COLOR_TEAM3 - 1)
917                 dteam = 3;
918         else if(dcolor == COLOR_TEAM4 - 1)
919                 dteam = 4;
920
921         CheckAllowedTeams(self);
922
923         if(dteam == 1 && c1 < 0) dteam = 4;
924         if(dteam == 4 && c4 < 0) dteam = 3;
925         if(dteam == 3 && c3 < 0) dteam = 2;
926         if(dteam == 2 && c2 < 0) dteam = 1;
927
928         // not changing teams
929         if(scolor == dcolor)
930         {
931                 //bprint("same team change\n");
932                 SetPlayerTeam(self, dteam, steam, TRUE);
933                 return;
934         }
935
936         if(cvar("teamplay"))
937         {
938                 if(cvar("g_campaign") || cvar("g_changeteam_banned"))
939                 {
940                         sprint(self, "Team changes not allowed\n");
941                         return; // changing teams is not allowed
942                 }
943
944                 if(!cvar("g_campaign") && cvar("g_balance_teams_prevent_imbalance"))
945                 {
946                         // only allow changing to a smaller or equal size team
947
948                         // find out what teams are available
949                         //CheckAllowedTeams();
950                         // count how many players on each team
951                         GetTeamCounts(world);
952
953                         // get desired team
954                         if(dteam == 1 && c1 >= 0)//dcolor == COLOR_TEAM1 - 1)
955                         {
956                                 dcount = c1;
957                                 dbotcount = cb1;
958                         }
959                         else if(dteam == 2 && c2 >= 0)//dcolor == COLOR_TEAM2 - 1)
960                         {
961                                 dcount = c2;
962                                 dbotcount = cb2;
963                         }
964                         else if(dteam == 3 && c3 >= 0)//dcolor == COLOR_TEAM3 - 1)
965                         {
966                                 dcount = c3;
967                                 dbotcount = cb3;
968                         }
969                         else if(dteam == 4 && c4 >= 0)//dcolor == COLOR_TEAM4 - 1)
970                         {
971                                 dcount = c4;
972                                 dbotcount = cb4;
973                         }
974                         else
975                         {
976                                 sprint(self, "Cannot change to an invalid team\n");
977
978                                 return;
979                         }
980
981                         // get starting team
982                         if(steam == 1)//scolor == COLOR_TEAM1 - 1)
983                                 scount = c1;
984                         else if(steam == 2)//scolor == COLOR_TEAM2 - 1)
985                                 scount = c2;
986                         else if(steam == 3)//scolor == COLOR_TEAM3 - 1)
987                                 scount = c3;
988                         else if(steam == 4)//scolor == COLOR_TEAM4 - 1)
989                                 scount = c4;
990
991                         if(scount) // started at a valid, nonempty team
992                         {
993                                 // check if we're trying to change to a larger team that doens't have bots to swap with
994                                 if(dcount >= scount && dbotcount <= 0)
995                                 {
996                                         sprint(self, "Cannot change to a larger team\n");
997                                         return; // can't change to a larger team
998                                 }
999                         }
1000                 }
1001         }
1002
1003 //      bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
1004
1005         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
1006         {
1007                 // reduce frags during a team change
1008                 TeamchangeFrags(self);
1009         }
1010
1011         SetPlayerTeam(self, dteam, steam, FALSE);
1012
1013         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
1014         {
1015                 // kill player when changing teams
1016                 if(self.deadflag == DEAD_NO)
1017                         Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
1018         }
1019         //ctf_playerchanged();
1020 }
1021
1022 void ShufflePlayerOutOfTeam (float source_team)
1023 {
1024         float smallestteam, smallestteam_count, steam;
1025         float lowest_bot_score, lowest_player_score;
1026         entity head, lowest_bot, lowest_player, selected;
1027
1028         smallestteam = 0;
1029         smallestteam_count = 999999999;
1030
1031         if(c1 >= 0 && c1 < smallestteam_count)
1032         {
1033                 smallestteam = 1;
1034                 smallestteam_count = c1;
1035         }
1036         if(c2 >= 0 && c2 < smallestteam_count)
1037         {
1038                 smallestteam = 2;
1039                 smallestteam_count = c2;
1040         }
1041         if(c3 >= 0 && c3 < smallestteam_count)
1042         {
1043                 smallestteam = 3;
1044                 smallestteam_count = c3;
1045         }
1046         if(c4 >= 0 && c4 < smallestteam_count)
1047         {
1048                 smallestteam = 4;
1049                 smallestteam_count = c4;
1050         }
1051
1052         if(!smallestteam)
1053         {
1054                 bprint("warning: no smallest team\n");
1055                 return;
1056         }
1057
1058         if(source_team == 1)
1059                 steam = COLOR_TEAM1;
1060         else if(source_team == 2)
1061                 steam = COLOR_TEAM2;
1062         else if(source_team == 3)
1063                 steam = COLOR_TEAM3;
1064         else if(source_team == 4)
1065                 steam = COLOR_TEAM4;
1066
1067         lowest_bot = world;
1068         lowest_bot_score = 999999999;
1069         lowest_player = world;
1070         lowest_player_score = 999999999;
1071
1072         // find the lowest-scoring player & bot of that team
1073         FOR_EACH_PLAYER(head)
1074         {
1075                 if(head.team == steam)
1076                 {
1077                         if(head.isbot)
1078                         {
1079                                 if(head.totalfrags < lowest_bot_score)
1080                                 {
1081                                         lowest_bot = head;
1082                                         lowest_bot_score = head.totalfrags;
1083                                 }
1084                         }
1085                         else
1086                         {
1087                                 if(head.totalfrags < lowest_player_score)
1088                                 {
1089                                         lowest_player = head;
1090                                         lowest_player_score = head.totalfrags;
1091                                 }
1092                         }
1093                 }
1094         }
1095
1096         // prefers to move a bot...
1097         if(lowest_bot != world)
1098                 selected = lowest_bot;
1099         // but it will move a player if it has to
1100         else
1101                 selected = lowest_player;
1102         // don't do anything if it couldn't find anyone
1103         if(!selected)
1104         {
1105                 bprint("warning: couldn't find a player to move from team\n");
1106                 return;
1107         }
1108
1109         // smallest team gains a member
1110         if(smallestteam == 1)
1111         {
1112                 c1 = c1 + 1;
1113         }
1114         else if(smallestteam == 2)
1115         {
1116                 c2 = c2 + 1;
1117         }
1118         else if(smallestteam == 3)
1119         {
1120                 c3 = c3 + 1;
1121         }
1122         else if(smallestteam == 4)
1123         {
1124                 c4 = c4 + 1;
1125         }
1126         else
1127         {
1128                 bprint("warning: destination team invalid\n");
1129                 return;
1130         }
1131         // source team loses a member
1132         if(source_team == 1)
1133         {
1134                 c1 = c1 + 1;
1135         }
1136         else if(source_team == 2)
1137         {
1138                 c2 = c2 + 2;
1139         }
1140         else if(source_team == 3)
1141         {
1142                 c3 = c3 + 3;
1143         }
1144         else if(source_team == 4)
1145         {
1146                 c4 = c4 + 4;
1147         }
1148         else
1149         {
1150                 bprint("warning: source team invalid\n");
1151                 return;
1152         }
1153
1154         // move the player to the new team
1155         TeamchangeFrags(selected);
1156         SetPlayerTeam(selected, smallestteam, source_team, FALSE);
1157
1158         if(selected.deadflag == DEAD_NO)
1159                 Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE, selected.origin, '0 0 0');
1160         centerprint(selected, strcat("You have been moved into a different team to improve team balance\nYou are now on: ", ColoredTeamName(selected.team)));
1161 }
1162
1163 float lastRebalanceInfo;
1164 void CauseRebalance(float source_team, float howmany_toomany)
1165 {
1166         float steam;
1167         entity head;
1168
1169         if(IsTeamBalanceForced() == 1)
1170         {
1171                 bprint("Rebalancing Teams\n");
1172                 ShufflePlayerOutOfTeam(source_team);
1173         }
1174         else
1175         {
1176                 if(howmany_toomany < cvar("g_balance_teams_complain"))
1177                         return;
1178                 if(time < lastRebalanceInfo + 90)
1179                         return;
1180                 lastRebalanceInfo = time;
1181                 if(source_team == 1)
1182                         steam = COLOR_TEAM1;
1183                 else if(source_team == 2)
1184                         steam = COLOR_TEAM2;
1185                 else if(source_team == 3)
1186                         steam = COLOR_TEAM3;
1187                 else if(source_team == 4)
1188                         steam = COLOR_TEAM4;
1189                 print("Team ", ftos(source_team), " too large, complaining.\n");
1190                 FOR_EACH_REALPLAYER(head)
1191                 {
1192                         if(head.team == steam)
1193                         {
1194                                 sprint(head, "\{1}\{13}^3SERVER NOTICE:^7 One of you please change teams!\n");
1195                                 centerprint_atprio(head, CENTERPRIO_REBALANCE, "^3SERVER NOTICE:\n\n^7Someone of you please change teams!");
1196                         }
1197                 }
1198         }
1199 }
1200
1201 // part of g_balance_teams_force
1202 // occasionally perform an audit of the teams to make
1203 // sure they're more or less balanced in player count.
1204 void AuditTeams()
1205 {
1206         float numplayers, numteams, smallest, toomany;
1207         float balance;
1208         balance = IsTeamBalanceForced();
1209         if(balance == 0)
1210                 return;
1211
1212         if(audit_teams_time > time)
1213                 return;
1214
1215         audit_teams_time = time + 4 + random();
1216
1217 //      bprint("Auditing teams\n");
1218
1219         CheckAllowedTeams(world);
1220         GetTeamCounts(world);
1221
1222
1223         numteams = numplayers = smallest = 0;
1224         if(c1 >= 0)
1225         {
1226                 numteams = numteams + 1;
1227                 numplayers = numplayers + c1;
1228                 smallest = c1;
1229         }
1230         if(c2 >= 0)
1231         {
1232                 numteams = numteams + 1;
1233                 numplayers = numplayers + c2;
1234                 if(c2 < smallest)
1235                         smallest = c2;
1236         }
1237         if(c3 >= 0)
1238         {
1239                 numteams = numteams + 1;
1240                 numplayers = numplayers + c3;
1241                 if(c3 < smallest)
1242                         smallest = c3;
1243         }
1244         if(c4 >= 0)
1245         {
1246                 numteams = numteams + 1;
1247                 numplayers = numplayers + c4;
1248                 if(c4 < smallest)
1249                         smallest = c4;
1250         }
1251
1252         if(numplayers <= 0)
1253                 return; // no players to move around
1254         if(numteams < 2)
1255                 return; // don't bother shuffling if for some reason there aren't any teams
1256
1257         toomany = smallest + 1;
1258
1259         if(c1 && c1 > toomany)
1260                 CauseRebalance(1, c1 - toomany);
1261         if(c2 && c2 > toomany)
1262                 CauseRebalance(2, c2 - toomany);
1263         if(c3 && c3 > toomany)
1264                 CauseRebalance(3, c3 - toomany);
1265         if(c4 && c4 > toomany)
1266                 CauseRebalance(4, c4 - toomany);
1267
1268         // if teams are still unbalanced, balance them further in the next audit,
1269         // which will happen sooner (keep doing rapid audits until things are in order)
1270         audit_teams_time = time + 0.7 + random()*0.3;
1271 }
1272
1273
1274
1275 // code from here on is just to support maps that don't have team entities
1276 void tdm_spawnteam (string teamname, float teamcolor)
1277 {
1278         local entity e;
1279         e = spawn();
1280         e.classname = "tdm_team";
1281         e.netname = teamname;
1282         e.cnt = teamcolor;
1283         e.team = e.cnt + 1;
1284 };
1285
1286 // spawn some default teams if the map is not set up for tdm
1287 void tdm_spawnteams()
1288 {
1289         float numteams;
1290
1291         numteams = cvar("g_tdm_teams");
1292
1293         tdm_spawnteam("Red", COLOR_TEAM1-1);
1294         tdm_spawnteam("Blue", COLOR_TEAM2-1);
1295         if(numteams >= 3)
1296                 tdm_spawnteam("Yellow", COLOR_TEAM3-1);
1297         if(numteams >= 4)
1298                 tdm_spawnteam("Pink", COLOR_TEAM4-1);
1299 };
1300
1301 void tdm_delayedinit()
1302 {
1303         // if no teams are found, spawn defaults
1304         if (find(world, classname, "tdm_team") == world)
1305                 tdm_spawnteams();
1306 };
1307
1308 void tdm_init()
1309 {
1310         InitializeEntity(world, tdm_delayedinit, INITPRIO_GAMETYPE);
1311 };