]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_towerdefense.qc
Don't reset generator health at round start
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_towerdefense.qc
1 float redalive, bluealive, total_alive;
2
3 var float max_monsters = 20;
4 var float max_alive = 10;
5
6 float total_killed;
7
8 var float max_turrets = 3;
9
10 .float newfuel; // hack to not give players fuel every time they spawn
11
12 float last_check;
13
14 .float turret_cnt;
15
16 .float level;
17 .float last_trace;
18
19 void td_debug(string input)
20 {
21         switch(autocvar_g_td_debug)
22         {
23                 case 1: dprint(input); break;
24                 case 2: print(input); break;
25         }
26 }
27
28 void td_waypoint_link(float tm, vector from, vector to)
29 {
30         switch(tm)
31         {
32                 case NUM_TEAM_1:
33                         WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_red"), from, to);
34                         break;
35                 case NUM_TEAM_2:
36                         WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_blue"), from, to);
37                         break;
38         }
39 }
40
41 void td_waypoint_think()
42 {
43         if(gameover)
44         {
45                 remove(self);
46                 return;
47         }
48         
49         if(time >= self.last_trace)
50         {
51                 entity e, e2, e3;
52                 
53                 e = find(world, targetname, self.target);
54                 e2 = find(world, target, self.targetname);
55                 e3 = find(world, targetname, self.target2);
56                 
57                 if(e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
58                         td_waypoint_link(self.team, self.origin, e.origin);
59                 if(e2.classname == "td_spawnpoint")
60                         td_waypoint_link(self.team, self.origin, e2.origin);
61                 if(e3.classname == "td_waypoint" || e3.flags & FL_GENERATOR)
62                         td_waypoint_link(self.team, self.origin, e3.origin);
63                 
64                 self.last_trace = time + 0.5;
65         }
66         
67         self.nextthink = time + 0.1;
68 }
69
70 void td_generator_die() 
71 {
72         if(autocvar_sv_eventlog)
73                 GameLogEcho(":gendestroyed");
74         
75         Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED);
76         
77         self.solid                      = SOLID_NOT;
78         self.takedamage         = DAMAGE_NO;
79         self.event_damage   = func_null;
80         self.enemy                      = world;
81         
82         WaypointSprite_Kill(self.sprite);
83 }
84
85 void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) 
86 {
87         if(IS_PLAYER(attacker) || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE || self.takedamage == DAMAGE_NO)
88                 return;
89                 
90         entity head;
91                 
92         if (time > self.pain_finished)
93         {
94                 self.pain_finished = time + 10;
95                 play2team(self.team, "onslaught/generator_underattack.wav");
96         }
97         
98         if (random() < 0.5)
99                 spamsound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTN_NORM);
100         else
101                 spamsound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM);
102         
103         
104         FOR_EACH_REALPLAYER(head)
105         if(!IsDifferentTeam(head, self))
106                 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_TD_GENDAMAGED);
107         
108         self.health -= damage;
109         
110         WaypointSprite_UpdateHealth(self.sprite, self.health);
111                 
112         if(self.health <= 0)
113         {
114                 FOR_EACH_REALPLAYER(head)
115                 if(!IsDifferentTeam(head, attacker))
116                         PlayerScore_Add(head, SP_TD_DESTROYS, 1);
117                 
118                 TeamScore_AddToTeam(attacker.team, ST_TD_DESTROYS, 1);
119                 td_generator_die();
120         }
121                 
122         self.SendFlags |= GSF_STATUS;
123 }
124
125 void td_generator_reset()
126 {
127         self.SendFlags |= GSF_SETUP;
128 }
129
130 void td_generator_setup()
131 {
132         self.think                      = func_null;
133         self.nextthink          = -1;
134         self.solid                      = SOLID_BBOX;
135         self.takedamage         = DAMAGE_AIM;
136         self.event_damage       = td_generator_damage;
137         self.movetype           = MOVETYPE_NONE;
138         self.monster_attack     = TRUE;
139         self.SendFlags          = GSF_SETUP;
140         self.netname            = "Generator";
141         self.reset                      = td_generator_reset;
142         
143         WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 90', self, sprite, RADARICON_OBJECTIVE, Team_ColorRGB(self.team));   
144         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
145         WaypointSprite_UpdateHealth(self.sprite, self.health);
146 }
147
148 void AnnounceSpawn(string anounce)
149 {
150         entity e;
151         Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce);
152         
153         FOR_EACH_REALCLIENT(e) soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTN_NONE);
154 }
155
156 entity PickSpawn (float tm)
157 {
158         entity e;
159         RandomSelection_Init();
160         for(e = world;(e = find(e, classname, "td_spawnpoint")); )
161         if(e.team == tm)
162                 RandomSelection_Add(e, 0, string_null, 1, 1);
163
164         return RandomSelection_chosen_ent;
165 }
166
167 void TD_SpawnMonster(float tm, string mnster)
168 {
169         entity e, mon;
170         
171         e = PickSpawn(tm);
172                 
173         if(e == world)
174         {
175                 td_debug("Warning: couldn't find any td_spawnpoint spawnpoints, no monsters will spawn!\n");
176                 return;
177         }
178   
179         mon = spawnmonster(mnster, e, e, e.origin, FALSE, 2);
180         if(e.target2)
181         {
182                 if(random() <= 0.5 && e.target)
183                         mon.target2 = e.target;
184                 else
185                         mon.target2 = e.target2;
186         }
187         else
188                 mon.target2 = e.target;
189 }
190
191 string monster_type2string(float mnster)
192 {
193         switch(mnster)
194         {
195                 case MONSTER_ZOMBIE: return "zombie";
196                 case MONSTER_BRUTE: return "brute";
197                 case MONSTER_ANIMUS: return "animus";
198                 case MONSTER_SHAMBLER: return "shambler";
199                 case MONSTER_BRUISER: return "bruiser";
200                 case MONSTER_WYVERN: return "wyvern";
201                 case MONSTER_CERBERUS: return "cerberus";
202                 case MONSTER_SLIME: return "slime";
203                 case MONSTER_KNIGHT: return "knight";
204                 case MONSTER_STINGRAY: return "stingray";
205                 case MONSTER_MAGE: return "mage";
206                 case MONSTER_SPIDER: return "spider";
207                 default: return "";
208         }
209 }
210
211 float RandomMonster()
212 {
213         RandomSelection_Init();
214         
215         float i;
216         
217         for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
218         {
219                 if(i == MONSTER_STINGRAY || i == MONSTER_WYVERN)
220                         continue; // flying/swimming monsters not yet supported
221                 
222                 RandomSelection_Add(world, i, "", 1, 1);
223         }
224         
225         return RandomSelection_chosen_float;
226 }
227
228 void SpawnMonsters(float tm)
229 {
230         float whichmon;
231         
232         whichmon = RandomMonster();
233         
234         TD_SpawnMonster(tm, monster_type2string(whichmon));
235 }
236
237 entity PickGenerator(float tm)
238 {
239         entity head;
240         
241         RandomSelection_Init();
242         for(head = world;(head = findflags(head, flags, FL_GENERATOR)); )
243         if(head.team != tm)
244                 RandomSelection_Add(head, 0, string_null, 1, 1);
245         
246         return RandomSelection_chosen_ent;
247 }
248
249 float td_checkfuel(entity ent, string tur)
250 {
251         float turcost = cvar(strcat("g_td_turret_", tur, "_cost"));
252         
253         if(ent.ammo_fuel < turcost)
254         {
255                 Send_Notification(NOTIF_ONE, ent, MSG_MULTI, MULTI_TD_NOFUEL);
256                 return FALSE;
257         }
258         
259         ent.ammo_fuel -= turcost;
260         
261         return TRUE;
262 }       
263
264 void spawnturret(entity spawnedby, entity own, string turet, vector orig)
265 {
266         if not(IS_PLAYER(spawnedby)) { dprint("Warning: A non-player entity tried to spawn a turret\n"); return; }
267         if not(td_checkfuel(spawnedby, turet)) { return; }
268                 
269         entity oldself;
270         
271         oldself = self;
272         self = spawn();
273         
274         setorigin(self, orig);
275         self.spawnflags = TSL_NO_RESPAWN;
276         self.monster_attack = TRUE;
277         self.realowner = own;
278         self.playerid = own.playerid;
279         self.angles_y = spawnedby.v_angle_y;
280         spawnedby.turret_cnt += 1;
281         self.team = own.team;
282         
283         switch(turet)
284         {
285                 case "plasma": spawnfunc_turret_plasma(); break;
286                 case "mlrs": spawnfunc_turret_mlrs(); break;
287                 case "walker": spawnfunc_turret_walker(); break;
288                 case "flac": spawnfunc_turret_flac(); break;
289                 case "towerbuff": spawnfunc_turret_fusionreactor(); break;
290                 default: Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_INVALID); remove(self); self = oldself; return;
291         }
292         
293         Send_Notification(NOTIF_ONE, spawnedby, MSG_MULTI, MULTI_TD_SPAWN);
294                 
295         self = oldself;
296 }
297
298 void spawn_td_fuel(float fuel_size)
299 {
300         if not(g_td) {remove(self); return; }
301         
302         self.ammo_fuel = fuel_size * monster_skill;
303         StartItem("models/items/g_fuel.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Turret Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
304         
305         self.velocity = randomvec() * 175 + '0 0 325';
306 }
307
308 // round handling
309 #define TD_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0))
310 #define TD_ALIVE_TEAMS_OK() (TD_ALIVE_TEAMS() == 2)
311 void TD_RoundStart()
312 {
313         allowed_to_spawn = TRUE;
314         
315         total_killed = 0;
316 }
317
318 void TD_count_alive_monsters()
319 {
320         entity head;
321         
322         total_alive = 0;
323         redalive = 0;
324         bluealive = 0;
325         
326         FOR_EACH_MONSTER(head)
327         {
328                 if(head.health <= 0) continue;
329                 
330                 ++total_alive;
331                 
332                 switch(head.team)
333                 {
334                         case NUM_TEAM_1: ++redalive; break;
335                         case NUM_TEAM_2: ++bluealive; break;
336                 }
337         }
338 }
339
340 float TD_GetWinnerTeam()
341 {
342         float winner_team = 0;
343         if(redalive >= 1)
344                 winner_team = NUM_TEAM_1;
345         if(bluealive >= 1)
346         {
347                 if(winner_team) return 0;
348                 winner_team = NUM_TEAM_2;
349         }
350         if(winner_team)
351                 return winner_team;
352         return -1; // no monster left
353 }
354
355 float TD_CheckWinner()
356 {
357         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
358         {
359                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
360                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
361                 round_handler_Init(5, 1, 180);
362                 return 1;
363         }
364         
365         TD_count_alive_monsters();
366         
367         if(time >= last_check)
368         if(total_alive < max_alive)
369         if(total_killed < max_monsters)
370         {
371                 SpawnMonsters(NUM_TEAM_1);
372                 SpawnMonsters(NUM_TEAM_2);
373                 
374                 last_check = time + 0.5;
375         }
376         
377         if(total_killed < 1)
378                 return 0; // nothing has died, can't be a tie
379         
380         if(TD_ALIVE_TEAMS_OK())
381                 return 0;
382
383         float winner_team = TD_GetWinnerTeam();
384         if(winner_team > 0)
385         {
386                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
387                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
388                 TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
389         }
390         else if(winner_team == -1)
391         {
392                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
393                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
394         }
395
396         round_handler_Init(5, 1, 180);
397         return 1;
398 }
399
400 float TD_CheckTeams()
401 {
402         allowed_to_spawn = TRUE;
403         
404         return TRUE;
405 }
406
407 // spawnfuncs   
408 void spawnfunc_td_generator()
409 {
410         if not(g_td) { remove(self); return; }
411         if not(self.team)
412         {
413                 td_debug("Generator without a team, removing it.\n");
414                 remove(self);
415                 return;
416         }
417         
418         precache_sound("onslaught/generator_underattack.wav");
419         precache_sound("onslaught/ons_hit1.wav");
420         precache_sound("onslaught/ons_hit2.wav");
421         precache_sound("weapons/rocket_impact.wav");
422         
423         if not(self.health)
424                 self.health = 1000;
425                 
426         self.max_health = self.health;
427         self.classname = "td_generator";
428         self.flags = FL_GENERATOR;
429         
430         setsize(self, GENERATOR_MIN, GENERATOR_MAX);
431         
432         setorigin(self, self.origin + '0 0 20');
433         droptofloor();
434         
435         generator_link(td_generator_setup);
436 }
437
438 void spawnfunc_td_waypoint()
439 {
440         if not(g_td) { remove(self); return; }
441         if not(self.team)
442         {
443                 td_debug("Tower Defense waypoint without a team, removing it.\n");
444                 remove(self);
445                 return;
446         }
447         
448         setsize(self, '-6 -6 -6', '6 6 6');
449         
450         if not(self.noalign)
451         {
452                 setorigin(self, self.origin + '0 0 20');
453                 droptofloor();
454         }
455         
456         self.classname = "td_waypoint";
457         self.think = td_waypoint_think;
458         self.nextthink = time + 0.1;
459 }
460
461 void spawnfunc_td_controller()
462 {
463         if not(g_td) { remove(self); return; }
464 }
465
466 void spawnfunc_td_spawnpoint()
467 {
468         if not(g_td) { remove(self); return; }
469         
470         self.classname = "td_spawnpoint";
471         
472         self.effects = EF_STARDUST;
473 }
474
475 // initialization stuff
476 void td_ScoreRules()
477 {
478         ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, TRUE);
479         ScoreInfo_SetLabel_TeamScore(ST_TD_DESTROYS,  "destroyed", SFL_SORT_PRIO_PRIMARY);
480         ScoreInfo_SetLabel_PlayerScore(SP_TD_DESTROYS,"destroyed", SFL_SORT_PRIO_PRIMARY);
481         ScoreRules_basics_end();
482 }
483
484 void td_SpawnController()
485 {
486         entity oldself = self;
487         self = spawn();
488         self.classname = "td_controller";
489         spawnfunc_td_controller();
490         self = oldself;
491 }
492
493 void td_DelayedInit()
494 {
495         if(find(world, classname, "td_controller") == world)
496         {
497                 print("No ""td_controller"" entity found on this map, creating it anyway.\n");
498                 td_SpawnController();
499         }
500         
501         td_ScoreRules();
502 }
503
504 void td_Initialize()
505 {
506         InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
507         
508         round_handler_Spawn(TD_CheckTeams, TD_CheckWinner, TD_RoundStart);
509         round_handler_Init(5, 10, 180);
510 }
511
512 // mutator hooks
513 MUTATOR_HOOKFUNCTION(td_TurretSpawn)
514 {
515         if(self.realowner == world)
516                 return TRUE;
517                 
518         self.bot_attack = FALSE;
519         
520         return FALSE;
521 }
522
523 MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
524 {
525         if(!self.team || !self.realowner)
526         {
527                 td_debug(strcat("Removed monster ", self.netname, " with team ", ftos(self.team), "\n"));
528                 WaypointSprite_Kill(self.sprite);
529                 if(self.weaponentity) remove(self.weaponentity);
530                 remove(self);
531                 return FALSE;
532         }
533         
534         WaypointSprite_Kill(self.sprite);
535         
536         self.candrop = FALSE;
537         self.bot_attack = FALSE;
538         self.ammo_fuel = bound(20, 20 * self.level, 100);
539         self.target_range = 300;
540         self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_CORPSE | DPCONTENTS_MONSTERCLIP;
541         
542         return FALSE;
543 }
544
545 MUTATOR_HOOKFUNCTION(td_MonsterDies)
546 {
547         vector backuporigin;
548         entity oldself;
549         
550         if(IS_PLAYER(frag_attacker.realowner))
551         {
552                 PlayerScore_Add(frag_attacker.realowner, SP_SCORE, 5);
553                 PlayerScore_Add(frag_attacker.realowner, SP_KILLS, 1);
554         }
555         
556         total_killed++;
557         
558         backuporigin = self.origin;
559         oldself = self;
560         self = spawn();
561         
562         self.gravity = 1;
563         setorigin(self, backuporigin + '0 0 5');
564         spawn_td_fuel(oldself.ammo_fuel);
565         self.touch = M_Item_Touch;
566         if(self == world)
567         {
568                 self = oldself;
569                 return FALSE;
570         }
571         SUB_SetFade(self, time + 5, 1);
572         
573         self = oldself;
574         
575         return FALSE;
576 }
577
578 MUTATOR_HOOKFUNCTION(td_MonsterThink)
579 {
580         if(time <= game_starttime && round_handler_IsActive())
581                 return TRUE;
582                 
583         if(IS_PLAYER(self.enemy))
584                 self.enemy = world;
585
586         if not(self.enemy) // don't change targets while attacking
587         if(vlen(monster_target.origin - self.origin) <= 100)
588         {
589                 if(monster_target.target2)
590                 {
591                         if(random() > 0.5)
592                                 self.target2 = monster_target.target2;
593                         else
594                                 self.target2 = monster_target.target;
595                 }
596                 else
597                         self.target2 = monster_target.target;
598                                 
599                 monster_target = find(world, targetname, self.target2);
600                 
601                 if(monster_target == world)
602                         monster_target = PickGenerator(self.team);
603                         
604                 if(monster_target == world)
605                         return TRUE; // no generators or waypoints?!
606         }
607         
608         td_debug(sprintf("Monster target: %s. Monster target2: %s. Monster target entity: %s.\n", self.target, self.target2, etos(monster_target)));
609         
610         if(!self.enemy && !monster_target)
611                 return TRUE; // no enemy or target, must be wandering
612         
613         monster_speed_run = (150 + random() * 4) * monster_skill;
614         monster_speed_walk = (100 + random() * 4) * monster_skill;
615         
616         return FALSE;
617 }
618
619 MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
620 {
621         entity e;
622         
623         for(e = world;(e = findflags(e, monster_attack, TRUE)); ) 
624         {
625                 if(ignore_turrets)
626                 if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
627                         continue;
628                         
629                 if(e.flags & FL_MONSTER)
630                         continue; // don't attack other monsters?
631                 
632                 if(monster_isvalidtarget(e, self))
633                         self.enemy = e;
634         }
635         
636         return TRUE;
637 }
638
639 MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
640 {
641         self.monster_attack = FALSE;
642         self.bot_attack = FALSE;
643         
644         if(self.newfuel)
645         {
646                 self.ammo_fuel = self.newfuel;
647                 self.newfuel = 0;
648         }
649         
650         return FALSE;
651 }
652
653 MUTATOR_HOOKFUNCTION(td_Damage)
654 {
655         if(IS_PLAYER(frag_attacker))
656         if(frag_target.flags & FL_MONSTER)
657                 frag_damage = 0;
658                 
659         if(IS_PLAYER(frag_target))
660         {
661                 frag_damage = 0;
662                 if(frag_attacker != frag_target)
663                         frag_force = '0 0 0';
664         }
665         
666         return FALSE;
667 }
668
669 MUTATOR_HOOKFUNCTION(td_PlayerCommand)
670 {
671         if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
672         
673         vector org;
674         
675         makevectors(self.v_angle);
676         
677         org = self.origin + self.view_ofs + v_forward * 100;
678         
679         tracebox(self.origin + self.view_ofs, '-16 -16 -16', '16 16 16', org, MOVE_NORMAL, self);
680         entity targ = trace_ent;
681         if(targ.owner.realowner == self)
682                 targ = targ.owner;
683         
684         if(cmd_name == "turretspawn")
685         {
686                 if(argv(1) == "list")
687                 {
688                         Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_LIST, "mlrs walker plasma towerbuff flac");
689                         return TRUE;
690                 }
691                 if(!IS_PLAYER(self) || self.health <= 0)
692                 { 
693                         Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_CANTSPAWN);
694                         return TRUE;
695                 }
696                 if(max_turrets <= 0)
697                 {
698                         Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_DISABLED);
699                         return TRUE;
700                 }
701                 if(self.turret_cnt >= max_turrets)
702                 {
703                         Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXTURRETS, max_turrets);
704                         return TRUE;
705                 }
706                 
707                 spawnturret(self, self, argv(1), trace_endpos);
708                 
709                 return TRUE;
710         }
711         if(cmd_name == "turretremove")
712         {
713                 if((targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && (targ.playerid == self.playerid || targ.realowner == self))
714                 {
715                         self.turret_cnt -= 1;
716                         Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_REMOVE);
717                         WaypointSprite_Kill(targ.sprite);
718                         remove(targ.tur_head);
719                         remove(targ);
720                         return TRUE;
721                 }
722                 Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_REMOVE);
723                 return TRUE;
724         }
725         
726         return FALSE;
727 }
728
729 MUTATOR_HOOKFUNCTION(td_ClientConnect)
730 {
731         self.newfuel = 75;
732         
733         entity t;
734         
735         self.turret_cnt = 0;
736         
737         for(t = world; (t = findflags(t, turrcaps_flags, TFL_TURRCAPS_ISTURRET)); )
738         if(t.playerid == self.playerid)
739         {
740                 t.realowner = self;
741                 self.turret_cnt += 1; // nice try
742         }
743         
744         return FALSE;
745 }
746
747 MUTATOR_HOOKFUNCTION(td_DisableVehicles)
748 {
749         return TRUE;
750 }
751
752 MUTATOR_HOOKFUNCTION(td_SetModname)
753 {
754         g_cloaked = 1;
755         
756         return FALSE;
757 }
758
759 MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
760 {
761         if(time < game_starttime || (time <= game_starttime && round_handler_IsActive()) || gameover)
762         {
763                 turret_target = world;
764                 return FALSE; // battle hasn't started
765         }
766
767         if(turret_flags & TFL_TARGETSELECT_MISSILESONLY)
768     if(turret_target.flags & FL_PROJECTILE)
769         if(turret_target.owner.flags & FL_MONSTER)
770         return TRUE; // flac support
771                         
772         if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
773                 return TRUE;
774         if not(turret_target.flags & FL_MONSTER)
775                 turret_target = world;
776                 
777         if(!IsDifferentTeam(turret_target, turret))
778                 turret_target = world;
779                 
780         return FALSE;
781 }
782
783 MUTATOR_HOOKFUNCTION(td_TurretDies)
784 {
785         if(self.realowner)
786                 self.realowner.turret_cnt -= 1;
787                         
788         return FALSE;
789 }
790
791 MUTATOR_HOOKFUNCTION(td_GetTeamCount)
792 {
793         ret_float = 2;
794         
795         return FALSE;
796 }
797
798 MUTATOR_DEFINITION(gamemode_towerdefense)
799 {
800         MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
801         MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
802         MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
803         MUTATOR_HOOK(MonsterMove, td_MonsterThink, CBC_ORDER_ANY);
804         MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
805         MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
806         MUTATOR_HOOK(PlayerDamage_Calculate, td_Damage, CBC_ORDER_ANY);
807         MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
808         MUTATOR_HOOK(ClientConnect, td_ClientConnect, CBC_ORDER_ANY);
809         MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY);
810         MUTATOR_HOOK(SetModname, td_SetModname, CBC_ORDER_ANY);
811         MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
812         MUTATOR_HOOK(TurretDies, td_TurretDies, CBC_ORDER_ANY);
813         MUTATOR_HOOK(GetTeamCount, td_GetTeamCount, CBC_ORDER_ANY);
814
815         MUTATOR_ONADD
816         {
817                 if(time > 1) // game loads at time 1
818                         error("This is a game type and it cannot be added at runtime.");        
819                 cvar_settemp("g_monsters", "1");
820                 cvar_settemp("g_turrets", "1");
821                 td_Initialize();
822         }
823         
824         MUTATOR_ONROLLBACK_OR_REMOVE
825         {
826                 // we actually cannot roll back td_Initialize here
827                 // BUT: we don't need to! If this gets called, adding always
828                 // succeeds.
829         }
830
831         MUTATOR_ONREMOVE
832         {
833                 error("This is a game type and it cannot be removed at runtime.");
834                 return -1;
835         }
836
837         return FALSE;
838 }