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