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