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