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