]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_freezetag.qc
Fix compilation units.
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_freezetag.qc
1 #include "gamemode_freezetag.qh"
2
3 #include <server/resources.qh>
4
5 float autocvar_g_freezetag_frozen_maxtime;
6 float autocvar_g_freezetag_revive_clearspeed;
7 float autocvar_g_freezetag_round_timelimit;
8 //int autocvar_g_freezetag_teams;
9 int autocvar_g_freezetag_teams_override;
10 float autocvar_g_freezetag_warmup;
11
12 void freezetag_count_alive_players()
13 {
14         total_players = 0;
15         for (int i = 1; i <= NUM_TEAMS; ++i)
16         {
17                 Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
18         }
19         FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
20         {
21                 ++total_players;
22                 if ((GetResourceAmount(it, RESOURCE_HEALTH) < 1) ||
23                         (STAT(FROZEN, it) == 1))
24                 {
25                         continue;
26                 }
27                 entity team_ = Entity_GetTeam(it);
28                 int num_alive = Team_GetNumberOfAlivePlayers(team_);
29                 ++num_alive;
30                 Team_SetNumberOfAlivePlayers(team_, num_alive);
31         });
32         FOREACH_CLIENT(IS_REAL_CLIENT(it),
33         {
34                 STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
35                         1));
36                 STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
37                         Team_GetTeamFromIndex(2));
38                 STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
39                         Team_GetTeamFromIndex(3));
40                 STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
41                         Team_GetTeamFromIndex(4));
42         });
43
44         eliminatedPlayers.SendFlags |= 1;
45 }
46
47 #define FREEZETAG_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(freezetag_teams))
48
49 float freezetag_CheckTeams()
50 {
51         static float prev_missing_teams_mask;
52         if(FREEZETAG_ALIVE_TEAMS_OK())
53         {
54                 if(prev_missing_teams_mask > 0)
55                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
56                 prev_missing_teams_mask = -1;
57                 return 1;
58         }
59         if(total_players == 0)
60         {
61                 if(prev_missing_teams_mask > 0)
62                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
63                 prev_missing_teams_mask = -1;
64                 return 0;
65         }
66         int missing_teams_mask = 0;
67         for (int i = 1; i <= NUM_TEAMS; ++i)
68         {
69                 if ((freezetag_teams & Team_IndexToBit(i)) &&
70                         (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
71                 {
72                         missing_teams_mask |= Team_IndexToBit(i);
73                 }
74         }
75         if(prev_missing_teams_mask != missing_teams_mask)
76         {
77                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
78                 prev_missing_teams_mask = missing_teams_mask;
79         }
80         return 0;
81 }
82
83 int freezetag_getWinnerTeam()
84 {
85         int winner_team = 0;
86         if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
87         {
88                 winner_team = NUM_TEAM_1;
89         }
90         for (int i = 2; i <= NUM_TEAMS; ++i)
91         {
92                 if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
93                 {
94                         if (winner_team != 0)
95                         {
96                                 return 0;
97                         }
98                         winner_team = Team_IndexToTeam(i);
99                 }
100         }
101         if (winner_team)
102         {
103                 return winner_team;
104         }
105         return -1; // no player left
106 }
107
108 void nades_Clear(entity);
109 void nades_GiveBonus(entity player, float score);
110
111 float freezetag_CheckWinner()
112 {
113         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
114         {
115                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
116                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
117                 FOREACH_CLIENT(IS_PLAYER(it), {
118                         it.freezetag_frozen_timeout = 0;
119                         nades_Clear(it);
120                 });
121                 game_stopped = true;
122                 round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
123                 return 1;
124         }
125
126         if (Team_GetNumberOfAliveTeams() > 1)
127         {
128                 return 0;
129         }
130
131         int winner_team = freezetag_getWinnerTeam();
132         if(winner_team > 0)
133         {
134                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
135                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
136                 TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
137         }
138         else if(winner_team == -1)
139         {
140                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
141                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
142         }
143
144         FOREACH_CLIENT(IS_PLAYER(it), {
145                 it.freezetag_frozen_timeout = 0;
146                 nades_Clear(it);
147         });
148
149         game_stopped = true;
150         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
151         return 1;
152 }
153
154 entity freezetag_LastPlayerForTeam(entity this)
155 {
156         entity last_pl = NULL;
157         FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
158                 if(it.health >= 1)
159                 if(!STAT(FROZEN, it))
160                 if(SAME_TEAM(it, this))
161                 if(!last_pl)
162                         last_pl = it;
163                 else
164                         return NULL;
165         });
166         return last_pl;
167 }
168
169 void freezetag_LastPlayerForTeam_Notify(entity this)
170 {
171         if(round_handler_IsActive())
172         if(round_handler_IsRoundStarted())
173         {
174                 entity pl = freezetag_LastPlayerForTeam(this);
175                 if(pl)
176                         Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
177         }
178 }
179
180 void freezetag_Add_Score(entity targ, entity attacker)
181 {
182         if(attacker == targ)
183         {
184                 // you froze your own dumb targ
185                 // counted as "suicide" already
186                 GameRules_scoring_add(targ, SCORE, -1);
187         }
188         else if(IS_PLAYER(attacker))
189         {
190                 // got frozen by an enemy
191                 // counted as "kill" and "death" already
192                 GameRules_scoring_add(targ, SCORE, -1);
193                 GameRules_scoring_add(attacker, SCORE, +1);
194         }
195         // else nothing - got frozen by the game type rules themselves
196 }
197
198 void freezetag_Freeze(entity targ, entity attacker)
199 {
200         if(STAT(FROZEN, targ))
201                 return;
202
203         if(autocvar_g_freezetag_frozen_maxtime > 0)
204                 targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
205
206         Freeze(targ, 0, 1, true);
207
208         freezetag_count_alive_players();
209
210         freezetag_Add_Score(targ, attacker);
211 }
212
213 void freezetag_Unfreeze(entity this)
214 {
215         this.freezetag_frozen_time = 0;
216         this.freezetag_frozen_timeout = 0;
217
218         Unfreeze(this);
219 }
220
221 float freezetag_isEliminated(entity e)
222 {
223         if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e)))
224                 return true;
225         return false;
226 }
227
228
229 // ================
230 // Bot player logic
231 // ================
232
233 void(entity this) havocbot_role_ft_freeing;
234 void(entity this) havocbot_role_ft_offense;
235
236 void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
237 {
238         float t;
239         FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
240                 if (STAT(FROZEN, it) == 1)
241                 {
242                         if(vdist(it.origin - org, >, sradius))
243                                 continue;
244                         navigation_routerating(this, it, ratingscale, 2000);
245                 }
246                 else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
247                 {
248                         // If teamate is not frozen still seek them out as fight better
249                         // in a group.
250                         t = 0.2 * 150 / (this.health + this.armorvalue);
251                         navigation_routerating(this, it, t * ratingscale, 2000);
252                 }
253         });
254 }
255
256 void havocbot_role_ft_offense(entity this)
257 {
258         if(IS_DEAD(this))
259                 return;
260
261         if (!this.havocbot_role_timeout)
262                 this.havocbot_role_timeout = time + random() * 10 + 20;
263
264         // Count how many players on team are unfrozen.
265         int unfrozen = 0;
266         FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
267
268         // If only one left on team or if role has timed out then start trying to free players.
269         if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
270         {
271                 LOG_TRACE("changing role to freeing");
272                 this.havocbot_role = havocbot_role_ft_freeing;
273                 this.havocbot_role_timeout = 0;
274                 return;
275         }
276
277         if (navigation_goalrating_timeout(this))
278         {
279                 navigation_goalrating_start(this);
280                 havocbot_goalrating_items(this, 10000, this.origin, 10000);
281                 havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
282                 havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
283                 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
284                 navigation_goalrating_end(this);
285
286                 navigation_goalrating_timeout_set(this);
287         }
288 }
289
290 void havocbot_role_ft_freeing(entity this)
291 {
292         if(IS_DEAD(this))
293                 return;
294
295         if (!this.havocbot_role_timeout)
296                 this.havocbot_role_timeout = time + random() * 10 + 20;
297
298         if (time > this.havocbot_role_timeout)
299         {
300                 LOG_TRACE("changing role to offense");
301                 this.havocbot_role = havocbot_role_ft_offense;
302                 this.havocbot_role_timeout = 0;
303                 return;
304         }
305
306         if (navigation_goalrating_timeout(this))
307         {
308                 navigation_goalrating_start(this);
309                 havocbot_goalrating_items(this, 8000, this.origin, 10000);
310                 havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
311                 havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
312                 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
313                 navigation_goalrating_end(this);
314
315                 navigation_goalrating_timeout_set(this);
316         }
317 }
318
319
320 // ==============
321 // Hook Functions
322 // ==============
323
324 void ft_RemovePlayer(entity this)
325 {
326         this.health = 0; // neccessary to update correctly alive stats
327         if(!STAT(FROZEN, this))
328                 freezetag_LastPlayerForTeam_Notify(this);
329         freezetag_Unfreeze(this);
330         freezetag_count_alive_players();
331 }
332
333 MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
334 {
335         entity player = M_ARGV(0, entity);
336
337         ft_RemovePlayer(player);
338         return true;
339 }
340
341 MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
342 {
343         entity player = M_ARGV(0, entity);
344
345         ft_RemovePlayer(player);
346 }
347
348 MUTATOR_HOOKFUNCTION(ft, PlayerDies)
349 {
350         entity frag_attacker = M_ARGV(1, entity);
351         entity frag_target = M_ARGV(2, entity);
352         float frag_deathtype = M_ARGV(3, float);
353
354         if(round_handler_IsActive())
355         if(round_handler_CountdownRunning())
356         {
357                 if(STAT(FROZEN, frag_target))
358                         freezetag_Unfreeze(frag_target);
359                 freezetag_count_alive_players();
360                 return true; // let the player die so that he can respawn whenever he wants
361         }
362
363         // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
364         // you succeed changing team through the menu: you both really die (gibbing) and get frozen
365         if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
366                 || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
367         {
368                 // let the player die, he will be automatically frozen when he respawns
369                 if(STAT(FROZEN, frag_target) != 1)
370                 {
371                         freezetag_Add_Score(frag_target, frag_attacker);
372                         freezetag_count_alive_players();
373                         freezetag_LastPlayerForTeam_Notify(frag_target);
374                 }
375                 else
376                         freezetag_Unfreeze(frag_target); // remove ice
377                 frag_target.health = 0; // Unfreeze resets health
378                 frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
379                 return true;
380         }
381
382         if(STAT(FROZEN, frag_target))
383                 return true;
384
385         freezetag_Freeze(frag_target, frag_attacker);
386         freezetag_LastPlayerForTeam_Notify(frag_target);
387
388         if(frag_attacker == frag_target || frag_attacker == NULL)
389         {
390                 if(IS_PLAYER(frag_target))
391                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
392                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
393         }
394         else
395         {
396                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
397         }
398
399         return true;
400 }
401
402 MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
403 {
404         entity player = M_ARGV(0, entity);
405
406         if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
407                 return true; // do nothing, round is starting right now
408
409         if(player.freezetag_frozen_timeout == -2) // player was dead
410         {
411                 freezetag_Freeze(player, NULL);
412                 return true;
413         }
414
415         freezetag_count_alive_players();
416
417         if(round_handler_IsActive())
418         if(round_handler_IsRoundStarted())
419         {
420                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
421                 freezetag_Freeze(player, NULL);
422         }
423
424         return true;
425 }
426
427 MUTATOR_HOOKFUNCTION(ft, reset_map_players)
428 {
429         FOREACH_CLIENT(IS_PLAYER(it), {
430                 CS(it).killcount = 0;
431                 it.freezetag_frozen_timeout = -1;
432                 PutClientInServer(it);
433                 it.freezetag_frozen_timeout = 0;
434         });
435         freezetag_count_alive_players();
436         return true;
437 }
438
439 MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
440 {
441         M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
442         return true;
443 }
444
445 MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
446 {
447         if(game_stopped)
448                 return true;
449
450         if(round_handler_IsActive())
451         if(!round_handler_IsRoundStarted())
452                 return true;
453
454         int n;
455         entity o = NULL;
456         entity player = M_ARGV(0, entity);
457         //if(STAT(FROZEN, player))
458         //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
459                 //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
460
461         if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
462                 n = -1;
463         else
464         {
465                 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
466                 n = 0;
467                 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
468                         if(STAT(FROZEN, it) == 0)
469                         if(!IS_DEAD(it))
470                         if(SAME_TEAM(it, player))
471                         if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
472                         {
473                                 if(!o)
474                                         o = it;
475                                 if(STAT(FROZEN, player) == 1)
476                                         it.reviving = true;
477                                 ++n;
478                         }
479                 });
480
481         }
482
483         if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
484         {
485                 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
486                 player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
487
488                 if(STAT(REVIVE_PROGRESS, player) >= 1)
489                 {
490                         freezetag_Unfreeze(player);
491                         freezetag_count_alive_players();
492
493                         if(n == -1)
494                         {
495                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
496                                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
497                                 return true;
498                         }
499
500                         // EVERY team mate nearby gets a point (even if multiple!)
501                         FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
502                                 GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
503                                 GameRules_scoring_add(it, SCORE, +1);
504                                 nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
505                         });
506
507                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
508                         Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
509                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
510                 }
511
512                 FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
513                         STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
514                         it.reviving = false;
515                 });
516         }
517         else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
518         {
519                 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
520                 player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
521         }
522         else if(!n && !STAT(FROZEN, player))
523         {
524                 STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
525         }
526
527         return true;
528 }
529
530 MUTATOR_HOOKFUNCTION(ft, SetStartItems)
531 {
532         start_items &= ~IT_UNLIMITED_AMMO;
533         //start_health       = warmup_start_health       = cvar("g_lms_start_health");
534         //start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
535         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
536         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
537         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
538         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
539         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
540         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
541 }
542
543 MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
544 {
545         entity bot = M_ARGV(0, entity);
546
547         if (!IS_DEAD(bot))
548         {
549                 if (random() < 0.5)
550                         bot.havocbot_role = havocbot_role_ft_freeing;
551                 else
552                         bot.havocbot_role = havocbot_role_ft_offense;
553         }
554
555         return true;
556 }
557
558 MUTATOR_HOOKFUNCTION(ft, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
559 {
560         M_ARGV(0, float) = freezetag_teams;
561         return true;
562 }
563
564 MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
565 {
566         // most weapons arena
567         if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
568                 M_ARGV(0, string) = "most";
569 }
570
571 MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
572 {
573         entity frag_attacker = M_ARGV(0, entity);
574         entity frag_target = M_ARGV(1, entity);
575         //float frag_deathtype = M_ARGV(2, float);
576         int kill_count_to_attacker = M_ARGV(3, int);
577         int kill_count_to_target = M_ARGV(4, int);
578
579         if(STAT(FROZEN, frag_target))
580                 return; // target was already frozen, so this is just pushing them off the cliff
581
582         Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
583         Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, frag_attacker.health, frag_attacker.armorvalue, (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
584
585         return true;
586 }
587
588 void freezetag_Initialize()
589 {
590         freezetag_teams = autocvar_g_freezetag_teams_override;
591         if(freezetag_teams < 2)
592                 freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
593
594         freezetag_teams = BITS(bound(2, freezetag_teams, 4));
595         GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
596             field(SP_FREEZETAG_REVIVALS, "revivals", 0);
597         });
598
599         round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
600         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
601
602         EliminatedPlayers_Init(freezetag_isEliminated);
603 }