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