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