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