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