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