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