diff options
author | joschiwald <joschiwald.trinity@gmail.com> | 2017-03-23 03:44:43 -0300 |
---|---|---|
committer | ariel- <ariel-@users.noreply.github.com> | 2017-05-16 20:17:58 -0300 |
commit | 5e90c76fd44e81e01e7bd5274867864d082059d2 (patch) | |
tree | 4c5b8ae0ef3abf3591766addba617d36cf972e56 /src | |
parent | b702e0eabd2ed05707106da8db451dbd2f85af71 (diff) |
Scripts/Ulduar: Initial implementation of the Thorim Encounter
Closes #7651
Fix logic fail in achievement:
SPELL_LIGHTNING_CHARGE aka 62279 is casted on Thorim itself to buff him, not the damage spell tracked by the achievement.
Diffstat (limited to 'src')
4 files changed, 1945 insertions, 36 deletions
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp index ea2fa4027a1..f65aa2d676a 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp @@ -15,27 +15,390 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "MoveSplineInit.h" +#include "Player.h" #include "ScriptMgr.h" #include "ScriptedCreature.h" +#include "SpellScript.h" #include "ulduar.h" +enum Spells +{ + // Thorim + SPELL_SHEAT_OF_LIGHTNING = 62276, + SPELL_STORMHAMMER = 62042, + SPELL_STORMHAMMER_SIF = 64767, + SPELL_STORMHAMMER_BOOMERANG = 64909, + SPELL_DEAFENING_THUNDER = 62470, + SPELL_CHARGE_ORB = 62016, + SPELL_SUMMON_LIGHTNING_ORB = 62391, + SPELL_LIGHTNING_DESTRUCTION = 62393, + SPELL_TOUCH_OF_DOMINION = 62507, + SPELL_TOUCH_OF_DOMINION_TRIGGERED = 62565, + SPELL_CHAIN_LIGHTNING = 62131, + SPELL_LIGHTNING_ORB_CHARGED = 62186, // wrong duration, triggered spell should handle lightning release + SPELL_LIGHTNING_CHARGE = 62279, + SPELL_LIGHTNING_RELEASE = 62466, + SPELL_LIGHTNING_PILLAR_2 = 62976, // caster high position, target low position + SPELL_LIGHTNING_PILLAR_1 = 63238, // caster high position, target low position + SPELL_UNBALANCING_STRIKE = 62130, + SPELL_BERSERK_PHASE_1 = 62560, + SPELL_BERSERK_PHASE_2 = 26662, + + // Credits + SPELL_CREDIT_SIFFED = 64980, + SPELL_CREDIT_KILL = 64985, + + // Lightning Field + SPELL_LIGHTNING_FIELD = 64972, + SPELL_LIGHTNING_BEAM_CHANNEL = 45537, + + // Sif + SPELL_BLIZZARD = 62577, + SPELL_BLINK = 62578, + SPELL_FROSTBOLT_VOLLEY = 62580, + SPELL_FROSTBOLT = 62583, + SPELL_FROSTNOVA = 62597, + SPELL_SIF_TRANSFORM = 64778, + + // Runic Colossus + SPELL_SMASH = 62339, + SPELL_RUNIC_BARRIER = 62338, + SPELL_RUNIC_CHARGE = 62613, + SPELL_RUNIC_SMASH = 62465, + SPELL_RUNIC_SMASH_RIGHT = 62057, + SPELL_RUNIC_SMASH_LEFT = 62058, + + // Ancient Rune Giant + SPELL_RUNIC_FORTIFICATION = 62942, + SPELL_RUNE_DETONATION = 62526, + SPELL_STOMP = 62411 +}; + +enum Phases +{ + PHASE_NULL, + PHASE_1, + PHASE_2 +}; + +enum Events +{ + // Thorim + EVENT_SAY_AGGRO_2 = 1, + EVENT_SAY_SIF_START, + EVENT_START_SIF_CHANNEL, + EVENT_STORMHAMMER, + EVENT_CHARGE_ORB, + EVENT_SUMMON_ADDS, + EVENT_BERSERK, + EVENT_JUMPDOWN, + EVENT_UNBALANCING_STRIKE, + EVENT_CHAIN_LIGHTNING, + EVENT_TRANSFER_ENERGY, + EVENT_RELEASE_ENERGY, + EVENT_ACTIVATE_LIGHNING_FIELD, + EVENT_CHECK_PLAYER, + EVENT_OUTRO_1, + EVENT_OUTRO_2, + EVENT_OUTRO_3, + + // Runic Colossus + EVENT_RUNIC_BARRIER, + EVENT_SMASH, + EVENT_RUNIC_CHARGE, + EVENT_RUNIC_SMASH, + + // Ancient Rune Giant + EVENT_RUNIC_FORTIFICATION, + EVENT_STOMP, + EVENT_RUNE_DETONATION, + + // Arena NPC + EVENT_PRIMARY_ABILITY, + EVENT_SECONDARY_ABILITY, + EVENT_THIRD_ABILITY, + EVENT_ABILITY_CHARGE, + + // Sif + EVENT_BLINK, + EVENT_FROST_NOVA, + EVENT_FROSTBOLT, + EVENT_FROSTBOLT_VOLLEY, + EVENT_BLIZZARD +}; + enum Yells { - SAY_AGGRO = 0, - SAY_SPECIAL_1 = 1, - SAY_SPECIAL_2 = 2, - SAY_SPECIAL_3 = 3, - SAY_JUMPDOWN = 4, - SAY_SLAY = 5, - SAY_BERSERK = 6, - SAY_WIPE = 7, - SAY_DEATH = 8, - SAY_END_NORMAL_1 = 9, - SAY_END_NORMAL_2 = 10, - SAY_END_NORMAL_3 = 11, - SAY_END_HARD_1 = 12, - SAY_END_HARD_2 = 13, - SAY_END_HARD_3 = 14 + // Thorim + SAY_AGGRO_1 = 0, + SAY_AGGRO_2 = 1, + //SAY_SPECIAL_1 = 2, + SAY_SPECIAL_2 = 3, + //SAY_SPECIAL_3 = 4, + SAY_JUMPDOWN = 5, + SAY_SLAY = 6, + SAY_BERSERK = 7, + SAY_WIPE = 8, + SAY_DEATH = 9, + SAY_END_NORMAL_1 = 10, + SAY_END_NORMAL_2 = 11, + SAY_END_NORMAL_3 = 12, + SAY_END_HARD_1 = 13, + SAY_END_HARD_2 = 14, + SAY_END_HARD_3 = 15, + + // Runic Colossus + EMOTE_RUNIC_BARRIER = 0, + + // Ancient Rune Giant + EMOTE_RUNIC_MIGHT = 0, + + // Sif + SAY_SIF_START = 0, + SAY_SIF_DESPAWN = 1, + SAY_SIF_EVENT = 2 +}; + +enum TrashTypes +{ + // Pre Phase Trash + BEHEMOTH, + MERCENARY_CAPTAIN, + MERCENARY_SOLDIER, + + // Arena Phase Trash + DARK_RUNE_CHAMPION, + DARK_RUNE_WARBRINGER, + DARK_RUNE_COMMONER, + DARK_RUNE_EVOKER, + + // Hall Way Trash + IRON_RING_GUARD, + IRON_HONOR_GUARD, + + // Shared + DARK_RUNE_ACOLYTE +}; + +enum PreAddSpells +{ + SPELL_ACID_BREATH = 62315, + SPELL_SWEEP = 62316, + + SPELL_DEVASTATE = 62317, + SPELL_HEROIC_SWIPE = 62444, + SPELL_SUNDER_ARMOR = 57807, + + SPELL_BARBED_SHOT = 62318, + SPELL_SHOOT = 16496, + + SPELL_RENEW = 62333, + SPELL_GREATER_HEAL = 62334, /// 61965 + SPELL_CIRCLE_OF_HEALING = 61964, + + SPELL_HOLY_SMITE = 62335, + + SPELL_CHARGE = 32323, + SPELL_MORTAL_STRIKE = 35054, + SPELL_WHIRLWIND = 33500, + + SPELL_LOW_BLOW = 62326, + SPELL_PUMMEL = 38313, + + SPELL_RUNIC_LIGHTNING = 62327, + SPELL_RUNIC_MENDING = 62328, + SPELL_RUNIC_SHIELD = 62321, + + SPELL_RUNIC_STRIKE = 62322, + SPELL_AURA_OF_CELERITY = 62320, + + SPELL_IMPALE = 62331, + SPELL_WHIRLING_TRIP = 64151, + + SPELL_CLEAVE = 42724, + SPELL_HAMSTRING = 48639, + SPELL_SHIELD_SMASH = 62332, +}; + +struct ThorimTrashInfo +{ + uint32 Type; + uint32 Entry; + uint32 PrimaryAbility; + uint32 SecondaryAbility; + uint32 ThirdAbility; +}; + +ThorimTrashInfo const StaticThorimTrashInfo[] = +{ + // Pre Phase + { BEHEMOTH, 32882, SPELL_ACID_BREATH, SPELL_SWEEP, 0 }, + { MERCENARY_CAPTAIN, 32908, SPELL_DEVASTATE, SPELL_HEROIC_SWIPE, SPELL_SUNDER_ARMOR }, // alliance? + { MERCENARY_SOLDIER, 32885, SPELL_BARBED_SHOT, SPELL_SHOOT, 0 }, // alliance? + { DARK_RUNE_ACOLYTE, 32886, SPELL_RENEW, SPELL_GREATER_HEAL, SPELL_CIRCLE_OF_HEALING }, + { MERCENARY_CAPTAIN, 32907, SPELL_DEVASTATE, SPELL_HEROIC_SWIPE, SPELL_SUNDER_ARMOR }, // horde? + { MERCENARY_SOLDIER, 32883, SPELL_BARBED_SHOT, SPELL_SHOOT, 0 }, // horde? + + // Arena Phase + { DARK_RUNE_CHAMPION, NPC_DARK_RUNE_CHAMPION, SPELL_MORTAL_STRIKE, SPELL_WHIRLWIND, 0 }, + { DARK_RUNE_WARBRINGER, NPC_DARK_RUNE_WARBRINGER, SPELL_RUNIC_STRIKE, 0, 0 }, + { DARK_RUNE_EVOKER, NPC_DARK_RUNE_EVOKER, SPELL_RUNIC_LIGHTNING, SPELL_RUNIC_SHIELD, SPELL_RUNIC_MENDING }, + { DARK_RUNE_COMMONER, NPC_DARK_RUNE_COMMONER, SPELL_LOW_BLOW, SPELL_PUMMEL, 0 }, + + // Hall Way + { IRON_RING_GUARD, NPC_IRON_RING_GUARD, SPELL_WHIRLING_TRIP, SPELL_IMPALE, 0 }, + { IRON_HONOR_GUARD, NPC_IRON_HONOR_GUARD, SPELL_CLEAVE, SPELL_SHIELD_SMASH, 0 }, + { DARK_RUNE_ACOLYTE, NPC_DARK_RUNE_ACOLYTE, SPELL_RENEW, SPELL_GREATER_HEAL, 0 }, +}; + +enum Actions +{ + ACTION_INCREASE_PREADDS_COUNT, + ACTION_ACTIVATE_RUNIC_SMASH, + ACTION_START_HARD_MODE, + ACTION_BERSERK, +}; + +#define MAX_HARD_MODE_TIME 180000 // 3 Minutes + +#define IN_ARENA(who) (who->GetPositionX() < 2181.19f && who->GetPositionY() > -299.12f) + +struct SummonLocation +{ + Position pos; + uint32 entry; +}; + +SummonLocation const PreAddLocations[] = +{ + { { 2149.68f, -263.477f, 419.679f, 3.120f }, 32882}, + { { 2131.31f, -271.640f, 419.840f, 2.188f }, 32908}, + { { 2127.24f, -259.182f, 419.974f, 5.917f }, 32885}, + { { 2123.32f, -254.770f, 419.840f, 6.170f }, 32885}, + { { 2120.10f, -258.990f, 419.840f, 6.250f }, 32885}, + { { 2129.09f, -277.142f, 419.756f, 1.222f }, 32886} +}; + +SummonLocation const ColossusAddLocations[] = +{ + { { 2218.38f, -297.50f, 412.18f, 1.030f }, NPC_IRON_RING_GUARD }, + { { 2235.07f, -297.98f, 412.18f, 1.613f }, NPC_IRON_RING_GUARD }, + { { 2235.26f, -338.34f, 412.18f, 1.589f }, NPC_IRON_RING_GUARD }, + { { 2217.69f, -337.39f, 412.18f, 1.241f }, NPC_IRON_RING_GUARD }, + { { 2227.58f, -308.30f, 412.18f, 1.591f }, NPC_DARK_RUNE_ACOLYTE }, + { { 2227.47f, -345.37f, 412.18f, 1.566f }, NPC_DARK_RUNE_ACOLYTE } +}; + +SummonLocation const GiantAddLocations[] = +{ + { { 2198.05f, -428.77f, 419.95f, 6.056f }, NPC_IRON_HONOR_GUARD }, + { { 2220.31f, -436.22f, 412.26f, 1.064f }, NPC_IRON_HONOR_GUARD }, + { { 2158.88f, -441.73f, 438.25f, 0.127f }, NPC_IRON_HONOR_GUARD }, + { { 2198.29f, -436.92f, 419.95f, 0.261f }, NPC_DARK_RUNE_ACOLYTE }, + { { 2230.93f, -434.27f, 412.26f, 1.931f }, NPC_DARK_RUNE_ACOLYTE } +}; + +Position const SifSpawnPosition = { 2148.301f, -297.8453f, 438.3308f, 2.687807f }; + +enum Achievements +{ + ACHIEVEMENT_DONT_STAND_IN_THE_LIGHTNING = 29712972, + ACHIEVEMENT_SIFFED = 29772978, + ACHIEVEMENT_LOSE_YOUR_ILLUSION = 31763183, +}; + +uint32 const LightningOrbPathSize = 8; +G3D::Vector3 const LightningOrbPath[LightningOrbPathSize] = +{ + { 2134.889893f, -298.632996f, 438.247467f }, + { 2134.570068f, -440.317993f, 438.247467f }, + { 2167.820312f, -440.330261f, 438.247589f }, + { 2213.394287f, -433.318298f, 412.665863f }, + { 2227.766113f, -433.275818f, 412.177032f }, + { 2227.551270f, -263.081085f, 412.176880f }, + { 2202.208008f, -262.939270f, 412.168976f }, + { 2182.310059f, -263.233093f, 414.739410f } +}; + +Position const ArenaCenter = { 2135.0f, -263.0f, 420.0f, 0.0f }; // used for trash jump calculation +Position const LightningFieldCenter = { 2135.0f, -312.5f, 438.0f, 0.0f }; // used for lightning field calculation + +// p2 start at 5357 + +class RunicSmashExplosionEvent : public BasicEvent +{ + public: + RunicSmashExplosionEvent(Creature* owner) : _owner(owner) { } + + bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/) override + { + _owner->CastSpell((Unit*)NULL, SPELL_RUNIC_SMASH); + return true; + } + + private: + Creature* _owner; +}; + +class TrashJumpEvent : public BasicEvent +{ + public: + TrashJumpEvent(Creature* owner) : _owner(owner), _stage(0) { } + + bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/) override + { + switch (_stage) + { + case 0: + if (Creature* stalker = _owner->FindNearestCreature(NPC_THORIM_INVISIBLE_STALKER, 30.0f)) + { + _owner->GetMotionMaster()->MoveJump(*stalker, SPEED_CHARGE, 15.0f); + _owner->SetHomePosition(*stalker); + } + ++_stage; + _owner->m_Events.AddEvent(this, _owner->m_Events.CalculateTime(2000)); + return false; + case 1: + _owner->SetReactState(REACT_AGGRESSIVE); + _owner->AI()->DoZoneInCombat(_owner, 200.0f); + return true; + default: + break; + } + + return true; + } + + private: + Creature* _owner; + uint8 _stage; +}; + +class LightningFieldEvent : public BasicEvent +{ + public: + LightningFieldEvent(Creature* owner) : _owner(owner) { } + + bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/) override + { + if (InstanceScript* instance = _owner->GetInstanceScript()) + { + if (instance->GetBossState(BOSS_THORIM) == IN_PROGRESS) + { + _owner->CastSpell((Unit*)NULL, SPELL_LIGHTNING_FIELD); + _owner->m_Events.AddEvent(this, _owner->m_Events.CalculateTime(1000)); + return false; + } + } + + _owner->InterruptNonMeleeSpells(false); + _owner->AI()->EnterEvadeMode(); + return true; + } + + private: + Creature* _owner; }; class boss_thorim : public CreatureScript @@ -47,17 +410,60 @@ class boss_thorim : public CreatureScript { boss_thorimAI(Creature* creature) : BossAI(creature, BOSS_THORIM) { + _encounterFinished = false; + Initialize(); + } + + void Initialize() + { + _killedCount = 0; + _waveType = 0; + _hardMode = true; + _orbSummoned = false; + _dontStandInTheLightning = true; } void Reset() override { + if (_encounterFinished) + return; + _Reset(); + Initialize(); + + me->SetReactState(REACT_PASSIVE); + SetCombatMovement(false); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + + events.SetPhase(PHASE_NULL); + + // Respawn Mini Bosses + for (uint8 i = DATA_RUNIC_COLOSSUS; i <= DATA_RUNE_GIANT; ++i) + if (Creature* miniBoss = ObjectAccessor::GetCreature(*me, instance->GetGuidData(i))) + miniBoss->Respawn(true); + + // Spawn Pre Phase Adds + for (SummonLocation const& s : PreAddLocations) + me->SummonCreature(s.entry, s.pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + + if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_THORIM_LEVER))) + go->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); } - void EnterEvadeMode(EvadeReason why) override + uint32 GetData(uint32 type) const override { - Talk(SAY_WIPE); - _EnterEvadeMode(why); + switch (type) + { + case ACHIEVEMENT_DONT_STAND_IN_THE_LIGHTNING: + return _dontStandInTheLightning ? 1 : 0; + case ACHIEVEMENT_LOSE_YOUR_ILLUSION: + case DATA_THORIM_HARDMODE: + return _hardMode ? 1 : 0; + default: + break; + } + + return 0; } void KilledUnit(Unit* who) override @@ -66,27 +472,406 @@ class boss_thorim : public CreatureScript Talk(SAY_SLAY); } - void JustDied(Unit* /*killer*/) override + void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override { - Talk(SAY_DEATH); + if (spellInfo->Id == SPELL_TOUCH_OF_DOMINION_TRIGGERED) + { + if (Creature* sif = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIF))) + { + sif->AI()->Talk(SAY_SIF_DESPAWN); + sif->DespawnOrUnsummon(6000); + _hardMode = false; + } + } + } + + void SpellHitTarget(Unit* who, SpellInfo const* spellInfo) override + { + if (who->GetTypeId() == TYPEID_PLAYER && spellInfo->Id == SPELL_LIGHTNING_RELEASE) + _dontStandInTheLightning = false; + } + + void FinishEncounter() + { + if (_encounterFinished) + return; + + _encounterFinished = true; + + DoCastAOE(SPELL_CREDIT_KILL, true); // before change faction + + me->SetReactState(REACT_PASSIVE); + me->InterruptNonMeleeSpells(true); + me->RemoveAllAttackers(); + me->AttackStop(); + me->SetFaction(35); + + if (Creature* sif = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIF))) + { + summons.Despawn(sif); + sif->DespawnOrUnsummon(10000); + } + _JustDied(); + + Talk(SAY_DEATH); + events.ScheduleEvent(EVENT_OUTRO_1, 4000); + events.ScheduleEvent(EVENT_OUTRO_2, _hardMode ? 8000 : 11000); + events.ScheduleEvent(EVENT_OUTRO_3, _hardMode ? 19000 : 21000); + + me->m_Events.AddEvent(new KeeperDespawnEvent(me), me->m_Events.CalculateTime(35000)); } void EnterCombat(Unit* /*who*/) override { - Talk(SAY_AGGRO); _EnterCombat(); + Talk(SAY_AGGRO_1); + + events.SetPhase(PHASE_1); + + events.ScheduleEvent(EVENT_SAY_AGGRO_2, 9000, 0, PHASE_1); + events.ScheduleEvent(EVENT_SAY_SIF_START, 16500, 0, PHASE_1); + events.ScheduleEvent(EVENT_START_SIF_CHANNEL, 25000, 0, PHASE_1); + + events.ScheduleEvent(EVENT_STORMHAMMER, 40000, 0, PHASE_1); + events.ScheduleEvent(EVENT_CHARGE_ORB, 30000, 0, PHASE_1); + events.ScheduleEvent(EVENT_SUMMON_ADDS, 15000, 0, PHASE_1); + events.ScheduleEvent(EVENT_BERSERK, 369000); + events.ScheduleEvent(EVENT_CHECK_PLAYER, 10000); + + DoCast(me, SPELL_SHEAT_OF_LIGHTNING); + + if (Creature* runic = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RUNIC_COLOSSUS))) + runic->AI()->DoAction(ACTION_ACTIVATE_RUNIC_SMASH); + + if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_THORIM_LEVER))) + go->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); + + // Summon Sif + me->SummonCreature(NPC_SIF, SifSpawnPosition); + } + + void JustSummoned(Creature* summon) override + { + switch (summon->GetEntry()) + { + case NPC_LIGHTNING_ORB: + { + summon->SetReactState(REACT_PASSIVE); + summon->CastSpell(summon, SPELL_LIGHTNING_DESTRUCTION, true); + + Position pos(LightningOrbPath[LightningOrbPathSize - 1].x, LightningOrbPath[LightningOrbPathSize - 1].y, LightningOrbPath[LightningOrbPathSize - 1].z); + summon->GetMotionMaster()->MovePoint(EVENT_CHARGE_PREPATH, pos, false); + + Movement::PointsArray path(LightningOrbPath, LightningOrbPath + LightningOrbPathSize); + + Movement::MoveSplineInit init(summon); + init.MovebyPath(path); + init.Launch(); + break; + } + case NPC_DARK_RUNE_CHAMPION: + case NPC_DARK_RUNE_WARBRINGER: + case NPC_DARK_RUNE_EVOKER: + case NPC_DARK_RUNE_COMMONER: + summon->SetReactState(REACT_PASSIVE); + summon->m_Events.AddEvent(new TrashJumpEvent(summon), summon->m_Events.CalculateTime(3000)); + break; + case NPC_SIF: + summon->SetReactState(REACT_PASSIVE); + break; + default: + break; + } + + BossAI::JustSummoned(summon); } - void UpdateAI(uint32 /*diff*/) override + void UpdateAI(uint32 diff) override { if (!UpdateVictim()) return; - //SPELLS @todo - // + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_SAY_AGGRO_2: + Talk(SAY_AGGRO_2); + break; + case EVENT_SAY_SIF_START: + if (Creature* sif = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIF))) + sif->AI()->Talk(SAY_SIF_START); + break; + case EVENT_START_SIF_CHANNEL: + if (Creature* sif = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIF))) + sif->CastSpell(me, SPELL_TOUCH_OF_DOMINION); + break; + case EVENT_STORMHAMMER: + DoCast(SPELL_STORMHAMMER); + events.ScheduleEvent(EVENT_STORMHAMMER, urand(15000, 20000), 0, PHASE_1); + break; + case EVENT_CHARGE_ORB: + DoCastAOE(SPELL_CHARGE_ORB); + events.ScheduleEvent(EVENT_CHARGE_ORB, urand(15000, 20000), 0, PHASE_1); + break; + case EVENT_SUMMON_ADDS: + SummonWave(); + events.ScheduleEvent(EVENT_SUMMON_ADDS, _orbSummoned ? 3000 : 10000, 0, PHASE_1); + break; + case EVENT_JUMPDOWN: + if (_hardMode) + if (Creature* sif = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIF))) + sif->AI()->DoAction(ACTION_START_HARD_MODE); + me->RemoveAurasDueToSpell(SPELL_SHEAT_OF_LIGHTNING); + me->SetReactState(REACT_AGGRESSIVE); + SetCombatMovement(true); + me->GetMotionMaster()->MoveJump(2134.79f, -263.03f, 419.84f, me->GetOrientation(), 30.0f, 20.0f); + events.ScheduleEvent(EVENT_UNBALANCING_STRIKE, 15000, 0, PHASE_2); + events.ScheduleEvent(EVENT_CHAIN_LIGHTNING, 20000, 0, PHASE_2); + events.ScheduleEvent(EVENT_TRANSFER_ENERGY, 20000, 0, PHASE_2); + break; + case EVENT_UNBALANCING_STRIKE: + DoCastVictim(SPELL_UNBALANCING_STRIKE); + events.ScheduleEvent(EVENT_UNBALANCING_STRIKE, urand(15000, 20000), 0, PHASE_2); + break; + case EVENT_CHAIN_LIGHTNING: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true)) + DoCast(target, SPELL_CHAIN_LIGHTNING); + events.ScheduleEvent(EVENT_CHAIN_LIGHTNING, urand(7000, 15000), 0, PHASE_2); + break; + case EVENT_TRANSFER_ENERGY: + { + std::list<Creature*> triggers; + me->GetCreatureListWithEntryInGrid(triggers, NPC_THUNDER_ORB, 200.0f); + triggers.remove_if([](Creature* trigger) + { + return trigger->GetPositionZ() < 425.0f; + }); + + if (!triggers.empty()) + { + Creature* pillar = Trinity::Containers::SelectRandomContainerElement(triggers); + _activePillarGUID = pillar->GetGUID(); + pillar->CastSpell(pillar, SPELL_LIGHTNING_ORB_CHARGED, true); + pillar->CastSpell((Unit*)nullptr, SPELL_LIGHTNING_PILLAR_2); + events.ScheduleEvent(EVENT_RELEASE_ENERGY, 8000, 0, PHASE_2); + } + + events.ScheduleEvent(EVENT_TRANSFER_ENERGY, 16000, 0, PHASE_2); + break; + } + case EVENT_RELEASE_ENERGY: + if (Creature* pillar = ObjectAccessor::GetCreature(*me, _activePillarGUID)) + DoCast(pillar, SPELL_LIGHTNING_RELEASE); + break; + case EVENT_BERSERK: + if (events.IsInPhase(PHASE_1)) + { + Talk(SAY_WIPE); + DoCastAOE(SPELL_BERSERK_PHASE_1, true); + DoCast(me, SPELL_SUMMON_LIGHTNING_ORB, true); + } + else + { + Talk(SAY_BERSERK); + DoCast(me, SPELL_BERSERK_PHASE_2, true); + } + break; + case EVENT_ACTIVATE_LIGHNING_FIELD: + { + std::list<Creature*> triggers; + me->GetCreatureListWithEntryInGrid(triggers, NPC_THORIM_EVENT_BUNNY, 100.0f); + triggers.remove_if([](Creature* bunny) + { + if (bunny->GetPositionZ() < 430.0f) + return true; + return LightningFieldCenter.GetExactDist2dSq(bunny) > 1225.0f; + }); + + uint64 timer = 1000; + for (Creature* bunny : triggers) + bunny->m_Events.AddEvent(new LightningFieldEvent(bunny), bunny->m_Events.CalculateTime(timer += 100)); + + triggers.remove_if([](Creature* bunny) + { + return LightningFieldCenter.GetExactDist2dSq(bunny) < 400.0f; + }); + + triggers.sort([](Creature* a, Creature* b) + { + return a->GetPositionX() < b->GetPositionX(); + }); + + for (std::list<Creature*>::const_iterator itr = triggers.begin(); itr != triggers.end();) + { + std::list<Creature*>::const_iterator prev = itr++; + if (itr != triggers.end()) + (*prev)->CastSpell(*itr, SPELL_LIGHTNING_BEAM_CHANNEL); + } + break; + } + case EVENT_CHECK_PLAYER: + if (!me->GetMap()->GetPlayersCountExceptGMs()) + EnterEvadeMode(); + events.ScheduleEvent(EVENT_CHECK_PLAYER, 10000); + break; + case EVENT_OUTRO_1: + Talk(_hardMode ? SAY_END_HARD_1 : SAY_END_NORMAL_1); + if (Creature* sif = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIF))) + DoCast(sif, SPELL_STORMHAMMER_SIF); + break; + case EVENT_OUTRO_2: + Talk(_hardMode ? SAY_END_HARD_2 : SAY_END_NORMAL_2); + break; + case EVENT_OUTRO_3: + Talk(_hardMode ? SAY_END_HARD_3 : SAY_END_NORMAL_3); + break; + default: + break; + } + } + DoMeleeAttackIfReady(); } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_BERSERK: + if (events.IsInPhase(PHASE_2)) + return; + + if (!_orbSummoned) + { + _orbSummoned = true; + events.RescheduleEvent(EVENT_BERSERK, 1000); + } + return; + case ACTION_INCREASE_PREADDS_COUNT: + if (++_killedCount >= 6) + { + // Event starts + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + DoZoneInCombat(me, 250.0f); + } + break; + default: + break; + } + } + + void GetTrashSpawnTriggers(std::list<Creature*>& triggerList, uint32 count = 1) + { + me->GetCreatureListWithEntryInGrid(triggerList, NPC_THORIM_EVENT_BUNNY, 100.0f); + triggerList.remove_if([](Creature* bunny) + { + if (bunny->GetPositionZ() < 430.0f) + return true; + return ArenaCenter.GetExactDist2dSq(bunny) < 3025.0f; + }); + + if (triggerList.empty()) + return; + + if (count == 1) + { + Creature* bunny = Trinity::Containers::SelectRandomContainerElement(triggerList); + triggerList.clear(); + triggerList.push_back(bunny); + } + else + Trinity::Containers::RandomResizeList(triggerList, count); + } + + void SummonWave() + { + switch (_waveType) + { + case 0: + { + // Dark Rune Commoner + std::list<Creature*> triggers; + GetTrashSpawnTriggers(triggers, urand(5, 6)); + + for (Creature* bunny : triggers) + me->SummonCreature(StaticThorimTrashInfo[6 + 3].Entry, *bunny, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + + ++_waveType; + break; + } + case 1: + if (urand(0, 1)) + { + // Dark Rune Champion or Dark Rune Evoker + std::list<Creature*> triggers; + GetTrashSpawnTriggers(triggers, urand(2, 4)); + + for (Creature* bunny : triggers) + me->SummonCreature(StaticThorimTrashInfo[6 + RAND(0, 2)].Entry, *bunny, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + } + else + { + // Dark Rune Warbringer + std::list<Creature*> triggers; + GetTrashSpawnTriggers(triggers); + + for (Creature* bunny : triggers) + me->SummonCreature(StaticThorimTrashInfo[6 + 1].Entry, *bunny, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + } + --_waveType; + break; + } + } + + bool CanStartPhase2(Unit* actor) const + { + if (actor->GetTypeId() != TYPEID_PLAYER || !me->IsWithinDistInMap(actor, 10.0f)) + return false; + + Creature* runicColossus = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RUNIC_COLOSSUS)); + Creature* runeGiant = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RUNE_GIANT)); + return runicColossus && !runicColossus->IsAlive() && runeGiant && !runeGiant->IsAlive(); + } + + void DamageTaken(Unit* attacker, uint32& damage) override + { + if (events.IsInPhase(PHASE_1) && CanStartPhase2(attacker)) + { + Talk(SAY_JUMPDOWN); + events.SetPhase(PHASE_2); + events.ScheduleEvent(EVENT_JUMPDOWN, 8000); + events.ScheduleEvent(EVENT_ACTIVATE_LIGHNING_FIELD, 15000); + events.RescheduleEvent(EVENT_BERSERK, 300000, 0, PHASE_2); + + if (Creature* sif = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIF))) + sif->InterruptNonMeleeSpells(false); + + // Hard Mode + if (_hardMode && events.GetTimer() <= MAX_HARD_MODE_TIME) + DoCastAOE(SPELL_CREDIT_SIFFED, true); + } + else if (me->HealthBelowPctDamaged(1, damage)) + { + damage = 0; + FinishEncounter(); + } + } + + private: + ObjectGuid _activePillarGUID; + uint8 _killedCount; + uint8 _waveType; + bool _hardMode; + bool _encounterFinished; + bool _orbSummoned; + bool _dontStandInTheLightning; }; CreatureAI* GetAI(Creature* creature) const override @@ -95,7 +880,1056 @@ class boss_thorim : public CreatureScript } }; +struct npc_thorim_trashAI : public ScriptedAI +{ + npc_thorim_trashAI(Creature* creature) : ScriptedAI(creature) + { + _instance = creature->GetInstanceScript(); + for (uint8 i = 0; i < 13; ++i) + if (me->GetEntry() == StaticThorimTrashInfo[i].Entry) + _info = &StaticThorimTrashInfo[i]; + + ASSERT(_info); + } + + struct AIHelper + { + /// returns heal amount of the given spell including hots + static uint32 GetTotalHeal(SpellInfo const* spellInfo, Unit const* caster) + { + uint32 heal = 0; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (spellInfo->Effects[i].IsEffect(SPELL_EFFECT_HEAL)) + heal += spellInfo->Effects[i].CalcValue(caster); + + if (spellInfo->Effects[i].IsEffect(SPELL_EFFECT_APPLY_AURA) && spellInfo->Effects[i].IsAura(SPELL_AURA_PERIODIC_HEAL)) + heal += spellInfo->GetMaxTicks() * spellInfo->Effects[i].CalcValue(caster); + } + return heal; + } + + /// returns heal amount of the given spell including hots + static uint32 GetTotalHeal(uint32 spellId, Unit const* caster) + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + return GetTotalHeal(spellInfo, caster); + return 0; + } + + /// returns remaining heal amount on given target + static uint32 GetRemainingHealOn(Unit* target) + { + uint32 heal = 0; + Unit::AuraApplicationMap const& auras = target->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura const* aura = itr->second->GetBase(); + + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (AuraEffect const* aurEff = aura->GetEffect(i)) + { + if (aurEff->GetAuraType() == SPELL_AURA_PERIODIC_HEAL) + heal += aurEff->GetAmount() * (aurEff->GetTotalTicks() - aurEff->GetTickNumber()); + } + } + } + return heal; + } + + class MostHPMissingInRange + { + public: + MostHPMissingInRange(Unit const* referer, float range, uint32 hp, uint32 exclAura = 0, bool exclSelf = false) + : _referer(referer), _range(range), _hp(hp), _exclAura(exclAura), _exclSelf(exclSelf) { } + + bool operator()(Unit* u) + { + if (_exclSelf && u == _referer) + return false; + + if (_exclAura && u->HasAura(sSpellMgr->GetSpellIdForDifficulty(_exclAura, _referer))) + return false; + + if ((u->GetHealth() + GetRemainingHealOn(u) + _hp) > u->GetMaxHealth()) + return false; + + uint32 missingHP = u->GetMaxHealth() - u->GetHealth(); + if (u->IsAlive() && _referer->IsFriendlyTo(u) && _referer->IsWithinDistInMap(u, _range) && missingHP > _hp) + { + _hp = missingHP; + return true; + } + + return false; + } + + private: + Unit const* _referer; + float _range; + uint32 _hp; + uint32 _exclAura; + bool _exclSelf; + }; + + static Unit* GetUnitWithMostMissingHp(SpellInfo const* spellInfo, Unit* caster) + { + float range = spellInfo->GetMaxRange(false); + uint32 heal = GetTotalHeal(spellInfo, caster); + + Unit* target = nullptr; + MostHPMissingInRange checker(caster, range, heal); + Trinity::UnitLastSearcher<MostHPMissingInRange> searcher(caster, target, checker); + caster->VisitNearbyObject(range, searcher); + + return target; + } + + static Unit* GetHealTarget(uint32 spellId, Unit* caster) + { + Unit* healTarget = nullptr; + + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + { + if (!spellInfo->HasAttribute(SPELL_ATTR1_CANT_TARGET_SELF) && !roll_chance_f(caster->GetHealthPct()) && !((caster->GetHealth() + GetRemainingHealOn(caster) + GetTotalHeal(spellInfo, caster)) > caster->GetMaxHealth())) + healTarget = caster; + else + healTarget = GetUnitWithMostMissingHp(spellInfo, caster); + } + + return healTarget; + } + }; + + bool UseAbility(uint32 spellId) + { + Unit* target = nullptr; + if (AIHelper::GetTotalHeal(spellId, me)) + target = AIHelper::GetHealTarget(spellId, me); + else + target = me->GetVictim(); + + if (target) + { + if (_info->Type == MERCENARY_SOLDIER) + { + bool allowMove = true; + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + { + if (me->IsInRange(target, spellInfo->GetMinRange(false), spellInfo->GetMaxRange(false)) + && me->IsWithinLOSInMap(target)) + allowMove = false; + } + SetCombatMovement(allowMove); + } + + DoCast(target, spellId); + return true; + } + + return false; + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = _events.ExecuteEvent()) + { + ExecuteEvent(eventId); + } + + if (_info->Type == DARK_RUNE_ACOLYTE) + DoSpellAttackIfReady(SPELL_HOLY_SMITE); + else + DoMeleeAttackIfReady(); + } + + virtual void ExecuteEvent(uint32 eventId) = 0; + +protected: + InstanceScript* _instance; + EventMap _events; + + ThorimTrashInfo const* _info = nullptr; +}; + +class npc_thorim_pre_phase : public CreatureScript +{ + public: + npc_thorim_pre_phase() : CreatureScript("npc_thorim_pre_phase") { } + + struct npc_thorim_pre_phaseAI : public npc_thorim_trashAI + { + npc_thorim_pre_phaseAI(Creature* creature) : npc_thorim_trashAI(creature) + { + me->setActive(true); // prevent grid unload + } + + void Reset() + { + _events.Reset(); + if (_info->PrimaryAbility) + _events.ScheduleEvent(EVENT_PRIMARY_ABILITY, urand(3000, 6000)); + if (_info->SecondaryAbility) + _events.ScheduleEvent(EVENT_SECONDARY_ABILITY, _info->SecondaryAbility == SPELL_SHOOT ? 2000 : urand(12000, 15000)); + if (_info->ThirdAbility) + _events.ScheduleEvent(EVENT_THIRD_ABILITY, urand(6000, 8000)); + if (_info->Type == MERCENARY_SOLDIER) + SetCombatMovement(false); + } + + void JustDied(Unit* /*victim*/) + { + if (Creature* thorim = _instance->GetCreature(BOSS_THORIM)) + thorim->AI()->DoAction(ACTION_INCREASE_PREADDS_COUNT); + } + + void DamageTaken(Unit* attacker, uint32& damage) override + { + if (!attacker->GetAffectingPlayer()) + damage = 0; + } + + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_PRIMARY_ABILITY: + if (UseAbility(_info->PrimaryAbility)) + _events.ScheduleEvent(eventId, urand(15000, 20000)); + else + _events.ScheduleEvent(eventId, 1000); + break; + case EVENT_SECONDARY_ABILITY: + if (UseAbility(_info->SecondaryAbility)) + _events.ScheduleEvent(eventId, _info->SecondaryAbility == SPELL_SHOOT ? 2000 : urand(4000, 8000)); + else + _events.ScheduleEvent(eventId, 1000); + break; + case EVENT_THIRD_ABILITY: + if (UseAbility(_info->ThirdAbility)) + _events.ScheduleEvent(eventId, urand(6000, 8000)); + else + _events.ScheduleEvent(eventId, 1000); + break; + default: + break; + } + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetUlduarAI<npc_thorim_pre_phaseAI>(creature); + } +}; + +class npc_thorim_arena_phase : public CreatureScript +{ + public: + npc_thorim_arena_phase() : CreatureScript("npc_thorim_arena_phase") { } + + struct npc_thorim_arena_phaseAI : public npc_thorim_trashAI + { + npc_thorim_arena_phaseAI(Creature* creature) : npc_thorim_trashAI(creature) + { + switch (_info->Type) + { + case DARK_RUNE_CHAMPION: + case DARK_RUNE_WARBRINGER: + case DARK_RUNE_COMMONER: + case DARK_RUNE_EVOKER: + _isInArena = true; + break; + default: + _isInArena = false; + break; + } + //_isInArena = IN_ARENA(me); + } + + bool CanAIAttack(Unit const* who) const override + { + return _isInArena == IN_ARENA(who); + } + + void Reset() override + { + _events.Reset(); + if (_info->PrimaryAbility) + _events.ScheduleEvent(EVENT_PRIMARY_ABILITY, urand(3000, 6000)); + if (_info->SecondaryAbility) + _events.ScheduleEvent(EVENT_SECONDARY_ABILITY, urand(7000, 9000)); + if (_info->ThirdAbility) + _events.ScheduleEvent(EVENT_THIRD_ABILITY, urand(6000, 8000)); + if (_info->Type == DARK_RUNE_CHAMPION) + _events.ScheduleEvent(EVENT_ABILITY_CHARGE, 8000); + } + + void EnterCombat(Unit* /*who*/) + { + if (_info->Type == DARK_RUNE_WARBRINGER) + DoCast(me, SPELL_AURA_OF_CELERITY); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + // this should only happen if theres no alive player in the arena -> summon orb + // might be called by mind control release or controllers death? + if (Creature* thorim = _instance->GetCreature(BOSS_THORIM)) + thorim->AI()->DoAction(ACTION_BERSERK); + ScriptedAI::EnterEvadeMode(); + } + + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_PRIMARY_ABILITY: + if (UseAbility(_info->PrimaryAbility)) + _events.ScheduleEvent(eventId, urand(3000, 6000)); + else + _events.ScheduleEvent(eventId, 1000); + break; + case EVENT_SECONDARY_ABILITY: + if (UseAbility(_info->SecondaryAbility)) + _events.ScheduleEvent(eventId, urand(12000, 16000)); + else + _events.ScheduleEvent(eventId, 1000); + break; + case EVENT_THIRD_ABILITY: + if (UseAbility(_info->ThirdAbility)) + _events.ScheduleEvent(eventId, urand(6000, 8000)); + else + _events.ScheduleEvent(eventId, 1000); + break; + case EVENT_ABILITY_CHARGE: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, [this](Unit* unit){ return unit->GetTypeId() == TYPEID_PLAYER && unit->IsInRange(me, 8.0f, 25.0f); })) + DoCast(target, SPELL_CHARGE); + _events.ScheduleEvent(eventId, 12000); + break; + default: + break; + } + } + + private: + bool _isInArena; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetUlduarAI<npc_thorim_arena_phaseAI>(creature); + } +}; + +struct npc_thorim_minibossAI : public ScriptedAI +{ + npc_thorim_minibossAI(Creature* creature) : ScriptedAI(creature), _summons(me) + { + _instance = creature->GetInstanceScript(); + } + + void JustSummoned(Creature* summon) override + { + _summons.Summon(summon); + ScriptedAI::JustSummoned(summon); + } + + void SummonedCreatureDespawn(Creature* summon) override + { + ScriptedAI::SummonedCreatureDespawn(summon); + _summons.Despawn(summon); + } + +protected: + InstanceScript* _instance; + EventMap _events; + SummonList _summons; +}; + +class npc_runic_colossus : public CreatureScript +{ + public: + npc_runic_colossus() : CreatureScript("npc_runic_colossus") { } + + struct npc_runic_colossusAI : public npc_thorim_minibossAI + { + npc_runic_colossusAI(Creature* creature) : npc_thorim_minibossAI(creature) + { + } + + void Reset() override + { + _events.Reset(); + + // Runed Door closed + _instance->HandleGameObject(_instance->GetGuidData(DATA_RUNIC_DOOR), false); + + // Spawn trashes + _summons.DespawnAll(); + for (SummonLocation const& s : ColossusAddLocations) + me->SummonCreature(s.entry, s.pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + // don't enter combat + } + + void JustDied(Unit* /*victim*/) override + { + // Runed Door opened + _instance->HandleGameObject(_instance->GetGuidData(DATA_RUNIC_DOOR), true); + + if (Creature* thorim = _instance->GetCreature(BOSS_THORIM)) + thorim->AI()->Talk(SAY_SPECIAL_2); + } + + void DoAction(int32 action) override + { + if (action == ACTION_ACTIVATE_RUNIC_SMASH) + _events.ScheduleEvent(EVENT_RUNIC_SMASH, 12000); + } + + void EnterCombat(Unit* /*who*/) override + { + DoZoneInCombat(); + _events.Reset(); + _events.ScheduleEvent(EVENT_RUNIC_BARRIER, urand(12000, 15000)); + _events.ScheduleEvent(EVENT_SMASH, urand(15000, 18000)); + _events.ScheduleEvent(EVENT_RUNIC_CHARGE, urand(20000, 24000)); + me->InterruptNonMeleeSpells(true); + } + + void UpdateAI(uint32 diff) override + { + _events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_RUNIC_BARRIER: + Talk(EMOTE_RUNIC_BARRIER); + DoCast(me, SPELL_RUNIC_BARRIER); + _events.ScheduleEvent(eventId, urand(35000, 45000)); + break; + case EVENT_SMASH: + DoCast(me, SPELL_SMASH); + _events.ScheduleEvent(eventId, urand(15000, 18000)); + break; + case EVENT_RUNIC_CHARGE: + { + Unit* referer = me; + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, [referer](Unit* unit){ return unit->GetTypeId() == TYPEID_PLAYER && unit->IsInRange(referer, 8.0f, 40.0f); })) + DoCast(target, SPELL_RUNIC_CHARGE); + _events.ScheduleEvent(eventId, 20000); + break; + } + case EVENT_RUNIC_SMASH: + DoCast(me, RAND(SPELL_RUNIC_SMASH_LEFT, SPELL_RUNIC_SMASH_RIGHT)); + _events.ScheduleEvent(eventId, 6000); + break; + default: + break; + } + } + + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetUlduarAI<npc_runic_colossusAI>(creature); + } +}; + +class npc_ancient_rune_giant : public CreatureScript +{ + public: + npc_ancient_rune_giant() : CreatureScript("npc_ancient_rune_giant") { } + + struct npc_ancient_rune_giantAI : public npc_thorim_minibossAI + { + npc_ancient_rune_giantAI(Creature* creature) : npc_thorim_minibossAI(creature) { } + + void Reset() override + { + _events.Reset(); + + // Stone Door closed + _instance->HandleGameObject(_instance->GetGuidData(DATA_STONE_DOOR), false); + + // Spawn trashes + _summons.DespawnAll(); + for (SummonLocation const& s : GiantAddLocations) + me->SummonCreature(s.entry, s.pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + } + + void EnterCombat(Unit* /*who*/) override + { + DoZoneInCombat(); + _events.Reset(); + _events.ScheduleEvent(EVENT_RUNIC_FORTIFICATION, 1); + _events.ScheduleEvent(EVENT_STOMP, urand(10000, 12000)); + _events.ScheduleEvent(EVENT_RUNE_DETONATION, 25000); + } + + void JustDied(Unit* /*victim*/) override + { + // Stone Door opened + _instance->HandleGameObject(_instance->GetGuidData(DATA_STONE_DOOR), true); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_RUNIC_FORTIFICATION: + Talk(EMOTE_RUNIC_MIGHT); + DoCastAOE(SPELL_RUNIC_FORTIFICATION); + break; + case EVENT_STOMP: + DoCastAOE(SPELL_STOMP); + _events.ScheduleEvent(eventId, urand(10000, 12000)); + break; + case EVENT_RUNE_DETONATION: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 60.0f, true)) + DoCast(target, SPELL_RUNE_DETONATION); + _events.ScheduleEvent(eventId, urand(10000, 12000)); + break; + default: + break; + } + } + + DoMeleeAttackIfReady(); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetUlduarAI<npc_ancient_rune_giantAI>(creature); + } +}; + +class npc_sif : public CreatureScript +{ + public: + npc_sif() : CreatureScript("npc_sif") { } + + struct npc_sifAI : public ScriptedAI + { + npc_sifAI(Creature* creature) : ScriptedAI(creature), _summons(me) + { + SetCombatMovement(false); + } + + void Reset() override + { + _events.Reset(); + _summons.DespawnAll(); + } + + void JustSummoned(Creature* summon) override + { + _summons.Summon(summon); + if (summon->GetEntry() == NPC_THORIM_EVENT_BUNNY) + summon->GetMotionMaster()->MoveRandom(60.0f); + } + + void SummonedCreatureDespawn(Creature* summon) override + { + _summons.Despawn(summon); + } + + void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override + { + if (spellInfo->Id == SPELL_STORMHAMMER_SIF) + { + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + } + } + + void DoAction(int32 action) override + { + if (action == ACTION_START_HARD_MODE) + { + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(me, 250.0f); + Talk(SAY_SIF_EVENT); + _events.Reset(); + _events.ScheduleEvent(EVENT_FROSTBOLT, 2000); + _events.ScheduleEvent(EVENT_FROSTBOLT_VOLLEY, 15000); + _events.ScheduleEvent(EVENT_BLINK, urand(20000, 25000)); + _events.ScheduleEvent(EVENT_BLIZZARD, 30000); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_BLINK: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true)) + DoCast(target, SPELL_BLINK); + _events.ScheduleEvent(EVENT_FROST_NOVA, 0); + _events.ScheduleEvent(eventId, urand(20000, 25000)); + return; + case EVENT_FROST_NOVA: + DoCastAOE(SPELL_FROSTNOVA); + return; + case EVENT_FROSTBOLT: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true)) + DoCast(target, SPELL_FROSTBOLT); + _events.ScheduleEvent(eventId, 2000); + return; + case EVENT_FROSTBOLT_VOLLEY: + DoCastAOE(SPELL_FROSTBOLT_VOLLEY); + _events.ScheduleEvent(eventId, urand(15000, 20000)); + return; + case EVENT_BLIZZARD: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 200.0f, true)) + me->SummonCreature(NPC_THORIM_EVENT_BUNNY, *target, TEMPSUMMON_TIMED_DESPAWN, 25000); + DoCastAOE(SPELL_BLIZZARD); + _events.ScheduleEvent(eventId, urand(35000, 45000)); + return; + default: + break; + } + } + + // no melee attack + } + + private: + EventMap _events; + SummonList _summons; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetUlduarAI<npc_sifAI>(creature); + } +}; + +class HeightPositionCheck +{ + public: + HeightPositionCheck(bool ret) : _ret(ret) { } + + bool operator()(WorldObject* obj) const + { + return obj->GetPositionZ() > 425.0f == _ret; + } + + private: + bool _ret; +}; + +// 62577 - Blizzard +// 62603 - Blizzard +class spell_thorim_blizzard : public SpellScriptLoader +{ + public: + spell_thorim_blizzard() : SpellScriptLoader("spell_thorim_blizzard") { } + + class spell_thorim_blizzard_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thorim_blizzard_SpellScript); + + void FilterTargets(std::list<WorldObject*>& targets) + { + targets.remove_if(HeightPositionCheck(true)); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thorim_blizzard_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thorim_blizzard_SpellScript(); + } +}; + +// 62576 - Blizzard +// 62602 - Blizzard +class spell_thorim_blizzard_effect : public SpellScriptLoader +{ + public: + spell_thorim_blizzard_effect() : SpellScriptLoader("spell_thorim_blizzard_effect") { } + + class spell_thorim_blizzard_effect_AuraScript : public AuraScript + { + PrepareAuraScript(spell_thorim_blizzard_effect_AuraScript); + + bool CheckAreaTarget(Unit* target) + { + /// @todo: fix this for all dynobj auras + if (target != GetOwner()) + { + // check if not stacking aura already on target + // this one prevents overriding auras periodically by 2 near area aura owners + Unit::AuraApplicationMap const& auraMap = target->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator iter = auraMap.begin(); iter != auraMap.end(); ++iter) + { + Aura const* aura = iter->second->GetBase(); + if (GetId() == aura->GetId() && GetOwner() != aura->GetOwner() /*!GetAura()->CanStackWith(aura)*/) + return false; + } + } + return true; + } + + void Register() override + { + DoCheckAreaTarget += AuraCheckAreaTargetFn(spell_thorim_blizzard_effect_AuraScript::CheckAreaTarget); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_thorim_blizzard_effect_AuraScript(); + } +}; + +// 62580, 62604 - Frostbolt Volley +class spell_thorim_frostbolt_volley : public SpellScriptLoader +{ + public: + spell_thorim_frostbolt_volley() : SpellScriptLoader("spell_thorim_frostbolt_volley") { } + + class spell_thorim_frostbolt_volley_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thorim_frostbolt_volley_SpellScript); + + void FilterTargets(std::list<WorldObject*>& unitList) + { + unitList.remove_if(PlayerOrPetCheck()); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thorim_frostbolt_volley_SpellScript::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thorim_frostbolt_volley_SpellScript(); + } +}; + +// 62016 - Charge Orb +class spell_thorim_charge_orb : public SpellScriptLoader +{ + public: + spell_thorim_charge_orb() : SpellScriptLoader("spell_thorim_charge_orb") { } + + class spell_thorim_charge_orb_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thorim_charge_orb_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_LIGHTNING_PILLAR_1)) + return false; + return true; + } + + void FilterTargets(std::list<WorldObject*>& targets) + { + targets.remove_if([](WorldObject* target) + { + return target->GetPositionZ() < 425.0f; + }); + + if (targets.empty()) + return; + + WorldObject* target = Trinity::Containers::SelectRandomContainerElement(targets); + targets.clear(); + targets.push_back(target); + } + + void HandleScript() + { + if (Unit* target = GetHitUnit()) + target->CastSpell((Unit*)nullptr, SPELL_LIGHTNING_PILLAR_1, true); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thorim_charge_orb_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + AfterHit += SpellHitFn(spell_thorim_charge_orb_SpellScript::HandleScript); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thorim_charge_orb_SpellScript(); + } +}; + +// 62466 - Lightning Charge +class spell_thorim_lightning_charge : public SpellScriptLoader +{ + public: + spell_thorim_lightning_charge() : SpellScriptLoader("spell_thorim_lightning_charge") { } + + class spell_thorim_lightning_charge_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thorim_lightning_charge_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_LIGHTNING_CHARGE)) + return false; + return true; + } + + void HandleFocus() + { + /// @workaround: focus target is not working because spell is triggered and instant + if (Creature* creature = GetCaster()->ToCreature()) + creature->FocusTarget(GetSpell(), GetExplTargetWorldObject()); + } + + void HandleCharge() + { + GetCaster()->CastSpell(GetCaster(), SPELL_LIGHTNING_CHARGE, true); + } + + void Register() override + { + BeforeCast += SpellCastFn(spell_thorim_lightning_charge_SpellScript::HandleFocus); + AfterCast += SpellCastFn(spell_thorim_lightning_charge_SpellScript::HandleCharge); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thorim_lightning_charge_SpellScript(); + } +}; + +// 62042 - Stormhammer +class spell_thorim_stormhammer : public SpellScriptLoader +{ + public: + spell_thorim_stormhammer() : SpellScriptLoader("spell_thorim_stormhammer") { } + + class spell_thorim_stormhammer_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thorim_stormhammer_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_STORMHAMMER_BOOMERANG) + || !sSpellMgr->GetSpellInfo(SPELL_DEAFENING_THUNDER)) + return false; + return true; + } + + void FilterTargets(std::list<WorldObject*>& targets) + { + targets.remove_if([](WorldObject* target) + { + return !IN_ARENA(target); + }); + + if (targets.empty()) + { + FinishCast(SPELL_FAILED_NO_VALID_TARGETS); + return; + } + + WorldObject* target = Trinity::Containers::SelectRandomContainerElement(targets); + targets.clear(); + targets.push_back(target); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + if (Unit* target = GetHitUnit()) + { + target->CastSpell(target, SPELL_DEAFENING_THUNDER, true); + target->CastSpell(GetCaster(), SPELL_STORMHAMMER_BOOMERANG, true); + } + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thorim_stormhammer_SpellScript::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY); + OnEffectHitTarget += SpellEffectFn(spell_thorim_stormhammer_SpellScript::HandleScript, EFFECT_2, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thorim_stormhammer_SpellScript(); + } +}; + +// 64767 - Stormhammer +class spell_thorim_stormhammer_sif : public SpellScriptLoader +{ + public: + spell_thorim_stormhammer_sif() : SpellScriptLoader("spell_thorim_stormhammer_sif") { } + + class spell_thorim_stormhammer_sif_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thorim_stormhammer_sif_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_STORMHAMMER_BOOMERANG) + || !sSpellMgr->GetSpellInfo(SPELL_SIF_TRANSFORM)) + return false; + return true; + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + if (Unit* target = GetHitUnit()) + { + target->CastSpell(GetCaster(), SPELL_STORMHAMMER_BOOMERANG, true); + target->CastSpell(target, SPELL_SIF_TRANSFORM, true); + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_thorim_stormhammer_sif_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thorim_stormhammer_sif_SpellScript(); + } +}; + +// 62057, 62058 - Runic Smash +class spell_thorim_runic_smash : public SpellScriptLoader +{ + public: + spell_thorim_runic_smash() : SpellScriptLoader("spell_thorim_runic_smash") { } + + class spell_thorim_runic_smash_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thorim_runic_smash_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_RUNIC_SMASH)) + return false; + return true; + } + + void HandleScript(SpellEffIndex effIndex) + { + PreventHitDefaultEffect(effIndex); + + std::list<Creature*> triggers; + GetCaster()->GetCreatureListWithEntryInGrid(triggers, GetSpellInfo()->Id == SPELL_RUNIC_SMASH_LEFT ? NPC_GOLEM_LEFT_HAND_BUNNY : NPC_GOLEM_RIGHT_HAND_BUNNY, 150.0f); + for (Creature* bunny : triggers) + { + float dist = GetCaster()->GetExactDist(bunny); + bunny->m_Events.AddEvent(new RunicSmashExplosionEvent(bunny), bunny->m_Events.CalculateTime(uint64(dist * 30))); + }; + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_thorim_runic_smash_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_TRIGGER_SPELL); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thorim_runic_smash_SpellScript(); + } +}; + +class achievement_dont_stand_in_the_lightning : public AchievementCriteriaScript +{ + public: + achievement_dont_stand_in_the_lightning() : AchievementCriteriaScript("achievement_dont_stand_in_the_lightning") { } + + bool OnCheck(Player* /*source*/, Unit* target) override + { + return target && target->GetAI()->GetData(ACHIEVEMENT_DONT_STAND_IN_THE_LIGHTNING); + } +}; + +class achievement_lose_your_illusion : public AchievementCriteriaScript +{ + public: + achievement_lose_your_illusion() : AchievementCriteriaScript("achievement_lose_your_illusion") { } + + bool OnCheck(Player* /*source*/, Unit* target) override + { + return target && target->GetAI()->GetData(ACHIEVEMENT_LOSE_YOUR_ILLUSION); + } +}; + void AddSC_boss_thorim() { new boss_thorim(); + new npc_thorim_pre_phase(); + new npc_thorim_arena_phase(); + new npc_runic_colossus(); + new npc_ancient_rune_giant(); + new npc_sif(); + new spell_thorim_blizzard(); + new spell_thorim_blizzard_effect(); + new spell_thorim_frostbolt_volley(); + new spell_thorim_charge_orb(); + new spell_thorim_lightning_charge(); + new spell_thorim_stormhammer(); + new spell_thorim_stormhammer_sif(); + new spell_thorim_runic_smash(); + new achievement_dont_stand_in_the_lightning(); + new achievement_lose_your_illusion(); } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp index c1695b2466c..2bd1c445ab7 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp @@ -53,6 +53,7 @@ static DoorData const doorData[] = { GO_MIMIRON_DOOR_1, BOSS_MIMIRON, DOOR_TYPE_ROOM }, { GO_MIMIRON_DOOR_2, BOSS_MIMIRON, DOOR_TYPE_ROOM }, { GO_MIMIRON_DOOR_3, BOSS_MIMIRON, DOOR_TYPE_ROOM }, + { GO_THORIM_ENCOUNTER_DOOR, BOSS_THORIM, DOOR_TYPE_ROOM }, { GO_VEZAX_DOOR, BOSS_VEZAX, DOOR_TYPE_PASSAGE }, { GO_YOGG_SARON_DOOR, BOSS_YOGG_SARON, DOOR_TYPE_ROOM }, { GO_DOODAD_UL_SIGILDOOR_03, BOSS_ALGALON, DOOR_TYPE_ROOM }, @@ -89,6 +90,9 @@ ObjectData const creatureData[] = { NPC_EXPEDITION_COMMANDER, DATA_EXPEDITION_COMMANDER }, { NPC_RAZORSCALE_CONTROLLER, DATA_RAZORSCALE_CONTROL }, + { NPC_SIF, DATA_SIF }, + { NPC_RUNIC_COLOSSUS, DATA_RUNIC_COLOSSUS }, + { NPC_RUNE_GIANT, DATA_RUNE_GIANT }, { NPC_COMPUTER, DATA_COMPUTER }, { NPC_WORLD_TRIGGER_MIMIRON, DATA_MIMIRON_WORLD_TRIGGER }, { NPC_VOICE_OF_YOGG_SARON, DATA_VOICE_OF_YOGG_SARON }, @@ -112,6 +116,9 @@ ObjectData const objectData[] = { GO_RAZOR_HARPOON_2, GO_RAZOR_HARPOON_2 }, { GO_RAZOR_HARPOON_3, GO_RAZOR_HARPOON_3 }, { GO_RAZOR_HARPOON_4, GO_RAZOR_HARPOON_4 }, + { GO_THORIM_LEVER, DATA_THORIM_LEVER }, + { GO_THORIM_STONE_DOOR, DATA_STONE_DOOR }, + { GO_THORIM_RUNIC_DOOR, DATA_RUNIC_DOOR }, { 0, 0 } }; @@ -167,7 +174,8 @@ class instance_ulduar : public InstanceMapScript ObjectGuid LeviathanGateGUID; ObjectGuid KologarnChestGUID; ObjectGuid KologarnBridgeGUID; - ObjectGuid ThorimChestGUID; + ObjectGuid CacheOfStormsGUID; + ObjectGuid CacheOfStormsHardmodeGUID; ObjectGuid HodirRareCacheGUID; ObjectGuid HodirChestGUID; ObjectGuid MimironTramGUID; @@ -439,9 +447,13 @@ class instance_ulduar : public InstanceMapScript if (GetBossState(BOSS_KOLOGARN) == DONE) HandleGameObject(ObjectGuid::Empty, false, gameObject); break; - case GO_THORIM_CHEST_HERO: - case GO_THORIM_CHEST: - ThorimChestGUID = gameObject->GetGUID(); + case GO_CACHE_OF_STORMS_10: + case GO_CACHE_OF_STORMS_25: + CacheOfStormsGUID = gameObject->GetGUID(); + break; + case GO_CACHE_OF_STORMS_HARDMODE_10: + case GO_CACHE_OF_STORMS_HARDMODE_25: + CacheOfStormsHardmodeGUID = gameObject->GetGUID(); break; case GO_HODIR_RARE_CACHE_OF_WINTER_HERO: case GO_HODIR_RARE_CACHE_OF_WINTER: @@ -648,9 +660,15 @@ class instance_ulduar : public InstanceMapScript case BOSS_THORIM: if (state == DONE) { - if (GameObject* gameObject = instance->GetGameObject(ThorimChestGUID)) - gameObject->SetRespawnTime(gameObject->GetRespawnDelay()); - + if (Creature* thorim = GetCreature(BOSS_THORIM)) + { + if (GameObject* cache = instance->GetGameObject(thorim->AI()->GetData(DATA_THORIM_HARDMODE) ? CacheOfStormsHardmodeGUID : CacheOfStormsGUID)) + { + cache->SetLootRecipient(thorim->GetLootRecipient()); + cache->SetRespawnTime(cache->GetRespawnDelay()); + cache->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE | GO_FLAG_NODESPAWN); + } + } instance->SummonCreature(NPC_THORIM_OBSERVATION_RING, ObservationRingKeepersPos[2]); } break; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h index 661d8d49ca0..7ef9faa030e 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h @@ -153,6 +153,24 @@ enum UlduarNPCs // Freya Achievement Trigger NPC_FREYA_ACHIEVE_TRIGGER = 33406, + // Thorim + NPC_THORIM_INVISIBLE_STALKER = 32780, + NPC_RUNIC_COLOSSUS = 32872, + NPC_RUNE_GIANT = 32873, + NPC_IRON_RING_GUARD = 32874, + NPC_IRON_HONOR_GUARD = 32875, + NPC_DARK_RUNE_CHAMPION = 32876, + NPC_DARK_RUNE_WARBRINGER = 32877, + NPC_DARK_RUNE_EVOKER = 32878, + NPC_DARK_RUNE_COMMONER = 32904, + NPC_DARK_RUNE_ACOLYTE = 33110, + NPC_THORIM_EVENT_BUNNY = 32892, + NPC_LIGHTNING_ORB = 33138, + NPC_GOLEM_RIGHT_HAND_BUNNY = 33140, + NPC_GOLEM_LEFT_HAND_BUNNY = 33141, + NPC_SIF = 33196, + NPC_THUNDER_ORB = 33378, + // Yogg-Saron NPC_SARA = 33134, NPC_GUARDIAN_OF_YOGG_SARON = 33136, @@ -237,8 +255,14 @@ enum UlduarGameObjects GO_HODIR_CHEST = 194307, // Thorim - GO_THORIM_CHEST_HERO = 194315, - GO_THORIM_CHEST = 194314, + GO_CACHE_OF_STORMS_10 = 194312, + GO_CACHE_OF_STORMS_HARDMODE_10 = 194313, + GO_CACHE_OF_STORMS_25 = 194315, + GO_CACHE_OF_STORMS_HARDMODE_25 = 194314, + GO_THORIM_RUNIC_DOOR = 194557, + GO_THORIM_STONE_DOOR = 194558, + GO_THORIM_ENCOUNTER_DOOR = 194559, + GO_THORIM_LEVER = 194265, // Mimiron GO_MIMIRON_TRAM = 194675, @@ -403,6 +427,15 @@ enum UlduarData DATA_ALGALON_TRAPDOOR, DATA_BRANN_BRONZEBEARD_ALG, + // Thorim + DATA_SIF, + DATA_THORIM_LEVER, + DATA_RUNIC_COLOSSUS, + DATA_RUNE_GIANT, + DATA_RUNIC_DOOR, + DATA_STONE_DOOR, + DATA_THORIM_HARDMODE, + // Misc DATA_BRANN_BRONZEBEARD_INTRO, DATA_LORE_KEEPER_OF_NORGANNON, @@ -419,10 +452,15 @@ enum UlduarWorldStates enum UlduarAchievementData { // FL Achievement boolean - DATA_UNBROKEN = 29052906, // 2905, 2906 are achievement IDs, + DATA_UNBROKEN = 29052906, // 2905, 2906 are achievement IDs, MAX_HERALD_ARMOR_ITEMLEVEL = 226, - MAX_HERALD_WEAPON_ITEMLEVEL = 232, - SPELL_LUMBERJACKED_CREDIT = 65296 + MAX_HERALD_WEAPON_ITEMLEVEL = 232 +}; + +enum UlduarSharedSpells +{ + SPELL_LUMBERJACKED_CREDIT = 65296, + SPELL_TELEPORT_KEEPER_VISUAL = 62940 // used by keepers }; enum UlduarEvents @@ -447,6 +485,23 @@ AI* GetUlduarAI(T* obj) return GetInstanceAI<AI, T>(obj, UlduarScriptName); } +class KeeperDespawnEvent : public BasicEvent +{ + public: + KeeperDespawnEvent(Creature* owner, uint32 despawnTimerOffset = 500) : _owner(owner), _despawnTimer(despawnTimerOffset) { } + + bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/) override + { + _owner->CastSpell(_owner, SPELL_TELEPORT_KEEPER_VISUAL); + _owner->DespawnOrUnsummon(1000 + _despawnTimer); + return true; + } + + private: + Creature* _owner; + uint32 _despawnTimer; +}; + class PlayerOrPetCheck { public: diff --git a/src/server/scripts/Northrend/northrend_script_loader.cpp b/src/server/scripts/Northrend/northrend_script_loader.cpp index 4678eb7843d..e3c5fb720a1 100644 --- a/src/server/scripts/Northrend/northrend_script_loader.cpp +++ b/src/server/scripts/Northrend/northrend_script_loader.cpp @@ -110,6 +110,7 @@ void AddSC_boss_general_vezax(); void AddSC_boss_mimiron(); void AddSC_boss_hodir(); void AddSC_boss_freya(); +void AddSC_boss_thorim(); void AddSC_boss_yogg_saron(); void AddSC_boss_algalon_the_observer(); void AddSC_instance_ulduar(); @@ -291,6 +292,7 @@ void AddNorthrendScripts() AddSC_boss_mimiron(); AddSC_boss_hodir(); AddSC_boss_freya(); + AddSC_boss_thorim(); AddSC_boss_yogg_saron(); AddSC_boss_algalon_the_observer(); AddSC_instance_ulduar(); |