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