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