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