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";
29 td_dont_end = ((self.dontend == 0) ? autocvar_g_td_generator_dontend : self.dontend);
30 max_waves = ((self.maxwaves == 0) ? autocvar_g_td_max_waves : self.maxwaves);
31 totalmonsters = ((self.monstercount == 0) ? autocvar_g_td_monster_count : self.monstercount);
32 wave_count = ((self.startwave == 0) ? autocvar_g_td_start_wave : self.startwave);
33 max_turrets = ((self.maxturrets == 0) ? autocvar_g_td_turret_max : self.maxturrets);
34 build_time = ((self.buildtime == 0) ? autocvar_g_td_buildphase_time : self.buildtime);
35 m_speed_walk = ((self.mspeed_walk == 0) ? autocvar_g_td_monsters_speed_walk : self.mspeed_walk);
36 m_speed_run = ((self.mspeed_run == 0) ? autocvar_g_td_monsters_speed_run : self.mspeed_run);
37 spawn_delay = ((self.spawndelay == 0) ? autocvar_g_td_monsters_spawn_delay : self.spawndelay);
38 max_current = ((self.maxcurrent == 0) ? autocvar_g_td_current_monsters : self.maxcurrent);
39 ignore_turrets = ((self.ignoreturrets == 0) ? autocvar_g_td_monsters_ignore_turrets : self.ignoreturrets);
41 if(autocvar_g_td_monsters_skill_start)
42 monster_skill = autocvar_g_td_monsters_skill_start;
47 void td_generator_die()
49 if(autocvar_sv_eventlog)
50 GameLogEcho(":gendestroyed");
54 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED);
56 self.solid = SOLID_NOT;
57 self.takedamage = DAMAGE_NO;
58 self.event_damage = func_null;
62 WaypointSprite_Kill(self.sprite);
65 void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
67 if(IS_PLAYER(attacker) || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE)
70 if (time > self.pain_finished)
72 self.pain_finished = time + 10;
73 play2all("onslaught/generator_underattack.wav");
76 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_GENDAMAGED);
78 self.health -= damage;
80 WaypointSprite_UpdateHealth(self.sprite, self.health);
85 self.SendFlags |= MSF_STATUS;
88 float td_generator_send(entity to, float sf)
90 WriteByte(MSG_ENTITY, ENT_CLIENT_GENERATOR);
91 WriteByte(MSG_ENTITY, sf);
94 WriteCoord(MSG_ENTITY, self.origin_x);
95 WriteCoord(MSG_ENTITY, self.origin_y);
96 WriteCoord(MSG_ENTITY, self.origin_z);
102 WriteByte(MSG_ENTITY, 0);
104 WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
110 void td_generator_think()
112 self.think = td_generator_think;
113 self.nextthink = time + 0.5;
116 void td_generator_setup()
119 self.think = td_generator_think;
120 self.nextthink = time + 0.1;
122 self.solid = SOLID_BBOX;
123 self.takedamage = DAMAGE_AIM;
124 self.event_damage = td_generator_damage;
126 self.max_health = self.health;
127 self.movetype = MOVETYPE_NONE;
128 self.monster_attack = TRUE;
129 self.netname = "Generator";
131 self.SendFlags |= MSF_SETUP;
133 WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 60', self, sprite, RADARICON_OBJECTIVE, '1 0.5 0');
134 WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
135 WaypointSprite_UpdateHealth(self.sprite, self.health);
138 void td_generator_link()
140 Net_LinkEntity(self, TRUE, 0, td_generator_send);
141 self.think = td_generator_setup;
142 self.nextthink = time;
145 void spawnfunc_td_generator()
147 if not(g_td) { remove(self); return; }
149 gendestroyed = FALSE;
152 self.health = autocvar_g_td_generator_health;
154 self.classname = "td_generator";
157 setsize(self, GENERATOR_MIN, GENERATOR_MAX);
164 entity PickGenerator()
166 entity generator, head;
168 generator = find(world, classname, "td_generator");
171 RandomSelection_Init();
172 for(head = world;(head = find(head, classname, "td_generator")); )
174 RandomSelection_Add(head, 0, string_null, 1, 1);
176 generator = RandomSelection_chosen_ent;
181 void spawn_td_fuel(float fuel_size)
183 if not(g_td) {remove(self); return; }
185 self.ammo_fuel = fuel_size * monster_skill;
186 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);
188 self.velocity = randomvec() * 175 + '0 0 325';
191 void spawnfunc_td_waypoint()
193 if not(g_td) { remove(self); return; }
195 self.classname = "td_waypoint";
198 void spawnfunc_monster_swarm()
200 if not(g_td) { remove(self); return; }
202 self.flags = SWARM_NORMAL; // marked as a spawnpoint
203 self.classname = "monster_swarm";
205 if(self.spawntype == SWARM_SWIM) waterspawns_count += 1;
206 if(self.spawntype == SWARM_FLY) flyspawns_count += 1;
208 WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '0 0 1');
210 if(self.target == "")
211 dprint("Warning: monster_swarm entity without a set target\n");
214 void barricade_touch()
216 if not(other.flags & FL_MONSTER)
219 if(time < self.dmg_time)
222 Damage(other, self, self, autocvar_g_td_barricade_damage, DEATH_HURTTRIGGER, self.origin, '0 0 0');
224 self.dmg_time = time + 1;
229 self.takedamage = DAMAGE_NO;
230 self.event_damage = func_null;
232 WaypointSprite_Kill(self.sprite);
234 pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
235 sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
238 self.realowner.turret_cnt -= 1;
240 self.think = SUB_Remove;
241 self.nextthink = time;
244 void barricade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
246 if not(attacker.flags & FL_MONSTER) return;
248 self.health -= damage;
250 WaypointSprite_UpdateHealth(self.sprite, self.health);
256 void spawn_barricade()
259 self.max_health = self.health;
260 self.dmg_time = time;
261 self.touch = barricade_touch;
262 self.think = func_null;
264 self.takedamage = DAMAGE_AIM;
265 self.turrcaps_flags = TFL_TURRCAPS_ISTURRET; // for turretremove commands etc.
266 self.solid = SOLID_CORPSE; // hax
267 self.event_damage = barricade_damage;
268 self.netname = "Barricade";
270 WaypointSprite_Spawn(self.netname, 0, 1200, self, '0 0 110', world, 0, self, sprite, FALSE, RADARICON_DOMPOINT, '1 1 0');
271 WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
272 WaypointSprite_UpdateHealth(self.sprite, self.health);
274 precache_model("models/td/barricade.md3");
275 setmodel(self, "models/td/barricade.md3");
279 self.movetype = MOVETYPE_NONE;
282 float td_checkfuel(entity ent, string tur)
284 float turcost = cvar(strcat("g_td_turret_", tur, "_cost"));
286 if(ent.ammo_fuel < turcost)
288 Send_Notification(NOTIF_ONE, ent, MSG_INFO, INFO_TD_NOFUEL);
292 ent.ammo_fuel -= turcost;
297 void spawnturret(entity spawnedby, entity own, string turet, vector orig)
299 if not(IS_PLAYER(spawnedby)) { dprint("Warning: A non-player entity tried to spawn a turret\n"); return; }
300 if not(td_checkfuel(spawnedby, turet)) { return; }
307 setorigin(self, orig);
308 self.spawnflags = TSL_NO_RESPAWN;
309 self.monster_attack = TRUE;
310 self.realowner = own;
311 self.playerid = own.playerid;
312 self.angles_y = spawnedby.v_angle_y;
313 spawnedby.turret_cnt += 1;
314 self.colormap = spawnedby.colormap;
315 self.colormod = '1 1 1';
319 case "plasma": spawnfunc_turret_plasma(); break;
320 case "mlrs": spawnfunc_turret_mlrs(); break;
321 case "walker": spawnfunc_turret_walker(); break;
322 case "flac": spawnfunc_turret_flac(); break;
323 case "towerbuff": spawnfunc_turret_fusionreactor(); break;
324 case "barricade": spawn_barricade(); break;
325 default: Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_INVALID); remove(self); self = oldself; return;
328 Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_SPAWN);
333 void buffturret (entity tur, float buff)
335 tur.turret_buff += 1;
336 tur.max_health *= buff;
337 tur.tur_health = tur.max_health;
338 tur.health = tur.max_health;
339 tur.ammo_max *= buff;
340 tur.ammo_recharge *= buff;
341 tur.shot_dmg *= buff;
342 tur.shot_refire -= buff * 0.2;
343 tur.shot_radius *= buff;
344 tur.shot_speed *= buff;
345 tur.shot_spread *= buff;
346 tur.shot_force *= buff;
349 void AnnounceSpawn(string anounce)
351 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce);
354 entity PickSpawn (float strngth, float type)
357 RandomSelection_Init();
358 for(e = world;(e = find(e, classname, "monster_swarm")); )
360 if(flyspawns_count > 0 && type == SWARM_FLY && e.spawntype != SWARM_FLY) continue;
361 if(waterspawns_count > 0 && type == SWARM_SWIM && e.spawntype != SWARM_SWIM) continue;
363 RandomSelection_Add(e, 0, string_null, 1, 1);
366 return RandomSelection_chosen_ent;
369 void TD_SpawnMonster(string mnster, float strngth, float type)
373 e = PickSpawn(strngth, type);
375 if(e == world) // couldn't find anything for our class, so check for normal spawns
376 e = PickSpawn(SWARM_NORMAL, SWARM_NORMAL);
380 dprint("Warning: couldn't find any monster_swarm spawnpoints, no monsters will spawn!\n");
384 mon = spawnmonster(mnster, e, e, e.origin, FALSE, 0);
388 mon.target2 = e.target2;
390 mon.target2 = e.target;
393 mon.target2 = e.target;
396 float Monster_GetStrength(string mnster)
420 float Monster_GetType(string mnster)
444 string RandomMonster()
446 RandomSelection_Init();
448 if(n_demons) RandomSelection_Add(world, 0, "demon", 1, 1);
449 if(n_shalraths) RandomSelection_Add(world, 0, "vore", 1, 1);
450 if(n_soldiers) RandomSelection_Add(world, 0, "soldier", 1, 1);
451 if(n_hknights) RandomSelection_Add(world, 0, "hellknight", 1, 1);
452 if(n_zombies) RandomSelection_Add(world, 0, "zombie", 1, 1);
453 if(n_spiders) RandomSelection_Add(world, 0, "spider", 1, 1);
454 if(n_ogres) RandomSelection_Add(world, 0, "ogre", 1, 1);
455 if(n_dogs) RandomSelection_Add(world, 0, "dog", 1, 1);
456 if(n_knights) RandomSelection_Add(world, 0, "knight", 1, 1);
457 if(n_shamblers) RandomSelection_Add(world, 0, "shambler", 0.2, 0.2);
458 if(n_tarbabies) RandomSelection_Add(world, 0, "spawn", 0.2, 0.2);
459 if(n_wizards && flyspawns_count) RandomSelection_Add(world, 0, "scrag", 1, 1);
460 if(n_fish && waterspawns_count) RandomSelection_Add(world, 0, "fish", 0.2, 0.2);
462 return RandomSelection_chosen_string;
468 float mstrength, montype;
470 current_phase = PHASE_COMBAT;
472 if(monster_count <= 0)
478 self.think = combat_phase;
480 whichmon = RandomMonster();
482 mstrength = Monster_GetStrength(whichmon);
483 montype = Monster_GetType(whichmon);
485 if(current_monsters <= max_current && whichmon != "")
487 TD_SpawnMonster(whichmon, mstrength, montype);
488 self.nextthink = time + spawn_delay;
491 self.nextthink = time + 6;
494 void queue_monsters(float maxmonsters)
496 float mc = 10; // note: shambler + tarbaby = 1
498 if(waterspawns_count > 0)
500 if(flyspawns_count > 0)
503 DistributeEvenly_Init(maxmonsters, mc);
504 n_demons = DistributeEvenly_Get(1);
505 n_ogres = DistributeEvenly_Get(1);
506 n_dogs = DistributeEvenly_Get(1);
507 n_knights = DistributeEvenly_Get(1);
508 n_shalraths = DistributeEvenly_Get(1);
509 n_soldiers = DistributeEvenly_Get(1);
510 n_hknights = DistributeEvenly_Get(1);
511 n_zombies = DistributeEvenly_Get(1);
512 n_spiders = DistributeEvenly_Get(1);
513 n_tarbabies = DistributeEvenly_Get(0.7);
514 n_shamblers = DistributeEvenly_Get(0.3);
515 if(flyspawns_count > 0)
516 n_wizards = DistributeEvenly_Get(1);
517 if(waterspawns_count > 0)
518 n_fish = DistributeEvenly_Get(1);
521 void combat_phase_begin()
523 monster_count = totalmonsters;
526 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_COMBAT);
528 if(autocvar_sv_eventlog)
529 GameLogEcho(":combatphase");
531 self.think = combat_phase;
532 self.nextthink = time + 1;
534 for(gen = world;(gen = find(gen, classname, "td_generator")); )
535 gen.takedamage = DAMAGE_AIM;
538 float cphase_updates;
539 void combat_phase_announce() // TODO: clean up these fail nextthinks...
543 if(cphase_updates == 0)
544 Announce("prepareforbattle");
545 else if(cphase_updates == 3)
547 else if(cphase_updates == 4)
549 else if(cphase_updates == 5)
551 else if(cphase_updates == 6)
554 combat_phase_begin();
557 if(cphase_updates >= 6)
560 self.think = combat_phase_announce;
561 self.nextthink = time + 1;
567 float n_players = 0, gen_washealed = FALSE, mcount, mskill;
569 current_phase = PHASE_BUILD;
571 for(head = world;(head = find(head, classname, "td_generator")); )
573 if(head.health <= 15 && head.max_health > 100)
574 Announce("lastsecond");
576 if(head.health < head.max_health)
578 gen_washealed = TRUE;
579 head.health = head.max_health;
580 WaypointSprite_UpdateHealth(head.sprite, head.health);
582 head.takedamage = DAMAGE_NO;
585 FOR_EACH_PLAYER(head)
587 if(head.health < 100) head.health = 100;
588 if(gen_washealed) PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
593 mcount = autocvar_g_td_monster_count_increment * wave_count;
594 mskill = n_players * 0.02;
596 totalmonsters += mcount;
597 monster_skill += autocvar_g_td_monsters_skill_increment;
598 monster_skill += mskill;
600 if(monster_skill < 1) monster_skill = 1;
601 if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
602 if(wave_count < 1) wave_count = 1;
604 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_BUILD, wave_count, totalmonsters, build_time);
606 FOR_EACH_MONSTER(head)
611 dprint(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
613 WaypointSprite_Kill(head.sprite);
617 monsters_total = totalmonsters;
620 queue_monsters(totalmonsters);
624 if(autocvar_sv_eventlog)
625 GameLogEcho(strcat(":buildphase:", ftos(wave_count), ":", ftos(totalmonsters)));
627 self.think = combat_phase_announce;
628 self.nextthink = time + build_time - 6;
631 void wave_end(float starting)
635 Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_VICTORY, ((wave_count >= max_waves) ? "Level" : "Wave"));
637 if(autocvar_sv_eventlog)
638 GameLogEcho(strcat(":wave:", ftos(wave_count), ":victory"));
641 if(wave_count >= max_waves)
650 self.think = build_phase;
651 self.nextthink = time + 3;
656 ScoreInfo_SetLabel_PlayerScore(SP_TD_SCORE, "score", SFL_SORT_PRIO_PRIMARY);
657 ScoreInfo_SetLabel_PlayerScore(SP_TD_KILLS, "kills", SFL_LOWER_IS_BETTER);
658 ScoreInfo_SetLabel_PlayerScore(SP_TD_TURKILLS, "frags", SFL_LOWER_IS_BETTER);
659 ScoreInfo_SetLabel_PlayerScore(SP_TD_DEATHS, "deaths", SFL_LOWER_IS_BETTER);
660 ScoreInfo_SetLabel_PlayerScore(SP_TD_SUICIDES, "suicides", SFL_LOWER_IS_BETTER | SFL_ALLOW_HIDE);
661 ScoreRules_basics_end();
664 void td_SpawnController()
666 entity oldself = self;
668 self.classname = "td_controller";
669 spawnfunc_td_controller();
673 void td_DelayedInit()
675 if(find(world, classname, "td_controller") == world)
677 print("No ""td_controller"" entity found on this map, creating it anyway.\n");
678 td_SpawnController();
686 InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
689 MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
691 if(turret_flags & TFL_TARGETSELECT_MISSILESONLY)
692 if(turret_target.flags & FL_PROJECTILE)
693 if(turret_target.owner.flags & FL_MONSTER)
694 return TRUE; // flac support
696 if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
698 if not(turret_target.flags & FL_MONSTER)
699 turret_target = world;
704 MUTATOR_HOOKFUNCTION(td_PlayerThink)
706 self.stat_current_wave = wave_count;
707 self.stat_totalwaves = max_waves;
712 MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
714 self.bot_attack = FALSE;
719 MUTATOR_HOOKFUNCTION(td_PlayerDies)
721 if(frag_attacker.flags & FL_MONSTER)
722 PlayerScore_Add(frag_target, SP_TD_DEATHS, 1);
724 if(frag_target == frag_attacker)
725 PlayerScore_Add(frag_attacker, SP_TD_SUICIDES, 1);
730 MUTATOR_HOOKFUNCTION(td_GiveFragsForKill)
734 return TRUE; // no frags counted in td
737 MUTATOR_HOOKFUNCTION(td_PlayerDamage)
739 if(frag_attacker.realowner == frag_target)
742 if(frag_target.flags & FL_MONSTER && time < frag_target.spawnshieldtime)
745 if(frag_target.vehicle_flags & VHF_ISVEHICLE && !(frag_attacker.flags & FL_MONSTER))
748 if(frag_attacker.vehicle_flags & VHF_ISVEHICLE && !(frag_target.flags & FL_MONSTER))
751 if(!autocvar_g_td_pvp && frag_attacker != frag_target && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
754 if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && IS_PLAYER(frag_target))
757 if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && !(frag_attacker.flags & FL_MONSTER || frag_attacker.turrcaps_flags & TFL_TURRCAPS_SUPPORT))
763 MUTATOR_HOOKFUNCTION(td_TurretDies)
766 self.realowner.turret_cnt -= 1;
771 MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag)
773 // No minibosses in tower defense
777 MUTATOR_HOOKFUNCTION(td_MonsterMove)
781 FOR_EACH_PLAYER(player) { ++n_players; }
783 if(n_players < 1) // no players online, so do nothing
785 monster_target = world;
786 monster_speed_run = monster_speed_walk = 0;
790 if not(self.enemy) // don't change targets while attacking
791 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"))
793 if(self.goalentity.target2)
796 self.target2 = self.goalentity.target2;
798 self.target2 = self.goalentity.target;
801 self.target2 = self.goalentity.target;
803 self.goalentity = find(world, targetname, self.target2);
805 if(self.goalentity == world)
806 self.goalentity = PickGenerator();
809 monster_speed_run = m_speed_run * monster_skill;
810 monster_speed_walk = m_speed_walk * monster_skill;
815 MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
817 if(self.realowner == world) // nothing spawned it, so kill it
819 WaypointSprite_Kill(self.sprite);
824 current_monsters += 1;
826 self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time;
828 self.drop_size = self.health * 0.05;
830 self.target_range = 600;
832 if(self.drop_size < 1) self.drop_size = 1;
834 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_BODY;
836 self.origin += '0 0 25'; // hopefully this fixes monsters falling through the floor
838 switch(self.monsterid)
840 case MONSTER_ZOMBIE: n_zombies -= 1; break;
841 case MONSTER_OGRE: n_ogres -= 1; break;
842 case MONSTER_DEMON: n_demons -= 1; break;
843 case MONSTER_SHAMBLER: n_shamblers -= 1; break;
844 case MONSTER_KNIGHT: n_knights -= 1; break;
845 case MONSTER_MARINE: n_soldiers -= 1; break;
846 case MONSTER_SCRAG: n_wizards -= 1; break;
847 case MONSTER_DOG: n_dogs -= 1; break;
848 case MONSTER_TARBABY: n_tarbabies -= 1; break;
849 case MONSTER_HELLKNIGHT: n_hknights -= 1; break;
850 case MONSTER_FISH: n_fish -= 1; break;
851 case MONSTER_MAGE: n_shalraths -= 1; break;
852 case MONSTER_SPIDER: n_spiders -= 1; break;
858 MUTATOR_HOOKFUNCTION(td_MonsterDies)
864 current_monsters -= 1;
865 monsters_killed += 1;
867 if(IS_PLAYER(frag_attacker))
869 PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
870 PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
872 else if(IS_PLAYER(frag_attacker.realowner) && frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
874 PlayerScore_Add(frag_attacker.realowner, SP_TD_SCORE, autocvar_g_td_turretkill_points);
875 PlayerScore_Add(frag_attacker.realowner, SP_TD_TURKILLS, 1);
878 backuporigin = self.origin;
883 setorigin(self, backuporigin + '0 0 5');
884 spawn_td_fuel(oldself.drop_size);
885 self.touch = M_Item_Touch;
891 SUB_SetFade(self, time + 5, 1);
898 MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
904 FOR_EACH_PLAYER(player) { ++n_players; }
906 if(n_players < 1) // no players online, so do nothing
912 for(e = world;(e = findflags(e, monster_attack, TRUE)); )
915 if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
918 if(monster_isvalidtarget(e, self))
919 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"))
928 MUTATOR_HOOKFUNCTION(td_SetStartItems)
930 start_ammo_fuel = 150; // to be nice...
935 MUTATOR_HOOKFUNCTION(td_TurretSpawn)
937 if(self.realowner == world)
938 return TRUE; // wasn't spawned by a player
940 self.bot_attack = FALSE;
941 self.turret_buff = 1;
946 MUTATOR_HOOKFUNCTION(td_DisableVehicles)
948 // you shall not spawn!
952 MUTATOR_HOOKFUNCTION(td_PlayerCommand)
954 if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
956 makevectors(self.v_angle);
957 WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_HITMODEL, self);
959 if(cmd_name == "turretspawn")
961 if(argv(1) == "list")
963 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_LIST, "mlrs walker plasma towerbuff flac barricade");
966 if(!IS_PLAYER(self) || self.health <= 0)
968 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_CANTSPAWN);
973 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_TURRETS_DISABLED);
976 if(self.turret_cnt >= max_turrets)
978 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXTURRETS, max_turrets);
982 spawnturret(self, self, argv(1), trace_endpos);
986 if(cmd_name == "repairturret")
988 if((trace_ent.playerid != self.playerid || trace_ent.realowner != self) || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
990 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REPAIR);
993 if(self.ammo_fuel < autocvar_g_td_turret_repair_cost)
995 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_REPAIR, autocvar_g_td_turret_repair_cost);
998 if(trace_ent.health >= trace_ent.max_health)
1000 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXHEALTH);
1004 self.ammo_fuel -= autocvar_g_td_turret_repair_cost;
1005 trace_ent.SendFlags |= TNSF_STATUS;
1006 trace_ent.health = bound(1, trace_ent.health + 100, trace_ent.max_health);
1007 WaypointSprite_UpdateHealth(trace_ent.sprite, trace_ent.health);
1008 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REPAIR);
1012 if(cmd_name == "buffturret")
1014 if((trace_ent.playerid != self.playerid || trace_ent.realowner != self) || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
1016 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_UPGRADE);
1019 if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)
1021 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_UPGRADE, autocvar_g_td_turret_upgrade_cost);
1024 if(trace_ent.turret_buff >= 3)
1026 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXPOWER);
1030 self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost;
1031 trace_ent.SendFlags |= TNSF_STATUS;
1032 buffturret(trace_ent, 1.2);
1033 WaypointSprite_UpdateHealth(trace_ent.sprite, trace_ent.health);
1034 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_UPGRADE);
1038 if(cmd_name == "turretremove")
1040 if((trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && (trace_ent.playerid == self.playerid || trace_ent.realowner == self))
1042 self.turret_cnt -= 1;
1043 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REMOVE);
1044 WaypointSprite_Kill(trace_ent.sprite);
1045 remove(trace_ent.tur_head);
1049 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REMOVE);
1056 MUTATOR_HOOKFUNCTION(td_ClientConnect)
1060 self.turret_cnt = 0;
1062 for(t = world; (t = findflags(t, turrcaps_flags, TFL_TURRCAPS_ISTURRET)); )
1063 if(t.playerid == self.playerid)
1066 self.turret_cnt += 1;
1072 MUTATOR_DEFINITION(gamemode_td)
1074 MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
1075 MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
1076 MUTATOR_HOOK(MonsterMove, td_MonsterMove, CBC_ORDER_ANY);
1077 MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
1078 MUTATOR_HOOK(MonsterCheckBossFlag, td_MonsterCheckBossFlag, CBC_ORDER_ANY);
1079 MUTATOR_HOOK(SetStartItems, td_SetStartItems, CBC_ORDER_ANY);
1080 MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
1081 MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
1082 MUTATOR_HOOK(TurretDies, td_TurretDies, CBC_ORDER_ANY);
1083 MUTATOR_HOOK(GiveFragsForKill, td_GiveFragsForKill, CBC_ORDER_ANY);
1084 MUTATOR_HOOK(PlayerPreThink, td_PlayerThink, CBC_ORDER_ANY);
1085 MUTATOR_HOOK(PlayerDies, td_PlayerDies, CBC_ORDER_ANY);
1086 MUTATOR_HOOK(PlayerDamage_Calculate, td_PlayerDamage, CBC_ORDER_ANY);
1087 MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
1088 MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY);
1089 MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
1090 MUTATOR_HOOK(ClientConnect, td_ClientConnect, CBC_ORDER_ANY);
1094 if(time > 1) // game loads at time 1
1095 error("This is a game type and it cannot be added at runtime.");
1096 cvar_settemp("g_monsters", "1");
1097 cvar_settemp("g_turrets", "1");
1101 MUTATOR_ONROLLBACK_OR_REMOVE
1103 // we actually cannot roll back td_Initialize here
1104 // BUT: we don't need to! If this gets called, adding always
1110 error("This is a game type and it cannot be removed at runtime.");