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