]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_td.qc
Remove some more unused counters
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_td.qc
1 // Tower Defense
2 // Gamemode by Mario
3  
4 void spawnfunc_td_controller()
5 {
6         if not(g_td)
7         {
8                 remove(self);
9                 return;
10         }
11         if(autocvar_g_td_force_settings)
12         {
13                 self.dontend = FALSE;
14                 self.maxwaves = 0;
15                 self.monstercount = 0;
16                 self.startwave = 0;
17                 self.maxturrets = 0;
18         }
19                 
20         self.netname = "Tower Defense controller entity";
21         self.classname = "td_controller";
22                 
23         gensurvived = FALSE;
24         td_dont_end = ((self.dontend) ? self.dontend : autocvar_g_td_generator_dontend);                
25         max_waves = ((self.maxwaves) ? self.maxwaves : autocvar_g_td_max_waves);        
26         totalmonsters = ((self.monstercount) ? self.monstercount : autocvar_g_td_monster_count);
27         wave_count = ((self.startwave) ? self.startwave : autocvar_g_td_start_wave);
28         max_turrets = ((self.maxturrets) ? self.maxturrets : autocvar_g_td_turret_max);
29         build_time = ((self.buildtime) ? self.buildtime : autocvar_g_td_buildphase_time);
30                 
31         wave_end(TRUE);
32 }
33
34 void td_generator_die() 
35 {
36         entity tail;
37         
38         print((td_gencount > 1) ? "A generator was destroyed!\n" : "The generator was destroyed.\n");
39                 
40         if(autocvar_sv_eventlog)
41                 GameLogEcho(":gendestroyed");
42                 
43         gendestroyed = TRUE;
44                 
45         FOR_EACH_PLAYER(tail)
46         {
47                 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, ((td_gencount > 1) ? "A generator was destroyed!" : "The generator was destroyed."), 0, 0);
48         }
49         
50         setmodel(self, "models/onslaught/generator_dead.md3");
51         self.solid                      = SOLID_NOT;
52         self.takedamage         = DAMAGE_NO;
53         self.event_damage   = func_null;
54         self.enemy                      = world;
55         td_gencount                     -= 1;
56                 
57         pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
58         
59         WaypointSprite_Kill(self.sprite);
60 }
61
62 void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) 
63 {
64         if(attacker.classname == STR_PLAYER || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE)
65                 return;
66                 
67         entity tail;
68         
69         FOR_EACH_PLAYER(tail)
70         {
71                 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "The generator is under attack!", 0, 0);
72         }
73         
74         self.health -= damage;
75         
76         WaypointSprite_UpdateHealth(self.sprite, self.health);
77                 
78         if(self.health <= 0) 
79                 td_generator_die();
80 }
81
82 void spawnfunc_td_generator() 
83 {
84         if not(g_td) 
85         {
86                 remove(self);
87                 return;
88         }
89         
90         gendestroyed = FALSE;
91         
92         if not(self.health)
93                 self.health = autocvar_g_td_generator_health;
94
95         // precache generator model
96         precache_model("models/onslaught/generator.md3");
97         precache_model("models/onslaught/generator_dead.md3");   
98         
99         self.model                  = "models/onslaught/generator.md3";
100         setmodel(self, self.model);
101         self.classname      = "td_generator";
102         self.solid                  = SOLID_BBOX;
103         self.takedamage     = DAMAGE_AIM;
104         self.event_damage   = td_generator_damage;
105         self.enemy                  = world;
106         self.nextthink      = -1;
107         self.think                  = func_null;
108         self.max_health     = self.health;
109         self.movetype       = MOVETYPE_NONE;
110         self.monster_attack = TRUE;
111         td_gencount                += 1;
112         self.netname            = "Generator";
113         
114         setsize(self, GENERATOR_MIN, GENERATOR_MAX);
115         
116         droptofloor();
117         
118         WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 60', self, sprite, RADARICON_OBJECTIVE, '1 0.5 0');  
119         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
120         WaypointSprite_UpdateHealth(self.sprite, self.health);
121 }
122
123 entity PickGenerator()
124 {
125         entity generator, head;
126         if(td_gencount == 1)
127                 generator = find(world, classname, "td_generator");
128         else
129         {
130                 RandomSelection_Init();
131                 for(head = world;(head = find(head, classname, "td_generator")); )
132                 {
133                         RandomSelection_Add(head, 0, string_null, 1, 1);
134                 }
135                 generator = RandomSelection_chosen_ent; 
136         }
137         return generator;
138 }
139
140 void spawn_td_fuel(float fuel_size)
141 {
142         if not(g_td)
143         {
144                 remove(self);
145                 return;
146         }
147         self.ammo_fuel = fuel_size * monster_skill;
148         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);
149         
150         self.velocity = randomvec() * 175 + '0 0 325';
151 }
152
153 void spawnfunc_td_waypoint() 
154 {
155         if not(g_td)
156         {
157                 remove(self);
158                 return;
159         }
160         string t1 = self.target;
161         
162         self.classname = "td_waypoint";
163         
164         if(self.target2 != "")
165         {
166                 RandomSelection_Init();
167                 RandomSelection_Add(world, 0, t1, 1, 1);
168                 RandomSelection_Add(world, 0, self.target2, 1, 1);
169                 
170                 self.target = RandomSelection_chosen_string;
171         }
172 }
173
174 void spawnfunc_monster_swarm()
175 {
176         if not(g_td)
177         {
178                 remove(self);
179                 return;
180         }
181         
182         string t1 = self.target;
183         
184         switch(self.spawntype)
185         {
186                 case SWARM_SWIM:
187                         waterspawns_count += 1; break;
188                 case SWARM_FLY:
189                         flyspawns_count += 1; break;
190                 default:
191                         break;
192         }
193         
194         switch(self.spawnflags)
195         {
196                 case SWARM_STRONG:
197                         self.classname = "swarm_strong"; break;
198                 case SWARM_WEAK:
199                         self.classname = "swarm_weak"; break;
200                 default:
201                         self.classname = "monster_swarm"; break;
202         }
203         
204         if(self.target2 != "")
205         {
206                 RandomSelection_Init();
207                 RandomSelection_Add(world, 0, t1, 1, 1);
208                 RandomSelection_Add(world, 0, self.target2, 1, 1);
209                 
210                 self.target = RandomSelection_chosen_string;
211         }       
212         
213         WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '1 0.5 0');
214         
215         if(self.target == "")
216                 print("monster_swarm entity without a valid target, monsters will try to follow waypoints instead.\n");
217 }
218
219 void spawnturret(entity spawnedby, entity own, string turet, vector orig)
220 {
221         if(spawnedby.classname != STR_PLAYER)
222         {
223                 print("Warning: A non-player entity tried to spawn a turret.\n");
224                 return;
225         }
226                 
227         entity oldself;
228         
229         oldself = self;
230         self = spawn();
231         
232         setorigin(self, orig);
233         self.spawnflags = TSL_NO_RESPAWN;
234         self.monster_attack = TRUE;
235         self.realowner = own;
236         self.angles_y = spawnedby.v_angle_y;
237         spawnedby.turret_cnt += 1;
238         self.colormap = spawnedby.colormap;
239         
240         switch(turet)
241         {
242                 default:
243                 case "turret_plasma": spawnfunc_turret_plasma(); break;
244                 case "turret_mlrs": spawnfunc_turret_mlrs(); break;
245                 case "turret_phaser": spawnfunc_turret_phaser(); break;
246                 case "turret_hellion": spawnfunc_turret_hellion(); break;
247                 case "turret_walker": spawnfunc_turret_walker(); break;
248                 case "turret_flac": spawnfunc_turret_flac(); break;
249                 case "turret_tesla": spawnfunc_turret_tesla(); break;
250                 case "turret_fusionreactor": spawnfunc_turret_fusionreactor(); break;
251         }
252                 
253         self = oldself;
254 }
255
256 void buffturret (entity tur, float buff)
257 {
258         tur.turret_buff           += 1;
259         tur.max_health            *= buff;
260         tur.tur_health             = tur.max_health;
261         tur.health                         = tur.max_health;
262         tur.ammo_max              *= buff;
263         tur.ammo_recharge     *= buff;
264     tur.shot_dmg          *= buff;
265     tur.shot_refire       -= buff * 0.2;
266     tur.shot_radius       *= buff;
267     tur.shot_speed        *= buff;
268     tur.shot_spread       *= buff;
269     tur.shot_force        *= buff;
270 }
271
272 void AnnounceSpawn(string anounce)
273 {
274         entity tail;
275         FOR_EACH_PLAYER(tail)
276         {
277                 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, strcat("^1A ", anounce, " has arrived!"), 0, 0);
278         }
279 }
280
281 entity PickSpawn (string strngth, string type)
282 {
283         entity e;
284         RandomSelection_Init();
285         for(e = world;(e = find(e, classname, strngth)); ) 
286         {
287                 RandomSelection_Add(e, 0, string_null, 1, 1); 
288         }
289
290         return RandomSelection_chosen_ent;
291 }
292
293 void TD_SpawnMonster(string mnster, string strngth, string type)
294 {
295         entity e, mon;
296         
297         e = PickSpawn(strngth, type);
298         
299         if(e == world)
300                 e = PickSpawn("monster_swarm", "");
301   
302         mon = spawnmonster(mnster, e, e, e.origin, FALSE, 0);
303         mon.target = e.target;
304 }
305
306 string Monster_GetStrength(string mnster)
307 {
308         switch(mnster)
309         {
310                 case "knight":
311                 case "wizard":
312                 case "soldier":
313                 case "enforcer":
314                 case "zombie":
315                 case "tarbaby":
316                 case "dog":
317                 case "spider":
318                 case "fish":
319                         return "swarm_weak";
320                 case "ogre":
321                 case "shambler":
322                 case "shalrath":
323                 case "hellknight":
324                 case "demon":
325                         return "swarm_strong";
326                 default:
327                         return "monster_swarm";
328         }
329 }
330
331 string Monster_GetType(string mnster)
332 {
333         switch(mnster)
334         {
335                 default:
336                 case "knight":
337                 case "soldier":
338                 case "enforcer":
339                 case "zombie":
340                 case "spider":
341                 case "tarbaby":
342                 case "dog":
343                 case "ogre":
344                 case "shambler":
345                 case "shalrath":
346                 case "hellknight":
347                 case "demon":
348                         return "monster_swarm";
349                 case "wizard":
350                         return "monster_fly";
351                 case "fish":
352                         return "monster_swim";
353         }
354 }
355
356 string RandomMonster()
357 {
358         RandomSelection_Init();
359         
360         if(n_demons) RandomSelection_Add(world, 0, "demon", 1, 1);
361         if(n_wizards && flyspawns_count > 0) RandomSelection_Add(world, 0, "scrag", 1, 1);
362         if(n_shalraths) RandomSelection_Add(world, 0, "vore", 1, 1);
363         if(n_soldiers) RandomSelection_Add(world, 0, "soldier", 1, 1);
364         if(n_hknights) RandomSelection_Add(world, 0, "hellknight", 1, 1);
365         if(n_enforcers) RandomSelection_Add(world, 0, "enforcer", 1, 1);
366         if(n_zombies) RandomSelection_Add(world, 0, "zombie", 1, 1);
367         if(n_spiders) RandomSelection_Add(world, 0, "spider", 1, 1);
368         if(n_ogres) RandomSelection_Add(world, 0, "ogre", 1, 1);
369         if(n_dogs) RandomSelection_Add(world, 0, "dog", 1, 1);
370         if(n_knights) RandomSelection_Add(world, 0, "knight", 1, 1);
371         if(n_shamblers) RandomSelection_Add(world, 0, "shambler", 0.2, 0.2);
372         if(n_tarbabies) RandomSelection_Add(world, 0, "spawn", 0.2, 0.2);
373         if(n_fish && waterspawns_count > 0) RandomSelection_Add(world, 0, "fish", 0.2, 0.2);
374         
375         return RandomSelection_chosen_string;
376 }
377
378 void combat_phase()
379 {
380         string monstrngth, whichmon, montype;
381         
382         current_phase = PHASE_COMBAT;
383         
384         if(monster_count <= 0)
385         {
386                 wave_end(FALSE);
387                 return;
388         }
389         
390         self.think = combat_phase;
391         
392         whichmon = RandomMonster();
393         
394         monstrngth = Monster_GetStrength(whichmon);
395         montype = Monster_GetType(whichmon);
396         
397         if(current_monsters < autocvar_g_td_current_monsters && whichmon != "")
398         {
399                 TD_SpawnMonster(whichmon, monstrngth, montype);
400                 self.nextthink = time + 3;
401         }
402         else
403                 self.nextthink = time + 6;
404 }
405
406 void queue_monsters(float maxmonsters)
407 {
408         float mc = 11; // note: shambler + tarbaby = 1
409         
410         if(waterspawns_count > 0)
411                 mc += 1;
412         if(flyspawns_count > 0)
413                 mc += 1;
414                 
415         DistributeEvenly_Init(maxmonsters, mc);
416         n_demons        = DistributeEvenly_Get(1);
417         n_ogres         = DistributeEvenly_Get(1);
418         n_dogs          = DistributeEvenly_Get(1);
419         n_knights   = DistributeEvenly_Get(1);
420         n_shalraths = DistributeEvenly_Get(1);
421         n_soldiers  = DistributeEvenly_Get(1);
422         n_hknights  = DistributeEvenly_Get(1);
423         n_enforcers = DistributeEvenly_Get(1);
424         n_zombies   = DistributeEvenly_Get(1);
425         n_spiders   = DistributeEvenly_Get(1);
426         n_tarbabies = DistributeEvenly_Get(0.7);
427         n_shamblers = DistributeEvenly_Get(0.3);
428         if(flyspawns_count > 0)
429                 n_wizards   = DistributeEvenly_Get(1);
430         if(waterspawns_count > 0)
431                 n_fish = DistributeEvenly_Get(1);
432 }
433
434 void combat_phase_begin()
435 {
436         if(autocvar_g_td_hardcore)
437                 cvar_set("g_td_respawn_delay", "999");
438                 
439         monster_count = totalmonsters;
440         entity head, tail;
441         
442         print("^1Combat phase!\n");
443         FOR_EACH_PLAYER(tail)
444         {
445                 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "^1Combat phase!", 0, 0);
446         }
447         if(autocvar_sv_eventlog)
448                 GameLogEcho(":combatphase");
449         self.think = combat_phase;
450         self.nextthink = time + 1;
451         
452         for(head = world;(head = find(head, classname, "td_generator")); )
453         {
454                 head.takedamage = DAMAGE_AIM;
455         }
456 }
457
458 float cphase_updates;
459 void combat_phase_announce() // TODO: clean up these fail nextthinks...
460 {
461         cphase_updates += 1;
462         
463         if(cphase_updates == 0)
464                 Announce("prepareforbattle");
465         else if(cphase_updates == 3)
466                 Announce("3");
467         else if(cphase_updates == 4)
468                 Announce("2");
469         else if(cphase_updates == 5)
470                 Announce("1");
471         else if(cphase_updates == 6)
472         {
473                 Announce("begin");
474                 combat_phase_begin();
475         }
476         
477         if(cphase_updates >= 6)
478                 return;
479
480         self.think = combat_phase_announce;
481         self.nextthink = time + 1;
482 }
483
484 void build_phase()
485 {
486         entity head;
487         float n_players = 0, gen_washealed = FALSE, player_washealed = FALSE;
488     string buildmsg, healmsg, countmsg, startmsg, genhealmsg;
489         
490         current_phase = PHASE_BUILD;
491         
492         for(head = world;(head = find(head, classname, "td_generator")); )
493         {
494                 if(head.health <= 5 && head.max_health > 10)
495                         Announce("lastsecond");
496                         
497                 if(head.health < head.max_health)
498                 {
499                         gen_washealed = TRUE;
500                         head.health = head.max_health;
501                         WaypointSprite_UpdateHealth(head.sprite, head.health);
502                 }
503                 head.takedamage = DAMAGE_NO;
504         }
505         
506         FOR_EACH_PLAYER(head)
507         {
508                 if(head.health < 100)
509                 {
510                         player_washealed = TRUE;
511                         break; // we found 1, so no need to check the others
512                 }
513         }
514                 
515         totalmonsters += autocvar_g_td_monster_count_increment * wave_count;
516         monster_skill += autocvar_g_td_monsters_skill_increment;
517         
518         if(wave_count < 1) wave_count = 1;
519
520         genhealmsg = (gen_washealed) ? ((td_gencount == 1) ? " and generator " : " and generators ") : "";
521         buildmsg = sprintf("%s build phase... ", (wave_count == max_waves) ? "^1Final wave^3" : sprintf("Wave %d", wave_count));
522         healmsg = (player_washealed) ? sprintf("All players %shealed. ", genhealmsg) : "";      
523     countmsg = sprintf("Next monsters: %d. ", totalmonsters);
524     startmsg = sprintf("Wave starts in %d seconds", autocvar_g_td_buildphase_time);
525         
526         FOR_EACH_PLAYER(head) 
527     {
528                 if(head.health < 100)
529                         head.health = 100;
530                         
531                 if(gen_washealed)
532                         PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
533                         
534         n_players += 1;
535         Send_CSQC_Centerprint_Generic(head, CPID_KH_MSG, strcat(buildmsg, healmsg, countmsg, startmsg), 5, 0);
536                 
537     }
538     
539     FOR_EACH_MONSTER(head)
540     {
541                 if(head.health <= 0)
542                         continue;
543         print(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
544                 if(head.sprite)
545                         WaypointSprite_Kill(head.sprite);
546         remove(head);
547     }
548         
549         if(n_players >= 2)
550         {
551                 totalmonsters += n_players;
552                 monster_skill += n_players * 0.05;
553         }
554         
555         if(monster_skill < 1) monster_skill = 1;
556                 
557         if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
558         
559         monsters_total = totalmonsters;
560         monsters_killed = 0;
561         
562         print(strcat(buildmsg, healmsg, countmsg, startmsg, "\n"));
563                 
564         queue_monsters(totalmonsters);
565         
566         cphase_updates = -1;
567         
568         if(autocvar_sv_eventlog)
569         GameLogEcho(sprintf(":buildphase:%d:%d", wave_count, totalmonsters));
570         
571         self.think = combat_phase_announce;
572         self.nextthink = time + build_time - 6;
573 }
574
575 void wave_end(float starting)
576 {
577         entity tail;
578         FOR_EACH_PLAYER(tail)
579         {
580                 if(starting)
581                         Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "Defend the generator from waves of monsters!", 0, 0);
582                 else
583                         Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, ((wave_count >= max_waves) ? "Level victory!" : "Wave victory!"), 0, 0);
584         }
585         
586         if not(starting)
587         {
588                 print((wave_count >= max_waves) ? "^2Level victory!\n" : "^2Wave victory!\n");
589                 if(autocvar_sv_eventlog)
590             GameLogEcho(sprintf(":wave:%d:victory", wave_count));
591         }
592         
593         if(wave_count >= max_waves)
594         {
595                 gensurvived = TRUE;
596                 return;
597         }
598         
599         if(starting)
600                 monster_skill = autocvar_g_td_monsters_skill_start;
601         else
602                 wave_count += 1;
603                 
604         self.think = build_phase;
605         self.nextthink = time + 3;
606 }
607
608 void td_ScoreRules()
609 {
610         ScoreInfo_SetLabel_PlayerScore(SP_TD_SCORE,             "score",         SFL_SORT_PRIO_PRIMARY);
611         ScoreInfo_SetLabel_PlayerScore(SP_TD_KILLS,             "kills",         SFL_LOWER_IS_BETTER);
612         ScoreInfo_SetLabel_PlayerScore(SP_TD_TURKILLS,  "frags",        SFL_LOWER_IS_BETTER);
613         ScoreInfo_SetLabel_PlayerScore(SP_TD_DEATHS,    "deaths",    SFL_LOWER_IS_BETTER);
614         ScoreInfo_SetLabel_PlayerScore(SP_TD_SUICIDES,  "suicides",  SFL_LOWER_IS_BETTER | SFL_ALLOW_HIDE);
615         ScoreRules_basics_end();
616 }
617
618 void td_SpawnController()
619 {
620         entity oldself = self;
621         self = spawn();
622         self.classname = "td_controller";
623         spawnfunc_td_controller();
624         self = oldself;
625 }
626
627 void td_DelayedInit()
628 {
629         if(find(world, classname, "td_controller") == world)
630         {
631                 print("No ""td_controller"" entity found on this map, creating it anyway.\n");
632                 td_SpawnController();
633         }
634         
635         td_ScoreRules();
636 }
637
638 void td_Init()
639 {
640         InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
641 }
642
643 MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
644 {
645         if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
646                 return TRUE;
647         if not(turret_target.flags & FL_MONSTER)
648                 turret_target = world;
649                 
650         return FALSE;
651 }
652
653 MUTATOR_HOOKFUNCTION(td_PlayerThink)
654 {
655         self.stat_current_wave = wave_count;
656         self.stat_totalwaves = max_waves;
657         
658         return FALSE;
659 }
660
661 MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
662 {
663         self.bot_attack = FALSE;
664         return FALSE;
665 }
666
667 MUTATOR_HOOKFUNCTION(td_PlayerDies)
668 {
669         if(frag_attacker.flags & FL_MONSTER)
670                 PlayerScore_Add(frag_target, SP_TD_DEATHS, 1);
671                 
672         if(frag_target == frag_attacker)
673                 PlayerScore_Add(frag_attacker, SP_TD_SUICIDES, 1);
674
675         return FALSE;
676 }
677
678 MUTATOR_HOOKFUNCTION(td_GiveFragsForKill)
679 {
680         frag_score = 0;
681                 
682         return TRUE; // no frags counted in td
683 }
684
685 MUTATOR_HOOKFUNCTION(td_PlayerDamage_Calculate)
686 {
687         if(frag_attacker.realowner == frag_target)
688                 frag_damage = 0;
689                 
690         if(frag_target.flags & FL_MONSTER && time < frag_target.spawnshieldtime)
691                 frag_damage = 0;
692                 
693         if(frag_target.vehicle_flags & VHF_ISVEHICLE && !(frag_attacker.flags & FL_MONSTER))
694                 frag_damage = 0;
695                 
696         if(frag_attacker.vehicle_flags & VHF_ISVEHICLE && !(frag_target.flags & FL_MONSTER))
697                 frag_damage = 0;
698                 
699         if(!autocvar_g_td_pvp && frag_attacker != frag_target && frag_target.classname == STR_PLAYER && frag_attacker.classname == STR_PLAYER)
700                 frag_damage = 0;
701                 
702         if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && frag_target.classname == STR_PLAYER)
703                 frag_damage = 0;
704                 
705         if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && !(frag_attacker.flags & FL_MONSTER || frag_attacker.turrcaps_flags & TFL_TURRCAPS_SUPPORT))
706                 frag_damage = 0;
707                 
708         if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && frag_target.health <= 0)
709         {
710                 // TODO: fix this? calling on damage may be unreliable
711                 if(frag_target.realowner)
712                         frag_target.realowner.turret_cnt -= 1;
713         }
714                 
715         return TRUE;
716 }
717
718 MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag)
719 {
720         // No minibosses in tower defense
721         return TRUE;
722 }
723
724 MUTATOR_HOOKFUNCTION(td_MonsterMove)
725 {
726         entity player;
727         float n_players = 0;
728         FOR_EACH_PLAYER(player) { ++n_players; }
729         
730         if(n_players < 1) // no players online, so do nothing
731         {
732                 monster_target = world;
733                 monster_speed_run = monster_speed_walk = 0;
734                 return FALSE;
735         }
736         
737         if((vlen(self.goalentity.origin - self.origin) <= 100 && self.goalentity.classname == "td_waypoint") || (vlen(self.goalentity.origin - self.origin) <= 200 && self.flags & FL_FLY && self.goalentity.classname == "td_waypoint"))
738         {
739                 self.target = self.goalentity.target;
740                 self.goalentity = find(world, targetname, self.target);
741         }
742         
743         if(self.goalentity == world)
744                 self.goalentity = PickGenerator();
745         
746         monster_speed_run = 110 * monster_skill;
747         monster_speed_walk = 75 * monster_skill;
748         
749         return FALSE;
750 }
751
752 MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
753 {
754         if(self.realowner && self.realowner.flags & FL_CLIENT)
755         {
756                 sprint(self.realowner, "You can't spawn monsters in Tower Defense mode. Removed monster.\n");
757                 if(self.sprite)
758                         WaypointSprite_Kill(self.sprite);
759                 remove(self);
760                 return TRUE;
761         }
762         
763         if(self.realowner == world) // nothing spawned it, so kill it
764         {
765                 if(self.sprite)
766                         WaypointSprite_Kill(self.sprite);
767                 remove(self);
768                 return TRUE;
769         }
770         
771         self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time;
772         
773         self.drop_size = self.health * 0.05;
774         
775         if(self.drop_size < 1) self.drop_size = 1;
776         
777         if(self.target) // follow target if available
778                 self.goalentity = find(world, targetname, self.target);
779         
780         self.origin += '0 0 25'; // hopefully this fixes monsters falling through the floor
781         
782         switch(self.classname)
783         {
784                 case "monster_knight": n_knights -= 1; break;
785                 case "monster_dog": n_dogs -= 1; break;
786                 case "monster_ogre": n_ogres -= 1; break;
787                 case "monster_shambler": n_shamblers -= 1; AnnounceSpawn("Shambler"); break;
788                 case "monster_wizard": n_wizards -= 1; break;
789                 case "monster_shalrath": n_shalraths -= 1; break;
790                 case "monster_soldier": n_soldiers -= 1; break;
791                 case "monster_hellknight": n_hknights -= 1; break;
792                 case "monster_enforcer": n_enforcers -= 1; break;
793                 case "monster_demon": n_demons -= 1; break;
794                 case "monster_zombie": n_zombies -= 1; break;
795                 case "monster_spider": n_spiders -= 1; break;
796                 case "monster_tarbaby": n_tarbabies -= 1; break;
797         }
798         
799         return TRUE;
800 }
801
802 MUTATOR_HOOKFUNCTION(td_MonsterDies)
803 {
804         entity oldself;
805         vector backuporigin;
806
807         monster_count -= 1;
808         current_monsters -= 1;
809         monsters_killed += 1;
810         
811         if(frag_attacker.classname == STR_PLAYER)
812         {
813                 PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
814                 PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
815         }
816         else if(frag_attacker.realowner.classname == STR_PLAYER)
817         {
818                 PlayerScore_Add(frag_attacker.realowner, SP_TD_SCORE, autocvar_g_td_turretkill_points);
819                 PlayerScore_Add(frag_attacker.realowner, SP_TD_TURKILLS, 1);
820         }
821
822         backuporigin = self.origin;
823         oldself = self;
824         self = spawn();
825         
826         self.gravity = 1;
827         setorigin(self, backuporigin + '0 0 5');
828         spawn_td_fuel(oldself.drop_size);
829         self.touch = M_Item_Touch;
830         if(self == world)
831         {
832                 self = oldself;
833                 return FALSE;
834         }
835         SUB_SetFade(self, time + 5, 1);
836         
837         self = oldself;
838
839         return FALSE;
840 }
841
842 MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
843 {
844         float n_players = 0;
845         entity player;
846         local entity e;
847         
848         FOR_EACH_PLAYER(player) { ++n_players; }
849         
850         if(n_players < 1) // no players online, so do nothing
851         {
852                 self.enemy = world;
853                 return TRUE;
854         }
855         
856         for(e = world;(e = findflags(e, monster_attack, TRUE)); ) 
857         {
858                 if(monster_isvalidtarget(e, self, FALSE))
859                 if((vlen(trace_endpos - self.origin) < 100 && e.turrcaps_flags & TFL_TURRCAPS_ISTURRET) || (vlen(trace_endpos - self.origin) < 200 && e.classname != "td_generator") || (vlen(trace_endpos - self.origin) < 500 && e.classname == "td_generator"))
860                 {
861                         self.enemy = e;
862                 }
863         }
864         
865         return TRUE;
866 }
867
868 MUTATOR_HOOKFUNCTION(td_SetStartItems)
869 {
870         // no start ammo, so player must rely on monster droppings (TODO: random drops for monsters)
871         start_ammo_rockets = 0;
872         start_ammo_cells = 0;
873         start_ammo_nails = 0;
874         start_ammo_fuel = 150; // to be nice...
875         
876         return FALSE;
877 }
878
879 MUTATOR_HOOKFUNCTION(td_TurretSpawn)
880 {
881         if(self.realowner == world)
882                 return TRUE; // wasn't spawned by a player
883                 
884         self.bot_attack = FALSE;
885         self.turret_buff = 1;
886         
887         return FALSE;
888 }
889
890 MUTATOR_HOOKFUNCTION(td_PlayerCommand)
891 {
892         if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
893         makevectors(self.v_angle);
894         WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
895         if(cmd_name == "turretspawn")
896         {
897                 if(argv(1) == "list")
898                 {
899                         sprint(self, "Available turrets:\n");
900                         sprint(self, "^3mlrs walker plasma towerbuff\n");
901                         return TRUE;
902                 }
903                 if(self.classname != STR_PLAYER || self.health <= 0)
904                 { 
905                         sprint(self, "Can't spawn turrets while spectating/dead\n");
906                         return TRUE;
907                 }
908                 if(self.turret_cnt >= max_turrets)
909                 {
910                         sprint(self, strcat("Can't spawn more than ", ftos(max_turrets), " turrets\n"));
911                         return TRUE;
912                 }
913                 switch(argv(1))
914                 {
915                         case "plasma":
916                         {
917                 if(self.ammo_fuel < autocvar_g_td_turret_plasma_cost) break;
918                                 self.ammo_fuel -= autocvar_g_td_turret_plasma_cost;
919                                 spawnturret(self, self, "turret_plasma", trace_endpos);
920                                 sprint(self, "Spawned 1 plasma turret", "\n");
921                                 return TRUE;
922                         }
923                         case "mlrs":
924                         {
925                 if(self.ammo_fuel < autocvar_g_td_turret_mlrs_cost) break;
926                                 self.ammo_fuel -= autocvar_g_td_turret_mlrs_cost;
927                                 spawnturret(self, self, "turret_mlrs", trace_endpos);
928                                 sprint(self, "Spawned 1 MLRS turret", "\n");
929                                 return TRUE;
930                         }
931                         case "walker":
932                         {
933                 if(self.ammo_fuel < autocvar_g_td_turret_walker_cost) break;
934                                 self.ammo_fuel -= autocvar_g_td_turret_walker_cost;
935                                 spawnturret(self, self, "turret_walker", trace_endpos);
936                                 sprint(self, "Spawned 1 walker turret", "\n");
937                                 return TRUE;
938                         }
939                         case "towerbuff":
940                         {
941                 if(self.ammo_fuel < autocvar_g_td_tower_buff_cost) break;
942                                 self.ammo_fuel -= autocvar_g_td_tower_buff_cost;
943                                 spawnturret(self, self, "turret_fusionreactor", trace_endpos);
944                                 sprint(self, "Spawned 1 tower buff turret\n");
945                                 return TRUE;
946                         }
947                         default:
948                         {
949                                 sprint(self, "Invalid turret. type 'cmd turret list' to see a list of all available turrets.\n");
950                                 return TRUE;
951                         }
952                 }
953                 sprint(self, strcat("You do not have enough fuel to spawn a ", argv(1), " turret\n"));
954                 return TRUE;
955         }
956         if(cmd_name == "buffturret")
957         {
958                 if(trace_ent.realowner != self || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
959                 {
960                         sprint(self, "You need to aim at your turret to upgrade it\n");
961                         return TRUE;
962                 }
963                 if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)  
964                 {
965                         sprint(self, strcat("You need ", ftos(autocvar_g_td_turret_upgrade_cost), " fuel to increase this turret's power\n"));
966                         return TRUE;
967                 }
968                 if(trace_ent.turret_buff >= 3)
969                 {
970                         sprint(self, "This turret cannot be buffed up any higher\n");
971                         return TRUE;
972                 }
973                 
974                 self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost;
975                 trace_ent.SendFlags |= TNSF_STATUS;
976                 buffturret(trace_ent, 1.2);
977                 sprint(self, "Turret power increased by 20%!\n");
978                 
979                 return TRUE;
980         }
981         if(cmd_name == "turretremove")
982         {
983                 if((trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && trace_ent.realowner == self)
984                 {
985                         self.turret_cnt -= 1;
986                         sprint(self, strcat("You removed your ", trace_ent.netname, "\n"));
987                         remove(trace_ent.tur_head);
988                         remove(trace_ent);
989                         return TRUE;
990                 }
991                 sprint(self, "You need to aim at your turret to remove it\n");
992                 return TRUE;
993         }
994         if(cmd_name == "debugmonsters")
995         {
996         sprint(self, strcat("^3Current wave: ^1", ftos(wave_count), "\n"));
997                 sprint(self, strcat("^3Maximum waves: ^1", ftos(max_waves), "\n"));
998                 sprint(self, strcat("^3Monster skill: ^1", ftos(monster_skill), "\n"));
999                 sprint(self, strcat("^3Current monsters: ^1", ftos(monster_count), "\n"));
1000                 sprint(self, strcat("^3Maximum monsters: ^1", ftos(totalmonsters), "\n"));
1001         sprint(self, strcat("^3Current ogres: ^1", ftos(n_ogres), "\n"));
1002         sprint(self, strcat("^3Current knights: ^1", ftos(n_knights), "\n"));
1003         sprint(self, strcat("^3Current dogs: ^1", ftos(n_dogs), "\n"));
1004         sprint(self, strcat("^3Current shamblers: ^1", ftos(n_shamblers), "\n"));
1005         sprint(self, strcat("^3Current scrags: ^1", ftos(n_wizards), "\n"));
1006         sprint(self, strcat("^3Current vores: ^1", ftos(n_shalraths), "\n"));
1007         sprint(self, strcat("^3Current grunts: ^1", ftos(n_soldiers), "\n"));
1008         sprint(self, strcat("^3Current hell knights: ^1", ftos(n_hknights), "\n"));
1009         sprint(self, strcat("^3Current enforcers: ^1", ftos(n_enforcers), "\n"));
1010         sprint(self, strcat("^3Current fiends: ^1", ftos(n_demons), "\n"));
1011                 sprint(self, strcat("^3Current zombies: ^1", ftos(n_zombies), "\n"));
1012                 sprint(self, strcat("^3Current spawns: ^1", ftos(n_tarbabies), "\n"));
1013                 sprint(self, strcat("^3Current rotfish: ^1", ftos(n_fish), "\n"));
1014                 sprint(self, strcat("^3Current spiders: ^1", ftos(n_spiders), "\n"));
1015                 return TRUE;
1016         }
1017         
1018         return FALSE;
1019 }
1020
1021 MUTATOR_DEFINITION(gamemode_td)
1022 {
1023         MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
1024         MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
1025         MUTATOR_HOOK(MonsterMove, td_MonsterMove, CBC_ORDER_ANY);
1026         MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
1027         MUTATOR_HOOK(MonsterCheckBossFlag, td_MonsterCheckBossFlag, CBC_ORDER_ANY);
1028         MUTATOR_HOOK(SetStartItems, td_SetStartItems, CBC_ORDER_ANY);
1029         MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
1030         MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
1031         MUTATOR_HOOK(GiveFragsForKill, td_GiveFragsForKill, CBC_ORDER_ANY);
1032         MUTATOR_HOOK(PlayerPreThink, td_PlayerThink, CBC_ORDER_ANY);
1033         MUTATOR_HOOK(PlayerDies, td_PlayerDies, CBC_ORDER_ANY);
1034         MUTATOR_HOOK(PlayerDamage_Calculate, td_PlayerDamage_Calculate, CBC_ORDER_ANY);
1035         MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
1036         MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
1037         
1038         MUTATOR_ONADD
1039         {
1040                 if(time > 1) // game loads at time 1
1041                         error("This is a game type and it cannot be added at runtime.");        
1042                 cvar_settemp("g_monsters", "1");
1043                 cvar_settemp("g_turrets", "1");
1044                 td_Init();
1045         }
1046
1047         MUTATOR_ONREMOVE
1048         {
1049                 error("This is a game type and it cannot be removed at runtime.");
1050         }
1051
1052         return FALSE;
1053 }