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