diff options
Diffstat (limited to 'src')
7 files changed, 2393 insertions, 640 deletions
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 7639f45ead8..a0cd1e7daab 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -89,6 +89,12 @@ public: return storage_.size(); } + // Clear the underlying storage. This does NOT despawn the creatures - use DespawnAll for that! + void clear() + { + storage_.clear(); + } + void Summon(Creature const* summon) { storage_.push_back(summon->GetGUID()); } void Despawn(Creature const* summon) { storage_.remove(summon->GetGUID()); } void DespawnEntry(uint32 entry); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 858665453fa..1b5b481f458 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3031,14 +3031,18 @@ void SpellMgr::LoadSpellInfoCorrections() case 45027: // Revitalize case 45976: // Muru Portal Channel case 52124: // Sky Darkener Assault - case 53096: // Quetz'lun's Judgment - case 70743: // AoD Special - case 70614: // AoD Special - Vegard - case 4020: // Safirdrang's Chill case 52479: // Gift of the Harvester case 61588: // Blazing Harpoon case 55479: // Force Obedience case 28560: // Summon Blizzard (Sapphiron) + case 53096: // Quetz'lun's Judgment + case 70743: // AoD Special + case 70614: // AoD Special - Vegard + case 4020: // Safirdrang's Chill + case 52438: // Summon Skittering Swarmer (Force Cast) + case 52449: // Summon Skittering Infector (Force Cast) + case 53609: // Summon Anub'ar Assassin (Force Cast) + case 53457: // Summon Impale Trigger (AoE) spellInfo->MaxAffectedTargets = 1; break; case 36384: // Skartax Purple Beam diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h index cc2e6a275f0..59429a4d6f6 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h @@ -31,9 +31,10 @@ enum ANDataTypes DATA_ANUBARAK = 2, // Additional Data - DATA_WATCHER_GASHRA = 3, - DATA_WATCHER_SILTHIK = 4, - DATA_WATCHER_NARJIL = 5 + DATA_WATCHER_NARJIL, + DATA_WATCHER_GASHRA, + DATA_WATCHER_SILTHIK, + DATA_ANUBARAK_WALL }; enum ANCreatureIds @@ -55,6 +56,12 @@ enum ANGameObjectIds GO_ANUBARAK_DOOR_3 = 192398 }; +// These are passed as -action to AI's DoAction to differentiate between them and boss scripts' own actions +enum ANInstanceActions +{ + ACTION_GATEWATCHER_GREET = 1 +}; + template<class AI> AI* GetAzjolNerubAI(Creature* creature) { diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp index 55bf1867797..74e89d66d4c 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp @@ -17,65 +17,90 @@ #include "ScriptMgr.h" #include "ScriptedCreature.h" +#include "SpellScript.h" +#include "PassiveAI.h" #include "azjol_nerub.h" enum Spells { - SPELL_CARRION_BEETLES = 53520, - SPELL_SUMMON_CARRION_BEETLES = 53521, - SPELL_LEECHING_SWARM = 53467, - SPELL_POUND = 53472, - SPELL_SUBMERGE = 53421, - SPELL_IMPALE_DMG = 53454, - SPELL_IMPALE_SHAKEGROUND = 53455, - SPELL_IMPALE_SPIKE = 53539, //this is not the correct visual effect - //SPELL_IMPALE_TARGET = 53458, + SPELL_EMERGE = 53500, + SPELL_SUBMERGE = 53421, + SPELL_IMPALE_AURA = 53456, + SPELL_IMPALE_VISUAL = 53455, + SPELL_IMPALE_DAMAGE = 53454, + SPELL_LEECHING_SWARM = 53467, + SPELL_POUND = 59433, + SPELL_POUND_DAMAGE = 59432, + SPELL_CARRION_BEETLES = 53520, + SPELL_CARRION_BEETLE = 53521, + + SPELL_SUMMON_DARTER = 53599, + SPELL_SUMMON_ASSASSIN = 53609, + SPELL_SUMMON_GUARDIAN = 53614, + SPELL_SUMMON_VENOMANCER = 53615, + + SPELL_DART = 59349, + SPELL_BACKSTAB = 52540, + SPELL_ASSASSIN_VISUAL = 53611, + SPELL_SUNDER_ARMOR = 53618, + SPELL_POISON_BOLT = 53617 }; enum Creatures { - CREATURE_GUARDIAN = 29216, - CREATURE_VENOMANCER = 29217, - CREATURE_DATTER = 29213, - CREATURE_IMPALE_TARGET = 89, - DISPLAY_INVISIBLE = 11686 + NPC_WORLD_TRIGGER = 22515, }; -// not in db enum Yells { - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_DEATH = 2, - SAY_LOCUST = 3, - SAY_SUBMERGE = 4, - SAY_INTRO = 5 + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_DEATH = 2, + SAY_LOCUST = 3, + SAY_SUBMERGE = 4, + SAY_INTRO = 5 +}; + +enum Events +{ + EVENT_POUND = 1, + EVENT_IMPALE, + EVENT_LEECHING_SWARM, + EVENT_CARRION_BEETLES, + EVENT_SUBMERGE, // use event for this so we don't submerge mid-cast + EVENT_DARTER, + EVENT_ASSASSIN, + EVENT_GUARDIAN, + EVENT_VENOMANCER, + EVENT_CLOSE_DOOR +}; + +enum Actions +{ + ACTION_PET_DIED = 1, + ACTION_PET_EVADE }; enum Misc { - ACHIEV_TIMED_START_EVENT = 20381, + ACHIEV_GOTTA_GO_START_EVENT = 20381, }; enum Phases { - PHASE_MELEE = 0, - PHASE_UNDERGROUND = 1, - IMPALE_PHASE_TARGET = 0, - IMPALE_PHASE_ATTACK = 1, - IMPALE_PHASE_DMG = 2 + PHASE_EMERGE = 1, + PHASE_SUBMERGE }; -const Position SpawnPoint[2] = +enum GUIDTypes { - { 550.7f, 282.8f, 224.3f, 0.0f }, - { 551.1f, 229.4f, 224.3f, 0.0f }, + GUID_TYPE_PET = 0, + GUID_TYPE_IMPALE }; -const Position SpawnPointGuardian[2] = +enum SummonGroups { - { 550.348633f, 316.006805f, 234.2947f, 0.0f }, - { 550.188660f, 324.264557f, 237.7412f, 0.0f }, + SUMMON_GROUP_WORLD_TRIGGER_GUARDIAN = 1 }; class boss_anub_arak : public CreatureScript @@ -83,97 +108,58 @@ class boss_anub_arak : public CreatureScript public: boss_anub_arak() : CreatureScript("boss_anub_arak") { } - struct boss_anub_arakAI : public ScriptedAI + struct boss_anub_arakAI : public BossAI { - boss_anub_arakAI(Creature* creature) : ScriptedAI(creature), Summons(me) - { - Initialize(); - instance = creature->GetInstanceScript(); - GuardianSummoned = false; - VenomancerSummoned = false; - DatterSummoned = false; - UndergroundTimer = 0; - VenomancerTimer = 0; - DatterTimer = 0; - DelayTimer = 0; - } - - void Initialize() - { - CarrionBeetlesTimer = 8 * IN_MILLISECONDS; - LeechingSwarmTimer = 20 * IN_MILLISECONDS; - ImpaleTimer = 9 * IN_MILLISECONDS; - PoundTimer = 15 * IN_MILLISECONDS; - - Phase = PHASE_MELEE; - UndergroundPhase = 0; - Channeling = false; - ImpalePhase = IMPALE_PHASE_TARGET; - ImpaleTarget.Clear(); - } - - InstanceScript* instance; - - bool Channeling; - bool GuardianSummoned; - bool VenomancerSummoned; - bool DatterSummoned; - uint8 Phase; - uint32 UndergroundPhase; - uint32 CarrionBeetlesTimer; - uint32 LeechingSwarmTimer; - uint32 PoundTimer; - uint32 UndergroundTimer; - uint32 VenomancerTimer; - uint32 DatterTimer; - uint32 DelayTimer; - - uint32 ImpaleTimer; - uint32 ImpalePhase; - ObjectGuid ImpaleTarget; - - SummonList Summons; + boss_anub_arakAI(Creature* creature) : BossAI(creature, DATA_ANUBARAK), _nextSubmerge(0), _petCount(0), _assassinCount(0), _guardianCount(0), _venomancerCount(0) { } void Reset() override { - Initialize(); - - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); - me->RemoveAura(SPELL_SUBMERGE); - - Summons.DespawnAll(); - - instance->SetBossState(DATA_ANUBARAK, NOT_STARTED); - instance->DoStopCriteriaTimer(CRITERIA_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); + BossAI::Reset(); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + instance->DoStopCriteriaTimer(CRITERIA_TIMED_TYPE_EVENT, ACHIEV_GOTTA_GO_START_EVENT); + _nextSubmerge = 75; + _petCount = 0; } - Creature* DoSummonImpaleTarget(Unit* target) + void EnterCombat(Unit* who) override { - Position targetPos = target->GetPosition(); + BossAI::EnterCombat(who); - if (TempSummon* impaleTarget = me->SummonCreature(CREATURE_IMPALE_TARGET, targetPos, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 6*IN_MILLISECONDS)) - { - ImpaleTarget = impaleTarget->GetGUID(); - impaleTarget->SetReactState(REACT_PASSIVE); - impaleTarget->SetDisplayId(DISPLAY_INVISIBLE); - impaleTarget->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - impaleTarget->SetControlled(true, UNIT_STATE_ROOT); - return impaleTarget; - } - - return NULL; - } + if (GameObject* door = instance->GetGameObject(DATA_ANUBARAK_WALL)) + door->SetGoState(GO_STATE_ACTIVE); // open door for now - void EnterCombat(Unit* /*who*/) override - { Talk(SAY_AGGRO); - DelayTimer = 0; - instance->DoStartCriteriaTimer(CRITERIA_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); + instance->DoStartCriteriaTimer(CRITERIA_TIMED_TYPE_EVENT, ACHIEV_GOTTA_GO_START_EVENT); + + events.SetPhase(PHASE_EMERGE); + events.ScheduleEvent(EVENT_CLOSE_DOOR, Seconds(5)); + events.ScheduleEvent(EVENT_POUND, randtime(Seconds(2), Seconds(4)), 0, PHASE_EMERGE); + events.ScheduleEvent(EVENT_LEECHING_SWARM, randtime(Seconds(5), Seconds(7)), 0, PHASE_EMERGE); + events.ScheduleEvent(EVENT_CARRION_BEETLES, randtime(Seconds(14), Seconds(17)), 0, PHASE_EMERGE); + + // set up world triggers + std::list<TempSummon*> summoned; + me->SummonCreatureGroup(SUMMON_GROUP_WORLD_TRIGGER_GUARDIAN, &summoned); + if (summoned.empty()) // something went wrong + { + EnterEvadeMode(EVADE_REASON_OTHER); + return; + } + _guardianTrigger = (*summoned.begin())->GetGUID(); + + if (Creature* trigger = DoSummon(NPC_WORLD_TRIGGER, me->GetPosition(), 0u, TEMPSUMMON_MANUAL_DESPAWN)) + _assassinTrigger = trigger->GetGUID(); + else + { + EnterEvadeMode(EVADE_REASON_OTHER); + return; + } } - void DelayEventStart() + void EnterEvadeMode(EvadeReason /*why*/) override { - instance->SetBossState(DATA_ANUBARAK, IN_PROGRESS); + summons.DespawnAll(); + _DespawnAtEvade(); } void UpdateAI(uint32 diff) override @@ -181,192 +167,549 @@ public: if (!UpdateVictim()) return; - if (DelayTimer && DelayTimer > 5000) - DelayEventStart(); - else DelayTimer+=diff; + events.Update(diff); - switch (Phase) + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - case PHASE_UNDERGROUND: - if (ImpaleTimer <= diff) + switch (eventId) { - switch (ImpalePhase) + case EVENT_CLOSE_DOOR: + if (GameObject* door = instance->GetGameObject(DATA_ANUBARAK_WALL)) + door->SetGoState(GO_STATE_READY); + break; + case EVENT_POUND: + DoCastVictim(SPELL_POUND); + events.Repeat(randtime(Seconds(26), Seconds(32))); + break; + case EVENT_LEECHING_SWARM: + Talk(SAY_LOCUST); + DoCastAOE(SPELL_LEECHING_SWARM); + events.Repeat(randtime(Seconds(25), Seconds(28))); + break; + case EVENT_CARRION_BEETLES: + DoCastAOE(SPELL_CARRION_BEETLES); + events.Repeat(randtime(Seconds(24), Seconds(27))); + break; + case EVENT_IMPALE: + if (Creature* impaleTarget = ObjectAccessor::GetCreature(*me, _impaleTarget)) + DoCast(impaleTarget, SPELL_IMPALE_DAMAGE, true); + break; + case EVENT_SUBMERGE: + Talk(SAY_SUBMERGE); + DoCastSelf(SPELL_SUBMERGE); + break; + case EVENT_DARTER: { - case IMPALE_PHASE_TARGET: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) + std::list<Creature*> triggers; + me->GetCreatureListWithEntryInGrid(triggers, NPC_WORLD_TRIGGER); + if (!triggers.empty()) { - if (Creature* impaleTarget = DoSummonImpaleTarget(target)) - impaleTarget->CastSpell(impaleTarget, SPELL_IMPALE_SHAKEGROUND, true); - ImpaleTimer = 3*IN_MILLISECONDS; - ImpalePhase = IMPALE_PHASE_ATTACK; + std::list<Creature*>::iterator it = triggers.begin(); + std::advance(it, urand(0, triggers.size()-1)); + (*it)->CastSpell(*it, SPELL_SUMMON_DARTER, true); + events.Repeat(Seconds(11)); } + else + EnterEvadeMode(EVADE_REASON_OTHER); break; - case IMPALE_PHASE_ATTACK: - if (Creature* impaleTarget = ObjectAccessor::GetCreature(*me, ImpaleTarget)) + } + case EVENT_ASSASSIN: + if (Creature* trigger = ObjectAccessor::GetCreature(*me, _assassinTrigger)) { - impaleTarget->CastSpell(impaleTarget, SPELL_IMPALE_SPIKE, false); - impaleTarget->RemoveAurasDueToSpell(SPELL_IMPALE_SHAKEGROUND); + trigger->CastSpell(trigger, SPELL_SUMMON_ASSASSIN, true); + trigger->CastSpell(trigger, SPELL_SUMMON_ASSASSIN, true); + if (_assassinCount > 2) + { + _assassinCount -= 2; + events.Repeat(Seconds(20)); + } + else + _assassinCount = 0; } - ImpalePhase = IMPALE_PHASE_DMG; - ImpaleTimer = 1*IN_MILLISECONDS; - break; - case IMPALE_PHASE_DMG: - if (Creature* impaleTarget = ObjectAccessor::GetCreature(*me, ImpaleTarget)) - me->CastSpell(impaleTarget, SPELL_IMPALE_DMG, true); - ImpalePhase = IMPALE_PHASE_TARGET; - ImpaleTimer = 9*IN_MILLISECONDS; + else // something went wrong + EnterEvadeMode(EVADE_REASON_OTHER); break; - } - } else ImpaleTimer -= diff; - - if (!GuardianSummoned) - { - for (uint8 i = 0; i < 2; ++i) - { - if (Creature* Guardian = me->SummonCreature(CREATURE_GUARDIAN, SpawnPointGuardian[i], TEMPSUMMON_CORPSE_DESPAWN, 0)) + case EVENT_GUARDIAN: + if (Creature* trigger = ObjectAccessor::GetCreature(*me, _guardianTrigger)) { - Guardian->AddThreat(me->GetVictim(), 0.0f); - DoZoneInCombat(Guardian); + trigger->CastSpell(trigger, SPELL_SUMMON_GUARDIAN, true); + trigger->CastSpell(trigger, SPELL_SUMMON_GUARDIAN, true); + if (_guardianCount > 2) + { + _guardianCount -= 2; + events.Repeat(Seconds(20)); + } + else + _guardianCount = 0; } - } - GuardianSummoned = true; - } - - if (!VenomancerSummoned) - { - if (VenomancerTimer <= diff) - { - if (UndergroundPhase > 1) + else + EnterEvadeMode(EVADE_REASON_OTHER); + break; + case EVENT_VENOMANCER: + if (Creature* trigger = ObjectAccessor::GetCreature(*me, _guardianTrigger)) { - for (uint8 i = 0; i < 2; ++i) + trigger->CastSpell(trigger, SPELL_SUMMON_VENOMANCER, true); + trigger->CastSpell(trigger, SPELL_SUMMON_VENOMANCER, true); + if (_venomancerCount > 2) { - if (Creature* Venomancer = me->SummonCreature(CREATURE_VENOMANCER, SpawnPoint[i], TEMPSUMMON_CORPSE_DESPAWN, 0)) - { - Venomancer->AddThreat(me->GetVictim(), 0.0f); - DoZoneInCombat(Venomancer); - } + _venomancerCount -= 2; + events.Repeat(Seconds(20)); } - VenomancerSummoned = true; + else + _venomancerCount = 0; } - } else VenomancerTimer -= diff; + else + EnterEvadeMode(EVADE_REASON_OTHER); + break; + default: + break; } - if (!DatterSummoned) + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + Talk(SAY_DEATH); + } + + void KilledUnit(Unit* victim) override + { + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_SLAY); + } + + void SetGUID(ObjectGuid guid, int32 type) override + { + switch (type) + { + case GUID_TYPE_PET: { - if (DatterTimer <= diff) + if (Creature* creature = ObjectAccessor::GetCreature(*me, guid)) + JustSummoned(creature); + else // something has gone horribly wrong + EnterEvadeMode(EVADE_REASON_OTHER); + break; + } + case GUID_TYPE_IMPALE: + _impaleTarget = guid; + events.ScheduleEvent(EVENT_IMPALE, Seconds(4)); + break; + } + } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_PET_DIED: + if (!_petCount) // underflow check - something has gone horribly wrong { - if (UndergroundPhase > 2) - { - for (uint8 i = 0; i < 2; ++i) - { - if (Creature* Datter = me->SummonCreature(CREATURE_DATTER, SpawnPoint[i], TEMPSUMMON_CORPSE_DESPAWN, 0)) - { - Datter->AddThreat(me->GetVictim(), 0.0f); - DoZoneInCombat(Datter); - } - } - DatterSummoned = true; - } - } else DatterTimer -= diff; + EnterEvadeMode(EVADE_REASON_OTHER); + return; + } + if (!--_petCount) // last pet died, emerge + { + me->RemoveAurasDueToSpell(SPELL_SUBMERGE); + me->RemoveAurasDueToSpell(SPELL_IMPALE_AURA); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + DoCastSelf(SPELL_EMERGE); + events.SetPhase(PHASE_EMERGE); + events.ScheduleEvent(EVENT_POUND, randtime(Seconds(13), Seconds(18)), 0, PHASE_EMERGE); + events.ScheduleEvent(EVENT_LEECHING_SWARM, randtime(Seconds(3), Seconds(7)), 0, PHASE_EMERGE); + events.ScheduleEvent(EVENT_CARRION_BEETLES, randtime(Seconds(10), Seconds(15)), 0, PHASE_EMERGE); + } + break; + case ACTION_PET_EVADE: + EnterEvadeMode(EVADE_REASON_OTHER); + break; + } + } - if (me->HasAura(SPELL_LEECHING_SWARM)) - me->RemoveAurasDueToSpell(SPELL_LEECHING_SWARM); + void DamageTaken(Unit* /*source*/, uint32& damage) override + { + if (me->HasAura(SPELL_SUBMERGE)) + damage = 0; + else + if (_nextSubmerge && me->HealthBelowPctDamaged(_nextSubmerge, damage)) + { + events.CancelEvent(EVENT_SUBMERGE); + events.ScheduleEvent(EVENT_SUBMERGE, 0, 0, PHASE_EMERGE); + _nextSubmerge = _nextSubmerge-25; } + } - if (UndergroundTimer <= diff) - { - me->RemoveAura(SPELL_SUBMERGE); - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); - Phase = PHASE_MELEE; - } else UndergroundTimer -= diff; - break; - - case PHASE_MELEE: - if (((UndergroundPhase == 0 && HealthBelowPct(75)) - || (UndergroundPhase == 1 && HealthBelowPct(50)) - || (UndergroundPhase == 2 && HealthBelowPct(25))) - && !me->HasUnitState(UNIT_STATE_CASTING)) + void SpellHit(Unit* /*whose*/, SpellInfo const* spell) override + { + if (spell->Id == SPELL_SUBMERGE) + { + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + me->RemoveAurasDueToSpell(SPELL_LEECHING_SWARM); + DoCastSelf(SPELL_IMPALE_AURA, true); + + events.SetPhase(PHASE_SUBMERGE); + switch (_nextSubmerge) { - GuardianSummoned = false; - VenomancerSummoned = false; - DatterSummoned = false; + case 50: // first submerge phase + _assassinCount = 4; + _guardianCount = 2; + _venomancerCount = 0; + break; + case 25: // second submerge phase + _assassinCount = 6; + _guardianCount = 2; + _venomancerCount = 2; + break; + case 0: // third submerge phase + _assassinCount = 6; + _guardianCount = 2; + _venomancerCount = 2; + events.ScheduleEvent(EVENT_DARTER, Seconds(0), 0, PHASE_SUBMERGE); + break; + } + _petCount = _guardianCount + _venomancerCount; + if (_assassinCount) + events.ScheduleEvent(EVENT_ASSASSIN, Seconds(0), 0, PHASE_SUBMERGE); + if (_guardianCount) + events.ScheduleEvent(EVENT_GUARDIAN, Seconds(4), 0, PHASE_SUBMERGE); + if (_venomancerCount) + events.ScheduleEvent(EVENT_VENOMANCER, Seconds(20), 0, PHASE_SUBMERGE); + } + } + + private: + ObjectGuid _impaleTarget; + uint32 _nextSubmerge; + uint32 _petCount; + ObjectGuid _guardianTrigger; + ObjectGuid _assassinTrigger; + uint8 _assassinCount; + uint8 _guardianCount; + uint8 _venomancerCount; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<boss_anub_arakAI>(creature); + } +}; - UndergroundTimer = 40*IN_MILLISECONDS; - VenomancerTimer = 25*IN_MILLISECONDS; - DatterTimer = 32*IN_MILLISECONDS; +class npc_anubarak_pet_template : public ScriptedAI +{ + public: + npc_anubarak_pet_template(Creature* creature, bool isLarge) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _isLarge(isLarge) { } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + if (Creature* anubarak = _instance->GetCreature(DATA_ANUBARAK)) + anubarak->AI()->SetGUID(me->GetGUID(), GUID_TYPE_PET); + else + me->DespawnOrUnsummon(); + } - ImpalePhase = 0; - ImpaleTimer = 9*IN_MILLISECONDS; + void JustDied(Unit* killer) override + { + ScriptedAI::JustDied(killer); + if (_isLarge) + if (Creature* anubarak = _instance->GetCreature(DATA_ANUBARAK)) + anubarak->AI()->DoAction(ACTION_PET_DIED); + } - DoCast(me, SPELL_SUBMERGE, false); - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); + void EnterEvadeMode(EvadeReason /*why*/) override + { + if (Creature* anubarak = _instance->GetCreature(DATA_ANUBARAK)) + anubarak->AI()->DoAction(ACTION_PET_EVADE); + else + me->DespawnOrUnsummon(); + } - Phase = PHASE_UNDERGROUND; - ++UndergroundPhase; - } + protected: + InstanceScript* _instance; + private: + bool const _isLarge; +}; - if (Channeling == true) +class npc_anubarak_anub_ar_darter : public CreatureScript +{ + public: + npc_anubarak_anub_ar_darter() : CreatureScript("npc_anubarak_anub_ar_darter") { } + + struct npc_anubarak_anub_ar_darterAI : public npc_anubarak_pet_template + { + npc_anubarak_anub_ar_darterAI(Creature* creature) : npc_anubarak_pet_template(creature, false) { } + + void InitializeAI() override + { + npc_anubarak_pet_template::InitializeAI(); + DoCastAOE(SPELL_DART); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anubarak_anub_ar_darterAI>(creature); + } +}; + +class npc_anubarak_anub_ar_assassin : public CreatureScript +{ + public: + npc_anubarak_anub_ar_assassin() : CreatureScript("npc_anubarak_anub_ar_assassin") { } + + struct npc_anubarak_anub_ar_assassinAI : public npc_anubarak_pet_template + { + npc_anubarak_anub_ar_assassinAI(Creature* creature) : npc_anubarak_pet_template(creature, false), _backstabTimer(6 * IN_MILLISECONDS) { } + + bool IsInBounds(Position const& jumpTo, CreatureBoundary const* boundary) + { + if (!boundary) + return true; + for (CreatureBoundary::const_iterator it = boundary->cbegin(); it != boundary->cend(); ++it) + if (!(*it)->IsWithinBoundary(&jumpTo)) + return false; + return true; + } + Position GetRandomPositionAround(Creature* anubarak) + { + static float DISTANCE_MIN = 10.0f; + static float DISTANCE_MAX = 30.0f; + double angle = rand_norm() * 2.0 * M_PI; + return { anubarak->GetPositionX() + (float)(frand(DISTANCE_MIN, DISTANCE_MAX)*std::sin(angle)), anubarak->GetPositionY() + (float)(frand(DISTANCE_MIN, DISTANCE_MAX)*std::cos(angle)), anubarak->GetPositionZ() }; + } + void InitializeAI() override + { + npc_anubarak_pet_template::InitializeAI(); + CreatureBoundary const* boundary = _instance->GetBossBoundary(DATA_ANUBARAK); + if (Creature* anubarak = _instance->GetCreature(DATA_ANUBARAK)) { - for (uint8 i = 0; i < 8; ++i) - DoCastVictim(SPELL_SUMMON_CARRION_BEETLES, true); - Channeling = false; + Position jumpTo; + do + jumpTo = GetRandomPositionAround(anubarak); + while (!IsInBounds(jumpTo, boundary)); + me->GetMotionMaster()->MoveJump(jumpTo, 40.0f, 40.0f); + DoCastSelf(SPELL_ASSASSIN_VISUAL, true); } - else if (CarrionBeetlesTimer <= diff) - { - Channeling = true; - DoCastVictim(SPELL_CARRION_BEETLES); - CarrionBeetlesTimer = 25*IN_MILLISECONDS; - } else CarrionBeetlesTimer -= diff; + } - if (LeechingSwarmTimer <= diff) - { - DoCast(me, SPELL_LEECHING_SWARM, true); - LeechingSwarmTimer = 19*IN_MILLISECONDS; - } else LeechingSwarmTimer -= diff; + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; - if (PoundTimer <= diff) + if (diff >= _backstabTimer) { - if (Unit* target = me->GetVictim()) - { - if (Creature* pImpaleTarget = DoSummonImpaleTarget(target)) - me->CastSpell(pImpaleTarget, SPELL_POUND, false); - } - PoundTimer = 16500; - } else PoundTimer -= diff; + if (me->GetVictim() && me->GetVictim()->isInBack(me)) + DoCastVictim(SPELL_BACKSTAB); + _backstabTimer = 6 * IN_MILLISECONDS; + } + else + _backstabTimer -= diff; DoMeleeAttackIfReady(); - break; } - } - void JustDied(Unit* /*killer*/) override + void MovementInform(uint32 /*type*/, uint32 id) override + { + if (id == EVENT_JUMP) + { + me->RemoveAurasDueToSpell(SPELL_ASSASSIN_VISUAL); + DoZoneInCombat(); + } + } + + private: + uint32 _backstabTimer; + }; + + CreatureAI* GetAI(Creature* creature) const override { - Talk(SAY_DEATH); - Summons.DespawnAll(); - instance->SetBossState(DATA_ANUBARAK, DONE); + return GetAzjolNerubAI<npc_anubarak_anub_ar_assassinAI>(creature); } +}; - void KilledUnit(Unit* victim) override +class npc_anubarak_anub_ar_guardian : public CreatureScript +{ + public: + npc_anubarak_anub_ar_guardian() : CreatureScript("npc_anubarak_anub_ar_guardian") { } + + struct npc_anubarak_anub_ar_guardianAI : public npc_anubarak_pet_template + { + npc_anubarak_anub_ar_guardianAI(Creature* creature) : npc_anubarak_pet_template(creature, true), _sunderTimer(6 * IN_MILLISECONDS) { } + + void UpdateAI(uint32 diff) override { - if (victim->GetTypeId() != TYPEID_PLAYER) + if (!UpdateVictim()) return; - Talk(SAY_SLAY); + if (diff >= _sunderTimer) + { + DoCastVictim(SPELL_SUNDER_ARMOR); + _sunderTimer = 12 * IN_MILLISECONDS; + } + else + _sunderTimer -= diff; + + DoMeleeAttackIfReady(); } - void JustSummoned(Creature* summon) override + private: + uint32 _sunderTimer; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anubarak_anub_ar_guardianAI>(creature); + } +}; + +class npc_anubarak_anub_ar_venomancer : public CreatureScript +{ + public: + npc_anubarak_anub_ar_venomancer() : CreatureScript("npc_anubarak_anub_ar_venomancer") { } + + struct npc_anubarak_anub_ar_venomancerAI : public npc_anubarak_pet_template + { + npc_anubarak_anub_ar_venomancerAI(Creature* creature) : npc_anubarak_pet_template(creature, true), _boltTimer(5 * IN_MILLISECONDS) { } + + void UpdateAI(uint32 diff) override { - Summons.Summon(summon); + if (!UpdateVictim()) + return; + + if (diff >= _boltTimer) + { + DoCastVictim(SPELL_POISON_BOLT); + _boltTimer = urandms(2, 3); + } + else + _boltTimer -= diff; + + DoMeleeAttackIfReady(); } + + private: + uint32 _boltTimer; }; CreatureAI* GetAI(Creature* creature) const override { - return GetInstanceAI<boss_anub_arakAI>(creature); + return GetAzjolNerubAI<npc_anubarak_anub_ar_venomancerAI>(creature); } }; +class npc_anubarak_impale_target : public CreatureScript +{ + public: + npc_anubarak_impale_target() : CreatureScript("npc_anubarak_impale_target") { } + + struct npc_anubarak_impale_targetAI : public NullCreatureAI + { + npc_anubarak_impale_targetAI(Creature* creature) : NullCreatureAI(creature) { } + + void InitializeAI() override + { + if (Creature* anubarak = me->GetInstanceScript()->GetCreature(DATA_ANUBARAK)) + { + DoCastSelf(SPELL_IMPALE_VISUAL); + me->DespawnOrUnsummon(Seconds(6)); + anubarak->AI()->SetGUID(me->GetGUID(), GUID_TYPE_IMPALE); + } + else + me->DespawnOrUnsummon(); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anubarak_impale_targetAI>(creature); + } +}; + +class spell_anubarak_pound : public SpellScriptLoader +{ + public: + spell_anubarak_pound() : SpellScriptLoader("spell_anubarak_pound") { } + + class spell_anubarak_pound_SpellScript : public SpellScript + { + PrepareSpellScript(spell_anubarak_pound_SpellScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return sSpellMgr->GetSpellInfo(SPELL_POUND_DAMAGE) != nullptr; + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + if (Unit* target = GetHitUnit()) + GetCaster()->CastSpell(target, SPELL_POUND_DAMAGE, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_anubarak_pound_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_anubarak_pound_SpellScript(); + } +}; + +class spell_anubarak_carrion_beetles : public SpellScriptLoader +{ + public: + spell_anubarak_carrion_beetles() : SpellScriptLoader("spell_anubarak_carrion_beetles") { } + + class spell_anubarak_carrion_beetles_AuraScript : public AuraScript + { + public: + PrepareAuraScript(spell_anubarak_carrion_beetles_AuraScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return (sSpellMgr->GetSpellInfo(SPELL_CARRION_BEETLE) != nullptr); + } + + void HandlePeriodic(AuraEffect const* /*eff*/) + { + GetCaster()->CastSpell(GetCaster(), SPELL_CARRION_BEETLE, true); + GetCaster()->CastSpell(GetCaster(), SPELL_CARRION_BEETLE, true); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_anubarak_carrion_beetles_AuraScript::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_anubarak_carrion_beetles_AuraScript(); + } +}; + void AddSC_boss_anub_arak() { new boss_anub_arak(); + + new npc_anubarak_anub_ar_darter(); + new npc_anubarak_anub_ar_assassin(); + new npc_anubarak_anub_ar_guardian(); + new npc_anubarak_anub_ar_venomancer(); + new npc_anubarak_impale_target(); + + new spell_anubarak_pound(); + new spell_anubarak_carrion_beetles(); } diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp index 45e984f8936..ac840c58f55 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp @@ -15,34 +15,136 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* -* Comment: No Waves atm and the doors spells are crazy... -* -* When your group enters the main room (the one after the bridge), you will notice a group of 3 Nerubians. -* When you engage them, 2 more groups like this one spawn behind the first one - it is important to pull the first group back, -* so you don't aggro all 3. Hadronox will be under you, fighting Nerubians. -* -* This is the timed gauntlet - waves of non-elite spiders -* will spawn from the 3 doors located a little above the main room, and will then head down to fight Hadronox. After clearing the -* main room, it is recommended to just stay in it, kill the occasional non-elites that will attack you instead of the boss, and wait for -* Hadronox to make his way to you. When Hadronox enters the main room, she will web the doors, and no more non-elites will spawn. -*/ - #include "ScriptMgr.h" #include "ScriptedCreature.h" +#include "SpellScript.h" +#include "SpellAuras.h" +#include "SpellAuraEffects.h" #include "azjol_nerub.h" +enum Events +{ + // Hadronox + EVENT_LEECH_POISON = 1, + EVENT_ACID_CLOUD, + EVENT_WEB_GRAB, + EVENT_PIERCE_ARMOR, + EVENT_PLAYER_CHECK, + + // Anub'ar Crusher + EVENT_SMASH, + + // Anub'ar foes - Shared + EVENT_TAUNT, + + // Anub'ar Champion + EVENT_REND, + EVENT_PUMMEL, + + // Anub'ar Crypt Guard + EVENT_CRUSHING_WEBS, + EVENT_INFECTED_WOUND, + + // Anub'ar Necromancer + EVENT_SHADOW_BOLT, + EVENT_ANIMATE_BONES +}; + enum Spells { - SPELL_ACID_CLOUD = 53400, // Victim - SPELL_LEECH_POISON = 53030, // Victim - SPELL_PIERCE_ARMOR = 53418, // Victim - SPELL_WEB_GRAB = 57731, // Victim - SPELL_WEB_FRONT_DOORS = 53177, // Self - SPELL_WEB_SIDE_DOORS = 53185, // Self - H_SPELL_ACID_CLOUD = 59419, - H_SPELL_LEECH_POISON = 59417, - H_SPELL_WEB_GRAB = 59421 + // Hadronox + SPELL_WEB_FRONT_DOORS = 53177, + SPELL_WEB_SIDE_DOORS = 53185, + SPELL_LEECH_POISON = 53030, + SPELL_LEECH_POISON_HEAL = 53800, + SPELL_ACID_CLOUD = 53400, + SPELL_WEB_GRAB = 57731, + SPELL_PIERCE_ARMOR = 53418, + + // Anub'ar opponent summoning spells + SPELL_SUMMON_CHAMPION_PERIODIC = 53035, + SPELL_SUMMON_CRYPT_FIEND_PERIODIC = 53037, + SPELL_SUMMON_NECROMANCER_PERIODIC = 53036, + SPELL_SUMMON_CHAMPION_TOP = 53064, + SPELL_SUMMON_CRYPT_FIEND_TOP = 53065, + SPELL_SUMMON_NECROMANCER_TOP = 53066, + SPELL_SUMMON_CHAMPION_BOTTOM = 53090, + SPELL_SUMMON_CRYPT_FIEND_BOTTOM = 53091, + SPELL_SUMMON_NECROMANCER_BOTTOM = 53092, + + // Anub'ar Crusher + SPELL_SMASH = 53318, + SPELL_FRENZY = 53801, + + // Anub'ar foes - Shared + SPELL_TAUNT = 53798, + + // Anub'ar Champion + SPELL_REND = 59343, + SPELL_PUMMEL = 59344, + + // Anub'ar Crypt Guard + SPELL_CRUSHING_WEBS = 59347, + SPELL_INFECTED_WOUND = 59348, + + // Anub'ar Necromancer + SPELL_SHADOW_BOLT = 53333, + SPELL_ANIMATE_BONES_1 = 53334, + SPELL_ANIMATE_BONES_2 = 53336, +}; + +enum SummonGroups +{ + SUMMON_GROUP_CRUSHER_1 = 1, + SUMMON_GROUP_CRUSHER_2 = 2, + SUMMON_GROUP_CRUSHER_3 = 3 +}; + +enum Actions +{ + ACTION_HADRONOX_MOVE = 1, + ACTION_CRUSHER_ENGAGED, + ACTION_PACK_WALK +}; + +enum Data +{ + DATA_CRUSHER_PACK_ID = 1, + DATA_HADRONOX_ENTERED_COMBAT, + DATA_HADRONOX_WEBBED_DOORS +}; + +enum Creatures +{ + NPC_CRUSHER = 28922, + NPC_WORLDTRIGGER_LARGE = 23472 +}; + +enum Talk +{ + CRUSHER_SAY_AGGRO = 1, + CRUSHER_EMOTE_FRENZY = 2, + HADRONOX_EMOTE_MOVE = 1 +}; + +// Movement IDs used by the permanently spawning Anub'ar opponents - they are done in sequence, as one finishes, the next one starts +enum Movements +{ + MOVE_NONE = 0, + MOVE_OUTSIDE, + MOVE_DOWNSTAIRS, + MOVE_DOWNSTAIRS_2, + MOVE_HADRONOX, // this one might have us take a detour to avoid pathfinding "through" the floor... + MOVE_HADRONOX_REAL // while this one will always make us movechase +}; + +static const uint8 NUM_STEPS = 4; +static const Position hadronoxStep[NUM_STEPS] = +{ + { 515.5848f, 544.2007f, 673.6272f }, + { 562.191f , 514.068f , 696.4448f }, + { 610.3828f, 518.6407f, 695.9385f }, + { 530.42f , 560.003f, 733.0308f } }; class boss_hadronox : public CreatureScript @@ -50,157 +152,1014 @@ class boss_hadronox : public CreatureScript public: boss_hadronox() : CreatureScript("boss_hadronox") { } - struct boss_hadronoxAI : public ScriptedAI + struct boss_hadronoxAI : public BossAI { - boss_hadronoxAI(Creature* creature) : ScriptedAI(creature) + boss_hadronoxAI(Creature* creature) : BossAI(creature, DATA_HADRONOX), _enteredCombat(false), _doorsWebbed(false), _lastPlayerCombatState(false), _step(0) { } + + bool IsInCombatWithPlayer() const { - Initialize(); - instance = creature->GetInstanceScript(); - fMaxDistance = 50.0f; - bFirstTime = true; + std::list<HostileReference*> const& refs = me->getThreatManager().getThreatList(); + for (HostileReference const* hostileRef : refs) + { + if (Unit const* target = hostileRef->getTarget()) + if (target->IsControlledByPlayer()) + return true; + } + return false; } - void Initialize() + void SetStep(uint8 step) { - uiAcidTimer = urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS); - uiLeechTimer = urand(3 * IN_MILLISECONDS, 9 * IN_MILLISECONDS); - uiPierceTimer = urand(1 * IN_MILLISECONDS, 3 * IN_MILLISECONDS); - uiGrabTimer = urand(15 * IN_MILLISECONDS, 19 * IN_MILLISECONDS); - uiDoorsTimer = urand(20 * IN_MILLISECONDS, 30 * IN_MILLISECONDS); - uiCheckDistanceTimer = 2 * IN_MILLISECONDS; + if (_lastPlayerCombatState) + return; + + _step = step; + me->SetHomePosition(hadronoxStep[step]); + me->GetMotionMaster()->Clear(); + me->AttackStop(); + SetCombatMovement(false); + me->GetMotionMaster()->MovePoint(0, hadronoxStep[step]); } - InstanceScript* instance; + void SummonCrusherPack(SummonGroups group) + { + std::list<TempSummon*> summoned; + me->SummonCreatureGroup(group, &summoned); + for (TempSummon* summon : summoned) + { + summon->AI()->SetData(DATA_CRUSHER_PACK_ID, group); + summon->AI()->DoAction(ACTION_PACK_WALK); + } + } + + void MovementInform(uint32 type, uint32 /*id*/) override + { + if (type != POINT_MOTION_TYPE) + return; + SetCombatMovement(true); + AttackStart(me->GetVictim()); + if (_step < NUM_STEPS-1) + return; + DoCastAOE(SPELL_WEB_FRONT_DOORS); + DoCastAOE(SPELL_WEB_SIDE_DOORS); + _doorsWebbed = true; + DoZoneInCombat(); + } - uint32 uiAcidTimer; - uint32 uiLeechTimer; - uint32 uiPierceTimer; - uint32 uiGrabTimer; - uint32 uiDoorsTimer; - uint32 uiCheckDistanceTimer; + uint32 GetData(uint32 data) const override + { + if (data == DATA_HADRONOX_ENTERED_COMBAT) + return _enteredCombat ? 1 : 0; + if (data == DATA_HADRONOX_WEBBED_DOORS) + return _doorsWebbed ? 1 : 0; + return 0; + } - bool bFirstTime; + bool CanAIAttack(Unit const* target) const override + { + // Prevent Hadronox from going too far from her current home position + if (!target->IsControlledByPlayer() && target->GetDistance(me->GetHomePosition()) > 20.0f) + return false; + return BossAI::CanAIAttack(target); + } - float fMaxDistance; + void EnterCombat(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_LEECH_POISON, randtime(Seconds(5), Seconds(7))); + events.ScheduleEvent(EVENT_ACID_CLOUD, randtime(Seconds(7), Seconds(13))); + events.ScheduleEvent(EVENT_WEB_GRAB, randtime(Seconds(13), Seconds(19))); + events.ScheduleEvent(EVENT_PIERCE_ARMOR, randtime(Seconds(4), Seconds(7))); + events.ScheduleEvent(EVENT_PLAYER_CHECK, Seconds(1)); + me->setActive(true); + } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_CRUSHER_ENGAGED: + if (_enteredCombat) + break; + instance->SetBossState(DATA_HADRONOX, IN_PROGRESS); + _enteredCombat = true; + SummonCrusherPack(SUMMON_GROUP_CRUSHER_2); + SummonCrusherPack(SUMMON_GROUP_CRUSHER_3); + break; + case ACTION_HADRONOX_MOVE: + if (_step < NUM_STEPS-1) + { + SetStep(_step + 1); + Talk(HADRONOX_EMOTE_MOVE); + } + break; + } + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + std::list<Creature*> triggers; + me->GetCreatureListWithEntryInGrid(triggers, NPC_WORLDTRIGGER_LARGE); + for (Creature* trigger : triggers) + if (trigger->HasAura(SPELL_SUMMON_CHAMPION_PERIODIC) || trigger->HasAura(SPELL_WEB_FRONT_DOORS) || trigger->HasAura(SPELL_WEB_SIDE_DOORS)) + _DespawnAtEvade(25, trigger); + _DespawnAtEvade(25); + summons.DespawnAll(); + for (ObjectGuid gNerubian : _anubar) + if (Creature* nerubian = ObjectAccessor::GetCreature(*me, gNerubian)) + nerubian->DespawnOrUnsummon(); + } + + void SetGUID(ObjectGuid guid, int32 /*what*/) override + { + _anubar.push_back(guid); + } - void Reset() override + void Initialize() { me->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, 9.0f); me->SetFloatValue(UNIT_FIELD_COMBATREACH, 9.0f); + _enteredCombat = false; + _doorsWebbed = false; + _lastPlayerCombatState = false; + SetStep(0); + SetCombatMovement(true); + SummonCrusherPack(SUMMON_GROUP_CRUSHER_1); + } + + void InitializeAI() override + { + BossAI::InitializeAI(); + if (me->IsAlive()) + Initialize(); + } + void JustRespawned() override + { + BossAI::JustRespawned(); Initialize(); + } - if (instance->GetBossState(DATA_HADRONOX) != DONE && !bFirstTime) - instance->SetBossState(DATA_HADRONOX, FAIL); + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; - bFirstTime = false; + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_LEECH_POISON: + DoCastAOE(SPELL_LEECH_POISON); + events.Repeat(randtime(Seconds(7), Seconds(9))); + break; + case EVENT_ACID_CLOUD: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f)) + DoCast(target, SPELL_ACID_CLOUD); + events.Repeat(randtime(Seconds(16), Seconds(23))); + break; + case EVENT_WEB_GRAB: + DoCastAOE(SPELL_WEB_GRAB); + events.Repeat(randtime(Seconds(20), Seconds(25))); + break; + case EVENT_PIERCE_ARMOR: + DoCastVictim(SPELL_PIERCE_ARMOR); + events.Repeat(randtime(Seconds(10), Seconds(15))); + break; + case EVENT_PLAYER_CHECK: + if (IsInCombatWithPlayer() != _lastPlayerCombatState) + { + _lastPlayerCombatState = !_lastPlayerCombatState; + if (_lastPlayerCombatState) // we are now in combat with players + { + if (!instance->CheckRequiredBosses(DATA_HADRONOX)) + { + EnterEvadeMode(EVADE_REASON_SEQUENCE_BREAK); + return; + } + // cancel current point movement if engaged by players + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) + { + me->GetMotionMaster()->Clear(); + SetCombatMovement(true); + AttackStart(me->GetVictim()); + } + } + else // we are no longer in combat with players - reset the encounter + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + } + events.Repeat(Seconds(1)); + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + DoMeleeAttackIfReady(); } - //when Hadronox kills any enemy (that includes a party member) she will regain 10% of her HP if the target had Leech Poison on - void KilledUnit(Unit* Victim) override + // Safeguard to prevent Hadronox dying to NPCs + void DamageTaken(Unit* who, uint32& damage) override { - // not sure if this aura check is correct, I think it is though - if (!Victim || !Victim->HasAura(DUNGEON_MODE(SPELL_LEECH_POISON, H_SPELL_LEECH_POISON)) || !me->IsAlive()) - return; + if (!who->IsControlledByPlayer() && me->HealthBelowPct(70)) + { + if (me->HealthBelowPctDamaged(5, damage)) + damage = 0; + else + damage *= (me->GetHealthPct()-5.0f)/ 65.0f; + } + } - me->ModifyHealth(int32(me->CountPctFromMaxHealth(10))); + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + // Do not enter combat with zone } + + private: + bool _enteredCombat; // has a player entered combat with the first crusher pack? (talk and spawn two more packs) + bool _doorsWebbed; // obvious - have we reached the top and webbed the doors shut? (trigger for hadronox denied achievement) + bool _lastPlayerCombatState; // was there a player in our threat list the last time we checked (we check every second) + uint8 _step; + std::list<ObjectGuid> _anubar; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<boss_hadronoxAI>(creature); + } +}; + +struct npc_hadronox_crusherPackAI : public ScriptedAI +{ + npc_hadronox_crusherPackAI(Creature* creature, Position const* positions) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _positions(positions), _myPack(SummonGroups(0)), _doFacing(false) { } - void JustDied(Unit* /*killer*/) override + void DoAction(int32 action) override + { + if (action == ACTION_PACK_WALK) { - instance->SetBossState(DATA_HADRONOX, DONE); + switch (_myPack) + { + case SUMMON_GROUP_CRUSHER_1: + case SUMMON_GROUP_CRUSHER_2: + case SUMMON_GROUP_CRUSHER_3: + me->GetMotionMaster()->MovePoint(ACTION_PACK_WALK, _positions[_myPack - SUMMON_GROUP_CRUSHER_1]); + break; + default: + break; + } } + } - void EnterCombat(Unit* /*who*/) override + void MovementInform(uint32 type, uint32 id) override + { + if (type == POINT_MOTION_TYPE && id == ACTION_PACK_WALK) + _doFacing = true; + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX)) + hadronox->AI()->EnterEvadeMode(EVADE_REASON_OTHER); + } + + uint32 GetData(uint32 data) const override + { + if (data == DATA_CRUSHER_PACK_ID) + return _myPack; + return 0; + } + + void SetData(uint32 data, uint32 value) override + { + if (data == DATA_CRUSHER_PACK_ID) { - instance->SetBossState(DATA_HADRONOX, IN_PROGRESS); - me->SetInCombatWithZone(); + _myPack = SummonGroups(value); + me->SetReactState(_myPack ? REACT_PASSIVE : REACT_AGGRESSIVE); } + } - void CheckDistance(float dist, const uint32 uiDiff) + void EnterCombat(Unit* who) override + { + if (me->HasReactState(REACT_PASSIVE)) { - if (!me->IsInCombat()) - return; + std::list<Creature*> others; + me->GetCreatureListWithEntryInGrid(others, 0, 40.0f); + for (Creature* other : others) + if (other->AI()->GetData(DATA_CRUSHER_PACK_ID) == _myPack) + { + other->SetReactState(REACT_AGGRESSIVE); + other->AI()->AttackStart(who); + } + } + _EnterCombat(); + ScriptedAI::EnterCombat(who); + } - float x=0.0f, y=0.0f, z=0.0f; - me->GetRespawnPosition(x, y, z); + virtual void _EnterCombat() = 0; + virtual void DoEvent(uint32 /*eventId*/) = 0; - if (uiCheckDistanceTimer <= uiDiff) - uiCheckDistanceTimer = 5*IN_MILLISECONDS; - else + void MoveInLineOfSight(Unit* who) override + { + if (!me->HasReactState(REACT_PASSIVE)) + { + ScriptedAI::MoveInLineOfSight(who); + return; + } + + if (me->CanStartAttack(who, false) && me->IsWithinDistInMap(who, me->GetAttackDistance(who) + me->m_CombatDistance)) + EnterCombat(who); + } + + void UpdateAI(uint32 diff) override + { + if (_doFacing) + { + _doFacing = false; + me->SetFacingTo(_positions[_myPack - SUMMON_GROUP_CRUSHER_1].GetOrientation()); + } + + if (!UpdateVictim()) + return; + + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + DoEvent(eventId); + + DoMeleeAttackIfReady(); + } + + protected: + InstanceScript* const _instance; + EventMap _events; + Position const* const _positions; + SummonGroups _myPack; + bool _doFacing; + +}; + +static const Position crusherWaypoints[] = +{ + { 529.6913f, 547.1257f, 731.9155f, 4.799650f }, + { 517.51f , 561.439f , 734.0306f, 4.520403f }, + { 543.414f , 551.728f , 732.0522f, 3.996804f } +}; +class npc_anub_ar_crusher : public CreatureScript +{ + public: + npc_anub_ar_crusher() : CreatureScript("npc_anub_ar_crusher") { } + + struct npc_anub_ar_crusherAI : public npc_hadronox_crusherPackAI + { + npc_anub_ar_crusherAI(Creature* creature) : npc_hadronox_crusherPackAI(creature, crusherWaypoints), _hadFrenzy(false) { } + + void _EnterCombat() override { - uiCheckDistanceTimer -= uiDiff; - return; + _events.ScheduleEvent(EVENT_SMASH, randtime(Seconds(8), Seconds(12))); + + if (_myPack != SUMMON_GROUP_CRUSHER_1) + return; + + if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX)) + { + if (hadronox->AI()->GetData(DATA_HADRONOX_ENTERED_COMBAT)) + return; + hadronox->AI()->DoAction(ACTION_CRUSHER_ENGAGED); + } + + Talk(CRUSHER_SAY_AGGRO); } - if (me->IsInEvadeMode() || !me->GetVictim()) - return; - if (me->GetDistance(x, y, z) > dist) - EnterEvadeMode(); + + void DamageTaken(Unit* /*source*/, uint32& damage) override + { + if (_hadFrenzy || !me->HealthBelowPctDamaged(25, damage)) + return; + _hadFrenzy = true; + Talk(CRUSHER_EMOTE_FRENZY); + DoCastSelf(SPELL_FRENZY); + } + + void DoEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_SMASH: + DoCastVictim(SPELL_SMASH); + _events.Repeat(randtime(Seconds(13), Seconds(21))); + break; + } + } + + void JustDied(Unit* killer) override + { + if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX)) + hadronox->AI()->DoAction(ACTION_HADRONOX_MOVE); + ScriptedAI::JustDied(killer); + } + + private: + bool _hadFrenzy; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anub_ar_crusherAI>(creature); } +}; - void UpdateAI(uint32 diff) override +static const Position championWaypoints[] = +{ + { 539.2076f, 549.7539f, 732.8668f, 4.55531f }, + { 527.3098f, 559.5197f, 732.9407f, 4.742493f }, + { } +}; +class npc_anub_ar_crusher_champion : public CreatureScript +{ + public: + npc_anub_ar_crusher_champion() : CreatureScript("npc_anub_ar_crusher_champion") { } + + struct npc_anub_ar_crusher_championAI : public npc_hadronox_crusherPackAI { - //Return since we have no target - if (!UpdateVictim()) - return; + npc_anub_ar_crusher_championAI(Creature* creature) : npc_hadronox_crusherPackAI(creature, championWaypoints) { } + + void DoEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_REND: + DoCastVictim(SPELL_REND); + _events.Repeat(randtime(Seconds(12), Seconds(16))); + break; + case EVENT_PUMMEL: + DoCastVictim(SPELL_PUMMEL); + _events.Repeat(randtime(Seconds(12), Seconds(17))); + break; + } + } + + void _EnterCombat() override + { + _events.ScheduleEvent(EVENT_REND, randtime(Seconds(4), Seconds(8))); + _events.ScheduleEvent(EVENT_PUMMEL, randtime(Seconds(15), Seconds(19))); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anub_ar_crusher_championAI>(creature); + } +}; + +static const Position cryptFiendWaypoints[] = +{ + { 520.3911f, 548.7895f, 732.0118f, 5.0091f }, + { }, + { 550.9611f, 545.1674f, 731.9031f, 3.996804f } +}; +class npc_anub_ar_crusher_crypt_fiend : public CreatureScript +{ + public: + npc_anub_ar_crusher_crypt_fiend() : CreatureScript("npc_anub_ar_crusher_crypt_fiend") { } + + struct npc_anub_ar_crusher_crypt_fiendAI : public npc_hadronox_crusherPackAI + { + npc_anub_ar_crusher_crypt_fiendAI(Creature* creature) : npc_hadronox_crusherPackAI(creature, cryptFiendWaypoints) { } - // Without he comes up through the air to players on the bridge after krikthir if players crossing this bridge! - CheckDistance(fMaxDistance, diff); + void DoEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_CRUSHING_WEBS: + DoCastVictim(SPELL_CRUSHING_WEBS); + _events.Repeat(randtime(Seconds(12), Seconds(16))); + break; + case EVENT_INFECTED_WOUND: + DoCastVictim(SPELL_INFECTED_WOUND); + _events.Repeat(randtime(Seconds(16), Seconds(25))); + break; + } + } - if (me->HasAura(SPELL_WEB_FRONT_DOORS) || me->HasAura(SPELL_WEB_SIDE_DOORS)) + void _EnterCombat() override { - if (IsCombatMovementAllowed()) - SetCombatMovement(false); + _events.ScheduleEvent(EVENT_CRUSHING_WEBS, randtime(Seconds(4), Seconds(8))); + _events.ScheduleEvent(EVENT_INFECTED_WOUND, randtime(Seconds(15), Seconds(19))); } - else if (!IsCombatMovementAllowed()) - SetCombatMovement(true); + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anub_ar_crusher_crypt_fiendAI>(creature); + } +}; + +static const Position necromancerWaypoints[] = +{ + { }, + { 507.6937f, 563.3471f, 734.8986f, 4.520403f }, + { 535.1049f, 552.8961f, 732.8441f, 3.996804f }, +}; +class npc_anub_ar_crusher_necromancer : public CreatureScript +{ + public: + npc_anub_ar_crusher_necromancer() : CreatureScript("npc_anub_ar_crusher_necromancer") { } + + struct npc_anub_ar_crusher_necromancerAI : public npc_hadronox_crusherPackAI + { + npc_anub_ar_crusher_necromancerAI(Creature* creature) : npc_hadronox_crusherPackAI(creature, necromancerWaypoints) { } - if (uiPierceTimer <= diff) + void DoEvent(uint32 eventId) override { - DoCastVictim(SPELL_PIERCE_ARMOR); - uiPierceTimer = 8*IN_MILLISECONDS; - } else uiPierceTimer -= diff; + switch (eventId) + { + case EVENT_SHADOW_BOLT: + DoCastVictim(SPELL_SHADOW_BOLT); + _events.Repeat(randtime(Seconds(2), Seconds(5))); + break; + case EVENT_ANIMATE_BONES: + DoCastVictim(urand(0, 1) ? SPELL_ANIMATE_BONES_2 : SPELL_ANIMATE_BONES_1); + _events.Repeat(randtime(Seconds(35), Seconds(50))); + break; + } + } - if (uiAcidTimer <= diff) + void _EnterCombat() override { - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - DoCast(target, SPELL_ACID_CLOUD); + _events.ScheduleEvent(EVENT_SHADOW_BOLT, randtime(Seconds(2), Seconds(4))); + _events.ScheduleEvent(EVENT_ANIMATE_BONES, randtime(Seconds(37), Seconds(45))); + } + }; - uiAcidTimer = urand(20*IN_MILLISECONDS, 30*IN_MILLISECONDS); - } else uiAcidTimer -= diff; + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anub_ar_crusher_necromancerAI>(creature); + } +}; - if (uiLeechTimer <= diff) +static const uint8 NUM_SPAWNS = 3; +static const Position initialMoves[NUM_SPAWNS] = +{ + { 485.314606f, 611.418640f, 771.428406f }, + { 575.760437f, 611.516418f, 771.427368f }, + { 588.930725f, 598.233276f, 739.142151f } +}; +static const Position downstairsMoves[NUM_SPAWNS] = +{ + { 513.574341f, 587.022156f, 736.229065f }, + { 537.920410f, 580.436157f, 732.796692f }, + { 601.289246f, 583.259644f, 725.443054f }, +}; +static const Position downstairsMoves2[NUM_SPAWNS] = +{ + { 571.498718f, 576.978333f, 727.582947f }, + { 571.498718f, 576.978333f, 727.582947f }, + { } +}; +struct npc_hadronox_foeAI : public ScriptedAI +{ + npc_hadronox_foeAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _nextMovement(MOVE_OUTSIDE), _mySpawn(0) { } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX)) + hadronox->AI()->SetGUID(me->GetGUID()); + } + + void MovementInform(uint32 type, uint32 id) override + { + if (type == POINT_MOTION_TYPE) + _nextMovement = Movements(id+1); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + me->DespawnOrUnsummon(); + } + + virtual void DoEvent(uint32 /*eventId*/) = 0; + + void UpdateAI(uint32 diff) override + { + if (_nextMovement) + { + switch (_nextMovement) { - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - DoCast(target, SPELL_LEECH_POISON); + case MOVE_OUTSIDE: + { + float dist = HUGE_VALF; + for (uint8 spawn = 0; spawn < NUM_SPAWNS; ++spawn) + { + float thisDist = initialMoves[spawn].GetExactDistSq(me); + if (thisDist < dist) + { + _mySpawn = spawn; + dist = thisDist; + } + } + me->GetMotionMaster()->MovePoint(MOVE_OUTSIDE, initialMoves[_mySpawn], false); // do not pathfind here, we have to pass through a "wall" of webbing + break; + } + case MOVE_DOWNSTAIRS: + me->GetMotionMaster()->MovePoint(MOVE_DOWNSTAIRS, downstairsMoves[_mySpawn]); + break; + case MOVE_DOWNSTAIRS_2: + if (downstairsMoves2[_mySpawn].GetPositionX() > 0.0f) // might be unset for this spawn - if yes, skip + { + me->GetMotionMaster()->MovePoint(MOVE_DOWNSTAIRS_2, downstairsMoves2[_mySpawn]); + break; + } + // intentional missing break + case MOVE_HADRONOX: + case MOVE_HADRONOX_REAL: + { + static const float zCutoff = 702.0f; + Creature* hadronox = _instance->GetCreature(DATA_HADRONOX); + if (hadronox && hadronox->IsAlive()) + { + if (_nextMovement != MOVE_HADRONOX_REAL) + if (hadronox->GetPositionZ() < zCutoff) + { + me->GetMotionMaster()->MovePoint(MOVE_HADRONOX, hadronoxStep[2]); + break; + } + me->GetMotionMaster()->MoveChase(hadronox); + } + break; + } + default: + break; + } + _nextMovement = MOVE_NONE; + } + + if (!UpdateVictim()) + return; + + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + DoEvent(eventId); + + DoMeleeAttackIfReady(); + } - uiLeechTimer = urand(11*IN_MILLISECONDS, 14*IN_MILLISECONDS); - } else uiLeechTimer -= diff; + protected: + EventMap _events; + InstanceScript* const _instance; - if (uiGrabTimer <= diff) + private: + Movements _nextMovement; + uint8 _mySpawn; +}; + +class npc_anub_ar_champion : public CreatureScript +{ + public: + npc_anub_ar_champion() : CreatureScript("npc_anub_ar_champion") { } + + struct npc_anub_ar_championAI : public npc_hadronox_foeAI + { + npc_anub_ar_championAI(Creature* creature) : npc_hadronox_foeAI(creature) { } + + void DoEvent(uint32 eventId) override { - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0)) // Draws all players (and attacking Mobs) to itself. - DoCast(target, SPELL_WEB_GRAB); + switch (eventId) + { + case EVENT_REND: + DoCastVictim(SPELL_REND); + _events.Repeat(randtime(Seconds(12), Seconds(16))); + break; + case EVENT_PUMMEL: + DoCastVictim(SPELL_PUMMEL); + _events.Repeat(randtime(Seconds(12), Seconds(17))); + break; + case EVENT_TAUNT: + DoCastVictim(SPELL_TAUNT); + _events.Repeat(randtime(Seconds(15), Seconds(50))); + break; + } + } - uiGrabTimer = urand(15*IN_MILLISECONDS, 30*IN_MILLISECONDS); - } else uiGrabTimer -= diff; + void EnterCombat(Unit* /*who*/) override + { + _events.ScheduleEvent(EVENT_REND, randtime(Seconds(4), Seconds(8))); + _events.ScheduleEvent(EVENT_PUMMEL, randtime(Seconds(15), Seconds(19))); + _events.ScheduleEvent(EVENT_TAUNT, randtime(Seconds(15), Seconds(50))); + } + }; - if (uiDoorsTimer <= diff) + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anub_ar_championAI>(creature); + } +}; + +class npc_anub_ar_crypt_fiend : public CreatureScript +{ + public: + npc_anub_ar_crypt_fiend() : CreatureScript("npc_anub_ar_crypt_fiend") { } + + struct npc_anub_ar_crypt_fiendAI : public npc_hadronox_foeAI + { + npc_anub_ar_crypt_fiendAI(Creature* creature) : npc_hadronox_foeAI(creature) { } + + void DoEvent(uint32 eventId) override + { + switch (eventId) { - uiDoorsTimer = urand(30*IN_MILLISECONDS, 60*IN_MILLISECONDS); - } else uiDoorsTimer -= diff; + case EVENT_CRUSHING_WEBS: + DoCastVictim(SPELL_CRUSHING_WEBS); + _events.Repeat(randtime(Seconds(12), Seconds(16))); + break; + case EVENT_INFECTED_WOUND: + DoCastVictim(SPELL_INFECTED_WOUND); + _events.Repeat(randtime(Seconds(16), Seconds(25))); + break; + case EVENT_TAUNT: + DoCastVictim(SPELL_TAUNT); + _events.Repeat(randtime(Seconds(15), Seconds(50))); + break; + } + } - DoMeleeAttackIfReady(); + void EnterCombat(Unit* /*who*/) override + { + _events.ScheduleEvent(EVENT_CRUSHING_WEBS, randtime(Seconds(4), Seconds(8))); + _events.ScheduleEvent(EVENT_INFECTED_WOUND, randtime(Seconds(15), Seconds(19))); + _events.ScheduleEvent(EVENT_TAUNT, randtime(Seconds(15), Seconds(50))); } }; CreatureAI* GetAI(Creature* creature) const override { - return GetInstanceAI<boss_hadronoxAI>(creature); + return GetAzjolNerubAI<npc_anub_ar_crypt_fiendAI>(creature); + } +}; + +class npc_anub_ar_necromancer : public CreatureScript +{ + public: + npc_anub_ar_necromancer() : CreatureScript("npc_anub_ar_necromancer") { } + + struct npc_anub_ar_necromancerAI : public npc_hadronox_foeAI + { + npc_anub_ar_necromancerAI(Creature* creature) : npc_hadronox_foeAI(creature) { } + + void DoEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_SHADOW_BOLT: + DoCastVictim(SPELL_SHADOW_BOLT); + _events.Repeat(randtime(Seconds(2), Seconds(5))); + break; + case EVENT_ANIMATE_BONES: + DoCastVictim(urand(0,1) ? SPELL_ANIMATE_BONES_2 : SPELL_ANIMATE_BONES_1); + _events.Repeat(randtime(Seconds(35), Seconds(50))); + break; + case EVENT_TAUNT: + DoCastVictim(SPELL_TAUNT); + _events.Repeat(randtime(Seconds(15), Seconds(50))); + break; + } + } + + void EnterCombat(Unit* /*who*/) override + { + _events.ScheduleEvent(EVENT_SHADOW_BOLT, randtime(Seconds(2), Seconds(4))); + _events.ScheduleEvent(EVENT_ANIMATE_BONES, randtime(Seconds(37), Seconds(45))); + _events.ScheduleEvent(EVENT_TAUNT, randtime(Seconds(15), Seconds(50))); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anub_ar_necromancerAI>(creature); + } +}; + +class spell_hadronox_periodic_summon_template_AuraScript : public AuraScript +{ + public: + spell_hadronox_periodic_summon_template_AuraScript(uint32 topSpellId, uint32 bottomSpellId) : AuraScript(), _topSpellId(topSpellId), _bottomSpellId(bottomSpellId) { } + PrepareAuraScript(spell_hadronox_periodic_summon_template_AuraScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return + (sSpellMgr->GetSpellInfo(_topSpellId) != nullptr) && + (sSpellMgr->GetSpellInfo(_bottomSpellId) != nullptr); + } + + void HandleApply(AuraEffect const* /*eff*/, AuraEffectHandleModes /*mode*/) + { + if (AuraEffect* effect = GetAura()->GetEffect(EFFECT_0)) + effect->SetPeriodicTimer(urandms(2, 17)); + } + + void HandlePeriodic(AuraEffect const* /*eff*/) + { + Unit* caster = GetCaster(); + if (!caster) + return; + InstanceScript* instance = caster->GetInstanceScript(); + if (!instance) + return; + if (instance->GetBossState(DATA_HADRONOX) == DONE) + GetAura()->Remove(); + else + { + if (caster->GetPositionZ() >= 750.0f) + caster->CastSpell(caster, _topSpellId, true); + else + caster->CastSpell(caster, _bottomSpellId, true); + } + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_hadronox_periodic_summon_template_AuraScript::HandleApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL); + OnEffectPeriodic += AuraEffectPeriodicFn(spell_hadronox_periodic_summon_template_AuraScript::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + + private: + uint32 _topSpellId; + uint32 _bottomSpellId; +}; + +class spell_hadronox_periodic_summon_champion : public SpellScriptLoader +{ + public: + spell_hadronox_periodic_summon_champion() : SpellScriptLoader("spell_hadronox_periodic_summon_champion") { } + + class spell_hadronox_periodic_summon_champion_AuraScript : public spell_hadronox_periodic_summon_template_AuraScript + { + public: + spell_hadronox_periodic_summon_champion_AuraScript() : spell_hadronox_periodic_summon_template_AuraScript(SPELL_SUMMON_CHAMPION_TOP, SPELL_SUMMON_CHAMPION_BOTTOM) { } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_hadronox_periodic_summon_champion_AuraScript(); + } +}; + +class spell_hadronox_periodic_summon_crypt_fiend : public SpellScriptLoader +{ + public: + spell_hadronox_periodic_summon_crypt_fiend() : SpellScriptLoader("spell_hadronox_periodic_summon_crypt_fiend") { } + + class spell_hadronox_periodic_summon_crypt_fiend_AuraScript : public spell_hadronox_periodic_summon_template_AuraScript + { + public: + spell_hadronox_periodic_summon_crypt_fiend_AuraScript() : spell_hadronox_periodic_summon_template_AuraScript(SPELL_SUMMON_CRYPT_FIEND_TOP, SPELL_SUMMON_CRYPT_FIEND_BOTTOM) { } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_hadronox_periodic_summon_crypt_fiend_AuraScript(); + } +}; + +class spell_hadronox_periodic_summon_necromancer : public SpellScriptLoader +{ + public: + spell_hadronox_periodic_summon_necromancer() : SpellScriptLoader("spell_hadronox_periodic_summon_necromancer") { } + + class spell_hadronox_periodic_summon_necromancer_AuraScript : public spell_hadronox_periodic_summon_template_AuraScript + { + public: + spell_hadronox_periodic_summon_necromancer_AuraScript() : spell_hadronox_periodic_summon_template_AuraScript(SPELL_SUMMON_NECROMANCER_TOP, SPELL_SUMMON_NECROMANCER_BOTTOM) { } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_hadronox_periodic_summon_necromancer_AuraScript(); + } +}; + +class spell_hadronox_leeching_poison : public SpellScriptLoader +{ + public: + spell_hadronox_leeching_poison() : SpellScriptLoader("spell_hadronox_leeching_poison") { } + + class spell_hadronox_leeching_poison_AuraScript : public AuraScript + { + PrepareAuraScript(spell_hadronox_leeching_poison_AuraScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return sSpellMgr->GetSpellInfo(SPELL_LEECH_POISON_HEAL) != nullptr; + } + + void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEATH) + return; + + if (GetTarget()->IsGuardian()) + return; + + if (Unit* caster = GetCaster()) + caster->CastSpell(caster, SPELL_LEECH_POISON_HEAL, true); + } + + void Register() override + { + OnEffectRemove += AuraEffectRemoveFn(spell_hadronox_leeching_poison_AuraScript::HandleEffectRemove, EFFECT_0, SPELL_AURA_PERIODIC_LEECH, AURA_EFFECT_HANDLE_REAL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_hadronox_leeching_poison_AuraScript(); } }; +class spell_hadronox_web_doors : public SpellScriptLoader +{ + public: + spell_hadronox_web_doors() : SpellScriptLoader("spell_hadronox_web_doors") { } + + class spell_hadronox_web_doors_SpellScript : public SpellScript + { + PrepareSpellScript(spell_hadronox_web_doors_SpellScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ( + sSpellMgr->GetSpellInfo(SPELL_SUMMON_CHAMPION_PERIODIC) && + sSpellMgr->GetSpellInfo(SPELL_SUMMON_CRYPT_FIEND_PERIODIC) && + sSpellMgr->GetSpellInfo(SPELL_SUMMON_NECROMANCER_PERIODIC) + ); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + if (Unit* target = GetHitUnit()) + { + target->RemoveAurasDueToSpell(SPELL_SUMMON_CHAMPION_PERIODIC); + target->RemoveAurasDueToSpell(SPELL_SUMMON_CRYPT_FIEND_PERIODIC); + target->RemoveAurasDueToSpell(SPELL_SUMMON_NECROMANCER_PERIODIC); + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_hadronox_web_doors_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_hadronox_web_doors_SpellScript(); + } +}; + +class achievement_hadronox_denied : public AchievementCriteriaScript +{ + public: + achievement_hadronox_denied() : AchievementCriteriaScript("achievement_hadronox_denied") { } + + bool OnCheck(Player* /*player*/, Unit* target) override + { + if (!target) + return false; + + if (Creature* cTarget = target->ToCreature()) + if (!cTarget->AI()->GetData(DATA_HADRONOX_WEBBED_DOORS)) + return true; + + return false; + } +}; + void AddSC_boss_hadronox() { new boss_hadronox(); + + new npc_anub_ar_crusher(); + new npc_anub_ar_crusher_champion(); + new npc_anub_ar_crusher_crypt_fiend(); + new npc_anub_ar_crusher_necromancer(); + + new npc_anub_ar_champion(); + new npc_anub_ar_crypt_fiend(); + new npc_anub_ar_necromancer(); + + new spell_hadronox_periodic_summon_champion(); + new spell_hadronox_periodic_summon_crypt_fiend(); + new spell_hadronox_periodic_summon_necromancer(); + + new spell_hadronox_leeching_poison(); + new spell_hadronox_web_doors(); + + new achievement_hadronox_denied(); } diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp index 5a6d8be1ec2..a06d915e594 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp @@ -21,63 +21,105 @@ #include "ScriptMgr.h" #include "ScriptedCreature.h" +#include "SpellScript.h" +#include "PassiveAI.h" +#include "SpellAuras.h" #include "azjol_nerub.h" -enum Spells +enum Events { - SPELL_MIND_FLAY = 52586, - SPELL_CURSE_OF_FATIGUE = 52592, - SPELL_FRENZY = 28747, //maybe 53361 - SPELL_SUMMON_SKITTERING_SWARMER = 52438, //AOE Effect 140, maybe 52439 - SPELL_SUMMON_SKITTERING_SWARMER_1 = 52439, //Summon 3x 28735 - SPELL_ACID_SPLASH = 52446, - SPELL_CHARGE = 16979, //maybe is another spell - SPELL_BACKSTAB = 52540, - SPELL_SHADOW_BOLT = 52534, - SPELL_SHADOW_NOVA = 52535, - SPELL_STRIKE = 52532, - SPELL_CLEAVE = 49806, - SPELL_ENRAGE = 52470, - SPELL_INFECTED_BITE = 52469, - SPELL_WEB_WRAP = 52086, //the spell is not working properly - SPELL_BLINDING_WEBS = 52524, - SPELL_POSION_SPRAY = 52493 + // Krik'thir the Gatewatcher + EVENT_SEND_GROUP = 1, + EVENT_SWARM, + EVENT_MIND_FLAY, + EVENT_FRENZY, + + // Watchers - Shared + EVENT_WEB_WRAP, + EVENT_INFECTED_BITE, + + // Watcher Gashra + EVENT_ENRAGE, + // Watcher Narjil + EVENT_BLINDING_WEBS, + // Watcher Silthik + EVENT_POISON_SPRAY, + + // Anubar Skirmisher + EVENT_ANUBAR_CHARGE, + EVENT_BACKSTAB, + + // Anubar Shadowcaster + EVENT_SHADOW_BOLT, + EVENT_SHADOW_NOVA, + + // Anubar Warrior + EVENT_STRIKE, + EVENT_CLEAVE }; -enum Mobs +enum Spells { - NPC_SKITTERING_SWARMER = 28735, - NPC_SKITTERING_SWARMER_CONTROLLER = 32593, - NPC_SKITTERING_INFECTIOR = 28736 + // Krik'thir the Gatewatcher + SPELL_SUBBOSS_AGGRO_TRIGGER = 52343, + SPELL_SWARM = 52440, + SPELL_MIND_FLAY = 52586, + SPELL_CURSE_OF_FATIGUE = 52592, + SPELL_FRENZY = 28747, + + // Watchers - Shared + SPELL_WEB_WRAP = 52086, + SPELL_WEB_WRAP_WRAPPED = 52087, + SPELL_INFECTED_BITE = 52469, + + // Watcher Gashra + SPELL_ENRAGE = 52470, + // Watcher Narjil + SPELL_BLINDING_WEBS = 52524, + // Watcher Silthik + SPELL_POISON_SPRAY = 52493, + + // Anub'ar Warrior + SPELL_CLEAVE = 49806, + SPELL_STRIKE = 52532, + + // Anub'ar Skirmisher + SPELL_CHARGE = 52538, + SPELL_BACKSTAB = 52540, + SPELL_FIXTATE_TRIGGER = 52536, + SPELL_FIXTATE_TRIGGERED = 52537, + + // Anub'ar Shadowcaster + SPELL_SHADOW_BOLT = 52534, + SPELL_SHADOW_NOVA = 52535, + + // Skittering Infector + SPELL_ACID_SPLASH = 52446 }; -enum Yells +enum Data { - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_DEATH = 2, - SAY_SWARM = 3, - SAY_PREFIGHT = 4, - SAY_SEND_GROUP = 5 + DATA_PET_GROUP }; -Position const SpawnPoint[] = +enum Actions { - { 566.164f, 682.087f, 769.079f, 2.21657f }, - { 529.042f, 706.941f, 777.298f, 1.0821f }, - { 489.975f, 671.239f, 772.131f, 0.261799f }, - { 488.556f, 692.95f, 771.764f, 4.88692f }, - { 553.34f, 640.387f, 777.419f, 1.20428f }, - { 517.486f, 706.398f, 777.335f, 5.35816f }, - { 504.01f, 637.693f, 777.479f, 0.506145f }, - { 552.625f, 706.408f, 777.177f, 3.4383f } + ACTION_GASHRA_DIED, + ACTION_NARJIL_DIED, + ACTION_SILTHIK_DIED, + ACTION_WATCHER_ENGAGED, + ACTION_PET_ENGAGED, + ACTION_PET_EVADE }; -enum Events +enum Yells { - EVENT_SUMMON = 1, - EVENT_MIND_FLAY, - EVENT_CURSE_FATIGUE + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_DEATH = 2, + SAY_SWARM = 3, + SAY_PREFIGHT = 4, + SAY_SEND_GROUP = 5 }; class boss_krik_thir : public CreatureScript @@ -87,37 +129,128 @@ class boss_krik_thir : public CreatureScript struct boss_krik_thirAI : public BossAI { - boss_krik_thirAI(Creature* creature) : BossAI(creature, DATA_KRIKTHIR_THE_GATEWATCHER) { } + boss_krik_thirAI(Creature* creature) : BossAI(creature, DATA_KRIKTHIR_THE_GATEWATCHER), _hadGreet(false), _hadFrenzy(false), _petsInCombat(false), _watchersActive(0) { } + + void SummonAdds() + { + if (instance->GetBossState(DATA_KRIKTHIR_THE_GATEWATCHER) == DONE) + return; + + for (uint8 i = 1; i <= 3; ++i) + { + std::list<TempSummon*> summons; + me->SummonCreatureGroup(i, &summons); + for (TempSummon* summon : summons) + summon->AI()->SetData(DATA_PET_GROUP, i); + } + } void Reset() override { - _Reset(); + BossAI::Reset(); + _hadFrenzy = false; + _petsInCombat = false; + _watchersActive = 0; + me->SetReactState(REACT_PASSIVE); } - void EnterCombat(Unit* /*who*/) override + void InitializeAI() override { - Talk(SAY_AGGRO); - _EnterCombat(); - Summon(); + BossAI::InitializeAI(); + SummonAdds(); + } - events.ScheduleEvent(EVENT_SUMMON, 15000); - events.ScheduleEvent(EVENT_MIND_FLAY, 15000); - events.ScheduleEvent(EVENT_CURSE_FATIGUE, 12000); + void JustRespawned() override + { + BossAI::JustRespawned(); + SummonAdds(); } - void Summon() + void KilledUnit(Unit* victim) override + { + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_SLAY); + } + + void JustDied(Unit* killer) override + { + summons.clear(); + BossAI::JustDied(killer); + Talk(SAY_DEATH); + } + + void EnterCombat(Unit* who) override { - for (uint8 i = 0; i < 8; i++) + _petsInCombat = false; + me->SetReactState(REACT_AGGRESSIVE); + summons.DoZoneInCombat(); + + events.CancelEvent(EVENT_SEND_GROUP); + events.ScheduleEvent(EVENT_SWARM, Seconds(5)); + events.ScheduleEvent(EVENT_MIND_FLAY, randtime(Seconds(1), Seconds(3))); + + BossAI::EnterCombat(who); + } + + void MoveInLineOfSight(Unit* who) override + { + if (!me->HasReactState(REACT_PASSIVE)) { - me->SummonCreature(NPC_SKITTERING_SWARMER, SpawnPoint[i], TEMPSUMMON_TIMED_DESPAWN, 25000); - uint32 summon_entry = (i == 4 || i == 5 || i == 6) ? NPC_SKITTERING_INFECTIOR : NPC_SKITTERING_SWARMER; - me->SummonCreature(summon_entry, SpawnPoint[i], TEMPSUMMON_TIMED_DESPAWN, 25000); + ScriptedAI::MoveInLineOfSight(who); + return; + } + + if (me->CanStartAttack(who, false) && me->IsWithinDistInMap(who, me->GetAttackDistance(who) + me->m_CombatDistance)) + EnterCombat(who); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + summons.DespawnAll(); + _DespawnAtEvade(); + } + + void DoAction(int32 action) override + { + switch (action) + { + case -ACTION_GATEWATCHER_GREET: + if (!_hadGreet && me->IsAlive() && !me->IsInCombat() && !_petsInCombat) + { + _hadGreet = true; + Talk(SAY_PREFIGHT); + } + break; + case ACTION_GASHRA_DIED: + case ACTION_NARJIL_DIED: + case ACTION_SILTHIK_DIED: + if (!_watchersActive) // something is wrong + { + EnterEvadeMode(EVADE_REASON_OTHER); + return; + } + if (!--_watchersActive) // if there are no watchers currently in combat... + events.RescheduleEvent(EVENT_SEND_GROUP, Seconds(5)); // ...send the next watcher after the targets sooner + break; + case ACTION_WATCHER_ENGAGED: + ++_watchersActive; + break; + case ACTION_PET_ENGAGED: + if (_petsInCombat || me->IsInCombat()) + break; + _petsInCombat = true; + Talk(SAY_AGGRO); + events.ScheduleEvent(EVENT_SEND_GROUP, Seconds(70)); + break; + case ACTION_PET_EVADE: + EnterEvadeMode(EVADE_REASON_OTHER); + break; } } void UpdateAI(uint32 diff) override { - if (!UpdateVictim()) + if (!UpdateVictim() && !_petsInCombat) return; events.Update(diff); @@ -125,52 +258,62 @@ class boss_krik_thir : public CreatureScript if (me->HasUnitState(UNIT_STATE_CASTING)) return; + if (me->HealthBelowPct(10) && !_hadFrenzy) + { + _hadFrenzy = true; + events.ScheduleEvent(EVENT_FRENZY, Seconds(1)); + } + while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { - case EVENT_SUMMON: - Summon(); - events.ScheduleEvent(EVENT_SUMMON, 15000); + case EVENT_SEND_GROUP: + DoCastAOE(SPELL_SUBBOSS_AGGRO_TRIGGER, true); + events.Repeat(Seconds(70)); break; + + case EVENT_SWARM: + DoCastAOE(SPELL_SWARM); + Talk(SAY_SWARM); + break; + case EVENT_MIND_FLAY: DoCastVictim(SPELL_MIND_FLAY); - events.ScheduleEvent(EVENT_MIND_FLAY, 15000); + events.Repeat(randtime(Seconds(9), Seconds(11))); break; - case EVENT_CURSE_FATIGUE: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - DoCast(target, SPELL_CURSE_OF_FATIGUE); - events.ScheduleEvent(EVENT_CURSE_FATIGUE, 10000); - break; - default: + + case EVENT_FRENZY: + DoCastSelf(SPELL_FRENZY); + DoCastAOE(SPELL_CURSE_OF_FATIGUE); + events.Repeat(Seconds(15)); break; } - } - if (!me->HasAura(SPELL_FRENZY) && HealthBelowPct(10)) - DoCast(me, SPELL_FRENZY, true); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } DoMeleeAttackIfReady(); } - void JustDied(Unit* /*killer*/) override + void SpellHit(Unit* /*whose*/, SpellInfo const* spell) override { - Talk(SAY_DEATH); - _JustDied(); + if (spell->Id == SPELL_SUBBOSS_AGGRO_TRIGGER) + DoZoneInCombat(); } - void KilledUnit(Unit* victim) override + void SpellHitTarget(Unit* /*who*/, SpellInfo const* spell) override { - if (victim->GetTypeId() != TYPEID_PLAYER) - return; - - Talk(SAY_SLAY); + if (spell->Id == SPELL_SUBBOSS_AGGRO_TRIGGER) + Talk(SAY_SEND_GROUP); } - void JustSummoned(Creature* summoned) override - { - summoned->GetMotionMaster()->MovePoint(0, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); - } + private: + bool _hadGreet; + bool _hadFrenzy; + bool _petsInCombat; + uint8 _watchersActive; }; CreatureAI* GetAI(Creature* creature) const override @@ -179,74 +322,123 @@ class boss_krik_thir : public CreatureScript } }; -class npc_skittering_infector : public CreatureScript +struct npc_gatewatcher_petAI : public ScriptedAI { - public: - npc_skittering_infector() : CreatureScript("npc_skittering_infector") { } + npc_gatewatcher_petAI(Creature* creature, bool isWatcher) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _petGroup(0), _isWatcher(isWatcher) { } - struct npc_skittering_infectorAI : public ScriptedAI + virtual void _EnterCombat() = 0; + void EnterCombat(Unit* who) override + { + if (_isWatcher) { - npc_skittering_infectorAI(Creature* creature) : ScriptedAI(creature) { } - - void JustDied(Unit* /*killer*/) override - { - DoCastAOE(SPELL_ACID_SPLASH); - } - }; + _isWatcher = false; + if (TempSummon* meSummon = me->ToTempSummon()) + if (Creature* summoner = meSummon->GetSummonerCreatureBase()) + summoner->AI()->DoAction(ACTION_WATCHER_ENGAGED); + } - CreatureAI* GetAI(Creature* creature) const override + if (me->HasReactState(REACT_PASSIVE)) { - return GetAzjolNerubAI<npc_skittering_infectorAI>(creature); - } -}; + std::list<Creature*> others; + me->GetCreatureListWithEntryInGrid(others, 0, 40.0f); + for (Creature* other : others) + if (other->AI()->GetData(DATA_PET_GROUP) == _petGroup) + { + other->SetReactState(REACT_AGGRESSIVE); + other->AI()->AttackStart(who); + } -enum TrashEvents -{ - // Anubar Skrimisher - EVENT_ANUBAR_CHARGE = 1, - EVENT_BACKSTAB, + if (TempSummon* meSummon = me->ToTempSummon()) + if (Creature* summoner = meSummon->GetSummonerCreatureBase()) + summoner->AI()->DoAction(ACTION_PET_ENGAGED); + } + _EnterCombat(); + ScriptedAI::EnterCombat(who); + } - // Anubar Shadowcaster - EVENT_SHADOW_BOLT, - EVENT_SHADOW_NOVA, + void SetData(uint32 data, uint32 value) override + { + if (data == DATA_PET_GROUP) + { + _petGroup = value; + me->SetReactState(_petGroup ? REACT_PASSIVE : REACT_AGGRESSIVE); + } + } + + uint32 GetData(uint32 data) const override + { + if (data == DATA_PET_GROUP) + return _petGroup; + return 0; + } + + void MoveInLineOfSight(Unit* who) override + { + if (!me->HasReactState(REACT_PASSIVE)) + { + ScriptedAI::MoveInLineOfSight(who); + return; + } - // Anubar Warrior - EVENT_STRIKE, - EVENT_CLEAVE, + if (me->CanStartAttack(who, false) && me->IsWithinDistInMap(who, me->GetAttackDistance(who) + me->m_CombatDistance)) + EnterCombat(who); + } - // Watcher Gashra - EVENT_WEB_WRAP_GASHRA, - EVENT_INFECTED_BITE_GASHRA, + void SpellHit(Unit* /*whose*/, SpellInfo const* spell) override + { + if (spell->Id == SPELL_SUBBOSS_AGGRO_TRIGGER) + DoZoneInCombat(); + } - // Watcher Narjil - EVENT_WEB_WRAP_NARJIL, - EVENT_INFECTED_BITE_NARJIL, - EVENT_BINDING_WEBS, + void EnterEvadeMode(EvadeReason why) override + { + if (TempSummon* meSummon = me->ToTempSummon()) + { + if (Creature* summoner = meSummon->GetSummonerCreatureBase()) + summoner->AI()->DoAction(ACTION_PET_EVADE); + else + me->DespawnOrUnsummon(); + return; + } + ScriptedAI::EnterEvadeMode(why); + } - // Watcher Silthik - EVENT_WEB_WRAP_SILTHIK, - EVENT_INFECTED_BITE_SILTHIK, - EVENT_POISON_SPRAY + EventMap _events; + InstanceScript* _instance; + uint32 _petGroup; + bool _isWatcher; }; -class npc_anub_ar_skirmisher : public CreatureScript +class npc_watcher_gashra : public CreatureScript { public: - npc_anub_ar_skirmisher() : CreatureScript("npc_anub_ar_skirmisher") { } + npc_watcher_gashra() : CreatureScript("npc_watcher_gashra") { } - struct npc_anub_ar_skirmisherAI : public ScriptedAI + struct npc_watcher_gashraAI : public npc_gatewatcher_petAI { - npc_anub_ar_skirmisherAI(Creature* creature) : ScriptedAI(creature) { } + npc_watcher_gashraAI(Creature* creature) : npc_gatewatcher_petAI(creature, true) + { + _instance = creature->GetInstanceScript(); + me->SetReactState(REACT_PASSIVE); + } void Reset() override { - events.Reset(); + _events.Reset(); } - void EnterCombat(Unit* /*who*/) override + void _EnterCombat() override { - events.ScheduleEvent(EVENT_ANUBAR_CHARGE, 11000); - events.ScheduleEvent(EVENT_BACKSTAB, 7000); + _events.ScheduleEvent(EVENT_ENRAGE, randtime(Seconds(3), Seconds(5))); + _events.ScheduleEvent(EVENT_WEB_WRAP, randtime(Seconds(16), Seconds(19))); + _events.ScheduleEvent(EVENT_INFECTED_BITE, randtime(Seconds(7),Seconds(11))); + } + + void JustDied(Unit* /*killer*/) override + { + Creature* krikthir = _instance->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER); + if (krikthir && krikthir->IsAlive()) + krikthir->AI()->DoAction(ACTION_GASHRA_DIED); } void UpdateAI(uint32 diff) override @@ -254,64 +446,79 @@ class npc_anub_ar_skirmisher : public CreatureScript if (!UpdateVictim()) return; - events.Update(diff); + _events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; - while (uint32 eventId = events.ExecuteEvent()) + while (uint32 eventId = _events.ExecuteEvent()) { switch (eventId) { - case EVENT_ANUBAR_CHARGE: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - { - DoResetThreat(); - me->AddThreat(target, 1.0f); - DoCast(target, SPELL_CHARGE, true); - } - events.ScheduleEvent(EVENT_ANUBAR_CHARGE, 15000); + case EVENT_ENRAGE: + DoCastSelf(SPELL_ENRAGE); + _events.Repeat(randtime(Seconds(12), Seconds(20))); break; - case EVENT_BACKSTAB: - DoCastVictim(SPELL_BACKSTAB); - events.ScheduleEvent(EVENT_BACKSTAB, 12000); + case EVENT_WEB_WRAP: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f)) + DoCast(target, SPELL_WEB_WRAP); + _events.Repeat(randtime(Seconds(13), Seconds(19))); + break; + case EVENT_INFECTED_BITE: + DoCastVictim(SPELL_INFECTED_BITE); + _events.Repeat(randtime(Seconds(23), Seconds(27))); break; default: break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } DoMeleeAttackIfReady(); } private: - EventMap events; + EventMap _events; + InstanceScript* _instance; }; CreatureAI* GetAI(Creature* creature) const override { - return GetAzjolNerubAI<npc_anub_ar_skirmisherAI>(creature); + return GetAzjolNerubAI<npc_watcher_gashraAI>(creature); } }; -class npc_anub_ar_shadowcaster : public CreatureScript +class npc_watcher_narjil : public CreatureScript { public: - npc_anub_ar_shadowcaster() : CreatureScript("npc_anub_ar_shadowcaster") { } + npc_watcher_narjil() : CreatureScript("npc_watcher_narjil") { } - struct npc_anub_ar_shadowcasterAI : public ScriptedAI + struct npc_watcher_narjilAI : public npc_gatewatcher_petAI { - npc_anub_ar_shadowcasterAI(Creature* creature) : ScriptedAI(creature) { } + npc_watcher_narjilAI(Creature* creature) : npc_gatewatcher_petAI(creature, true) + { + _instance = creature->GetInstanceScript(); + } void Reset() override { - events.Reset(); + _events.Reset(); } - void EnterCombat(Unit* /*who*/) override + void _EnterCombat() override { - events.ScheduleEvent(EVENT_SHADOW_BOLT, 6000); - events.ScheduleEvent(EVENT_SHADOW_NOVA, 15000); + _events.ScheduleEvent(EVENT_BLINDING_WEBS, randtime(Seconds(13), Seconds(18))); + _events.ScheduleEvent(EVENT_WEB_WRAP, randtime(Seconds(3), Seconds(5))); + _events.ScheduleEvent(EVENT_INFECTED_BITE, randtime(Seconds(7), Seconds(11))); + } + + void JustDied(Unit* /*killer*/) override + { + Creature* krikthir = _instance->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER); + if (krikthir && krikthir->IsAlive()) + krikthir->AI()->DoAction(ACTION_NARJIL_DIED); } void UpdateAI(uint32 diff) override @@ -319,60 +526,79 @@ class npc_anub_ar_shadowcaster : public CreatureScript if (!UpdateVictim()) return; - events.Update(diff); + _events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; - while (uint32 eventId = events.ExecuteEvent()) + while (uint32 eventId = _events.ExecuteEvent()) { switch (eventId) { - case EVENT_SHADOW_BOLT: + case EVENT_BLINDING_WEBS: + DoCastVictim(SPELL_BLINDING_WEBS); + _events.Repeat(randtime(Seconds(23), Seconds(27))); + break; + case EVENT_WEB_WRAP: if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - DoCast(target, SPELL_SHADOW_BOLT, true); - events.ScheduleEvent(EVENT_SHADOW_BOLT, 15000); + DoCast(target, SPELL_WEB_WRAP); + _events.Repeat(randtime(Seconds(13), Seconds(19))); break; - case EVENT_SHADOW_NOVA: - DoCastVictim(SPELL_SHADOW_NOVA, true); - events.ScheduleEvent(EVENT_SHADOW_NOVA, 17000); + case EVENT_INFECTED_BITE: + DoCastVictim(SPELL_INFECTED_BITE); + _events.Repeat(randtime(Seconds(20), Seconds(25))); break; default: break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } DoMeleeAttackIfReady(); } private: - EventMap events; + EventMap _events; + InstanceScript* _instance; }; CreatureAI* GetAI(Creature* creature) const override { - return GetAzjolNerubAI<npc_anub_ar_shadowcasterAI>(creature); + return GetAzjolNerubAI<npc_watcher_narjilAI>(creature); } }; -class npc_anub_ar_warrior : public CreatureScript +class npc_watcher_silthik : public CreatureScript { public: - npc_anub_ar_warrior() : CreatureScript("npc_anub_ar_warrior") { } + npc_watcher_silthik() : CreatureScript("npc_watcher_silthik") { } - struct npc_anub_ar_warriorAI : public ScriptedAI + struct npc_watcher_silthikAI : public npc_gatewatcher_petAI { - npc_anub_ar_warriorAI(Creature* creature) : ScriptedAI(creature) { } + npc_watcher_silthikAI(Creature* creature) : npc_gatewatcher_petAI(creature, true) + { + _instance = creature->GetInstanceScript(); + } void Reset() override { - events.Reset(); + _events.Reset(); } - void EnterCombat(Unit* /*who*/) override + void _EnterCombat() override { - events.ScheduleEvent(EVENT_CLEAVE, 11000); - events.ScheduleEvent(EVENT_STRIKE, 6000); + _events.ScheduleEvent(EVENT_POISON_SPRAY, randtime(Seconds(16), Seconds(19))); + _events.ScheduleEvent(EVENT_WEB_WRAP, randtime(Seconds(7), Seconds(11))); + _events.ScheduleEvent(EVENT_INFECTED_BITE, randtime(Seconds(3), Seconds(5))); + } + + void JustDied(Unit* /*killer*/) override + { + Creature* krikthir = _instance->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER); + if (krikthir && krikthir->IsAlive()) + krikthir->AI()->DoAction(ACTION_SILTHIK_DIED); } void UpdateAI(uint32 diff) override @@ -380,70 +606,68 @@ class npc_anub_ar_warrior : public CreatureScript if (!UpdateVictim()) return; - events.Update(diff); + _events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; - while (uint32 eventId = events.ExecuteEvent()) + while (uint32 eventId = _events.ExecuteEvent()) { switch (eventId) { - case EVENT_CLEAVE: - DoCastVictim(SPELL_STRIKE, true); - events.ScheduleEvent(EVENT_CLEAVE, 15000); + case EVENT_POISON_SPRAY: + DoCastVictim(SPELL_POISON_SPRAY); + _events.Repeat(randtime(Seconds(13), Seconds(19))); break; - case EVENT_STRIKE: - DoCastVictim(SPELL_CLEAVE, true); - events.ScheduleEvent(EVENT_STRIKE, 17000); + case EVENT_WEB_WRAP: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) + DoCast(target, SPELL_WEB_WRAP); + _events.Repeat(randtime(Seconds(13), Seconds(17))); + break; + case EVENT_INFECTED_BITE: + DoCastVictim(SPELL_INFECTED_BITE); + _events.Repeat(randtime(Seconds(20), Seconds(24))); break; default: break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } DoMeleeAttackIfReady(); } private: - EventMap events; + EventMap _events; + InstanceScript* _instance; }; CreatureAI* GetAI(Creature* creature) const override { - return GetAzjolNerubAI<npc_anub_ar_warriorAI>(creature); + return GetAzjolNerubAI<npc_watcher_silthikAI>(creature); } }; -class npc_watcher_gashra : public CreatureScript +class npc_anub_ar_warrior : public CreatureScript { public: - npc_watcher_gashra() : CreatureScript("npc_watcher_gashra") { } + npc_anub_ar_warrior() : CreatureScript("npc_anub_ar_warrior") { } - struct npc_watcher_gashraAI : public ScriptedAI + struct npc_anub_ar_warriorAI : public npc_gatewatcher_petAI { - npc_watcher_gashraAI(Creature* creature) : ScriptedAI(creature) - { - _instance = creature->GetInstanceScript(); - } + npc_anub_ar_warriorAI(Creature* creature) : npc_gatewatcher_petAI(creature, false) { } void Reset() override { _events.Reset(); } - void EnterCombat(Unit* /*who*/) override + void _EnterCombat() override { - DoCast(me, SPELL_ENRAGE, true); - _events.ScheduleEvent(EVENT_WEB_WRAP_GASHRA, 11000); - _events.ScheduleEvent(EVENT_INFECTED_BITE_GASHRA, 4000); - } - - void JustDied(Unit* /*killer*/) override - { - Creature* krikthir = _instance->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER); - if (krikthir && krikthir->IsAlive()) - krikthir->AI()->Talk(SAY_PREFIGHT); + _events.ScheduleEvent(EVENT_CLEAVE, randtime(Seconds(7), Seconds(9))); + _events.ScheduleEvent(EVENT_STRIKE, randtime(Seconds(5), Seconds(10))); } void UpdateAI(uint32 diff) override @@ -460,63 +684,50 @@ class npc_watcher_gashra : public CreatureScript { switch (eventId) { - case EVENT_WEB_WRAP_GASHRA: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - DoCast(target, SPELL_WEB_WRAP, true); - _events.ScheduleEvent(EVENT_WEB_WRAP_GASHRA, 17000); + case EVENT_CLEAVE: + DoCastVictim(SPELL_CLEAVE); + _events.Repeat(randtime(Seconds(10), Seconds(16))); break; - case EVENT_INFECTED_BITE_GASHRA: - DoCastVictim(SPELL_INFECTED_BITE, true); - _events.ScheduleEvent(EVENT_INFECTED_BITE_GASHRA, 15000); + case EVENT_STRIKE: + DoCastVictim(SPELL_STRIKE); + _events.Repeat(randtime(Seconds(15), Seconds(19))); break; default: break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } DoMeleeAttackIfReady(); } - - private: - EventMap _events; - InstanceScript* _instance; }; CreatureAI* GetAI(Creature* creature) const override { - return GetAzjolNerubAI<npc_watcher_gashraAI>(creature); + return GetAzjolNerubAI<npc_anub_ar_warriorAI>(creature); } }; -class npc_watcher_narjil : public CreatureScript +class npc_anub_ar_skirmisher : public CreatureScript { public: - npc_watcher_narjil() : CreatureScript("npc_watcher_narjil") { } + npc_anub_ar_skirmisher() : CreatureScript("npc_anub_ar_skirmisher") { } - struct npc_watcher_narjilAI : public ScriptedAI + struct npc_anub_ar_skirmisherAI : public npc_gatewatcher_petAI { - npc_watcher_narjilAI(Creature* creature) : ScriptedAI(creature) - { - _instance = creature->GetInstanceScript(); - } + npc_anub_ar_skirmisherAI(Creature* creature) : npc_gatewatcher_petAI(creature, false) { } void Reset() override { _events.Reset(); } - void EnterCombat(Unit* /*who*/) override + void _EnterCombat() override { - _events.ScheduleEvent(EVENT_WEB_WRAP_NARJIL, 11000); - _events.ScheduleEvent(EVENT_INFECTED_BITE_NARJIL, 4000); - _events.ScheduleEvent(EVENT_BINDING_WEBS, 17000); - } - - void JustDied(Unit* /*killer*/) override - { - Creature* krikthir = _instance->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER); - if (krikthir && krikthir->IsAlive()) - krikthir->AI()->Talk(SAY_PREFIGHT); + _events.ScheduleEvent(EVENT_ANUBAR_CHARGE, randtime(Seconds(6), Seconds(8))); + _events.ScheduleEvent(EVENT_BACKSTAB, randtime(Seconds(7), Seconds(9))); } void UpdateAI(uint32 diff) override @@ -533,67 +744,58 @@ class npc_watcher_narjil : public CreatureScript { switch (eventId) { - case EVENT_WEB_WRAP_NARJIL: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - DoCast(target, SPELL_WEB_WRAP, true); - _events.ScheduleEvent(EVENT_WEB_WRAP_NARJIL, 15000); - break; - case EVENT_INFECTED_BITE_NARJIL: - DoCastVictim(SPELL_INFECTED_BITE, true); - _events.ScheduleEvent(EVENT_INFECTED_BITE_NARJIL, 11000); + case EVENT_ANUBAR_CHARGE: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true)) + DoCast(target, SPELL_CHARGE); + _events.Repeat(randtime(Seconds(20), Seconds(25))); break; - case EVENT_BINDING_WEBS: - DoCastVictim(SPELL_BLINDING_WEBS, true); - _events.ScheduleEvent(EVENT_BINDING_WEBS, 17000); + case EVENT_BACKSTAB: + if (me->GetVictim() && me->GetVictim()->isInBack(me)) + DoCastVictim(SPELL_BACKSTAB); + _events.Repeat(randtime(Seconds(10), Seconds(13))); break; default: break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } DoMeleeAttackIfReady(); } - private: - EventMap _events; - InstanceScript* _instance; + void SpellHitTarget(Unit* target, SpellInfo const* spell) override + { + if (spell->Id == SPELL_CHARGE && target) + DoCast(target, SPELL_FIXTATE_TRIGGER); + } }; CreatureAI* GetAI(Creature* creature) const override { - return GetAzjolNerubAI<npc_watcher_narjilAI>(creature); + return GetAzjolNerubAI<npc_anub_ar_skirmisherAI>(creature); } }; -class npc_watcher_silthik : public CreatureScript +class npc_anub_ar_shadowcaster : public CreatureScript { public: - npc_watcher_silthik() : CreatureScript("npc_watcher_silthik") { } + npc_anub_ar_shadowcaster() : CreatureScript("npc_anub_ar_shadowcaster") { } - struct npc_watcher_silthikAI : public ScriptedAI + struct npc_anub_ar_shadowcasterAI : public npc_gatewatcher_petAI { - npc_watcher_silthikAI(Creature* creature) : ScriptedAI(creature) - { - _instance = creature->GetInstanceScript(); - } + npc_anub_ar_shadowcasterAI(Creature* creature) : npc_gatewatcher_petAI(creature, false) { } void Reset() override { _events.Reset(); } - void EnterCombat(Unit* /*who*/) override + void _EnterCombat() override { - _events.ScheduleEvent(EVENT_WEB_WRAP_SILTHIK, 11000); - _events.ScheduleEvent(EVENT_INFECTED_BITE_SILTHIK, 4000); - _events.ScheduleEvent(EVENT_POISON_SPRAY, 15000); - } - - void JustDied(Unit* /*killer*/) override - { - Creature* krikthir = _instance->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER); - if (krikthir && krikthir->IsAlive()) - krikthir->AI()->Talk(SAY_PREFIGHT); + _events.ScheduleEvent(EVENT_SHADOW_BOLT, Seconds(4)); + _events.ScheduleEvent(EVENT_SHADOW_NOVA, randtime(Seconds(10), Seconds(14))); } void UpdateAI(uint32 diff) override @@ -610,35 +812,230 @@ class npc_watcher_silthik : public CreatureScript { switch (eventId) { - case EVENT_WEB_WRAP_SILTHIK: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) - DoCast(target, SPELL_WEB_WRAP, true); - _events.ScheduleEvent(EVENT_WEB_WRAP_SILTHIK, 15000); - break; - case EVENT_INFECTED_BITE_SILTHIK: - DoCastVictim(SPELL_INFECTED_BITE, true); - _events.ScheduleEvent(EVENT_INFECTED_BITE_SILTHIK, 11000); + case EVENT_SHADOW_BOLT: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true)) + DoCast(target, SPELL_SHADOW_BOLT); + _events.Repeat(randtime(Seconds(2), Seconds(4))); break; - case EVENT_POISON_SPRAY: - DoCastVictim(SPELL_POSION_SPRAY, true); - _events.ScheduleEvent(EVENT_POISON_SPRAY, 17000); + case EVENT_SHADOW_NOVA: + DoCastVictim(SPELL_SHADOW_NOVA); + _events.Repeat(randtime(Seconds(10), Seconds(16))); break; default: break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } DoMeleeAttackIfReady(); } + }; - private: - EventMap _events; - InstanceScript* _instance; + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_anub_ar_shadowcasterAI>(creature); + } +}; + +class npc_skittering_swarmer : public CreatureScript +{ + public: + npc_skittering_swarmer() : CreatureScript("npc_skittering_swarmer") { } + + struct npc_skittering_swarmerAI : public ScriptedAI + { + npc_skittering_swarmerAI(Creature* creature) : ScriptedAI(creature) { } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + if (Creature* gatewatcher = me->GetInstanceScript()->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER)) + { + if (Unit* target = gatewatcher->getAttackerForHelper()) + AttackStart(target); + gatewatcher->AI()->JustSummoned(me); + } + } }; CreatureAI* GetAI(Creature* creature) const override { - return GetAzjolNerubAI<npc_watcher_silthikAI>(creature); + return GetAzjolNerubAI<npc_skittering_swarmerAI>(creature); + } +}; + +class npc_skittering_infector : public CreatureScript +{ + public: + npc_skittering_infector() : CreatureScript("npc_skittering_infector") { } + + struct npc_skittering_infectorAI : public ScriptedAI + { + npc_skittering_infectorAI(Creature* creature) : ScriptedAI(creature) { } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + if (Creature* gatewatcher = me->GetInstanceScript()->GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER)) + { + if (Unit* target = gatewatcher->getAttackerForHelper()) + AttackStart(target); + gatewatcher->AI()->JustSummoned(me); + } + } + + void JustDied(Unit* killer) override + { + DoCastAOE(SPELL_ACID_SPLASH); + ScriptedAI::JustDied(killer); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_skittering_infectorAI>(creature); + } +}; + +class npc_gatewatcher_web_wrap : public CreatureScript +{ + public: + npc_gatewatcher_web_wrap() : CreatureScript("npc_gatewatcher_web_wrap") { } + + struct npc_gatewatcher_web_wrapAI : public NullCreatureAI + { + npc_gatewatcher_web_wrapAI(Creature* creature) : NullCreatureAI(creature) { } + + void JustDied(Unit* /*killer*/) override + { + if (TempSummon* meSummon = me->ToTempSummon()) + if (Unit* summoner = meSummon->GetSummoner()) + summoner->RemoveAurasDueToSpell(SPELL_WEB_WRAP_WRAPPED); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetAzjolNerubAI<npc_gatewatcher_web_wrapAI>(creature); + } +}; + +class spell_gatewatcher_subboss_trigger : public SpellScriptLoader +{ + public: + spell_gatewatcher_subboss_trigger() : SpellScriptLoader("spell_gatewatcher_subboss_trigger") { } + + class spell_gatewatcher_subboss_trigger_SpellScript : public SpellScript + { + PrepareSpellScript(spell_gatewatcher_subboss_trigger_SpellScript); + + void HandleTargets(std::list<WorldObject*>& targetList) + { + // Remove any Watchers that are already in combat + for (std::list<WorldObject*>::iterator it = targetList.begin(); it != targetList.end(); ++it) + { + if (Creature* creature = (*it)->ToCreature()) + if (creature->IsAlive() && !creature->IsInCombat()) + continue; + it = targetList.erase(it); + } + + // Default to Krik'thir himself if he isn't engaged + WorldObject* target = nullptr; + if (GetCaster() && !GetCaster()->IsInCombat()) + target = GetCaster(); + // Unless there are Watchers that aren't engaged yet + if (!targetList.empty()) + { + // If there are, pick one of them at random + std::list<WorldObject*>::iterator it = targetList.begin(); + std::advance(it, urand(0, targetList.size() - 1)); + target = *it; + } + // And hit only that one + targetList.clear(); + if (target) + targetList.push_back(target); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_gatewatcher_subboss_trigger_SpellScript::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_gatewatcher_subboss_trigger_SpellScript(); + } +}; + +class spell_anub_ar_skirmisher_fixtate : public SpellScriptLoader +{ + public: + spell_anub_ar_skirmisher_fixtate() : SpellScriptLoader("spell_anub_ar_skirmisher_fixtate") { } + + class spell_anub_ar_skirmisher_fixtate_SpellScript : public SpellScript + { + PrepareSpellScript(spell_anub_ar_skirmisher_fixtate_SpellScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return sSpellMgr->GetSpellInfo(SPELL_FIXTATE_TRIGGERED) != nullptr; + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + if (Unit* target = GetHitUnit()) + target->CastSpell(GetCaster(), SPELL_FIXTATE_TRIGGERED, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_anub_ar_skirmisher_fixtate_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_anub_ar_skirmisher_fixtate_SpellScript(); + } +}; + +class spell_gatewatcher_web_wrap : public SpellScriptLoader +{ + public: + spell_gatewatcher_web_wrap() : SpellScriptLoader("spell_gatewatcher_web_wrap") { } + + class spell_gatewatcher_web_wrap_AuraScript : public AuraScript + { + PrepareAuraScript(spell_gatewatcher_web_wrap_AuraScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return sSpellMgr->GetSpellInfo(SPELL_WEB_WRAP_WRAPPED) != nullptr; + } + + void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_EXPIRE) + return; + + if (Unit* target = GetTarget()) + target->CastSpell(target, SPELL_WEB_WRAP_WRAPPED, true); + } + + void Register() override + { + OnEffectRemove += AuraEffectRemoveFn(spell_gatewatcher_web_wrap_AuraScript::HandleEffectRemove, EFFECT_0, SPELL_AURA_MOD_ROOT, AURA_EFFECT_HANDLE_REAL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_gatewatcher_web_wrap_AuraScript(); } }; @@ -656,11 +1053,12 @@ class achievement_watch_him_die : public AchievementCriteriaScript if (!instance) return false; - for (uint8 n = 0; n < 3; ++n) + for (ANDataTypes watcherData : {DATA_WATCHER_GASHRA, DATA_WATCHER_NARJIL, DATA_WATCHER_SILTHIK}) { - if (Creature* watcher = instance->GetCreature(DATA_WATCHER_GASHRA + n)) - if (!watcher->IsAlive()) - return false; + if (Creature* watcher = instance->GetCreature(watcherData)) + if (watcher->IsAlive()) + continue; + return false; } return true; @@ -670,12 +1068,22 @@ class achievement_watch_him_die : public AchievementCriteriaScript void AddSC_boss_krik_thir() { new boss_krik_thir(); - new npc_skittering_infector(); - new npc_anub_ar_skirmisher(); - new npc_anub_ar_shadowcaster(); + new npc_watcher_gashra(); - new npc_anub_ar_warrior(); - new npc_watcher_silthik(); new npc_watcher_narjil(); + new npc_watcher_silthik(); + + new npc_anub_ar_warrior(); + new npc_anub_ar_skirmisher(); + new npc_anub_ar_shadowcaster(); + + new npc_skittering_swarmer(); + new npc_skittering_infector(); + new npc_gatewatcher_web_wrap(); + + new spell_gatewatcher_subboss_trigger(); + new spell_anub_ar_skirmisher_fixtate(); + new spell_gatewatcher_web_wrap(); + new achievement_watch_him_die(); } diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp index bba3a87efcf..7ebf309c74f 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp @@ -31,12 +31,27 @@ DoorData const doorData[] = ObjectData const creatureData[] = { { NPC_KRIKTHIR, DATA_KRIKTHIR_THE_GATEWATCHER }, + { NPC_HADRONOX, DATA_HADRONOX }, + { NPC_ANUBARAK, DATA_ANUBARAK }, { NPC_WATCHER_NARJIL, DATA_WATCHER_GASHRA }, { NPC_WATCHER_GASHRA, DATA_WATCHER_SILTHIK }, { NPC_WATCHER_SILTHIK, DATA_WATCHER_NARJIL }, { 0, 0 } // END }; +ObjectData const gameobjectData[] = +{ + { GO_ANUBARAK_DOOR_3, DATA_ANUBARAK_WALL }, + { 0, 0 } // END +}; + +BossBoundaryData const boundaries = +{ + { DATA_KRIKTHIR_THE_GATEWATCHER, new RectangleBoundary(400.0f, 580.0f, 623.5f, 810.0f) }, + { DATA_HADRONOX, new ZRangeBoundary(666.0f, 776.0f) }, + { DATA_ANUBARAK, new CircleBoundary(Position(550.6178f, 253.5917f), 26.0f) } +}; + class instance_azjol_nerub : public InstanceMapScript { public: @@ -48,8 +63,19 @@ class instance_azjol_nerub : public InstanceMapScript { SetHeaders(DataHeader); SetBossNumber(EncounterCount); + LoadBossBoundaries(boundaries); LoadDoorData(doorData); - LoadObjectData(creatureData, nullptr); + LoadObjectData(creatureData, gameobjectData); + } + + void OnUnitDeath(Unit* who) override + { + InstanceScript::OnUnitDeath(who); + Creature* creature = who->ToCreature(); + if (!creature || creature->IsCritter() || creature->IsControlledByPlayer()) + return; + if (Creature* gatewatcher = GetCreature(DATA_KRIKTHIR_THE_GATEWATCHER)) + gatewatcher->AI()->DoAction(-ACTION_GATEWATCHER_GREET); } }; |
