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