]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/teamplay.qc
move player selection display to prethink
[divverent/nexuiz.git] / data / qcsrc / server / teamplay.qc
1 float GAME_DEATHMATCH           = 1;
2 float GAME_TEAM_DEATHMATCH      = 2;
3 float GAME_DOMINATION           = 3;
4 float GAME_CTF                  = 4;
5 float GAME_RUNEMATCH            = 5;
6 float GAME_LMS                  = 6;
7 float GAME_ARENA                = 7;
8
9 // client counts for each team
10 float c1, c2, c3, c4;
11 // # of bots on those teams
12 float cb1, cb2, cb3, cb4;
13
14 float g_domination, g_ctf, g_tdm;
15
16 float audit_teams_time;
17
18 float() IsTeamBalanceForced = {
19         if(intermission_running)
20                 return 0; // no rebalancing whatsoever please
21         if(!cvar("teamplay"))
22                 return 0;
23         if(cvar("g_campaign"))
24                 return 0;
25         if(!cvar("g_balance_teams_force"))
26                 return -1;
27         return 1;
28 }
29
30 void TeamchangeFrags(entity e)
31 {
32         if(e.classname == "player")
33         {
34                 // reduce frags during a team change
35                 DistributeFragsAmongTeam(e, (e.clientcolors & 0x0F) + 1, 1 - cvar("g_changeteam_fragtransfer") / 100);
36         }
37 }
38
39 string TeamName(float t)
40 {
41         // fixme: Search for team entities and get their .netname's!
42         if(t == COLOR_TEAM1)
43                 return "Red Team";
44         if(t == COLOR_TEAM2)
45                 return "Blue Team";
46         if(t == COLOR_TEAM3)
47                 return "Pink Team";
48         if(t == COLOR_TEAM4)
49                 return "Yellow Team";
50         return "Neutral Team";
51 }
52 string ColoredTeamName(float t)
53 {
54         // fixme: Search for team entities and get their .netname's!
55         if(t == COLOR_TEAM1)
56                 return "^1Red Team^7";
57         if(t == COLOR_TEAM2)
58                 return "^4Blue Team^7";
59         if(t == COLOR_TEAM3)
60                 return "^6Pink Team^7";
61         if(t == COLOR_TEAM4)
62                 return "^3Yellow Team^7";
63         return "Neutral Team";
64 }
65 string TeamNoName(float t)
66 {
67         // fixme: Search for team entities and get their .netname's!
68         if(t == 1)
69                 return "Red Team";
70         if(t == 2)
71                 return "Blue Team";
72         if(t == 3)
73                 return "Pink Team";
74         if(t == 4)
75                 return "Yellow Team";
76         return "Neutral Team";
77 }
78
79 void dom_init();
80 void ctf_init();
81 void runematch_init();
82 void tdm_init();
83
84 void(entity pl) LogTeamchange =
85 {
86         string str;
87         if(!cvar("sv_eventlog"))
88                 return;
89         if(pl.playerid < 1)
90                 return;
91         str = strcat(":team:", ftos(pl.playerid), ":");
92         str = strcat(str, ftos(pl.team));
93         GameLogEcho(str, FALSE);
94 }
95
96 void ResetGameCvars()
97 {
98         cvar_set("g_tdm", "0");
99         cvar_set("g_domination", "0");
100         cvar_set("g_ctf", "0");
101         cvar_set("g_runematch", "0");
102         cvar_set("g_lms", "0");
103         cvar_set("g_arena", "0");
104         cvar_set("teamplay", "0");
105 }
106
107 void ActivateTeamplay()
108 {
109         float teamplay_default;
110         teamplay_default = cvar("teamplay_default");
111
112         if(teamplay_default)
113                 cvar_set("teamplay", ftos(teamplay_default));
114         else
115                 cvar_set("teamplay", "3");
116 }
117
118 void InitGameplayMode()
119 {
120         float fraglimit_override, timelimit_override;
121
122         VoteReset();
123
124         game = cvar ("gamecfg");        // load game options
125
126         // game cvars get reset before map changes
127         // then map's cfg sets them as desired
128
129         // FIXME: also set a message or game mode name to print to players when the join
130
131         // set both here, gamemode can override it later
132         timelimit_override = cvar("timelimit_override");
133         fraglimit_override = cvar("fraglimit_override");
134
135         if(game == GAME_DOMINATION || cvar("g_domination"))
136         {
137                 ResetGameCvars();
138                 game = GAME_DOMINATION;
139                 cvar_set("g_domination", "1");
140
141                 ActivateTeamplay();
142
143                 fraglimit_override = cvar("g_domination_point_limit");
144
145                 gamemode_name = "Domination";
146                 teams_matter = 1;
147         }
148         else if(game == GAME_CTF || cvar("g_ctf"))
149         {
150                 ResetGameCvars();
151                 game = GAME_CTF;
152                 cvar_set("g_ctf", "1");
153
154                 ActivateTeamplay();
155
156                 fraglimit_override = cvar("g_ctf_capture_limit");
157
158                 gamemode_name = "Capture the Flag";
159                 teams_matter = 1;
160         }
161         else if((game == GAME_RUNEMATCH || cvar("g_runematch")) && !cvar("g_minstagib"))
162         {
163                 game = GAME_RUNEMATCH;
164                 cvar_set("g_runematch", "1");
165
166                 if(cvar("deathmatch_force_teamplay"))
167                         ActivateTeamplay();
168
169                 fraglimit_override = cvar("g_runematch_point_limit");
170
171                 gamemode_name = "Rune Match";
172                 if(cvar("teamplay"))
173                         teams_matter = 1;
174                 else
175                         teams_matter = 0;
176         }
177         else if(game == GAME_DEATHMATCH || game == GAME_TEAM_DEATHMATCH || cvar("g_tdm"))
178         {
179                 if(!cvar("deathmatch"))
180                         cvar_set("deathmatch", "1");
181
182
183                 if(game == GAME_TEAM_DEATHMATCH || cvar("g_tdm") || cvar("deathmatch_force_teamplay"))
184                 {
185                         ResetGameCvars();
186                         game = GAME_TEAM_DEATHMATCH;
187                         gamemode_name = "Team Deathmatch";
188                         ActivateTeamplay();
189                         teams_matter = 1;
190                         cvar_set("g_tdm", "1");
191                 }
192                 else
193                 {
194                         ResetGameCvars();
195                         game = GAME_DEATHMATCH;
196                         gamemode_name = "Deathmatch";
197                         teams_matter = 0;
198                 }
199
200                 fraglimit_override = cvar("fraglimit_override");
201         }
202         else if(game == GAME_LMS || cvar("g_lms"))
203         {
204                 ResetGameCvars();
205                 game = GAME_LMS;
206                 cvar_set("g_lms", "1");
207                 fraglimit_override = cvar("g_lms_lives_override");
208                 if(fraglimit_override == 0)
209                         fraglimit_override = -1;
210                 gamemode_name = "Last Man Standing";
211                 teams_matter = 0;
212                 lms_lowest_lives = 999;
213         }
214         else if(game == GAME_ARENA || cvar("g_arena"))
215         {
216                 ResetGameCvars();
217                 game = GAME_ARENA;
218                 cvar_set("g_arena", "1");
219                 fraglimit_override = cvar("g_arena_point_limit");
220                 maxspawned = cvar("g_arena_maxspawned");
221                 if(maxspawned < 2)
222                         maxspawned = 2;
223                 arena_roundbased = cvar("g_arena_roundbased");
224                 gamemode_name = "Arena";
225                 teams_matter = 0;
226         }
227         else
228         {
229                 // we can only assume...
230                 ResetGameCvars();
231                 gamemode_name = "Deathmatch";
232                 teams_matter = 0;
233         }
234 /*      else if(game == GAME_TEAM_DEATHMATCH)
235         {
236                 if(!cvar("deathmatch"))
237                         cvar_set("deathmatch", "1");
238
239                 //if(!cvar("teamplay"))
240                 //      cvar_set("teamplay", "3");
241                 ActivateTeamplay();
242
243                 fraglimit_override = cvar("fraglimit_override");
244         }*/
245
246         // enforce the server's universal frag/time limits
247         if(!cvar("g_campaign"))
248         {
249                 if(fraglimit_override >= 0)
250                         cvar_set("fraglimit", ftos(fraglimit_override));
251                 if(timelimit_override >= 0)
252                         cvar_set("timelimit", ftos(timelimit_override));
253         }
254
255         if (game == GAME_DOMINATION)//cvar("g_domination"))
256                 dom_init();
257         else if (game == GAME_CTF)//cvar("g_ctf"))
258                 ctf_init();
259         else if (game == GAME_RUNEMATCH)//cvar("g_runematch"))
260                 runematch_init();
261         else if (game == GAME_TEAM_DEATHMATCH)//cvar("g_runematch"))
262                 tdm_init();
263
264         // those mutators rule each other out
265         if(cvar("g_minstagib"))
266         {
267                 cvar_set("g_instagib", "0");
268                 cvar_set("g_rocketarena", "0");
269         }
270         if(cvar("g_instagib"))
271         {
272                 cvar_set("g_minstagib", "0");
273                 cvar_set("g_rocketarena", "0");
274         }
275         if(cvar("g_rocketarena"))
276         {
277                 cvar_set("g_instagib", "0");
278                 cvar_set("g_minstagib", "0");
279         }
280
281         registercvar("_motd", "");
282         registercvar("_mutatormsg", "");
283         cvar_set("_motd", wordwrap(cvar_string("sv_motd"), 50));
284         cvar_set("_mutatormsg", wordwrap(cvar_string("g_mutatormsg"), 50));
285
286         g_domination = cvar("g_domination");
287         g_ctf = cvar("g_ctf");
288         g_tdm = cvar("g_tdm");
289 }
290
291 string GetClientVersionMessage(float v) {
292         local string versionmsg;
293         if (v == 1) {
294                 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";
295                 // either that or someone wants to be funny
296         } else if (v != cvar("gameversion")) {
297                 if(v < cvar("gameversion")) {
298                         versionmsg = "^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8";
299                 } else {
300                         versionmsg = "^3This server is using an outdated Nexuiz version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8";
301                 }
302         } else {
303                 versionmsg = "^2client version and server version are compatible.^8";
304         }
305         return strzone(versionmsg);
306
307 }
308
309
310 void PrintWelcomeMessage(entity pl)
311 {
312         string s, mutator, modifications, padding;
313
314         /*if(self.welcomemessage_time > time)
315                 return;
316         self.welcomemessage_time = time + 0.8; */
317
318         if(self.cvar_scr_centertime == 0) return;
319         if(self.welcomemessage_time > time) return;
320         self.welcomemessage_time = time + self.cvar_scr_centertime * 0.6;
321
322         if(cvar("g_campaign"))
323         {
324                 centerprint(pl, campaign_message);
325                 return;
326         }
327
328         if(self.classname == "observer")
329         {
330                 if(cvar("g_lms") && self.frags <= 0 && self.frags > -666)
331                         return centerprint(self, strcat(newlines, "^1You have no more lives left\nwait for next round\n\n\n^7press attack to spectate other players"));
332                 else if(cvar("g_lms") && self.frags == -666)
333                         return centerprint(self, strcat(newlines, "^1Match has already begun\nwait for next round\n\n\n^7press attack to spectate other players"));
334         }
335         else if(self.classname == "spectator")
336         {
337                 if ((cvar("g_lms") && self.frags < 1) || cvar("g_arena"))
338                         return centerprint(self, strcat(newlines, "spectating ", self.enemy.netname, "\n\n\n^7press attack for next player\npress attack2 for free fly mode"));
339                 else
340                         return centerprint(self, strcat(newlines, "spectating ", self.enemy.netname, "\n\n\n^7press jump to play\n^7press attack for next player\npress attack2 for free fly mode"));
341         }
342
343
344         if(cvar("g_minstagib"))
345                 mutator = "^2Minstagib ^1";
346         else if(cvar("g_instagib"))
347                 mutator = "^2Instagib ^1";
348         else if(cvar("g_rocketarena"))
349                 mutator = "^2Rocketarena ^1";
350         else if(cvar("g_nixnex"))
351                 mutator = "^2No Items Nexuiz ^1";
352
353         if(cvar("g_midair")) {
354                 // to protect against unheedingly made changes
355                 if (modifications) {
356                         modifications = strcat(modifications, ", ");
357                 }
358                 modifications = "midair";
359         }
360         if(cvar("g_vampire")) {
361                 if (modifications) {
362                         modifications = strcat(modifications, ", ");
363                 }
364                 modifications = strcat(modifications, "vampire");
365         }
366         if(cvar("g_laserguided_missile")) {
367                 if (modifications) {
368                         modifications = strcat(modifications, ", ");
369                 }
370                 modifications = strcat(modifications, "laser-guided-missiles");
371         }
372
373         local string versionmessage;
374         versionmessage = GetClientVersionMessage(self.version);
375
376         s = strcat(s, newlines, "This is Nexuiz ", cvar_string("g_nexuizversion"), "\n", versionmessage);
377         s = strcat(s, "^8\n\nmatch type is ^1", mutator, gamemode_name, "^8\n");
378
379         if(modifications != "")
380                 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
381
382         if((self.classname == "observer" || self.classname == "spectator") && self.version == cvar("gameversion")) {
383                 if(!cvar("g_arena"))
384                         s = strcat(s,"^7\n\n\npress jump to play\npress attack to spectate other players\n\n");
385                 else if(player_count < 2 && arena_roundbased)
386                 {
387                         s = strcat(s, "\n\n\n^1waiting for second player to start match^7\n\n");
388                 }
389                 else
390                 {
391                         s = strcat(s, "\n\n\n");
392                         if(champion)
393                                 s = strcat(s, "^7current champion is: ", champion.netname, "\n\n");
394                         s = strcat(s,"^7press attack to spectate other players\n\n");
395                 }
396         }
397
398
399         s = strzone(s);
400
401         if (cvar("g_grappling_hook"))
402                 s = strcat(s, "\n\n^8grappling hook is enabled, press 'e' to use it\n");
403
404         if (cvar_string("_mutatormsg") != "") {
405                 s = strcat(s, "\n\n^8special gameplay tips: ^7", cvar_string("_mutatormsg"));
406         }
407
408         if (cvar_string("_motd") != "") {
409                 s = strcat(s, "\n\n^8MOTD: ^7", cvar_string("_motd"));
410         }
411
412         s = strcat(s, "\n");
413         if(cvar("fraglimit"))
414         {
415                 padding = "";
416                 if(cvar("timelimit"))
417                         padding = "        ";
418                         //        " minutes"
419                 s = strcat(s, "\n^8frag limit: ^7", cvar_string("fraglimit"), padding);
420         }
421         if(cvar("timelimit"))
422                 s = strcat(s, "\n^8time limit: ^7", cvar_string("timelimit"), " minutes");
423
424         s = strzone(s);
425
426         centerprint(pl, s);
427         //sprint(pl, s);
428
429         strunzone(s);
430 }
431
432
433 void SetPlayerColors(entity pl, float _color)
434 {
435         /*string s;
436         s = ftos(cl);
437         stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
438         pl.team = cl + 1;
439         //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
440         pl.clientcolors = 16*cl + cl;*/
441
442         float pants, shirt;
443         pants = _color & 0x0F;
444         shirt = _color & 0xF0;
445
446
447         if(teamplay) {
448                 setcolor(pl, 16*pants + pants);
449         } else {
450                 setcolor(pl, shirt + pants);
451         }
452 }
453
454 void SetPlayerTeam(entity pl, float t, float s, float noprint)
455 {
456         float _color;
457
458         if(t == 4)
459                 _color = COLOR_TEAM4 - 1;
460         else if(t == 3)
461                 _color = COLOR_TEAM3 - 1;
462         else if(t == 2)
463                 _color = COLOR_TEAM2 - 1;
464         else
465                 _color = COLOR_TEAM1 - 1;
466
467         SetPlayerColors(pl,_color);
468
469         if(!noprint && t != s)
470         {
471                 //bprint(pl.netname, " has changed to ", TeamNoName(t), "\n");
472                 bprint(pl.netname, "^7 has changed from ", TeamNoName(s), " to ", TeamNoName(t), "\n");
473         }
474
475         if(t != s)
476                 LogTeamchange(pl);
477 }
478
479
480
481
482
483
484 // set c1...c4 to show what teams are allowed
485 void CheckAllowedTeams ()
486 {
487         string teament_name;
488         float dm;
489         entity head;
490
491 //      if(!dom && !ctf)
492 //              dm = 1;
493
494         c1 = c2 = c3 = c4 = -1;
495         cb1 = cb2 = cb3 = cb4 = 0;
496
497         if(g_domination)
498                 teament_name = "dom_team";
499         else if(g_ctf)
500                 teament_name = "ctf_team";
501         else if(g_tdm)
502                 teament_name = "tdm_team";
503         else
504         {
505                 // cover anything else by treating it like tdm with no teams spawned
506                 dm = cvar("g_tdm_teams");
507                 if(dm < 2)
508                         error("g_tdm_teams < 2, not enough teams to play team deathmatch\n");
509
510                 if(dm >= 4)
511                 {
512                         c1 = c2 = c3 = c4 = 0;
513                 }
514                 else if(dm >= 3)
515                 {
516                         c1 = c2 = c3 = 0;
517                 }
518                 else// if(dm >= 2)
519                 {
520                         c1 = c2 = 0;
521                 }
522                 return;
523         }
524
525         // first find out what teams are allowed
526         head = find(world, classname, teament_name);
527         while(head)
528         {
529                 if(!(g_domination && head.netname == ""))
530                 {
531                         if(head.team == COLOR_TEAM1)
532                         {
533                                 c1 = 0;
534                         }
535                         if(head.team == COLOR_TEAM2)
536                         {
537                                 c2 = 0;
538                         }
539                         if(head.team == COLOR_TEAM3)
540                         {
541                                 c3 = 0;
542                         }
543                         if(head.team == COLOR_TEAM4)
544                         {
545                                 c4 = 0;
546                         }
547                 }
548                 head = find(head, classname, teament_name);
549         }
550 }
551
552 float PlayerValue(entity p)
553 {
554         if(IsTeamBalanceForced() == 1)
555                 return 1;
556         return 1;
557 }
558
559 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
560 // teams that are allowed will now have their player counts stored in c1...c4
561 void GetTeamCounts(entity ignore)
562 {
563         entity head;
564         float value;
565         // now count how many players are on each team already
566
567         // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
568         // also remember the lowest-scoring player
569
570         FOR_EACH_PLAYER(head)
571         {
572                 if(head != ignore)// && head.netname != "")
573                 {
574                         value = PlayerValue(head);
575                         if(head.team == COLOR_TEAM1)
576                         {
577                                 if(c1 >= 0)
578                                 {
579                                         c1 = c1 + value;
580                                         cb1 = cb1 + value;
581                                 }
582                         }
583                         if(head.team == COLOR_TEAM2)
584                         {
585                                 if(c2 >= 0)
586                                 {
587                                         c2 = c2 + value;
588                                         cb2 = cb2 + value;
589                                 }
590                         }
591                         if(head.team == COLOR_TEAM3)
592                         {
593                                 if(c3 >= 0)
594                                 {
595                                         c3 = c3 + value;
596                                         cb3 = cb3 + value;
597                                 }
598                         }
599                         if(head.team == COLOR_TEAM4)
600                         {
601                                 if(c4 >= 0)
602                                 {
603                                         c4 = c4 + value;
604                                         cb4 = cb4 + value;
605                                 }
606                         }
607                 }
608         }
609 }
610
611 // returns # of smallest team (1, 2, 3, 4)
612 // NOTE: Assumes CheckAllowedTeams has already been called!
613 float FindSmallestTeam(entity pl, float ignore_pl)
614 {
615         float totalteams, smallestteam, smallestteam_count, smallestteam_score, balance_type;
616         totalteams = 0;
617
618         // find out what teams are available
619         //CheckAllowedTeams();
620
621         // make sure there are at least 2 teams to join
622         if(c1 >= 0)
623                 totalteams = totalteams + 1;
624         if(c2 >= 0)
625                 totalteams = totalteams + 1;
626         if(c3 >= 0)
627                 totalteams = totalteams + 1;
628         if(c4 >= 0)
629                 totalteams = totalteams + 1;
630
631         if(totalteams <= 1)
632         {
633                 if(g_domination)
634                         error("Too few teams available for domination\n");
635                 else if(g_ctf)
636                         error("Too few teams available for ctf\n");
637                 else
638                         error("Too few teams available for team deathmatch\n");
639         }
640
641
642         // count how many players are in each team
643         if(ignore_pl)
644                 GetTeamCounts(pl);
645         else
646                 GetTeamCounts(world);
647
648         // c1...c4 now have counts of each team
649         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
650
651         smallestteam = 0;
652         smallestteam_count = 999999999;
653         smallestteam_score = 999999999;
654
655         // 2 gives priority to what team you're already on, 1 goes in order
656         // 2 doesn't seem to work though...
657         balance_type = 1;
658
659         if(balance_type == 1)
660         {
661                 if(c1 >= 0 && (c1 < smallestteam_count || (c1 <= smallestteam_count && team1_score < smallestteam_score)))
662                 {
663                         smallestteam = 1;
664                         smallestteam_count = c1;
665                         smallestteam_score = team1_score;
666                 }
667                 if(c2 >= 0 && (c2 < smallestteam_count || (c2 <= smallestteam_count && team2_score < smallestteam_score)))
668                 {
669                         smallestteam = 2;
670                         smallestteam_count = c2;
671                         smallestteam_score = team2_score;
672                 }
673                 if(c3 >= 0 && (c3 < smallestteam_count || (c3 <= smallestteam_count && team3_score < smallestteam_score)))
674                 {
675                         smallestteam = 3;
676                         smallestteam_count = c3;
677                         smallestteam_score = team3_score;
678                 }
679                 if(c4 >= 0 && (c4 < smallestteam_count || (c4 <= smallestteam_count && team4_score < smallestteam_score)))
680                 {
681                         smallestteam = 4;
682                         smallestteam_count = c4;
683                         smallestteam_score = team4_score;
684                 }
685         }
686         else
687         {
688                 if(c1 >= 0 && (c1 < smallestteam_count ||
689                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
690                 {
691                         smallestteam = 1;
692                         smallestteam_count = c1;
693                 }
694                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
695                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
696                 {
697                         smallestteam = 2;
698                         smallestteam_count = c2;
699                 }
700                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
701                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
702                 {
703                         smallestteam = 3;
704                         smallestteam_count = c3;
705                 }
706                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
707                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
708                 {
709                         smallestteam = 4;
710                         smallestteam_count = c4;
711                 }
712         }
713
714         return smallestteam;
715 }
716
717 float JoinBestTeam(entity pl, float only_return_best)
718 {
719         float smallest, selectedteam;
720
721         // don't join a team if we're not playing a team game
722         if(!cvar("teamplay") && !g_domination && !g_ctf)
723                 return 0;
724
725         // find out what teams are available
726         CheckAllowedTeams();
727
728         if(cvar("g_domination"))
729         {
730                 if(cvar("g_domination_default_teams") < 3)
731                         c3 = 999999999;
732                 if(cvar("g_domination_default_teams") < 4)
733                         c4 = 999999999;
734         }
735
736         // if we don't care what team he ends up on, put him on whatever team he entered as.
737         // if he's not on a valid team, then let other code put him on the smallest team
738         if(!cvar("g_campaign") && !cvar("g_balance_teams") && !cvar("g_balance_teams_force"))
739         {
740                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
741                         selectedteam = pl.team;
742                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
743                         selectedteam = pl.team;
744                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
745                         selectedteam = pl.team;
746                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
747                         selectedteam = pl.team;
748                 else
749                         selectedteam = -1;
750                 if(selectedteam > 0)
751                 {
752                         if(!only_return_best)
753                         {
754                                 SetPlayerColors(pl, selectedteam - 1);
755                                 LogTeamchange(pl);
756                         }
757                         return selectedteam;
758                 }
759                 // otherwise end up on the smallest team (handled below)
760         }
761
762         smallest = FindSmallestTeam(pl, TRUE);
763
764
765         if(!only_return_best)
766         {
767                 TeamchangeFrags(self);
768                 if(smallest == 1)
769                 {
770                         SetPlayerColors(pl, COLOR_TEAM1 - 1);
771                 }
772                 else if(smallest == 2)
773                 {
774                         SetPlayerColors(pl, COLOR_TEAM2 - 1);
775                 }
776                 else if(smallest == 3)
777                 {
778                         SetPlayerColors(pl, COLOR_TEAM3 - 1);
779                 }
780                 else if(smallest == 4)
781                 {
782                         SetPlayerColors(pl, COLOR_TEAM4 - 1);
783                 }
784                 else
785                 {
786                         error("smallest team: invalid team\n");
787                 }
788                 LogTeamchange(pl);
789                 if(pl.deadflag == DEAD_NO)
790                         Damage(pl, pl, pl, 100000, DEATH_TEAMCHANGE, pl.origin, '0 0 0');
791         }
792
793         return smallest;
794 }
795
796
797 void SV_ChangeTeam(float _color)
798 {
799         float scolor, dcolor, steam, dteam, dbotcount, scount, dcount;
800
801         // in normal deathmatch we can just apply the color and we're done
802         if(!cvar("teamplay")) {
803                 SetPlayerColors(self, _color);
804                 return;
805         }
806
807         scolor = self.clientcolors & 0x0F;
808         dcolor = _color & 0x0F;
809
810         if(scolor == COLOR_TEAM1 - 1)
811                 steam = 1;
812         else if(scolor == COLOR_TEAM2 - 1)
813                 steam = 2;
814         else if(scolor == COLOR_TEAM3 - 1)
815                 steam = 3;
816         else if(scolor == COLOR_TEAM4 - 1)
817                 steam = 4;
818         if(dcolor == COLOR_TEAM1 - 1)
819                 dteam = 1;
820         else if(dcolor == COLOR_TEAM2 - 1)
821                 dteam = 2;
822         else if(dcolor == COLOR_TEAM3 - 1)
823                 dteam = 3;
824         else if(dcolor == COLOR_TEAM4 - 1)
825                 dteam = 4;
826
827         // remap invalid teams in dom & ctf
828         /*
829         if(cvar("g_ctf") && dteam == 3)
830                 dteam = 2;
831         else if(cvar("g_ctf") && dteam == 4)
832                 dteam = 1;
833         else if((cvar("g_domination") && cvar("g_domination_default_teams") < 3) || (cvar("g_tdm") && cvar("g_tdm_teams") < 3))
834         {
835                 if(dteam == 3)
836                         dteam = 2;
837                 else if(dteam == 4)
838                         dteam = 1;
839         }
840         else if((cvar("g_domination") && cvar("g_domination_default_teams") < 4) || (cvar("g_tdm") && cvar("g_tdm_teams") < 4))
841         {
842                 if(dteam == 4)
843                         dteam = 1;
844         }
845         */
846
847         CheckAllowedTeams();
848         if(dteam == 3 && c3 < 0)
849                 dteam = 2;
850         if(dteam == 4 && c4 < 0)
851                 dteam = 1;
852
853         // not changing teams
854         if(scolor == dcolor)
855         {
856                 //bprint("same team change\n");
857                 SetPlayerTeam(self, dteam, steam, TRUE);
858                 return;
859         }
860
861         if(cvar("teamplay"))
862         {
863                 if(cvar("g_campaign") || cvar("g_changeteam_banned"))
864                 {
865                         sprint(self, "Team changes not allowed\n");
866                         return; // changing teams is not allowed
867                 }
868
869                 if(!cvar("g_campaign") && cvar("g_balance_teams_prevent_imbalance"))
870                 {
871                         // only allow changing to a smaller or equal size team
872
873                         // find out what teams are available
874                         CheckAllowedTeams();
875                         // count how many players on each team
876                         GetTeamCounts(world);
877
878                         // get desired team
879                         if(dteam == 1 && c1 >= 0)//dcolor == COLOR_TEAM1 - 1)
880                         {
881                                 dcount = c1;
882                                 dbotcount = cb1;
883                         }
884                         else if(dteam == 2 && c2 >= 0)//dcolor == COLOR_TEAM2 - 1)
885                         {
886                                 dcount = c2;
887                                 dbotcount = cb2;
888                         }
889                         else if(dteam == 3 && c3 >= 0)//dcolor == COLOR_TEAM3 - 1)
890                         {
891                                 dcount = c3;
892                                 dbotcount = cb3;
893                         }
894                         else if(dteam == 4 && c4 >= 0)//dcolor == COLOR_TEAM4 - 1)
895                         {
896                                 dcount = c4;
897                                 dbotcount = cb4;
898                         }
899                         else
900                         {
901                                 sprint(self, "Cannot change to an invalid team\n");
902
903                                 return;
904                         }
905
906                         // get starting team
907                         if(steam == 1)//scolor == COLOR_TEAM1 - 1)
908                                 scount = c1;
909                         else if(steam == 2)//scolor == COLOR_TEAM2 - 1)
910                                 scount = c2;
911                         else if(steam == 3)//scolor == COLOR_TEAM3 - 1)
912                                 scount = c3;
913                         else if(steam == 4)//scolor == COLOR_TEAM4 - 1)
914                                 scount = c4;
915
916                         if(scount) // started at a valid, nonempty team
917                         {
918                                 // check if we're trying to change to a larger team that doens't have bots to swap with
919                                 if(dcount >= scount && dbotcount <= 0)
920                                 {
921                                         sprint(self, "Cannot change to a larger team\n");
922                                         return; // can't change to a larger team
923                                 }
924                         }
925                 }
926         }
927
928 //      bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
929
930         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
931         {
932                 // reduce frags during a team change
933                 TeamchangeFrags(self);
934         }
935
936         SetPlayerTeam(self, dteam, steam, FALSE);
937
938         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
939         {
940                 // kill player when changing teams
941                 if(self.deadflag == DEAD_NO)
942                         Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
943         }
944 }
945
946 void ShufflePlayerOutOfTeam (float source_team)
947 {
948         float smallestteam, smallestteam_count, steam;
949         float lowest_bot_score, lowest_player_score;
950         entity head, lowest_bot, lowest_player, selected;
951         string m;
952
953         smallestteam = 0;
954         smallestteam_count = 999999999;
955
956         if(c1 >= 0 && c1 < smallestteam_count)
957         {
958                 smallestteam = 1;
959                 smallestteam_count = c1;
960         }
961         if(c2 >= 0 && c2 < smallestteam_count)
962         {
963                 smallestteam = 2;
964                 smallestteam_count = c2;
965         }
966         if(c3 >= 0 && c3 < smallestteam_count)
967         {
968                 smallestteam = 3;
969                 smallestteam_count = c3;
970         }
971         if(c4 >= 0 && c4 < smallestteam_count)
972         {
973                 smallestteam = 4;
974                 smallestteam_count = c4;
975         }
976
977         if(!smallestteam)
978         {
979                 bprint("warning: no smallest team\n");
980                 return;
981         }
982
983         if(source_team == 1)
984                 steam = COLOR_TEAM1;
985         else if(source_team == 2)
986                 steam = COLOR_TEAM2;
987         else if(source_team == 3)
988                 steam = COLOR_TEAM3;
989         else if(source_team == 4)
990                 steam = COLOR_TEAM4;
991
992         lowest_bot = world;
993         lowest_bot_score = 999999999;
994         lowest_player = world;
995         lowest_player_score = 999999999;
996
997         // find the lowest-scoring player & bot of that team
998         FOR_EACH_PLAYER(head)
999         {
1000                 if(head.team == steam)
1001                 {
1002                         if(head.isbot)
1003                         {
1004                                 if(head.frags < lowest_bot_score)
1005                                 {
1006                                         lowest_bot = head;
1007                                         lowest_bot_score = head.frags;
1008                                 }
1009                         }
1010                         else
1011                         {
1012                                 if(head.frags < lowest_player_score)
1013                                 {
1014                                         lowest_player = head;
1015                                         lowest_player_score = head.frags;
1016                                 }
1017                         }
1018                 }
1019         }
1020
1021         // prefers to move a bot...
1022         if(lowest_bot != world)
1023                 selected = lowest_bot;
1024         // but it will move a player if it has to
1025         else
1026                 selected = lowest_player;
1027         // don't do anything if it couldn't find anyone
1028         if(!selected)
1029         {
1030                 bprint("warning: couldn't find a player to move from team\n");
1031                 return;
1032         }
1033
1034         // smallest team gains a member
1035         if(smallestteam == 1)
1036         {
1037                 c1 = c1 + 1;
1038         }
1039         else if(smallestteam == 2)
1040         {
1041                 c2 = c2 + 1;
1042         }
1043         else if(smallestteam == 3)
1044         {
1045                 c3 = c3 + 1;
1046         }
1047         else if(smallestteam == 4)
1048         {
1049                 c4 = c4 + 1;
1050         }
1051         else
1052         {
1053                 bprint("warning: destination team invalid\n");
1054                 return;
1055         }
1056         // source team loses a member
1057         if(source_team == 1)
1058         {
1059                 c1 = c1 + 1;
1060         }
1061         else if(source_team == 2)
1062         {
1063                 c2 = c2 + 2;
1064         }
1065         else if(source_team == 3)
1066         {
1067                 c3 = c3 + 3;
1068         }
1069         else if(source_team == 4)
1070         {
1071                 c4 = c4 + 4;
1072         }
1073         else
1074         {
1075                 bprint("warning: source team invalid\n");
1076                 return;
1077         }
1078
1079         // move the player to the new team
1080         TeamchangeFrags(selected);
1081         SetPlayerTeam(selected, smallestteam, source_team, FALSE);
1082
1083         if(selected.deadflag == DEAD_NO)
1084                         Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE, selected.origin, '0 0 0');
1085         m = "You have been moved into a different team to improve team balance\nYou are now on: ";
1086         if (selected.team == 5)
1087                 m = strcat(m, "^1Red Team");
1088         else if (selected.team == 14)
1089                 m = strcat(m, "^4Blue Team");
1090         else if (selected.team == 10)
1091                 m = strcat(m, "^6Pink Team");
1092         else if (selected.team == 13)
1093                 m = strcat(m, "^3Yellow Team");
1094         centerprint(selected, m);
1095 }
1096
1097 float lastRebalanceInfo;
1098 void CauseRebalance(float source_team, float howmany_toomany)
1099 {
1100         float steam;
1101         entity head;
1102
1103         if(IsTeamBalanceForced() == 1)
1104         {
1105                 bprint("Rebalancing Teams\n");
1106                 ShufflePlayerOutOfTeam(source_team);
1107         }
1108         else
1109         {
1110                 if(1+floor(howmany_toomany) < cvar("g_balance_teams_complain"))
1111                         return;
1112                 if(time < lastRebalanceInfo + 90)
1113                         return;
1114                 lastRebalanceInfo = time;
1115                 if(source_team == 1)
1116                         steam = COLOR_TEAM1;
1117                 else if(source_team == 2)
1118                         steam = COLOR_TEAM2;
1119                 else if(source_team == 3)
1120                         steam = COLOR_TEAM3;
1121                 else if(source_team == 4)
1122                         steam = COLOR_TEAM4;
1123                 ServerConsoleEcho(strcat("Team ", ftos(source_team), " too large, complaining."), TRUE);
1124                 FOR_EACH_REALPLAYER(head)
1125                 {
1126                         if(head.team == steam)
1127                         {
1128                                 sprint(head, "\{1}\{13}^3SERVER NOTICE:^7 One of you please change teams!\n");
1129                                 centerprint_atprio(head, CENTERPRIO_REBALANCE, "^3SERVER NOTICE:\n\n^7Someone of you please change teams!");
1130                         }
1131                 }
1132         }
1133 }
1134
1135 // part of g_balance_teams_force
1136 // occasionally perform an audit of the teams to make
1137 // sure they're more or less balanced in player count.
1138 void AuditTeams()
1139 {
1140         float numplayers, numteams, toomany;
1141         float balance;
1142         balance = IsTeamBalanceForced();
1143         if(balance == 0)
1144                 return;
1145
1146         if(audit_teams_time > time)
1147                 return;
1148
1149         audit_teams_time = time + 4 + random();
1150
1151 //      bprint("Auditing teams\n");
1152
1153         CheckAllowedTeams();
1154         GetTeamCounts(world);
1155
1156
1157         numteams = numplayers = 0;
1158         if(c1 >= 0)
1159         {
1160                 numteams = numteams + 1;
1161                 numplayers = numplayers + c1;
1162         }
1163         if(c2 >= 0)
1164         {
1165                 numteams = numteams + 1;
1166                 numplayers = numplayers + c2;
1167         }
1168         if(c3 >= 0)
1169         {
1170                 numteams = numteams + 1;
1171                 numplayers = numplayers + c3;
1172         }
1173         if(c4 >= 0)
1174         {
1175                 numteams = numteams + 1;
1176                 numplayers = numplayers + c4;
1177         }
1178
1179         if(numplayers <= 0)
1180                 return; // no players to move around
1181         if(numteams < 2)
1182                 return; // don't bother shuffling if for some reason there aren't any teams
1183
1184         toomany = (numplayers / numteams) + 1;
1185
1186         if(toomany <= 0)
1187                 return; // that's weird...
1188
1189         if(c1 && c1 >= toomany)
1190                 CauseRebalance(1, c1 - toomany);
1191         if(c2 && c2 >= toomany)
1192                 CauseRebalance(2, c2 - toomany);
1193         if(c3 && c3 >= toomany)
1194                 CauseRebalance(3, c3 - toomany);
1195         if(c4 && c4 >= toomany)
1196                 CauseRebalance(4, c4 - toomany);
1197
1198         // if teams are still unbalanced, balance them further in the next audit,
1199         // which will happen sooner (keep doing rapid audits until things are in order)
1200         audit_teams_time = time + 0.7 + random()*0.3;
1201 }
1202
1203
1204
1205 /*void(entity e, float first) UpdateTeamScore =
1206 {
1207         clientno = e.FIXME;
1208         if(first)
1209         {
1210                 WriteByte (MSG_ALL, SVC_UPDATENAME);
1211                 WriteByte (MSG_ALL, clientno);
1212                 WriteString (MSG_ALL, e.netname);
1213
1214                 WriteByte (MSG_ALL, SVC_UPDATECOLORS);
1215                 WriteByte (MSG_ALL, clientno);
1216                 WriteByte (MSG_ALL, e.b_shirt * 16 + who.b_pants);
1217         }
1218
1219         WriteByte (MSG_ALL, SVC_UPDATEFRAGS);
1220         WriteByte (MSG_ALL, clientno);
1221         WriteShort (MSG_ALL, e.frags + 10000);
1222 };
1223
1224 */
1225
1226
1227 // code from here on is just to support maps that don't have team entities
1228 void tdm_spawnteam (string teamname, float teamcolor)
1229 {
1230         local entity e;
1231         e = spawn();
1232         e.classname = "tdm_team";
1233         e.netname = teamname;
1234         e.cnt = teamcolor;
1235         e.team = e.cnt + 1;
1236 };
1237
1238 // spawn some default teams if the map is not set up for tdm
1239 void() tdm_spawnteams =
1240 {
1241         float numteams;
1242
1243         numteams = cvar("g_tdm_teams");
1244
1245         tdm_spawnteam("Red", 4);
1246         tdm_spawnteam("Blue", 13);
1247 };
1248
1249 void() tdm_delayedinit =
1250 {
1251         self.think = SUB_Remove;
1252         self.nextthink = time;
1253         // if no teams are found, spawn defaults
1254         if (find(world, classname, "tdm_team") == world)
1255                 tdm_spawnteams();
1256 };
1257
1258 void() tdm_init =
1259 {
1260         local entity e;
1261         e = spawn();
1262         e.think = tdm_delayedinit;
1263         e.nextthink = time + 0.1;
1264 };
1265
1266