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()
48 if(autocvar_sv_eventlog)
49 GameLogEcho(":gendestroyed");
53 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED);
55 setmodel(self, "models/onslaught/generator_dead.md3");
56 self.solid = SOLID_NOT;
57 self.takedamage = DAMAGE_NO;
58 self.event_damage = func_null;
62 pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
64 WaypointSprite_Kill(self.sprite);
67 void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
69 if(IS_PLAYER(attacker) || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE)
72 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_GENDAMAGED);
74 self.health -= damage;
76 WaypointSprite_UpdateHealth(self.sprite, self.health);
82 void spawnfunc_td_generator()
84 if not(g_td) { remove(self); return; }
89 self.health = autocvar_g_td_generator_health;
91 // precache generator model
92 precache_model("models/onslaught/generator.md3");
93 precache_model("models/onslaught/generator_dead.md3");
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;
103 self.think = func_null;
104 self.max_health = self.health;
105 self.movetype = MOVETYPE_NONE;
106 self.monster_attack = TRUE;
108 self.netname = "Generator";
110 setsize(self, GENERATOR_MIN, GENERATOR_MAX);
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);
119 entity PickGenerator()
121 entity generator, head;
123 generator = find(world, classname, "td_generator");
126 RandomSelection_Init();
127 for(head = world;(head = find(head, classname, "td_generator")); )
129 RandomSelection_Add(head, 0, string_null, 1, 1);
131 generator = RandomSelection_chosen_ent;
136 void spawn_td_fuel(float fuel_size)
138 if not(g_td) {remove(self); return; }
140 self.ammo_fuel = fuel_size * monster_skill;
141 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);
143 self.velocity = randomvec() * 175 + '0 0 325';
146 void spawnfunc_td_waypoint()
148 if not(g_td) { remove(self); return; }
150 self.classname = "td_waypoint";
153 void spawnfunc_monster_swarm()
155 if not(g_td) { remove(self); return; }
157 self.flags = SWARM_NORMAL; // marked as a spawnpoint
158 self.classname = "monster_swarm";
160 if(self.spawntype == SWARM_SWIM) waterspawns_count += 1;
161 if(self.spawntype == SWARM_FLY) flyspawns_count += 1;
163 WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '0 0 1');
165 if(self.target == "")
166 dprint("Warning: monster_swarm entity without a set target\n");
169 void barricade_touch()
171 if not(other.flags & FL_MONSTER)
174 if(time < self.dmg_time)
177 Damage(other, self, self, autocvar_g_td_barricade_damage, DEATH_HURTTRIGGER, self.origin, '0 0 0');
179 self.dmg_time = time + 1;
184 self.takedamage = DAMAGE_NO;
185 self.event_damage = func_null;
187 WaypointSprite_Kill(self.sprite);
189 pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
190 sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
193 self.realowner.turret_cnt -= 1;
195 self.think = SUB_Remove;
196 self.nextthink = time;
199 void barricade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
201 if not(attacker.flags & FL_MONSTER) return;
203 self.health -= damage;
205 WaypointSprite_UpdateHealth(self.sprite, self.health);
211 void spawn_barricade()
214 self.max_health = self.health;
215 self.dmg_time = time;
216 self.touch = barricade_touch;
217 self.think = func_null;
219 self.takedamage = DAMAGE_AIM;
220 self.turrcaps_flags = TFL_TURRCAPS_ISTURRET; // for turretremove commands etc.
221 self.solid = SOLID_CORPSE; // hax
222 self.event_damage = barricade_damage;
223 self.netname = "Barricade";
225 WaypointSprite_Spawn(self.netname, 0, 1200, self, '0 0 110', world, 0, self, sprite, FALSE, RADARICON_DOMPOINT, '1 1 0');
226 WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
227 WaypointSprite_UpdateHealth(self.sprite, self.health);
229 precache_model("models/td/barricade.md3");
230 setmodel(self, "models/td/barricade.md3");
234 self.movetype = MOVETYPE_NONE;
237 float td_checkfuel(entity ent, string tur)
239 float turcost = cvar(strcat("g_td_turret_", tur, "_cost"));
241 if(ent.ammo_fuel < turcost)
243 Send_Notification(NOTIF_ONE, ent, MSG_INFO, INFO_TD_NOFUEL);
247 ent.ammo_fuel -= turcost;
252 void spawnturret(entity spawnedby, entity own, string turet, vector orig)
254 if not(IS_PLAYER(spawnedby)) { dprint("Warning: A non-player entity tried to spawn a turret\n"); return; }
261 setorigin(self, orig);
262 self.spawnflags = TSL_NO_RESPAWN;
263 self.monster_attack = TRUE;
264 self.realowner = own;
265 self.playerid = own.playerid;
266 self.angles_y = spawnedby.v_angle_y;
267 spawnedby.turret_cnt += 1;
268 self.colormap = spawnedby.colormap;
272 case "plasma": if not(td_checkfuel(spawnedby, turet)) return; spawnfunc_turret_plasma(); break;
273 case "mlrs": if not(td_checkfuel(spawnedby, turet)) return; spawnfunc_turret_mlrs(); break;
274 case "walker": if not(td_checkfuel(spawnedby, turet)) return; spawnfunc_turret_walker(); break;
275 case "flac": if not(td_checkfuel(spawnedby, turet)) return; spawnfunc_turret_flac(); break;
276 case "towerbuff": if not(td_checkfuel(spawnedby, turet)) return; spawnfunc_turret_fusionreactor(); break;
277 case "barricade": if not(td_checkfuel(spawnedby, turet)) return; spawn_barricade(); break;
278 default: Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_INVALID); remove(self); self = oldself; return;
281 Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_SPAWN);
286 void buffturret (entity tur, float buff)
288 tur.turret_buff += 1;
289 tur.max_health *= buff;
290 tur.tur_health = tur.max_health;
291 tur.health = tur.max_health;
292 tur.ammo_max *= buff;
293 tur.ammo_recharge *= buff;
294 tur.shot_dmg *= buff;
295 tur.shot_refire -= buff * 0.2;
296 tur.shot_radius *= buff;
297 tur.shot_speed *= buff;
298 tur.shot_spread *= buff;
299 tur.shot_force *= buff;
302 void AnnounceSpawn(string anounce)
304 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce);
307 entity PickSpawn (float strngth, float type)
310 RandomSelection_Init();
311 for(e = world;(e = find(e, classname, "monster_swarm")); )
313 if(flyspawns_count > 0 && type == SWARM_FLY && e.spawntype != SWARM_FLY) continue;
314 if(waterspawns_count > 0 && type == SWARM_SWIM && e.spawntype != SWARM_SWIM) continue;
316 RandomSelection_Add(e, 0, string_null, 1, 1);
319 return RandomSelection_chosen_ent;
322 void TD_SpawnMonster(string mnster, float strngth, float type)
326 e = PickSpawn(strngth, type);
328 if(e == world) // couldn't find anything for our class, so check for normal spawns
329 e = PickSpawn(SWARM_NORMAL, SWARM_NORMAL);
333 dprint("Warning: couldn't find any monster_swarm spawnpoints, no monsters will spawn!\n");
337 mon = spawnmonster(mnster, e, e, e.origin, FALSE, 0);
341 mon.target = e.target2;
343 mon.target = e.target;
346 mon.target = e.target;
349 float Monster_GetStrength(string mnster)
374 float Monster_GetType(string mnster)
399 string RandomMonster()
401 RandomSelection_Init();
403 if(n_demons) RandomSelection_Add(world, 0, "demon", 1, 1);
404 if(n_shalraths) RandomSelection_Add(world, 0, "vore", 1, 1);
405 if(n_soldiers) RandomSelection_Add(world, 0, "soldier", 1, 1);
406 if(n_hknights) RandomSelection_Add(world, 0, "hellknight", 1, 1);
407 if(n_enforcers) RandomSelection_Add(world, 0, "enforcer", 1, 1);
408 if(n_zombies) RandomSelection_Add(world, 0, "zombie", 1, 1);
409 if(n_spiders) RandomSelection_Add(world, 0, "spider", 1, 1);
410 if(n_ogres) RandomSelection_Add(world, 0, "ogre", 1, 1);
411 if(n_dogs) RandomSelection_Add(world, 0, "dog", 1, 1);
412 if(n_knights) RandomSelection_Add(world, 0, "knight", 1, 1);
413 if(n_shamblers) RandomSelection_Add(world, 0, "shambler", 0.2, 0.2);
414 if(n_tarbabies) RandomSelection_Add(world, 0, "spawn", 0.2, 0.2);
415 if(n_wizards && flyspawns_count) RandomSelection_Add(world, 0, "scrag", 1, 1);
416 if(n_fish && waterspawns_count) RandomSelection_Add(world, 0, "fish", 0.2, 0.2);
418 return RandomSelection_chosen_string;
424 float mstrength, montype;
426 current_phase = PHASE_COMBAT;
428 if(monster_count <= 0)
434 self.think = combat_phase;
436 whichmon = RandomMonster();
438 mstrength = Monster_GetStrength(whichmon);
439 montype = Monster_GetType(whichmon);
441 if(current_monsters <= max_current && whichmon != "")
443 TD_SpawnMonster(whichmon, mstrength, montype);
444 self.nextthink = time + spawn_delay;
447 self.nextthink = time + 6;
450 void queue_monsters(float maxmonsters)
452 float mc = 11; // note: shambler + tarbaby = 1
454 if(waterspawns_count > 0)
456 if(flyspawns_count > 0)
459 DistributeEvenly_Init(maxmonsters, mc);
460 n_demons = DistributeEvenly_Get(1);
461 n_ogres = DistributeEvenly_Get(1);
462 n_dogs = DistributeEvenly_Get(1);
463 n_knights = DistributeEvenly_Get(1);
464 n_shalraths = DistributeEvenly_Get(1);
465 n_soldiers = DistributeEvenly_Get(1);
466 n_hknights = DistributeEvenly_Get(1);
467 n_enforcers = DistributeEvenly_Get(1);
468 n_zombies = DistributeEvenly_Get(1);
469 n_spiders = DistributeEvenly_Get(1);
470 n_tarbabies = DistributeEvenly_Get(0.7);
471 n_shamblers = DistributeEvenly_Get(0.3);
472 if(flyspawns_count > 0)
473 n_wizards = DistributeEvenly_Get(1);
474 if(waterspawns_count > 0)
475 n_fish = DistributeEvenly_Get(1);
478 void combat_phase_begin()
480 monster_count = totalmonsters;
483 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_COMBAT);
485 if(autocvar_sv_eventlog)
486 GameLogEcho(":combatphase");
488 self.think = combat_phase;
489 self.nextthink = time + 1;
491 for(gen = world;(gen = find(gen, classname, "td_generator")); )
492 gen.takedamage = DAMAGE_AIM;
495 float cphase_updates;
496 void combat_phase_announce() // TODO: clean up these fail nextthinks...
500 if(cphase_updates == 0)
501 Announce("prepareforbattle");
502 else if(cphase_updates == 3)
504 else if(cphase_updates == 4)
506 else if(cphase_updates == 5)
508 else if(cphase_updates == 6)
511 combat_phase_begin();
514 if(cphase_updates >= 6)
517 self.think = combat_phase_announce;
518 self.nextthink = time + 1;
524 float n_players = 0, gen_washealed = FALSE, player_washealed = FALSE;
526 current_phase = PHASE_BUILD;
528 for(head = world;(head = find(head, classname, "td_generator")); )
530 if(head.health <= 5 && head.max_health > 10)
531 Announce("lastsecond");
533 if(head.health < head.max_health)
535 gen_washealed = TRUE;
536 head.health = head.max_health;
537 WaypointSprite_UpdateHealth(head.sprite, head.health);
539 head.takedamage = DAMAGE_NO;
542 FOR_EACH_PLAYER(head)
544 if(head.health < 100)
546 player_washealed = TRUE;
547 break; // we found 1, so no need to check the others
551 totalmonsters += autocvar_g_td_monster_count_increment * wave_count;
552 monster_skill += autocvar_g_td_monsters_skill_increment;
554 if(wave_count < 1) wave_count = 1;
556 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_BUILD, wave_count, totalmonsters, autocvar_g_td_buildphase_time);
558 FOR_EACH_PLAYER(head)
560 if(head.health < 100) head.health = 100;
562 if(gen_washealed) PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
567 FOR_EACH_MONSTER(head)
572 dprint(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
574 WaypointSprite_Kill(head.sprite);
580 totalmonsters += n_players;
581 monster_skill += n_players * 0.05;
584 if(monster_skill < 1) monster_skill = 1;
586 if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
588 monsters_total = totalmonsters;
591 queue_monsters(totalmonsters);
595 if(autocvar_sv_eventlog)
596 GameLogEcho(strcat(":buildphase:", ftos(wave_count), ":", ftos(totalmonsters)));
598 self.think = combat_phase_announce;
599 self.nextthink = time + build_time - 6;
602 void wave_end(float starting)
606 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_VICTORY, ((wave_count >= max_waves) ? "Level" : "Wave"));
608 if(autocvar_sv_eventlog)
609 GameLogEcho(strcat(":wave:", ftos(wave_count), ":victory"));
612 if(wave_count >= max_waves)
621 self.think = build_phase;
622 self.nextthink = time + 3;
627 ScoreInfo_SetLabel_PlayerScore(SP_TD_SCORE, "score", SFL_SORT_PRIO_PRIMARY);
628 ScoreInfo_SetLabel_PlayerScore(SP_TD_KILLS, "kills", SFL_LOWER_IS_BETTER);
629 ScoreInfo_SetLabel_PlayerScore(SP_TD_TURKILLS, "frags", SFL_LOWER_IS_BETTER);
630 ScoreInfo_SetLabel_PlayerScore(SP_TD_DEATHS, "deaths", SFL_LOWER_IS_BETTER);
631 ScoreInfo_SetLabel_PlayerScore(SP_TD_SUICIDES, "suicides", SFL_LOWER_IS_BETTER | SFL_ALLOW_HIDE);
632 ScoreRules_basics_end();
635 void td_SpawnController()
637 entity oldself = self;
639 self.classname = "td_controller";
640 spawnfunc_td_controller();
644 void td_DelayedInit()
646 if(find(world, classname, "td_controller") == world)
648 print("No ""td_controller"" entity found on this map, creating it anyway.\n");
649 td_SpawnController();
657 InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
660 MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
662 if(turret_flags & TFL_TARGETSELECT_MISSILESONLY)
663 if(turret_target.flags & FL_PROJECTILE)
664 if(turret_target.owner.flags & FL_MONSTER)
665 return TRUE; // flac support
667 if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
669 if not(turret_target.flags & FL_MONSTER)
670 turret_target = world;
675 MUTATOR_HOOKFUNCTION(td_PlayerThink)
677 self.stat_current_wave = wave_count;
678 self.stat_totalwaves = max_waves;
683 MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
685 self.bot_attack = FALSE;
690 MUTATOR_HOOKFUNCTION(td_PlayerDies)
692 if(frag_attacker.flags & FL_MONSTER)
693 PlayerScore_Add(frag_target, SP_TD_DEATHS, 1);
695 if(frag_target == frag_attacker)
696 PlayerScore_Add(frag_attacker, SP_TD_SUICIDES, 1);
701 MUTATOR_HOOKFUNCTION(td_GiveFragsForKill)
705 return TRUE; // no frags counted in td
708 MUTATOR_HOOKFUNCTION(td_PlayerDamage)
710 if(frag_attacker.realowner == frag_target)
713 if(frag_target.flags & FL_MONSTER && time < frag_target.spawnshieldtime)
716 if(frag_target.vehicle_flags & VHF_ISVEHICLE && !(frag_attacker.flags & FL_MONSTER))
719 if(frag_attacker.vehicle_flags & VHF_ISVEHICLE && !(frag_target.flags & FL_MONSTER))
722 if(!autocvar_g_td_pvp && frag_attacker != frag_target && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
725 if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && IS_PLAYER(frag_target))
728 if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && !(frag_attacker.flags & FL_MONSTER || frag_attacker.turrcaps_flags & TFL_TURRCAPS_SUPPORT))
734 MUTATOR_HOOKFUNCTION(td_TurretDies)
737 self.realowner.turret_cnt -= 1;
742 MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag)
744 // No minibosses in tower defense
748 MUTATOR_HOOKFUNCTION(td_MonsterMove)
752 FOR_EACH_PLAYER(player) { ++n_players; }
754 if(n_players < 1) // no players online, so do nothing
756 monster_target = world;
757 monster_speed_run = monster_speed_walk = 0;
761 if not(self.enemy) // don't change targets while attacking
762 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"))
764 if(self.goalentity.target2)
767 self.target = self.goalentity.target2;
769 self.target = self.goalentity.target;
772 self.target = self.goalentity.target;
774 self.goalentity = find(world, targetname, self.target);
776 if(self.goalentity == world)
777 self.goalentity = PickGenerator();
780 monster_speed_run = m_speed_run * monster_skill;
781 monster_speed_walk = m_speed_walk * monster_skill;
786 MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
788 if(self.realowner == world) // nothing spawned it, so kill it
790 WaypointSprite_Kill(self.sprite);
795 current_monsters += 1;
797 self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time;
799 self.drop_size = self.health * 0.05;
801 if(self.drop_size < 1) self.drop_size = 1;
803 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_BODY;
805 self.origin += '0 0 25'; // hopefully this fixes monsters falling through the floor
807 switch(self.classname)
809 case "monster_knight": n_knights -= 1; break;
810 case "monster_dog": n_dogs -= 1; break;
811 case "monster_ogre": n_ogres -= 1; break;
812 case "monster_shambler": n_shamblers -= 1; AnnounceSpawn("Shambler"); break;
813 case "monster_wizard": n_wizards -= 1; break;
814 case "monster_shalrath": n_shalraths -= 1; break;
815 case "monster_soldier": n_soldiers -= 1; break;
816 case "monster_hellknight": n_hknights -= 1; break;
817 case "monster_enforcer": n_enforcers -= 1; break;
818 case "monster_demon": n_demons -= 1; break;
819 case "monster_zombie": n_zombies -= 1; break;
820 case "monster_spider": n_spiders -= 1; break;
821 case "monster_tarbaby": n_tarbabies -= 1; break;
827 MUTATOR_HOOKFUNCTION(td_MonsterDies)
833 current_monsters -= 1;
834 monsters_killed += 1;
836 if(IS_PLAYER(frag_attacker))
838 PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
839 PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
841 else if(IS_PLAYER(frag_attacker.realowner))
843 PlayerScore_Add(frag_attacker.realowner, SP_TD_SCORE, autocvar_g_td_turretkill_points);
844 PlayerScore_Add(frag_attacker.realowner, SP_TD_TURKILLS, 1);
847 backuporigin = self.origin;
852 setorigin(self, backuporigin + '0 0 5');
853 spawn_td_fuel(oldself.drop_size);
854 self.touch = M_Item_Touch;
860 SUB_SetFade(self, time + 5, 1);
867 MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
873 FOR_EACH_PLAYER(player) { ++n_players; }
875 if(n_players < 1) // no players online, so do nothing
881 for(e = world;(e = findflags(e, monster_attack, TRUE)); )
884 if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
887 if(monster_isvalidtarget(e, self))
888 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"))
897 MUTATOR_HOOKFUNCTION(td_SetStartItems)
899 start_ammo_fuel = 150; // to be nice...
904 MUTATOR_HOOKFUNCTION(td_TurretSpawn)
906 if(self.realowner == world)
907 return TRUE; // wasn't spawned by a player
909 self.bot_attack = FALSE;
910 self.turret_buff = 1;
915 MUTATOR_HOOKFUNCTION(td_DisableVehicles)
917 // you shall not spawn!
921 MUTATOR_HOOKFUNCTION(td_PlayerCommand)
923 if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
925 makevectors(self.v_angle);
926 WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_HITMODEL, self);
928 if(cmd_name == "turretspawn")
930 if(argv(1) == "list")
932 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_LIST, "mlrs walker plasma towerbuff flac barricade");
935 if(!IS_PLAYER(self) || self.health <= 0)
937 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_CANTSPAWN);
940 if(self.turret_cnt >= max_turrets)
942 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXTURRETS, max_turrets);
946 spawnturret(self, self, argv(1), trace_endpos);
950 if(cmd_name == "repairturret")
952 if((trace_ent.playerid != self.playerid || trace_ent.realowner != self) || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
954 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REPAIR);
957 if(self.ammo_fuel < autocvar_g_td_turret_repair_cost)
959 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_REPAIR, autocvar_g_td_turret_repair_cost);
962 if(trace_ent.health >= trace_ent.max_health)
964 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXHEALTH);
968 self.ammo_fuel -= autocvar_g_td_turret_repair_cost;
969 trace_ent.SendFlags |= TNSF_STATUS;
970 trace_ent.health = bound(1, trace_ent.health + 100, trace_ent.max_health);
971 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REPAIR);
975 if(cmd_name == "buffturret")
977 if((trace_ent.playerid != self.playerid || trace_ent.realowner != self) || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
979 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_UPGRADE);
982 if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)
984 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_UPGRADE, autocvar_g_td_turret_upgrade_cost);
987 if(trace_ent.turret_buff >= 3)
989 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXPOWER);
993 self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost;
994 trace_ent.SendFlags |= TNSF_STATUS;
995 buffturret(trace_ent, 1.2);
996 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_UPGRADE);
1000 if(cmd_name == "turretremove")
1002 if((trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && (trace_ent.playerid == self.playerid || trace_ent.realowner == self))
1004 self.turret_cnt -= 1;
1005 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REMOVE);
1006 WaypointSprite_Kill(trace_ent.sprite);
1007 remove(trace_ent.tur_head);
1011 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REMOVE);
1018 MUTATOR_DEFINITION(gamemode_td)
1020 MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
1021 MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
1022 MUTATOR_HOOK(MonsterMove, td_MonsterMove, CBC_ORDER_ANY);
1023 MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
1024 MUTATOR_HOOK(MonsterCheckBossFlag, td_MonsterCheckBossFlag, CBC_ORDER_ANY);
1025 MUTATOR_HOOK(SetStartItems, td_SetStartItems, CBC_ORDER_ANY);
1026 MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
1027 MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
1028 MUTATOR_HOOK(TurretDies, td_TurretDies, CBC_ORDER_ANY);
1029 MUTATOR_HOOK(GiveFragsForKill, td_GiveFragsForKill, CBC_ORDER_ANY);
1030 MUTATOR_HOOK(PlayerPreThink, td_PlayerThink, CBC_ORDER_ANY);
1031 MUTATOR_HOOK(PlayerDies, td_PlayerDies, CBC_ORDER_ANY);
1032 MUTATOR_HOOK(PlayerDamage_Calculate, td_PlayerDamage, CBC_ORDER_ANY);
1033 MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
1034 MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY);
1035 MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
1039 if(time > 1) // game loads at time 1
1040 error("This is a game type and it cannot be added at runtime.");
1041 cvar_settemp("g_monsters", "1");
1042 cvar_settemp("g_turrets", "1");
1046 MUTATOR_ONROLLBACK_OR_REMOVE
1048 // we actually cannot roll back td_Initialize here
1049 // BUT: we don't need to! If this gets called, adding always
1055 error("This is a game type and it cannot be removed at runtime.");