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