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