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