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