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