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