]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/gamec/teamplay.c
more elegant checking to see if stuffcmds are safe
[divverent/nexuiz.git] / data / qcsrc / gamec / teamplay.c
1 float COLOR_TEAM1       = 5;  // red
2 float COLOR_TEAM2       = 14; // blue
3 float COLOR_TEAM3       = 10; // pink
4 float COLOR_TEAM4       = 13; // yellow
5
6
7 float GAME_DEATHMATCH           = 1;
8 float GAME_TEAM_DEATHMATCH      = 2;
9 float GAME_DOMINATION           = 3;
10 float GAME_CTF                  = 4;
11 float GAME_RUNEMATCH            = 5;
12 float GAME_LMS                  = 6;
13
14
15 // client counts for each team
16 float c1, c2, c3, c4;
17 // # of bots on those teams
18 float cb1, cb2, cb3, cb4;
19
20 float g_domination, g_ctf, g_tdm;
21
22 float audit_teams_time;
23
24
25 string TeamName(float t)
26 {
27         // fixme: Search for team entities and get their .netname's!
28         if(t == COLOR_TEAM1)
29                 return "Red Team";
30         if(t == COLOR_TEAM2)
31                 return "Blue Team";
32         if(t == COLOR_TEAM3)
33                 return "Pink Team";
34         if(t == COLOR_TEAM4)
35                 return "Yellow Team";
36         return "Neutral Team";
37 }
38 string ColoredTeamName(float t)
39 {
40         // fixme: Search for team entities and get their .netname's!
41         if(t == COLOR_TEAM1)
42                 return "^1Red Team^7";
43         if(t == COLOR_TEAM2)
44                 return "^4Blue Team^7";
45         if(t == COLOR_TEAM3)
46                 return "^6Pink Team^7";
47         if(t == COLOR_TEAM4)
48                 return "^3Yellow Team^7";
49         return "Neutral Team";
50 }
51 string TeamNoName(float t)
52 {
53         // fixme: Search for team entities and get their .netname's!
54         if(t == 1)
55                 return "Red Team";
56         if(t == 2)
57                 return "Blue Team";
58         if(t == 3)
59                 return "Pink Team";
60         if(t == 4)
61                 return "Yellow Team";
62         return "Neutral Team";
63 }
64
65 void dom_init();
66 void ctf_init();
67 void runematch_init();
68 void tdm_init();
69
70
71 void ResetGameCvars()
72 {
73         cvar_set("g_tdm", "0");
74         cvar_set("g_domination", "0");
75         cvar_set("g_ctf", "0");
76         cvar_set("g_runematch", "0");
77         cvar_set("g_lms", "0");
78         cvar_set("teamplay", "0");
79
80
81         cvar_set("exit_cfg", "");
82 }
83
84 void ActivateTeamplay()
85 {
86         float teamplay_default;
87         teamplay_default = cvar("teamplay_default");
88
89         if(teamplay_default)
90                 cvar_set("teamplay", ftos(teamplay_default));
91         else
92                 cvar_set("teamplay", "3");
93 }
94
95 string gamemode_name;
96 float teams_matter;
97
98 void InitGameplayMode()
99 {
100         float fraglimit_override, timelimit_override;
101
102         game = cvar ("gamecfg");        // load game options
103
104         // game cvars get reset before map changes
105         // then map's cfg sets them as desired
106
107         // FIXME: also set a message or game mode name to print to players when the join
108
109         // set both here, gamemode can override it later
110         timelimit_override = cvar("timelimit_override");
111         fraglimit_override = cvar("fraglimit_override");
112
113         if(game == GAME_DOMINATION || cvar("g_domination"))
114         {
115                 game = GAME_DOMINATION;
116                 cvar_set("g_domination", "1");
117
118                 ActivateTeamplay();
119
120                 fraglimit_override = cvar("g_domination_point_limit");
121
122                 gamemode_name = "Domination";
123                 teams_matter = 1;
124         }
125         else if(game == GAME_CTF || cvar("g_ctf"))
126         {
127                 game = GAME_CTF;
128                 cvar_set("g_ctf", "1");
129
130                 ActivateTeamplay();
131
132                 fraglimit_override = cvar("g_ctf_capture_limit");
133
134                 gamemode_name = "Capture the Flag";
135                 teams_matter = 1;
136         }
137         else if((game == GAME_RUNEMATCH || cvar("g_runematch")) && !cvar("g_minstagib"))
138         {
139                 game = GAME_RUNEMATCH;
140                 cvar_set("g_runematch", "1");
141
142                 if(cvar("deathmatch_force_teamplay"))
143                         ActivateTeamplay();
144
145                 fraglimit_override = cvar("g_runematch_point_limit");
146
147                 gamemode_name = "Rune Match";
148                 if(cvar("teamplay"))
149                         teams_matter = 1;
150                 else
151                         teams_matter = 0;
152         }
153         else if(game == GAME_DEATHMATCH || game == GAME_TEAM_DEATHMATCH || cvar("g_tdm"))
154         {
155                 if(!cvar("deathmatch"))
156                         cvar_set("deathmatch", "1");
157
158
159                 if(game == GAME_TEAM_DEATHMATCH || cvar("g_tdm") || cvar("deathmatch_force_teamplay"))
160                 {
161                         game = GAME_TEAM_DEATHMATCH;
162                         gamemode_name = "Team Deathmatch";
163                         ActivateTeamplay();
164                         teams_matter = 1;
165                 }
166                 else
167                 {
168                         game = GAME_DEATHMATCH;
169                         gamemode_name = "Deathmatch";
170                         teams_matter = 0;
171                 }
172
173                 fraglimit_override = cvar("fraglimit_override");
174         }
175         else if(game == GAME_LMS || cvar("g_lms"))
176         {
177                 game = GAME_LMS;
178                 cvar_set("g_lms", "1");
179                 fraglimit_override = cvar("fraglimit_override");
180                 gamemode_name = "Last Man Standing";
181                 teams_matter = 0;
182                 cvar_set("teamplay", "0");
183                 lms_lowest_lives = 999;
184         }
185         else
186         {
187                 // we can only assume...
188                 gamemode_name = "Deathmatch";
189                 teams_matter = 0;
190         }
191 /*      else if(game == GAME_TEAM_DEATHMATCH)
192         {
193                 if(!cvar("deathmatch"))
194                         cvar_set("deathmatch", "1");
195
196                 //if(!cvar("teamplay"))
197                 //      cvar_set("teamplay", "3");
198                 ActivateTeamplay();
199
200                 fraglimit_override = cvar("fraglimit_override");
201         }*/
202
203         // enforce the server's universal frag/time limits
204         if(fraglimit_override >= 0)
205                 cvar_set("fraglimit", ftos(fraglimit_override));
206         if(timelimit_override >= 0)
207                 cvar_set("timelimit", ftos(timelimit_override));
208
209         if (game == GAME_DOMINATION)//cvar("g_domination"))
210                 dom_init();
211         else if (game == GAME_CTF)//cvar("g_ctf"))
212                 ctf_init();
213         else if (game == GAME_RUNEMATCH)//cvar("g_runematch"))
214                 runematch_init();
215         else if (game == GAME_TEAM_DEATHMATCH)//cvar("g_runematch"))
216                 tdm_init();
217
218         // those mutators rule each other out
219         if(cvar("g_minstagib"))
220         {
221                 cvar_set("g_instagib", "0");
222                 cvar_set("g_rocketarena", "0");
223         }
224         if(cvar("g_instagib"))
225         {
226                 cvar_set("g_minstagib", "0");
227                 cvar_set("g_rocketarena", "0");
228         }
229         if(cvar("g_rocketarena"))
230         {
231                 cvar_set("g_instagib", "0");
232                 cvar_set("g_minstagib", "0");
233         }
234 }
235
236 void PrintWelcomeMessage(entity pl)
237 {
238         string s, grap_msg, temp, mutator;
239
240         /*if(self.welcomemessage_time < time)
241                 return;
242         if(self.welcomemessage_time2 > time)
243                 return;
244         self.welcomemessage_time2 = time + 0.8; */
245
246         if(self.welcomemessage_time2 > time) return;
247         self.welcomemessage_time2 = time + 1.0;
248
249         if(cvar("g_minstagib"))
250                 mutator = "^2Minstagib ^1";
251         else if(cvar("g_instagib"))
252                 mutator = "^2Instagib ^1";
253         else if(cvar("g_rocketarena"))
254                 mutator = "^2Rocketarena ^1";
255         else
256                 mutator = "";
257
258         if(cvar("g_grappling_hook"))
259                 grap_msg = strzone("\n\nBind a key to ^1+hook^8 to use the grappling hook\n");
260
261         s = strcat(s, "\n\nThis is Nexuiz ", cvar_string("g_nexuizversion"), "\n", self.versionmessage);
262         s = strcat(s, "^8\n\nMatch type is ^1", mutator, gamemode_name, "^8\n");
263
264         if(self.classname == "observer" || self.classname == "spectator") {
265                 s = strcat(s,"^7\n\n\npress jump to play\npress attack to spectate other players\n\n");
266         }
267
268
269         s = strzone(s);
270
271         temp = strcat(
272                 "\n\n\n^8Welcome, ", pl.netname, "^8\n",
273                 s
274                 );
275         temp = strzone(temp);
276
277
278         if (cvar_string("g_mutatormsg") != "") {
279                 s = strcat(s, "\n\nSpecial gameplay tips: ", cvar_string("g_mutatormsg"));
280         }
281
282         if (cvar_string("sv_motd") != "") {
283                 s = strcat(s, "\n\nMOTD: ", cvar_string("sv_motd"));
284         }
285
286         centerprint(pl, s);
287         //sprint(pl, s);
288
289         strunzone(temp);
290 }
291
292
293 void SetPlayerColors(entity pl, float _color)
294 {
295         /*string s;
296         s = ftos(cl);
297         stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
298         pl.team = cl + 1;
299         //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
300         pl.clientcolors = 16*cl + cl;*/
301
302         float pants, shirt; 
303         pants = _color & 0x0F;
304         shirt = _color & 0xF0;
305         
306         
307         if(teamplay) {
308                 setcolor(pl, 16*pants + pants);
309         } else {
310                 setcolor(pl, shirt + pants);
311         }
312 }
313
314 void SetPlayerTeam(entity pl, float t, float s, float noprint)
315 {
316         float _color;
317         
318         if(t == 4)
319                 _color = COLOR_TEAM4 - 1;
320         else if(t == 3)
321                 _color = COLOR_TEAM3 - 1;
322         else if(t == 2)
323                 _color = COLOR_TEAM2 - 1;
324         else
325                 _color = COLOR_TEAM1 - 1;
326
327         SetPlayerColors(pl,_color);
328
329         if(!noprint && t != s)
330         {
331                 //bprint(strcat(pl.netname, " has changed to ", TeamNoName(t), "\n"));
332                 bprint(strcat(pl.netname, "^7 has changed from ", TeamNoName(s), " to ", TeamNoName(t), "\n"));
333         }
334 }
335
336
337
338
339
340
341 // set c1...c4 to show what teams are allowed
342 void CheckAllowedTeams ()
343 {
344         string teament_name;
345         float dm;
346         entity head;
347
348 //      if(!dom && !ctf)
349 //              dm = 1;
350
351         c1 = c2 = c3 = c4 = -1;
352         cb1 = cb2 = cb3 = cb4 = 0;
353
354         if(g_domination)
355                 teament_name = "dom_team";
356         else if(g_ctf)
357                 teament_name = "ctf_team";
358         else if(g_tdm)
359                 teament_name = "tdm_team";
360         else
361         {
362                 // cover anything else by treating it like tdm with no teams spawned
363                 dm = cvar("g_tdm_teams");
364                 if(dm < 2)
365                         error("g_tdm_teams < 2, not enough teams to play team deathmatch\n");
366
367                 if(dm >= 4)
368                 {
369                         c1 = c2 = c3 = c4 = 0;
370                 }
371                 else if(dm >= 3)
372                 {
373                         c1 = c2 = c3 = 0;
374                 }
375                 else// if(dm >= 2)
376                 {
377                         c1 = c2 = 0;
378                 }
379                 return;
380         }
381
382         // first find out what teams are allowed
383         head = find(world, classname, teament_name);
384         while(head)
385         {
386                 if(!(g_domination && head.netname == ""))
387                 {
388                         if(head.team == COLOR_TEAM1)
389                         {
390                                 c1 = 0;
391                         }
392                         if(head.team == COLOR_TEAM2)
393                         {
394                                 c2 = 0;
395                         }
396                         if(head.team == COLOR_TEAM3)
397                         {
398                                 c3 = 0;
399                         }
400                         if(head.team == COLOR_TEAM4)
401                         {
402                                 c4 = 0;
403                         }
404                 }
405                 head = find(head, classname, teament_name);
406         }
407 }
408
409 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
410 // teams that are allowed will now have their player counts stored in c1...c4
411 void GetTeamCounts(entity ignore)
412 {
413         entity head;
414         // now count how many players are on each team already
415
416         // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
417         // also remember the lowest-scoring player
418
419         head = find(world, classname, "player");
420         while(head)
421         {
422                 if(head != ignore)// && head.netname != "")
423                 {
424                         if(head.team == COLOR_TEAM1)
425                         {
426                                 if(c1 >= 0)
427                                 {
428                                         c1 = c1 + 1;
429                                         cb1 = cb1 + 1;
430                                 }
431                         }
432                         if(head.team == COLOR_TEAM2)
433                         {
434                                 if(c2 >= 0)
435                                 {
436                                         c2 = c2 + 1;
437                                         cb2 = cb2 + 1;
438                                 }
439                         }
440                         if(head.team == COLOR_TEAM3)
441                         {
442                                 if(c3 >= 0)
443                                 {
444                                         c3 = c3 + 1;
445                                         cb3 = cb3 + 1;
446                                 }
447                         }
448                         if(head.team == COLOR_TEAM4)
449                         {
450                                 if(c4 >= 0)
451                                 {
452                                         c4 = c4 + 1;
453                                         cb4 = cb4 + 1;
454                                 }
455                         }
456                 }
457                 head = find(head, classname, "player");
458         }
459 }
460
461 // returns # of smallest team (1, 2, 3, 4)
462 // NOTE: Assumes CheckAllowedTeams has already been called!
463 float FindSmallestTeam(entity pl, float ignore_pl)
464 {
465         float totalteams, smallestteam, smallestteam_count, balance_type;
466         totalteams = 0;
467
468         // find out what teams are available
469         //CheckAllowedTeams();
470
471         // make sure there are at least 2 teams to join
472         if(c1 >= 0)
473                 totalteams = totalteams + 1;
474         if(c2 >= 0)
475                 totalteams = totalteams + 1;
476         if(c3 >= 0)
477                 totalteams = totalteams + 1;
478         if(c4 >= 0)
479                 totalteams = totalteams + 1;
480
481         if(totalteams <= 1)
482         {
483                 if(g_domination)
484                         error("Too few teams available for domination\n");
485                 else if(g_ctf)
486                         error("Too few teams available for ctf\n");
487                 else
488                         error("Too few teams available for team deathmatch\n");
489         }
490
491
492         // count how many players are in each team
493         if(ignore_pl)
494                 GetTeamCounts(world);
495         else
496                 GetTeamCounts(pl);
497
498
499
500         // c1...c4 now have counts of each team
501         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
502
503         smallestteam = 0;
504         smallestteam_count = 999;
505
506         // 2 gives priority to what team you're already on, 1 goes in order
507         // 2 doesn't seem to work though...
508         balance_type = 1;
509
510         if(balance_type == 1)
511         {
512                 if(c1 >= 0 && c1 < smallestteam_count)
513                 {
514                         smallestteam = 1;
515                         smallestteam_count = c1;
516                 }
517                 if(c2 >= 0 && c2 < smallestteam_count)
518                 {
519                         smallestteam = 2;
520                         smallestteam_count = c2;
521                 }
522                 if(c3 >= 0 && c3 < smallestteam_count)
523                 {
524                         smallestteam = 3;
525                         smallestteam_count = c3;
526                 }
527                 if(c4 >= 0 && c4 < smallestteam_count)
528                 {
529                         smallestteam = 4;
530                         smallestteam_count = c4;
531                 }
532         }
533         else
534         {
535                 if(c1 >= 0 && (c1 < smallestteam_count ||
536                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
537                 {
538                         smallestteam = 1;
539                         smallestteam_count = c1;
540                 }
541                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
542                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
543                 {
544                         smallestteam = 2;
545                         smallestteam_count = c2;
546                 }
547                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
548                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
549                 {
550                         smallestteam = 3;
551                         smallestteam_count = c3;
552                 }
553                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
554                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
555                 {
556                         smallestteam = 4;
557                         smallestteam_count = c4;
558                 }
559         }
560
561         return smallestteam;
562 }
563
564 float JoinBestTeam(entity pl, float only_return_best)
565 {
566         float smallest, selectedteam;
567
568         g_domination = cvar("g_domination");
569         g_ctf = cvar("g_ctf");
570
571         // don't join a team if we're not playing a team game
572         if(!cvar("teamplay") && !g_domination && !g_ctf)
573                 return 0;
574
575         // find out what teams are available
576         CheckAllowedTeams();
577
578         // if we don't care what team he ends up on, put him on whatever team he entered as.
579         // if he's not on a valid team, then let other code put him on the smallest team
580         if(!cvar("balance_teams") && !cvar("force_balance"))
581         {
582                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
583                         selectedteam = pl.team;
584                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
585                         selectedteam = pl.team;
586                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
587                         selectedteam = pl.team;
588                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
589                         selectedteam = pl.team;
590                 else
591                         selectedteam = -1;
592                 if(selectedteam > 0)
593                 {
594                         if(!only_return_best)
595                                 SetPlayerColors(pl, selectedteam - 1);
596                         return selectedteam;
597                 }
598                 // otherwise end up on the smallest team (handled below)
599         }
600
601         smallest = FindSmallestTeam(pl, TRUE);
602
603
604         if(!only_return_best)
605         {
606                 if(smallest == 1)
607                 {
608                         SetPlayerColors(pl, COLOR_TEAM1 - 1);
609                 }
610                 else if(smallest == 2)
611                 {
612                         SetPlayerColors(pl, COLOR_TEAM2 - 1);
613                 }
614                 else if(smallest == 3)
615                 {
616                         SetPlayerColors(pl, COLOR_TEAM3 - 1);
617                 }
618                 else if(smallest == 4)
619                 {
620                         SetPlayerColors(pl, COLOR_TEAM4 - 1);
621                 }
622                 else
623                 {
624                         error("smallest team: invalid team\n");
625                 }
626         }
627
628         return smallest;
629 }
630
631
632 void SV_ChangeTeam(float _color)
633 {
634         float scolor, dcolor, steam, dteam, dbotcount, scount, dcount;
635
636         // in normal deathmatch we can just apply the color and we're done
637         if(!cvar("teamplay")) {
638                 SetPlayerColors(self, _color);
639                 return;
640         }
641         
642         scolor = self.clientcolors & 0x0F;
643         dcolor = _color & 0x0F;
644
645         if(scolor == COLOR_TEAM1 - 1)
646                 steam = 1;
647         else if(scolor == COLOR_TEAM2 - 1)
648                 steam = 2;
649         else if(scolor == COLOR_TEAM3 - 1)
650                 steam = 3;
651         else if(scolor == COLOR_TEAM4 - 1)
652                 steam = 4;
653         if(dcolor == COLOR_TEAM1 - 1)
654                 dteam = 1;
655         else if(dcolor == COLOR_TEAM2 - 1)
656                 dteam = 2;
657         else if(dcolor == COLOR_TEAM3 - 1)
658                 dteam = 3;
659         else if(dcolor == COLOR_TEAM4 - 1)
660                 dteam = 4;
661
662         // remap invalid teams in dom & ctf
663         if(cvar("g_ctf") && dteam == 3)
664                 dteam = 2;
665         else if(cvar("g_ctf") && dteam == 4)
666                 dteam = 1;
667         else if(cvar("g_domination") && cvar("g_domination_default_teams") < 3)
668         {
669                 if(dteam == 3)
670                         dteam = 2;
671                 else if(dteam == 4)
672                         dteam = 1;
673         }
674         else if(cvar("g_domination") && cvar("g_domination_default_teams") < 4)
675         {
676                 if(dteam == 4)
677                         dteam = 1;
678         }
679         
680         // not changing teams
681         if(scolor == dcolor)
682         {
683                 //bprint("same team change\n");
684                 SetPlayerTeam(self, dteam, steam, TRUE);
685                 return;
686         }
687
688         if(cvar("teamplay"))
689         {
690                 if(self.classname == "player" && steam != dteam)
691                 {
692                         // kill player when changing teams
693                         if(self.deadflag == DEAD_NO)
694                                 self.event_damage(self, self, 10000, DEATH_KILL, self.origin, '0 0 0');
695                 
696                         // reduce frags during a team change
697                         self.frags = floor(self.frags * (cvar("g_changeteam_fragtransfer") / 100));
698                 }
699                 
700                 if(cvar("g_changeteam_banned"))
701                 {
702                         sprint(self, "Team changes not allowed\n");
703                         return; // changing teams is not allowed
704                 }
705
706                 if(cvar("g_balance_teams_prevent_imbalance"))
707                 {
708                         // only allow changing to a smaller or equal size team
709
710                         // find out what teams are available
711                         CheckAllowedTeams();
712                         // count how many players on each team
713                         GetTeamCounts(world);
714
715                         // get desired team
716                         if(dteam == 1 && c1 >= 0)//dcolor == COLOR_TEAM1 - 1)
717                         {
718                                 dcount = c1;
719                                 dbotcount = cb1;
720                         }
721                         else if(dteam == 2 && c2 >= 0)//dcolor == COLOR_TEAM2 - 1)
722                         {
723                                 dcount = c2;
724                                 dbotcount = cb2;
725                         }
726                         else if(dteam == 3 && c3 >= 0)//dcolor == COLOR_TEAM3 - 1)
727                         {
728                                 dcount = c3;
729                                 dbotcount = cb3;
730                         }
731                         else if(dteam == 4 && c4 >= 0)//dcolor == COLOR_TEAM4 - 1)
732                         {
733                                 dcount = c4;
734                                 dbotcount = cb4;
735                         }
736                         else
737                         {
738                                 sprint(self, "Cannot change to an invalid team\n");
739
740                                 return;
741                         }
742
743                         // get starting team
744                         if(steam == 1)//scolor == COLOR_TEAM1 - 1)
745                                 scount = c1;
746                         else if(steam == 2)//scolor == COLOR_TEAM2 - 1)
747                                 scount = c2;
748                         else if(steam == 3)//scolor == COLOR_TEAM3 - 1)
749                                 scount = c3;
750                         else if(steam == 4)//scolor == COLOR_TEAM4 - 1)
751                                 scount = c4;
752
753                         if(scount) // started at a valid, nonempty team
754                         {
755                                 // check if we're trying to change to a larger team that doens't have bots to swap with
756                                 if(dcount >= scount && dbotcount <= 0)
757                                 {
758                                         sprint(self, "Cannot change to a larger team\n");
759                                         return; // can't change to a larger team
760                                 }
761                         }
762                 }
763         }
764
765 //      bprint(strcat("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n"));
766
767         SetPlayerTeam(self, dteam, steam, FALSE);
768 }
769
770
771 void ShufflePlayerOutOfTeam (float source_team)
772 {
773         float smallestteam, smallestteam_count, steam;
774         float lowest_bot_score, lowest_player_score;
775         entity head, lowest_bot, lowest_player, selected;
776
777         smallestteam = 0;
778         smallestteam_count = 999;
779
780         if(c1 >= 0 && c1 < smallestteam_count)
781         {
782                 smallestteam = 1;
783                 smallestteam_count = c1;
784         }
785         if(c2 >= 0 && c2 < smallestteam_count)
786         {
787                 smallestteam = 2;
788                 smallestteam_count = c2;
789         }
790         if(c3 >= 0 && c3 < smallestteam_count)
791         {
792                 smallestteam = 3;
793                 smallestteam_count = c3;
794         }
795         if(c4 >= 0 && c4 < smallestteam_count)
796         {
797                 smallestteam = 4;
798                 smallestteam_count = c4;
799         }
800
801         if(!smallestteam)
802         {
803                 bprint("warning: no smallest team\n");
804                 return;
805         }
806
807         if(source_team == 1)
808                 steam = COLOR_TEAM1;
809         else if(source_team == 2)
810                 steam = COLOR_TEAM2;
811         else if(source_team == 3)
812                 steam = COLOR_TEAM3;
813         else if(source_team == 4)
814                 steam = COLOR_TEAM4;
815
816         lowest_bot = world;
817         lowest_bot_score = 9999;
818         lowest_player = world;
819         lowest_player_score = 9999;
820
821         // find the lowest-scoring player & bot of that team
822         head = find(world, classname, "player");
823         while(head)
824         {
825                 if(head.team == steam)
826                 {
827                         if(head.isbot)
828                         {
829                                 if(head.frags < lowest_bot_score)
830                                 {
831                                         lowest_bot = head;
832                                         lowest_bot_score = head.frags;
833                                 }
834                         }
835                         else
836                         {
837                                 if(head.frags < lowest_player_score)
838                                 {
839                                         lowest_player = head;
840                                         lowest_player_score = head.frags;
841                                 }
842                         }
843                 }
844                 head = find(head, classname, "player");
845         }
846
847         // prefers to move a bot...
848         if(lowest_bot != world)
849                 selected = lowest_bot;
850         // but it will move a player if it has to
851         else
852                 selected = lowest_player;
853         // don't do anything if it couldn't find anyone
854         if(!selected)
855         {
856                 bprint("warning: couldn't find a player to move from team\n");
857                 return;
858         }
859
860         // smallest team gains a member
861         if(smallestteam == 1)
862         {
863                 c1 = c1 + 1;
864         }
865         else if(smallestteam == 2)
866         {
867                 c2 = c2 + 1;
868         }
869         else if(smallestteam == 3)
870         {
871                 c3 = c3 + 1;
872         }
873         else if(smallestteam == 4)
874         {
875                 c4 = c4 + 1;
876         }
877         else
878         {
879                 bprint("warning: destination team invalid\n");
880                 return;
881         }
882         // source team loses a member
883         if(source_team == 1)
884         {
885                 c1 = c1 + 1;
886         }
887         else if(source_team == 2)
888         {
889                 c2 = c2 + 2;
890         }
891         else if(source_team == 3)
892         {
893                 c3 = c3 + 3;
894         }
895         else if(source_team == 4)
896         {
897                 c4 = c4 + 4;
898         }
899         else
900         {
901                 bprint("warning: source team invalid\n");
902                 return;
903         }
904
905         // move the player to the new team
906         SetPlayerTeam(selected, smallestteam, source_team, FALSE);
907 }
908
909 // part of g_balance_teams_force
910 // occasionally perform an audit of the teams to make
911 // sure they're more or less balanced in player count.
912 void AuditTeams()
913 {
914         float numplayers, numteams, average;
915         if(!cvar("g_balance_teams_force"))
916                 return;
917         if(!cvar("teamplay"))
918                 return;
919
920         if(audit_teams_time > time)
921                 return;
922
923         audit_teams_time = time + 4 + random();
924
925 //      bprint("Auditing teams\n");
926
927         CheckAllowedTeams();
928         GetTeamCounts(world);
929
930
931         numteams = numplayers = 0;
932         if(c1 >= 0)
933         {
934                 numteams = numteams + 1;
935                 numplayers = numplayers + c1;
936         }
937         if(c2 >= 0)
938         {
939                 numteams = numteams + 1;
940                 numplayers = numplayers + c2;
941         }
942         if(c3 >= 0)
943         {
944                 numteams = numteams + 1;
945                 numplayers = numplayers + c3;
946         }
947         if(c4 >= 0)
948         {
949                 numteams = numteams + 1;
950                 numplayers = numplayers + c4;
951         }
952
953         if(numplayers <= 0)
954                 return; // no players to move around
955         if(numteams < 2)
956                 return; // don't bother shuffling if for some reason there aren't any teams
957
958         average = ceil(numplayers / numteams);
959
960         if(average <= 0)
961                 return; // that's weird...
962
963         if(c1 && c1 > average)
964         {
965                 bprint("Rebalancing Teams\n");
966                 //bprint("Shuffle from team 1\n");
967                 ShufflePlayerOutOfTeam(1);
968         }
969         if(c2 && c2 > average)
970         {
971                 bprint("Rebalancing Teams\n");
972                 //bprint("Shuffle from team 2\n");
973                 ShufflePlayerOutOfTeam(2);
974         }
975         if(c3 && c3 > average)
976         {
977                 bprint("Rebalancing Teams\n");
978                 //bprint("Shuffle from team 3\n");
979                 ShufflePlayerOutOfTeam(3);
980         }
981         if(c4 && c4 > average)
982         {
983                 bprint("Rebalancing Teams\n");
984                 //bprint("Shuffle from team 4\n");
985                 ShufflePlayerOutOfTeam(4);
986         }
987
988         // if teams are still unbalanced, balance them further in the next audit,
989         // which will happen sooner (keep doing rapid audits until things are in order)
990         audit_teams_time = time + 0.7 + random()*0.3;
991 }
992
993
994
995 /*void(entity e, float first) UpdateTeamScore =
996 {
997         clientno = e.FIXME;
998         if(first)
999         {
1000                 WriteByte (MSG_ALL, SVC_UPDATENAME);
1001                 WriteByte (MSG_ALL, clientno);
1002                 WriteString (MSG_ALL, e.netname);
1003
1004                 WriteByte (MSG_ALL, SVC_UPDATECOLORS);
1005                 WriteByte (MSG_ALL, clientno);
1006                 WriteByte (MSG_ALL, e.b_shirt * 16 + who.b_pants);
1007         }
1008
1009         WriteByte (MSG_ALL, SVC_UPDATEFRAGS);
1010         WriteByte (MSG_ALL, clientno);
1011         WriteShort (MSG_ALL, e.frags + 10000);
1012 };
1013
1014 */
1015
1016
1017 // code from here on is just to support maps that don't have team entities
1018 void tdm_spawnteam (string teamname, float teamcolor)
1019 {
1020         local entity e;
1021         e = spawn();
1022         e.classname = "tdm_team";
1023         e.netname = teamname;
1024         e.cnt = teamcolor;
1025         e.team = e.cnt + 1;
1026 };
1027
1028 // spawn some default teams if the map is not set up for tdm
1029 void() tdm_spawnteams =
1030 {
1031         float numteams;
1032
1033         numteams = cvar("g_tdm_teams");
1034
1035         tdm_spawnteam("Red", 4);
1036         tdm_spawnteam("Blue", 13);
1037 };
1038
1039 void() tdm_delayedinit =
1040 {
1041         self.think = SUB_Remove;
1042         self.nextthink = time;
1043         // if no teams are found, spawn defaults
1044         if (find(world, classname, "tdm_team") == world)
1045                 tdm_spawnteams();
1046 };
1047
1048 void() tdm_init =
1049 {
1050         local entity e;
1051         e = spawn();
1052         e.think = tdm_delayedinit;
1053         e.nextthink = time + 0.1;
1054 };
1055
1056