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