4 void spawnfunc_td_controller()
6 if not(g_td) { remove(self); return; }
8 if(autocvar_g_td_force_settings)
10 // TODO: find a better way to do this?
13 self.monstercount = 0;
21 self.ignoreturrets = 0;
24 self.netname = "Tower Defense controller entity";
25 self.classname = "td_controller";
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);
40 if(autocvar_g_td_monsters_skill_start)
41 monster_skill = autocvar_g_td_monsters_skill_start;
46 void td_generator_die()
50 print((td_gencount > 1) ? "A generator was destroyed!\n" : "The generator was destroyed.\n");
52 if(autocvar_sv_eventlog)
53 GameLogEcho(":gendestroyed");
59 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, ((td_gencount > 1) ? "A generator was destroyed!" : "The generator was destroyed."), 0, 0);
62 setmodel(self, "models/onslaught/generator_dead.md3");
63 self.solid = SOLID_NOT;
64 self.takedamage = DAMAGE_NO;
65 self.event_damage = func_null;
69 pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
71 WaypointSprite_Kill(self.sprite);
74 void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
76 if(attacker.classname == STR_PLAYER || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE)
83 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "The generator is under attack!", 0, 0);
86 self.health -= damage;
88 WaypointSprite_UpdateHealth(self.sprite, self.health);
94 void spawnfunc_td_generator()
96 if not(g_td) { remove(self); return; }
101 self.health = autocvar_g_td_generator_health;
103 // precache generator model
104 precache_model("models/onslaught/generator.md3");
105 precache_model("models/onslaught/generator_dead.md3");
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;
115 self.think = func_null;
116 self.max_health = self.health;
117 self.movetype = MOVETYPE_NONE;
118 self.monster_attack = TRUE;
120 self.netname = "Generator";
122 setsize(self, GENERATOR_MIN, GENERATOR_MAX);
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);
131 entity PickGenerator()
133 entity generator, head;
135 generator = find(world, classname, "td_generator");
138 RandomSelection_Init();
139 for(head = world;(head = find(head, classname, "td_generator")); )
141 RandomSelection_Add(head, 0, string_null, 1, 1);
143 generator = RandomSelection_chosen_ent;
148 void spawn_td_fuel(float fuel_size)
150 if not(g_td) {remove(self); return; }
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);
155 self.velocity = randomvec() * 175 + '0 0 325';
158 void spawnfunc_td_waypoint()
160 if not(g_td) { remove(self); return; }
162 self.classname = "td_waypoint";
165 void spawnfunc_monster_swarm()
167 if not(g_td) { remove(self); return; }
169 self.flags = SWARM_NORMAL; // marked as a spawnpoint
170 self.classname = "monster_swarm";
172 if(self.spawntype == SWARM_SWIM) waterspawns_count += 1;
173 if(self.spawntype == SWARM_FLY) flyspawns_count += 1;
175 WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '0 0 1');
177 if(self.target == "")
178 print("Warning: monster_swarm entity without a set target\n");
181 void barricade_touch()
183 if not(other.flags & FL_MONSTER)
186 if(time < self.dmg_time)
189 Damage(other, self, self, autocvar_g_td_barricade_damage, DEATH_HURTTRIGGER, self.origin, '0 0 0');
191 self.dmg_time = time + 1;
196 self.takedamage = DAMAGE_NO;
197 self.event_damage = func_null;
199 WaypointSprite_Kill(self.sprite);
201 pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
202 sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
205 self.realowner.turret_cnt -= 1;
207 self.think = SUB_Remove;
208 self.nextthink = time;
211 void barricade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
213 if not(attacker.flags & FL_MONSTER) return;
215 self.health -= damage;
217 WaypointSprite_UpdateHealth(self.sprite, self.health);
223 void spawn_barricade()
226 self.max_health = self.health;
227 self.dmg_time = time;
228 self.touch = barricade_touch;
229 self.think = func_null;
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";
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);
241 precache_model("models/td/barricade.md3");
242 setmodel(self, "models/td/barricade.md3");
246 self.movetype = MOVETYPE_NONE;
249 void spawnturret(entity spawnedby, entity own, string turet, vector orig)
251 if(spawnedby.classname != STR_PLAYER)
253 print("Warning: A non-player entity tried to spawn a turret\n");
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;
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;
287 void buffturret (entity tur, float buff)
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;
303 void AnnounceSpawn(string anounce)
306 FOR_EACH_PLAYER(tail)
308 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, strcat("^1A ", anounce, " has arrived!"), 0, 0);
312 entity PickSpawn (float strngth, float type)
315 RandomSelection_Init();
316 for(e = world;(e = find(e, classname, "monster_swarm")); )
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;
321 RandomSelection_Add(e, 0, string_null, 1, 1);
324 return RandomSelection_chosen_ent;
327 void TD_SpawnMonster(string mnster, float strngth, float type)
331 e = PickSpawn(strngth, type);
333 if(e == world) // couldn't find anything for our class, so check for normal spawns
334 e = PickSpawn(SWARM_NORMAL, SWARM_NORMAL);
338 print("Warning: couldn't find any monster_swarm spawnpoints, no monsters will spawn!\n");
342 mon = spawnmonster(mnster, e, e, e.origin, FALSE, 0);
346 mon.target = e.target2;
348 mon.target = e.target;
351 mon.target = e.target;
354 float Monster_GetStrength(string mnster)
379 float Monster_GetType(string mnster)
404 string RandomMonster()
406 RandomSelection_Init();
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);
423 return RandomSelection_chosen_string;
429 float mstrength, montype;
431 current_phase = PHASE_COMBAT;
433 if(monster_count <= 0)
439 self.think = combat_phase;
441 whichmon = RandomMonster();
443 mstrength = Monster_GetStrength(whichmon);
444 montype = Monster_GetType(whichmon);
446 if(current_monsters <= max_current && whichmon != "")
448 TD_SpawnMonster(whichmon, mstrength, montype);
449 self.nextthink = time + spawn_delay;
452 self.nextthink = time + 6;
455 void queue_monsters(float maxmonsters)
457 float mc = 11; // note: shambler + tarbaby = 1
459 if(waterspawns_count > 0)
461 if(flyspawns_count > 0)
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);
483 void combat_phase_begin()
485 monster_count = totalmonsters;
488 print("^1Combat phase!\n");
489 FOR_EACH_PLAYER(tail)
491 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "^1Combat phase!", 0, 0);
493 if(autocvar_sv_eventlog)
494 GameLogEcho(":combatphase");
495 self.think = combat_phase;
496 self.nextthink = time + 1;
498 for(head = world;(head = find(head, classname, "td_generator")); )
499 head.takedamage = DAMAGE_AIM;
502 float cphase_updates;
503 void combat_phase_announce() // TODO: clean up these fail nextthinks...
507 if(cphase_updates == 0)
508 Announce("prepareforbattle");
509 else if(cphase_updates == 3)
511 else if(cphase_updates == 4)
513 else if(cphase_updates == 5)
515 else if(cphase_updates == 6)
518 combat_phase_begin();
521 if(cphase_updates >= 6)
524 self.think = combat_phase_announce;
525 self.nextthink = time + 1;
531 float n_players = 0, gen_washealed = FALSE, player_washealed = FALSE;
532 string buildmsg, healmsg, countmsg, startmsg, genhealmsg;
534 current_phase = PHASE_BUILD;
536 for(head = world;(head = find(head, classname, "td_generator")); )
538 if(head.health <= 5 && head.max_health > 10)
539 Announce("lastsecond");
541 if(head.health < head.max_health)
543 gen_washealed = TRUE;
544 head.health = head.max_health;
545 WaypointSprite_UpdateHealth(head.sprite, head.health);
547 head.takedamage = DAMAGE_NO;
550 FOR_EACH_PLAYER(head)
552 if(head.health < 100)
554 player_washealed = TRUE;
555 break; // we found 1, so no need to check the others
559 totalmonsters += autocvar_g_td_monster_count_increment * wave_count;
560 monster_skill += autocvar_g_td_monsters_skill_increment;
562 if(wave_count < 1) wave_count = 1;
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");
570 FOR_EACH_PLAYER(head)
572 if(head.health < 100)
576 PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
579 Send_CSQC_Centerprint_Generic(head, CPID_KH_MSG, strcat(buildmsg, healmsg, countmsg, startmsg), 5, 0);
582 FOR_EACH_MONSTER(head)
587 print(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
589 WaypointSprite_Kill(head.sprite);
595 totalmonsters += n_players;
596 monster_skill += n_players * 0.05;
599 if(monster_skill < 1) monster_skill = 1;
601 if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
603 monsters_total = totalmonsters;
606 print(strcat(buildmsg, healmsg, countmsg, startmsg, "\n"));
608 queue_monsters(totalmonsters);
612 if(autocvar_sv_eventlog)
613 GameLogEcho(strcat(":buildphase:", ftos(wave_count), ":", ftos(totalmonsters)));
615 self.think = combat_phase_announce;
616 self.nextthink = time + build_time - 6;
619 void wave_end(float starting)
622 FOR_EACH_PLAYER(tail)
625 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "Protect the generator from waves of monsters!", 0, 0);
627 Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, ((wave_count >= max_waves) ? "Level victory!" : "Wave victory!"), 0, 0);
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"));
637 if(wave_count >= max_waves)
646 self.think = build_phase;
647 self.nextthink = time + 3;
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();
660 void td_SpawnController()
662 entity oldself = self;
664 self.classname = "td_controller";
665 spawnfunc_td_controller();
669 void td_DelayedInit()
671 if(find(world, classname, "td_controller") == world)
673 print("No ""td_controller"" entity found on this map, creating it anyway.\n");
674 td_SpawnController();
682 InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
685 MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
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
692 if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
694 if not(turret_target.flags & FL_MONSTER)
695 turret_target = world;
700 MUTATOR_HOOKFUNCTION(td_PlayerThink)
702 self.stat_current_wave = wave_count;
703 self.stat_totalwaves = max_waves;
708 MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
710 self.bot_attack = FALSE;
715 MUTATOR_HOOKFUNCTION(td_PlayerDies)
717 if(frag_attacker.flags & FL_MONSTER)
718 PlayerScore_Add(frag_target, SP_TD_DEATHS, 1);
720 if(frag_target == frag_attacker)
721 PlayerScore_Add(frag_attacker, SP_TD_SUICIDES, 1);
726 MUTATOR_HOOKFUNCTION(td_GiveFragsForKill)
730 return TRUE; // no frags counted in td
733 MUTATOR_HOOKFUNCTION(td_PlayerDamage)
735 if(frag_attacker.realowner == frag_target)
738 if(frag_target.flags & FL_MONSTER && time < frag_target.spawnshieldtime)
741 if(frag_target.vehicle_flags & VHF_ISVEHICLE && !(frag_attacker.flags & FL_MONSTER))
744 if(frag_attacker.vehicle_flags & VHF_ISVEHICLE && !(frag_target.flags & FL_MONSTER))
747 if(!autocvar_g_td_pvp && frag_attacker != frag_target && frag_target.classname == STR_PLAYER && frag_attacker.classname == STR_PLAYER)
750 if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && frag_target.classname == STR_PLAYER)
753 if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && !(frag_attacker.flags & FL_MONSTER || frag_attacker.turrcaps_flags & TFL_TURRCAPS_SUPPORT))
759 MUTATOR_HOOKFUNCTION(td_TurretDies)
762 self.realowner.turret_cnt -= 1;
767 MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag)
769 // No minibosses in tower defense
773 MUTATOR_HOOKFUNCTION(td_MonsterMove)
777 FOR_EACH_PLAYER(player) { ++n_players; }
779 if(n_players < 1) // no players online, so do nothing
781 monster_target = world;
782 monster_speed_run = monster_speed_walk = 0;
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"))
789 if(self.goalentity.target2)
792 self.target = self.goalentity.target2;
794 self.target = self.goalentity.target;
797 self.target = self.goalentity.target;
799 self.goalentity = find(world, targetname, self.target);
801 if(self.goalentity == world)
802 self.goalentity = PickGenerator();
805 monster_speed_run = m_speed_run * monster_skill;
806 monster_speed_walk = m_speed_walk * monster_skill;
811 MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
813 if(self.realowner == world) // nothing spawned it, so kill it
815 WaypointSprite_Kill(self.sprite);
820 current_monsters += 1;
822 self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time;
824 self.drop_size = self.health * 0.05;
826 if(self.drop_size < 1) self.drop_size = 1;
828 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_BODY;
830 self.origin += '0 0 25'; // hopefully this fixes monsters falling through the floor
832 switch(self.classname)
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;
852 MUTATOR_HOOKFUNCTION(td_MonsterDies)
858 current_monsters -= 1;
859 monsters_killed += 1;
861 if(frag_attacker.classname == STR_PLAYER)
863 PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
864 PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
866 else if(frag_attacker.realowner.classname == STR_PLAYER)
868 PlayerScore_Add(frag_attacker.realowner, SP_TD_SCORE, autocvar_g_td_turretkill_points);
869 PlayerScore_Add(frag_attacker.realowner, SP_TD_TURKILLS, 1);
872 backuporigin = self.origin;
877 setorigin(self, backuporigin + '0 0 5');
878 spawn_td_fuel(oldself.drop_size);
879 self.touch = M_Item_Touch;
885 SUB_SetFade(self, time + 5, 1);
892 MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
898 FOR_EACH_PLAYER(player) { ++n_players; }
900 if(n_players < 1) // no players online, so do nothing
906 for(e = world;(e = findflags(e, monster_attack, TRUE)); )
909 if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
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"))
922 MUTATOR_HOOKFUNCTION(td_SetStartItems)
924 start_ammo_fuel = 150; // to be nice...
929 MUTATOR_HOOKFUNCTION(td_TurretSpawn)
931 if(self.realowner == world)
932 return TRUE; // wasn't spawned by a player
934 self.bot_attack = FALSE;
935 self.turret_buff = 1;
940 MUTATOR_HOOKFUNCTION(td_DisableVehicles)
942 // you shall not spawn!
946 MUTATOR_HOOKFUNCTION(td_PlayerCommand)
948 if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
950 makevectors(self.v_angle);
951 WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
953 if(cmd_name == "turretspawn")
955 if(argv(1) == "list")
957 sprint(self, "Available turrets:\n");
958 sprint(self, "^3mlrs walker plasma towerbuff\n");
961 if(self.classname != STR_PLAYER || self.health <= 0)
963 sprint(self, "Can't spawn turrets while spectating/dead\n");
966 if(self.turret_cnt >= max_turrets)
968 sprint(self, strcat("Can't spawn more than ", ftos(max_turrets), " turrets\n"));
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");
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");
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");
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");
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");
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");
1023 sprint(self, "Invalid turret. type 'cmd turret list' to see a list of all available turrets.\n");
1027 sprint(self, strcat("You do not have enough fuel to spawn a ", argv(1), " turret\n"));
1030 if(cmd_name == "repairturret")
1032 if(trace_ent.realowner != self || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
1034 sprint(self, "You need to aim at your turret to repair it\n");
1037 if(self.ammo_fuel < autocvar_g_td_turret_repair_cost)
1039 sprint(self, strcat("You need ", ftos(autocvar_g_td_turret_repair_cost), " fuel to repair this turret\n"));
1042 if(trace_ent.health >= trace_ent.max_health)
1044 sprint(self, "This turret is already at max health\n");
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");
1055 if(cmd_name == "buffturret")
1057 if(trace_ent.realowner != self || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
1059 sprint(self, "You need to aim at your turret to upgrade it\n");
1062 if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)
1064 sprint(self, strcat("You need ", ftos(autocvar_g_td_turret_upgrade_cost), " fuel to increase this turret's power\n"));
1067 if(trace_ent.turret_buff >= 3)
1069 sprint(self, "This turret cannot be buffed up any higher\n");
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");
1080 if(cmd_name == "turretremove")
1082 if((trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && trace_ent.realowner == self)
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);
1091 sprint(self, "You need to aim at your turret to remove it\n");
1094 if(cmd_name == "debugmonsters")
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"));
1121 MUTATOR_DEFINITION(gamemode_td)
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);
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");
1151 error("This is a game type and it cannot be removed at runtime.");