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