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