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