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