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