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