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