Merge branch 'master' into terencehill/ca_fixes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_freezetag.qc
1 .float freezetag_frozen_time;
2 .float freezetag_frozen_timeout;
3 .float freezetag_revive_progress;
4 .entity freezetag_ice;
5 #define ICE_MAX_ALPHA 1
6 #define ICE_MIN_ALPHA 0.1
7 float freezetag_teams;
8
9 void freezetag_count_alive_players()
10 {
11         entity e;
12         total_players = redalive = bluealive = yellowalive = pinkalive = 0;
13         FOR_EACH_PLAYER(e) {
14                 if(e.team == NUM_TEAM_1 && e.health >= 1)
15                 {
16                         ++total_players;
17                         if (!e.freezetag_frozen) ++redalive;
18                 }
19                 else if(e.team == NUM_TEAM_2 && e.health >= 1)
20                 {
21                         ++total_players;
22                         if (!e.freezetag_frozen) ++bluealive;
23                 }
24                 else if(e.team == NUM_TEAM_3 && e.health >= 1)
25                 {
26                         ++total_players;
27                         if (!e.freezetag_frozen) ++yellowalive;
28                 }
29                 else if(e.team == NUM_TEAM_4 && e.health >= 1)
30                 {
31                         ++total_players;
32                         if (!e.freezetag_frozen) ++pinkalive;
33                 }
34         }
35         FOR_EACH_REALCLIENT(e) {
36                 e.redalive_stat = redalive;
37                 e.bluealive_stat = bluealive;
38                 e.yellowalive_stat = yellowalive;
39                 e.pinkalive_stat = pinkalive;
40         }
41 }
42 #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
43 #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
44
45 float prev_missing_teams_mask;
46 float freezetag_CheckTeams()
47 {
48         if(FREEZETAG_ALIVE_TEAMS_OK())
49         {
50                 if(prev_missing_teams_mask > 0)
51                         Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
52                 prev_missing_teams_mask = -1;
53                 return 1;
54         }
55         if(total_players == 0)
56         {
57                 if(prev_missing_teams_mask > 0)
58                         Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
59                 prev_missing_teams_mask = -1;
60                 return 0;
61         }
62         float missing_teams_mask = (!redalive) + (!bluealive) * 2;
63         if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
64         if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
65         if(prev_missing_teams_mask != missing_teams_mask)
66         {
67                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
68                 prev_missing_teams_mask = missing_teams_mask;
69         }
70         return 0;
71 }
72
73 float freezetag_getWinnerTeam()
74 {
75         float winner_team = 0;
76         if(redalive >= 1)
77                 winner_team = NUM_TEAM_1;
78         if(bluealive >= 1)
79         {
80                 if(winner_team) return 0;
81                 winner_team = NUM_TEAM_2;
82         }
83         if(yellowalive >= 1)
84         {
85                 if(winner_team) return 0;
86                 winner_team = NUM_TEAM_3;
87         }
88         if(pinkalive >= 1)
89         {
90                 if(winner_team) return 0;
91                 winner_team = NUM_TEAM_4;
92         }
93         if(winner_team)
94                 return winner_team;
95         return -1; // no player left
96 }
97
98 float freezetag_CheckWinner()
99 {
100         entity e;
101         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
102         {
103                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
104                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
105                 FOR_EACH_PLAYER(e)
106                 {
107                         e.freezetag_frozen_timeout = 0;
108                         e.freezetag_revive_progress = 0;
109                 }
110                 round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
111                 return 1;
112         }
113
114         if(FREEZETAG_ALIVE_TEAMS() > 1)
115                 return 0;
116
117         float winner_team;
118         winner_team = freezetag_getWinnerTeam();
119         if(winner_team > 0)
120         {
121                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
122                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
123                 TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
124         }
125         else if(winner_team == -1)
126         {
127                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
128                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
129         }
130
131         FOR_EACH_PLAYER(e)
132         {
133                 e.freezetag_frozen_timeout = 0;
134                 e.freezetag_revive_progress = 0;
135         }
136         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
137         return 1;
138 }
139
140 entity freezetag_LastPlayerForTeam()
141 {
142         entity pl, last_pl = world;
143         FOR_EACH_PLAYER(pl)
144         {
145                 if(pl.health >= 1)
146                 if(!pl.freezetag_frozen)
147                 if(pl != self)
148                 if(pl.team == self.team)
149                 if(!last_pl)
150                         last_pl = pl;
151                 else
152                         return world;
153         }
154         return last_pl;
155 }
156
157 void freezetag_LastPlayerForTeam_Notify()
158 {
159         if(round_handler_IsActive())
160         if(round_handler_IsRoundStarted())
161         {
162                 entity pl = freezetag_LastPlayerForTeam();
163                 if(pl)
164                         Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
165         }
166 }
167
168 // this is needed to allow the player to turn his view around (fixangle can't
169 // be used to freeze his view, as that also changes the angles), while not
170 // turning that ice object with the player
171 void freezetag_Ice_Think()
172 {
173         setorigin(self, self.owner.origin - '0 0 16');
174         self.nextthink = time;
175 }
176
177 void freezetag_Add_Score(entity attacker)
178 {
179         if(attacker == self)
180         {
181                 // you froze your own dumb self
182                 // counted as "suicide" already
183                 PlayerScore_Add(self, SP_SCORE, -1);
184         }
185         else if(IS_PLAYER(attacker))
186         {
187                 // got frozen by an enemy
188                 // counted as "kill" and "death" already
189                 PlayerScore_Add(self, SP_SCORE, -1);
190                 PlayerScore_Add(attacker, SP_SCORE, +1);
191         }
192         // else nothing - got frozen by the game type rules themselves
193 }
194
195 void freezetag_Freeze(entity attacker)
196 {
197         if(self.freezetag_frozen)
198                 return;
199         self.freezetag_frozen = 1;
200         self.freezetag_frozen_time = time;
201         self.freezetag_revive_progress = 0;
202         self.health = 1;
203         if(autocvar_g_freezetag_frozen_maxtime > 0)
204                 self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
205
206         freezetag_count_alive_players();
207
208         entity ice;
209         ice = spawn();
210         ice.owner = self;
211         ice.classname = "freezetag_ice";
212         ice.think = freezetag_Ice_Think;
213         ice.nextthink = time;
214         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
215         ice.alpha = ICE_MAX_ALPHA;
216         ice.colormod = Team_ColorRGB(self.team);
217         ice.glowmod = ice.colormod;
218         setmodel(ice, "models/ice/ice.md3");
219
220         self.freezetag_ice = ice;
221
222         RemoveGrapplingHook(self);
223
224         // add waypoint
225         WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
226
227         freezetag_Add_Score(attacker);
228 }
229
230 void freezetag_Unfreeze(entity attacker)
231 {
232         self.freezetag_frozen = 0;
233         self.freezetag_frozen_time = 0;
234         self.freezetag_frozen_timeout = 0;
235         self.freezetag_revive_progress = 0;
236
237         remove(self.freezetag_ice);
238         self.freezetag_ice = world;
239
240         if(self.waypointsprite_attached)
241                 WaypointSprite_Kill(self.waypointsprite_attached);
242 }
243
244
245 // ================
246 // Bot player logic
247 // ================
248
249 void() havocbot_role_ft_freeing;
250 void() havocbot_role_ft_offense;
251
252 void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
253 {
254         entity head;
255         float distance;
256
257         FOR_EACH_PLAYER(head)
258         {
259                 if ((head != self) && (head.team == self.team))
260                 {
261                         if (head.freezetag_frozen)
262                         {
263                                 distance = vlen(head.origin - org);
264                                 if (distance > sradius)
265                                         continue;
266                                 navigation_routerating(head, ratingscale, 2000);
267                         }
268                         else
269                         {
270                                 // If teamate is not frozen still seek them out as fight better
271                                 // in a group.
272                                 navigation_routerating(head, ratingscale/3, 2000);
273                         }
274                 }
275         }
276 }
277
278 void havocbot_role_ft_offense()
279 {
280         entity head;
281         float unfrozen;
282
283         if(self.deadflag != DEAD_NO)
284                 return;
285
286         if (!self.havocbot_role_timeout)
287                 self.havocbot_role_timeout = time + random() * 10 + 20;
288
289         // Count how many players on team are unfrozen.
290         unfrozen = 0;
291         FOR_EACH_PLAYER(head)
292         {
293                 if ((head.team == self.team) && (!head.freezetag_frozen))
294                         unfrozen++;
295         }
296
297         // If only one left on team or if role has timed out then start trying to free players.
298         if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
299         {
300                 dprint("changing role to freeing\n");
301                 self.havocbot_role = havocbot_role_ft_freeing;
302                 self.havocbot_role_timeout = 0;
303                 return;
304         }
305
306         if (time > self.bot_strategytime)
307         {
308                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
309
310                 navigation_goalrating_start();
311                 havocbot_goalrating_items(10000, self.origin, 10000);
312                 havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
313                 havocbot_goalrating_freeplayers(9000, self.origin, 10000);
314                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
315                 navigation_goalrating_end();
316         }
317 }
318
319 void havocbot_role_ft_freeing()
320 {
321         if(self.deadflag != DEAD_NO)
322                 return;
323
324         if (!self.havocbot_role_timeout)
325                 self.havocbot_role_timeout = time + random() * 10 + 20;
326
327         if (time > self.havocbot_role_timeout)
328         {
329                 dprint("changing role to offense\n");
330                 self.havocbot_role = havocbot_role_ft_offense;
331                 self.havocbot_role_timeout = 0;
332                 return;
333         }
334
335         if (time > self.bot_strategytime)
336         {
337                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
338
339                 navigation_goalrating_start();
340                 havocbot_goalrating_items(8000, self.origin, 10000);
341                 havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
342                 havocbot_goalrating_freeplayers(20000, self.origin, 10000);
343                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
344                 navigation_goalrating_end();
345         }
346 }
347
348
349 // ==============
350 // Hook Functions
351 // ==============
352
353 MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer)
354 {
355         self.health = 0; // neccessary to update correctly alive stats
356         if(!self.freezetag_frozen)
357                 freezetag_LastPlayerForTeam_Notify();
358         freezetag_Unfreeze(world);
359         freezetag_count_alive_players();
360         return 1;
361 }
362
363 MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
364 {
365         if(round_handler_IsActive())
366         if(round_handler_CountdownRunning())
367         {
368                 if(self.freezetag_frozen)
369                         freezetag_Unfreeze(world);
370                 freezetag_count_alive_players();
371                 return 1; // let the player die so that he can respawn whenever he wants
372         }
373
374         // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
375         // you succeed changing team through the menu: you both really die (gibbing) and get frozen
376         if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
377                 || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE)
378         {
379                 // let the player die, he will be automatically frozen when he respawns
380                 if(!self.freezetag_frozen)
381                 {
382                         freezetag_Add_Score(frag_attacker);
383                         freezetag_count_alive_players();
384                         freezetag_LastPlayerForTeam_Notify();
385                 }
386                 else
387                         freezetag_Unfreeze(world); // remove ice
388                 self.freezetag_frozen_timeout = -2; // freeze on respawn
389                 return 1;
390         }
391
392         if(self.freezetag_frozen)
393                 return 1;
394
395         freezetag_Freeze(frag_attacker);
396         freezetag_LastPlayerForTeam_Notify();
397
398         if(frag_attacker == frag_target || frag_attacker == world)
399         {
400                 if(IS_PLAYER(frag_target))
401                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
402                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
403         }
404         else
405         {
406                 if(IS_PLAYER(frag_target))
407                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname);
408                 if(IS_PLAYER(frag_attacker))
409                         Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname);
410                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
411         }
412
413         frag_target.health = 1; // "respawn" the player :P
414
415         return 1;
416 }
417
418 MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn)
419 {
420         if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
421                 return 1; // do nothing, round is starting right now
422
423         if(self.freezetag_frozen_timeout == -2) // player was dead
424         {
425                 freezetag_Freeze(world);
426                 return 1;
427         }
428
429         freezetag_count_alive_players();
430
431         if(round_handler_IsActive())
432         if(round_handler_IsRoundStarted())
433         {
434                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
435                 freezetag_Freeze(world);
436         }
437
438         return 1;
439 }
440
441 MUTATOR_HOOKFUNCTION(freezetag_reset_map_players)
442 {
443         FOR_EACH_PLAYER(self)
444         {
445                 self.killcount = 0;
446                 if (self.freezetag_frozen)
447                         freezetag_Unfreeze(world);
448                 self.freezetag_frozen_timeout = -1;
449                 PutClientInServer();
450                 self.freezetag_frozen_timeout = 0;
451         }
452         freezetag_count_alive_players();
453         return 1;
454 }
455
456 MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill)
457 {
458         frag_score = 0; // no frags counted in Freeze Tag
459         return 1;
460 }
461
462 .float reviving; // temp var
463 MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
464 {
465         float n;
466
467         if(gameover)
468                 return 1;
469
470         if(self.freezetag_frozen)
471         {
472                 // keep health = 1
473                 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
474         }
475
476         if(round_handler_IsActive())
477         if(!round_handler_IsRoundStarted())
478                 return 1;
479
480         entity o;
481         o = world;
482         if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
483                 self.freezetag_ice.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
484
485         if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
486                 n = -1;
487         else
488         {
489                 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
490                 n = 0;
491                 FOR_EACH_PLAYER(other) if(self != other)
492                 {
493                         if(other.freezetag_frozen == 0)
494                         {
495                                 if(other.team == self.team)
496                                 {
497                                         if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
498                                         {
499                                                 if(!o)
500                                                         o = other;
501                                                 if(self.freezetag_frozen)
502                                                         other.reviving = TRUE;
503                                                 ++n;
504                                         }
505                                 }
506                         }
507                 }
508         }
509
510         if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us
511         {
512                 self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
513                 if(warmup_stage)
514                         self.health = max(1, self.freezetag_revive_progress * warmup_start_health);
515                 else
516                         self.health = max(1, self.freezetag_revive_progress * start_health);
517
518                 if(self.freezetag_revive_progress >= 1)
519                 {
520                         freezetag_Unfreeze(self);
521                         freezetag_count_alive_players();
522
523                         if(n == -1)
524                         {
525                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
526                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime);
527                                 return 1;
528                         }
529
530                         // EVERY team mate nearby gets a point (even if multiple!)
531                         FOR_EACH_PLAYER(other)
532                         {
533                                 if(other.reviving)
534                                 {
535                                         PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
536                                         PlayerScore_Add(other, SP_SCORE, +1);
537                                 }
538                         }
539
540                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
541                         Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
542                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname);
543                 }
544
545                 FOR_EACH_PLAYER(other)
546                 {
547                         if(other.reviving)
548                         {
549                                 other.freezetag_revive_progress = self.freezetag_revive_progress;
550                                 other.reviving = FALSE;
551                         }
552                 }
553         }
554         else if(!n && self.freezetag_frozen) // only if no teammate is nearby will we reset
555         {
556                 self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
557                 if(warmup_stage)
558                         self.health = max(1, self.freezetag_revive_progress * warmup_start_health);
559                 else
560                         self.health = max(1, self.freezetag_revive_progress * start_health);
561         }
562         else if(!n)
563         {
564                 self.freezetag_revive_progress = 0; // thawing nobody
565         }
566
567         return 1;
568 }
569
570 MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
571 {
572         if(self.freezetag_frozen)
573         {
574                 if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self))
575                 {
576                         self.movement_x = bound(-5, self.movement_x, 5);
577                         self.movement_y = bound(-5, self.movement_y, 5);
578                         self.movement_z = bound(-5, self.movement_z, 5);
579                 }
580                 else
581                         self.movement = '0 0 0';
582
583                 self.disableclientprediction = 1;
584         }
585         return 1;
586 }
587
588 MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
589 {
590         if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER)
591         {
592                 if(autocvar_g_freezetag_revive_falldamage > 0)
593                 if(frag_deathtype == DEATH_FALL)
594                 if(frag_damage >= autocvar_g_freezetag_revive_falldamage)
595                 {
596                         freezetag_Unfreeze(frag_target);
597                         frag_target.health = autocvar_g_freezetag_revive_falldamage_health;
598                         pointparticles(particleeffectnum("iceorglass"), frag_target.origin, '0 0 0', 3);
599                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, frag_target.netname);
600                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_FALL);
601                 }
602
603                 frag_damage = 0;
604                 frag_force = frag_force * autocvar_g_freezetag_frozen_force;
605         }
606         return 1;
607 }
608
609 MUTATOR_HOOKFUNCTION(freezetag_PlayerJump)
610 {
611         if(self.freezetag_frozen)
612                 return TRUE; // no jumping in freezetag when frozen
613
614         return FALSE;
615 }
616
617 MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
618 {
619         if (self.freezetag_frozen)
620                 return 1;
621         return 0;
622 }
623
624 MUTATOR_HOOKFUNCTION(freezetag_ItemTouch)
625 {
626         if (other.freezetag_frozen)
627                 return MUT_ITEMTOUCH_RETURN;
628         return MUT_ITEMTOUCH_CONTINUE;
629 }
630
631 MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
632 {
633         if (!self.deadflag)
634         {
635                 if (random() < 0.5)
636                         self.havocbot_role = havocbot_role_ft_freeing;
637                 else
638                         self.havocbot_role = havocbot_role_ft_offense;
639         }
640
641         return TRUE;
642 }
643
644 MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy)
645 {
646         self.freezetag_frozen = other.freezetag_frozen;
647         self.freezetag_revive_progress = other.freezetag_revive_progress;
648         return 0;
649 }
650
651 MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
652 {
653         ret_float = freezetag_teams;
654         return 0;
655 }
656
657 MUTATOR_HOOKFUNCTION(freezetag_VehicleTouch)
658 {
659         if(other.freezetag_frozen)
660                 return TRUE;
661
662         return FALSE;
663 }
664
665 void freezetag_Initialize()
666 {
667         precache_model("models/ice/ice.md3");
668
669         freezetag_teams = autocvar_g_freezetag_teams_override;
670         if(freezetag_teams < 2)
671                 freezetag_teams = autocvar_g_freezetag_teams;
672         freezetag_teams = bound(2, freezetag_teams, 4);
673         ScoreRules_freezetag(freezetag_teams);
674
675         round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
676         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
677
678         addstat(STAT_REDALIVE, AS_INT, redalive_stat);
679         addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
680         addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
681         addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
682
683         addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
684         addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
685 }
686
687 MUTATOR_DEFINITION(gamemode_freezetag)
688 {
689         MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
690         MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY);
691         MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY);
692         MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY);
693         MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
694         MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
695         MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
696         MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
697         MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY);
698         MUTATOR_HOOK(PlayerJump, freezetag_PlayerJump, CBC_ORDER_ANY);
699         MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY);
700         MUTATOR_HOOK(ItemTouch, freezetag_ItemTouch, CBC_ORDER_ANY);
701         MUTATOR_HOOK(HavocBot_ChooseRole, freezetag_BotRoles, CBC_ORDER_ANY);
702         MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY);
703         MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE);
704         MUTATOR_HOOK(VehicleTouch, freezetag_VehicleTouch, CBC_ORDER_ANY);
705
706         MUTATOR_ONADD
707         {
708                 if(time > 1) // game loads at time 1
709                         error("This is a game type and it cannot be added at runtime.");
710                 freezetag_Initialize();
711         }
712
713         MUTATOR_ONROLLBACK_OR_REMOVE
714         {
715                 // we actually cannot roll back freezetag_Initialize here
716                 // BUT: we don't need to! If this gets called, adding always
717                 // succeeds.
718         }
719
720         MUTATOR_ONREMOVE
721         {
722                 print("This is a game type and it cannot be removed at runtime.");
723                 return -1;
724         }
725
726         return 0;
727 }