6919518b8340ff1e65fa1f16d615eaed9eb7582b
[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_ft_freeplayers(entity this, float ratingscale, vector org, float sradius)
233 {
234         entity best_pl = NULL;
235         float best_dist2 = FLOAT_MAX;
236         FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
237                 if (STAT(FROZEN, it) == 1)
238                 {
239                         if(vdist(it.origin - org, >, sradius))
240                                 continue;
241                         navigation_routerating(this, it, ratingscale, 2000);
242                 }
243                 else if (best_dist2
244                         && GetResourceAmount(it, RESOURCE_HEALTH) < GetResourceAmount(this, RESOURCE_HEALTH) + 30
245                         && vlen2(it.origin - org) < best_dist2)
246                 {
247                         // If teamate is not frozen still seek them out as fight better
248                         // in a group.
249                         best_dist2 = vlen2(it.origin - org);
250                         if (best_dist2 < 700 ** 2)
251                         {
252                                 best_pl = NULL;
253                                 best_dist2 = 0; // already close to a teammate
254                         }
255                         else
256                                 best_pl = it;
257                 }
258         });
259         if (best_pl)
260                 navigation_routerating(this, best_pl, ratingscale / 2, 2000);
261 }
262
263 void havocbot_role_ft_offense(entity this)
264 {
265         if(IS_DEAD(this))
266                 return;
267
268         if (!this.havocbot_role_timeout)
269                 this.havocbot_role_timeout = time + random() * 10 + 20;
270
271         // Count how many players on team are unfrozen.
272         int unfrozen = 0;
273         FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !STAT(FROZEN, it), { unfrozen++; });
274
275         // If only one left on team or if role has timed out then start trying to free players.
276         if ((unfrozen == 0 && !STAT(FROZEN, this)) || time > this.havocbot_role_timeout)
277         {
278                 LOG_TRACE("changing role to freeing");
279                 this.havocbot_role = havocbot_role_ft_freeing;
280                 this.havocbot_role_timeout = 0;
281                 return;
282         }
283
284         if (navigation_goalrating_timeout(this))
285         {
286                 navigation_goalrating_start(this);
287                 havocbot_goalrating_items(this, 12000, this.origin, 10000);
288                 havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
289                 havocbot_goalrating_ft_freeplayers(this, 9000, this.origin, 10000);
290                 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
291                 navigation_goalrating_end(this);
292
293                 navigation_goalrating_timeout_set(this);
294         }
295 }
296
297 void havocbot_role_ft_freeing(entity this)
298 {
299         if(IS_DEAD(this))
300                 return;
301
302         if (!this.havocbot_role_timeout)
303                 this.havocbot_role_timeout = time + random() * 10 + 20;
304
305         if (time > this.havocbot_role_timeout)
306         {
307                 LOG_TRACE("changing role to offense");
308                 this.havocbot_role = havocbot_role_ft_offense;
309                 this.havocbot_role_timeout = 0;
310                 return;
311         }
312
313         if (navigation_goalrating_timeout(this))
314         {
315                 navigation_goalrating_start(this);
316                 havocbot_goalrating_items(this, 10000, this.origin, 10000);
317                 havocbot_goalrating_enemyplayers(this, 5000, this.origin, 10000);
318                 havocbot_goalrating_ft_freeplayers(this, 20000, this.origin, 10000);
319                 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
320                 navigation_goalrating_end(this);
321
322                 navigation_goalrating_timeout_set(this);
323         }
324 }
325
326
327 // ==============
328 // Hook Functions
329 // ==============
330
331 void ft_RemovePlayer(entity this)
332 {
333         SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to update correctly alive stats
334         if(!STAT(FROZEN, this))
335                 freezetag_LastPlayerForTeam_Notify(this);
336         Unfreeze(this);
337         freezetag_count_alive_players();
338 }
339
340 MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
341 {
342         entity player = M_ARGV(0, entity);
343
344         ft_RemovePlayer(player);
345         return true;
346 }
347
348 MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
349 {
350         entity player = M_ARGV(0, entity);
351
352         ft_RemovePlayer(player);
353 }
354
355 MUTATOR_HOOKFUNCTION(ft, PlayerDies)
356 {
357         entity frag_attacker = M_ARGV(1, entity);
358         entity frag_target = M_ARGV(2, entity);
359         float frag_deathtype = M_ARGV(3, float);
360
361         if(round_handler_IsActive())
362         if(round_handler_CountdownRunning())
363         {
364                 if(STAT(FROZEN, frag_target))
365                         Unfreeze(frag_target);
366                 freezetag_count_alive_players();
367                 return true; // let the player die so that he can respawn whenever he wants
368         }
369
370         // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
371         // you succeed changing team through the menu: you both really die (gibbing) and get frozen
372         if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
373                 || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
374         {
375                 // let the player die, he will be automatically frozen when he respawns
376                 if(STAT(FROZEN, frag_target) != 1)
377                 {
378                         freezetag_Add_Score(frag_target, frag_attacker);
379                         freezetag_count_alive_players();
380                         freezetag_LastPlayerForTeam_Notify(frag_target);
381                 }
382                 else
383                         Unfreeze(frag_target); // remove ice
384                 SetResourceAmountExplicit(frag_target, RESOURCE_HEALTH, 0); // Unfreeze resets health
385                 frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
386                 return true;
387         }
388
389         if(STAT(FROZEN, frag_target))
390                 return true;
391
392         freezetag_Freeze(frag_target, frag_attacker);
393         freezetag_LastPlayerForTeam_Notify(frag_target);
394
395         if(frag_attacker == frag_target || frag_attacker == NULL)
396         {
397                 if(IS_PLAYER(frag_target))
398                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
399                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
400         }
401         else
402         {
403                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
404         }
405
406         return true;
407 }
408
409 MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
410 {
411         entity player = M_ARGV(0, entity);
412
413         if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
414                 return true; // do nothing, round is starting right now
415
416         if(player.freezetag_frozen_timeout == -2) // player was dead
417         {
418                 freezetag_Freeze(player, NULL);
419                 return true;
420         }
421
422         freezetag_count_alive_players();
423
424         if(round_handler_IsActive())
425         if(round_handler_IsRoundStarted())
426         {
427                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
428                 freezetag_Freeze(player, NULL);
429         }
430
431         return true;
432 }
433
434 MUTATOR_HOOKFUNCTION(ft, reset_map_players)
435 {
436         FOREACH_CLIENT(IS_PLAYER(it), {
437                 CS(it).killcount = 0;
438                 it.freezetag_frozen_timeout = -1;
439                 PutClientInServer(it);
440                 it.freezetag_frozen_timeout = 0;
441         });
442         freezetag_count_alive_players();
443         return true;
444 }
445
446 MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
447 {
448         M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
449         return true;
450 }
451
452 MUTATOR_HOOKFUNCTION(ft, Unfreeze)
453 {
454         entity targ = M_ARGV(0, entity);
455         targ.freezetag_frozen_time = 0;
456         targ.freezetag_frozen_timeout = 0;
457
458         freezetag_count_alive_players();
459 }
460
461 MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
462 {
463         if(game_stopped)
464                 return true;
465
466         if(round_handler_IsActive())
467         if(!round_handler_IsRoundStarted())
468                 return true;
469
470         int n;
471         entity o = NULL;
472         entity player = M_ARGV(0, entity);
473         //if(STAT(FROZEN, player))
474         //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
475                 //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);
476
477         if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
478                 n = -1;
479         else
480         {
481                 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
482                 n = 0;
483                 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
484                         if(STAT(FROZEN, it) == 0)
485                         if(!IS_DEAD(it))
486                         if(SAME_TEAM(it, player))
487                         if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
488                         {
489                                 if(!o)
490                                         o = it;
491                                 if(STAT(FROZEN, player) == 1)
492                                         it.reviving = true;
493                                 ++n;
494                         }
495                 });
496
497         }
498
499         if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
500         {
501                 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
502                 SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
503
504                 if(STAT(REVIVE_PROGRESS, player) >= 1)
505                 {
506                         Unfreeze(player);
507                         freezetag_count_alive_players();
508
509                         if(n == -1)
510                         {
511                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
512                                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
513                                 return true;
514                         }
515
516                         // EVERY team mate nearby gets a point (even if multiple!)
517                         FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
518                                 GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
519                                 GameRules_scoring_add(it, SCORE, +1);
520                                 nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
521                         });
522
523                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
524                         Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
525                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
526                 }
527
528                 FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
529                         STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
530                         it.reviving = false;
531                 });
532         }
533         else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
534         {
535                 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
536                 SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
537         }
538         else if(!n && !STAT(FROZEN, player))
539         {
540                 STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
541         }
542
543         return true;
544 }
545
546 MUTATOR_HOOKFUNCTION(ft, SetStartItems)
547 {
548         start_items &= ~IT_UNLIMITED_AMMO;
549         //start_health       = warmup_start_health       = cvar("g_lms_start_health");
550         //start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
551         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
552         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
553         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
554         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
555         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
556         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
557 }
558
559 MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
560 {
561         entity bot = M_ARGV(0, entity);
562
563         if (!IS_DEAD(bot))
564         {
565                 if (random() < 0.5)
566                         bot.havocbot_role = havocbot_role_ft_freeing;
567                 else
568                         bot.havocbot_role = havocbot_role_ft_offense;
569         }
570
571         // if bots spawn all at once assign them a more appropriated role after a while
572         if (time < CS(bot).jointime + 1)
573                 bot.havocbot_role_timeout = time + 10 + random() * 10;
574
575         return true;
576 }
577
578 MUTATOR_HOOKFUNCTION(ft, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
579 {
580         M_ARGV(0, float) = freezetag_teams;
581         return true;
582 }
583
584 MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
585 {
586         // most weapons arena
587         if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
588                 M_ARGV(0, string) = "most";
589 }
590
591 MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
592 {
593         entity frag_attacker = M_ARGV(0, entity);
594         entity frag_target = M_ARGV(1, entity);
595         //float frag_deathtype = M_ARGV(2, float);
596         int kill_count_to_attacker = M_ARGV(3, int);
597         int kill_count_to_target = M_ARGV(4, int);
598
599         if(STAT(FROZEN, frag_target))
600                 return; // target was already frozen, so this is just pushing them off the cliff
601
602         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));
603         Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
604                 GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
605
606         return true;
607 }
608
609 void freezetag_Initialize()
610 {
611         freezetag_teams = autocvar_g_freezetag_teams_override;
612         if(freezetag_teams < 2)
613                 freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
614
615         freezetag_teams = BITS(bound(2, freezetag_teams, 4));
616         GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
617                 field(SP_FREEZETAG_REVIVALS, "revivals", 0);
618         });
619
620         round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
621         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
622
623         EliminatedPlayers_Init(freezetag_isEliminated);
624 }
625 #endif