diff options
4 files changed, 405 insertions, 300 deletions
diff --git a/sql/updates/world/3.3.5/2022_09_25_02_world.sql b/sql/updates/world/3.3.5/2022_09_25_02_world.sql new file mode 100644 index 00000000000..6a3871d0bd5 --- /dev/null +++ b/sql/updates/world/3.3.5/2022_09_25_02_world.sql @@ -0,0 +1,44 @@ +-- +UPDATE `creature_template` SET `ScriptName`= 'npc_volkhan_molten_golem' WHERE `entry`= 28695; +UPDATE `creature_template` SET `mechanic_immune_mask`= `mechanic_immune_mask` | 0x2000000 | 0x100 WHERE `entry` IN (28587, 31536); + +UPDATE `creature` SET `wander_distance`= 0, `MovementType`= 0 WHERE `guid`= 126875; +DELETE FROM `creature_template_movement` WHERE `CreatureId`= 28823; +INSERT INTO `creature_template_movement` (`CreatureId`, `Ground`, `Swim`, `Flight`, `Rooted`) VALUES +(28823, 0, 0, 1, 1); + +DELETE FROM `creature_text` WHERE `CreatureID`= 28587; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `comment`) VALUES +(28587, 0, 0, 'It is you who have destroyed my children? You... shall... pay!', 14, 0, 100, 0, 0, 13960, 31415, 'Volkhan - Aggro'), +(28587, 1, 0, '%s runs to his anvil!', 41, 0, 100, 0, 0, 0, 32214, 'Volkhan - Announce Run to Anvil'), +(28587, 2, 0, 'Nothing is wasted in the process. You will see....', 14, 0, 100, 0, 0, 13962, 31417, 'Volkhan - Run to Anvil'), +(28587, 2, 1, 'Life from lifelessness... death for you.', 14, 0, 100, 0, 0, 13961, 31416, 'Volkhan - Run to Anvil'), +(28587, 3, 0, 'All my work... undone!', 14, 0, 100, 0, 0, 13964, 31419, 'Volkhan - Shattering Stomp'), +(28587, 3, 1, 'I will crush you beneath my boots!', 14, 0, 100, 0, 0, 13963, 31418, 'Volkhan - Shattering Stomp'), +(28587, 4, 0, '%s prepares to shatter his Brittle Golems!', 41, 0, 100, 0, 0, 0, 29823, 'Volkhan - Announce Shattering Stomp'), +(28587, 5, 0, 'The master was right... to be concerned.', 14, 0, 100, 0, 0, 13968, 31423, 'Volkhan - Death'), +(28587, 6, 0, 'The armies of iron will conquer all!', 14, 0, 100, 0, 0, 13965, 31420, 'Volkhan - Slay'), +(28587, 6, 1, 'Feh! Pathetic!', 14, 0, 100, 0, 0, 13966, 31421, 'Volkhan - Slay'), +(28587, 6, 2, 'You have cost me too much work!', 14, 0, 100, 0, 0, 13967, 31422, 'Volkhan - Slay'); + +DELETE FROM `conditions` WHERE `SourceEntry` IN (52661, 52654, 52238, 52387, 59528) AND `SourceTypeOrReferenceId`= 13; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ScriptName`, `Comment`) VALUES +(13, 1, 52661, 0, 0, 31, 0, 3, 28823, 0, 0, 0, '', 'Temper - Target Volkhan''s Anvil'), +(13, 1, 52654, 0, 0, 31, 0, 3, 28823, 0, 0, 0, '', 'Temper - Target Volkhan''s Anvil'), +(13, 1, 52238, 0, 0, 31, 0, 3, 28823, 0, 0, 0, '', 'Temper - Target Volkhan''s Anvil'), +(13, 1, 52387, 0, 0, 31, 0, 3, 28695, 0, 0, 0, '', 'Heat - Target Molten Golem'), +(13, 1, 59528, 0, 0, 31, 0, 3, 28695, 0, 0, 0, '', 'Heat - Target Molten Golem'); + +DELETE FROM `spell_script_names` WHERE `ScriptName` IN +('spell_volkhan_temper_dummy', +'spell_volkhan_cool_down', +'spell_volkhan_cosmetic_stun_immune_permanent', +'spell_volkhan_shattering_stomp'); + +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(52654, 'spell_volkhan_temper_dummy'), +(52238, 'spell_volkhan_temper_dummy'), +(52441, 'spell_volkhan_cool_down'), +(59123, 'spell_volkhan_cosmetic_stun_immune_permanent'), +(52237, 'spell_volkhan_shattering_stomp'), +(59529, 'spell_volkhan_shattering_stomp'); diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_volkhan.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_volkhan.cpp index fd4f0d20aab..0b9bd800795 100644 --- a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_volkhan.cpp +++ b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_volkhan.cpp @@ -15,215 +15,222 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* ScriptData -SDName: Boss Volkhan -SD%Complete: 90% -SDComment: Event should be pretty close minus a few visual flaws -SDCategory: Halls of Lightning -EndScriptData */ - -#include "ScriptMgr.h" #include "halls_of_lightning.h" +#include "ScriptMgr.h" +#include "GameObject.h" +#include "InstanceScript.h" +#include "Map.h" #include "MotionMaster.h" #include "ObjectAccessor.h" #include "ScriptedCreature.h" +#include "SpellAuraEffects.h" #include "SpellInfo.h" -#include "SpellMgr.h" +#include "SpellScript.h" enum Texts { - SAY_AGGRO = 0, - SAY_FORGE = 1, - SAY_STOMP = 2, - SAY_SLAY = 3, - SAY_DEATH = 4, - EMOTE_TO_ANVIL = 5, - EMOTE_SHATTER = 6, + // Volkhan + SAY_AGGRO = 0, + SAY_ANNOUNCE_RUN_TO_ANVIL = 1, + SAY_RUN_TO_ANVIL = 2, + SAY_SHATTERING_STOMP = 3, + SAY_ANNOUNCE_SHATTERING_STOMP = 4, + SAY_DEATH = 5, + SAY_SLAY = 6 }; enum Spells { - SPELL_HEAT = 52387, + // Volkhan + SPELL_TEMPER_SUMMON_OBJECT = 52661, + SPELL_TEMPER_DUMMY_INTRO = 52654, + SPELL_TEMPER_DUMMY_COMBAT = 52238, SPELL_SHATTERING_STOMP = 52237, - SPELL_TEMPER = 52238, - SPELL_TEMPER_DUMMY = 52654, + SPELL_HEAT = 52387, + SPELL_DAZE_IMMUNITY_CANCEL = 59556, + + // Volkhan's Anvil SPELL_SUMMON_MOLTEN_GOLEM = 52405, - SPELL_FORGE_VISUAL = 52654, // Molten Golem - SPELL_BLAST_WAVE = 23113, - SPELL_IMMOLATION_STRIKE = 52433, + SPELL_COOL_DOWN = 52441, + SPELL_COOL_DOWN_SLOW = 52443, + SPELL_STUN_SELF = 47067, // Serverside spell @todo + SPELL_COSMETIC_STUN_IMMUNE_FREEZE_AMNIM = 59123, SPELL_SHATTER = 52429, + SPELL_INSTAKILL_SELF = 29878, // Serverside spell + SPELL_IMMOLATION_STRIKE = 52433 }; enum Events { - EVENT_PAUSE = 1, - EVENT_SHATTERING_STOMP = 2, - EVENT_SHATTER = 3, - EVENT_FORGE_CAST = 4, + // Volkhan + EVENT_TEMPER_INTRO = 1, + EVENT_RUN_TO_ANVIL, + EVENT_TEMPER, + EVENT_HEAT, + EVENT_SHATTERING_STOMP, // Molten Golem - EVENT_BLAST = 5, - EVENT_IMMOLATION = 6 + EVENT_IMMOLATION_STRIKE }; -enum Npcs +enum Actions { - NPC_VOLKHAN_ANVIL = 28823, - NPC_MOLTEN_GOLEM = 28695, - NPC_BRITTLE_GOLEM = 28681, - MAX_GOLEM = 2, - DATA_SHATTER_RESISTANT = 2042 + // Volkhan + ACTION_SHATTER_GOLEMS = 0, + ACTION_GOLEMS_TEMPERED = 1, + + // Molten Golem + ACTION_SHATTER = 0 }; enum Phases { - PHASE_INTRO = 1, - PHASE_NORMAL + // Volkhan + PHASE_INTRO = 1, + PHASE_COMBAT }; -/*###### -## Boss Volkhan -######*/ -struct boss_volkhan : public BossAI +enum MovePoints { - boss_volkhan(Creature* creature) : BossAI(creature, DATA_VOLKHAN) - { - Initialize(); - } + // Volkhan + POINT_ID_ANVIL = 0 +}; - void Initialize() - { - m_bIsStriking = false; - m_bHasTemper = false; - m_bCanShatterGolem = false; - m_uiDelay_Timer = 1000; - m_uiSummonPhase = 0; - GolemsShattered = 0; - - m_uiHealthAmountModifier = 1; - } +enum Misc +{ + ENTRY_BRITTLE_GOLEM = 28681 +}; + +enum Data +{ + DATA_SHATTER_RESISTANT = 0 +}; - void Reset() override +static Position const AnvilPosition = { 1333.5901f, -103.67797f, 56.7177f }; + +struct boss_volkhan : public BossAI +{ + boss_volkhan(Creature* creature) : BossAI(creature, DATA_VOLKHAN), + _shatteredGolems(false), _temperingGolems(false), _temperCycles(0), _shatteredGolemsCount(0) { } + + void JustAppeared() override { - Initialize(); - _Reset(); - DespawnGolem(); - m_lGolemGUIDList.clear(); events.SetPhase(PHASE_INTRO); - events.ScheduleEvent(EVENT_FORGE_CAST, 2s, 0, PHASE_INTRO); + events.ScheduleEvent(EVENT_TEMPER_INTRO, 13s, 0, PHASE_INTRO); } void JustEngagedWith(Unit* who) override { - Talk(SAY_AGGRO); - events.SetPhase(PHASE_NORMAL); - events.ScheduleEvent(EVENT_PAUSE, 3500ms, 0, PHASE_NORMAL); - events.ScheduleEvent(EVENT_SHATTERING_STOMP, 0s, 0, PHASE_NORMAL); - events.ScheduleEvent(EVENT_SHATTER, 5s, 0, PHASE_NORMAL); BossAI::JustEngagedWith(who); + Talk(SAY_AGGRO, who); + events.SetPhase(PHASE_COMBAT); } - void AttackStart(Unit* who) override + void EnterEvadeMode(EvadeReason /*why*/) override { - if (me->Attack(who, true)) - { - AddThreat(who, 0.0f); - me->SetInCombatWith(who); - who->SetInCombatWith(me); - - if (!m_bHasTemper) - me->GetMotionMaster()->MoveChase(who); - } + summons.DespawnAll(); + _DespawnAtEvade(); } - void JustDied(Unit* /*killer*/) override + void JustDied(Unit* killer) override { - Talk(SAY_DEATH); - DespawnGolem(); - - _JustDied(); + BossAI::JustDied(killer); + Talk(SAY_DEATH, killer); } void KilledUnit(Unit* who) override { - if (who->GetTypeId() == TYPEID_PLAYER) - Talk(SAY_SLAY); + if (who->IsPlayer()) + Talk(SAY_SLAY, who); } - void DespawnGolem() + void MovementInform(uint32 motionType, uint32 id) override { - if (m_lGolemGUIDList.empty()) + if (motionType != POINT_MOTION_TYPE) return; - for (ObjectGuid guid : m_lGolemGUIDList) + switch (id) { - if (Creature* temp = ObjectAccessor::GetCreature(*me, guid)) - if (temp->IsAlive()) - temp->DespawnOrUnsummon(); + case POINT_ID_ANVIL: + events.ScheduleEvent(EVENT_TEMPER, 1s, 0, PHASE_COMBAT); + break; + default: + break; } - - m_lGolemGUIDList.clear(); } - void ShatterGolem() + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override { - if (m_lGolemGUIDList.empty()) + if (damage >= me->GetHealth() || _shatteredGolems) return; - for (ObjectGuid guid : m_lGolemGUIDList) + if (_temperCycles >= 3 && !_temperingGolems && !_shatteredGolems && me->HealthBelowPctDamaged(25, damage)) { - if (Creature* temp = ObjectAccessor::GetCreature(*me, guid)) + events.CancelEvent(EVENT_RUN_TO_ANVIL); + events.ScheduleEvent(EVENT_SHATTERING_STOMP, 1ms, 0, PHASE_COMBAT); + _shatteredGolems = true; + return; + } + + if (_temperCycles < 3 && me->HealthBelowPctDamaged(100.f - (20.f + 20.f * _temperCycles), damage)) + { + if (!_temperingGolems) { - // Only shatter brittle golems - if (temp->IsAlive() && temp->GetEntry() == NPC_BRITTLE_GOLEM) - { - temp->CastSpell(temp, SPELL_SHATTER, false); - GolemsShattered += 1; - } + events.ScheduleEvent(EVENT_RUN_TO_ANVIL, 1ms, 0, PHASE_COMBAT); + _temperingGolems = true; } + ++_temperCycles; } + } - void JustSummoned(Creature* summoned) override + void DoAction(int32 action) override { - if (summoned->GetEntry() == NPC_MOLTEN_GOLEM) + switch (action) { - m_lGolemGUIDList.push_back(summoned->GetGUID()); - - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) - summoned->GetMotionMaster()->MoveFollow(target, 0.0f, 0.0f); - - // Why healing when just summoned? - summoned->CastSpell(summoned, SPELL_HEAT, CastSpellExtraArgs().SetOriginalCaster(me->GetGUID())); + case ACTION_SHATTER_GOLEMS: + summons.RemoveNotExisting(); + for (ObjectGuid const& guid : summons) + { + if (Creature* golem = ObjectAccessor::GetCreature(*me, guid)) + { + if (golem->HasAura(SPELL_COSMETIC_STUN_IMMUNE_FREEZE_AMNIM)) + { + if (CreatureAI* ai = golem->AI()) + { + ai->DoAction(ACTION_SHATTER); + ++_shatteredGolemsCount; + } + } + } + } + break; + case ACTION_GOLEMS_TEMPERED: + _temperingGolems = false; + break; + default: + break; } } - void MovementInform(uint32 type, uint32 data) override + uint32 GetData(uint32 type) const override { - if (type == POINT_MOTION_TYPE && data == EVENT_FORGE_CAST) + switch (type) { - if (m_uiSummonPhase == 2) - { - me->SetOrientation(2.29f); - m_uiSummonPhase = 3; - } + case DATA_SHATTER_RESISTANT: + return _shatteredGolemsCount; + default: + return 0; } - } - - uint32 GetData(uint32 data) const override - { - if (data == DATA_SHATTER_RESISTANT) - return GolemsShattered; return 0; } void UpdateAI(uint32 diff) override { - // Return since we have no target and are in CombatPhase - if (events.IsInPhase(PHASE_NORMAL) && !UpdateVictim()) + if (!UpdateVictim() && !events.IsInPhase(PHASE_INTRO)) return; events.Update(diff); @@ -235,42 +242,38 @@ struct boss_volkhan : public BossAI { switch (eventId) { - case EVENT_PAUSE: - if (m_bIsStriking) - { - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() != CHASE_MOTION_TYPE) - if (me->GetVictim()) - me->GetMotionMaster()->MoveChase(me->GetVictim()); - - m_bHasTemper = false; - m_bIsStriking = false; - events.ScheduleEvent(EVENT_PAUSE, 3500ms, 0, PHASE_NORMAL); - } + case EVENT_TEMPER_INTRO: + DoCastAOE(SPELL_TEMPER_SUMMON_OBJECT); + DoCastAOE(SPELL_TEMPER_DUMMY_INTRO); + events.Repeat(13s); break; - case EVENT_SHATTERING_STOMP: - if (!m_bHasTemper && m_uiHealthAmountModifier >= 3) - { - // Should he stomp even if he has no brittle golem to shatter? - Talk(SAY_STOMP); - - DoCast(me, SPELL_SHATTERING_STOMP); - - Talk(EMOTE_SHATTER); - m_bCanShatterGolem = true; - } - events.ScheduleEvent(EVENT_SHATTERING_STOMP, 30s, 0, PHASE_NORMAL); + case EVENT_RUN_TO_ANVIL: + me->AttackStop(); + me->SetReactState(REACT_PASSIVE); + me->GetMotionMaster()->MovePoint(POINT_ID_ANVIL, AnvilPosition); + events.CancelEvent(EVENT_HEAT); + Talk(SAY_ANNOUNCE_RUN_TO_ANVIL); + Talk(SAY_RUN_TO_ANVIL); break; - case EVENT_SHATTER: - if (m_bCanShatterGolem) + case EVENT_TEMPER: + if (Creature const* anvil = instance->GetCreature(DATA_VOLKHANS_ANVIL)) { - ShatterGolem(); - events.ScheduleEvent(EVENT_SHATTER, 3s, 0, PHASE_NORMAL); - m_bCanShatterGolem = false; + me->SetFacingToObject(anvil); + DoCastAOE(SPELL_TEMPER_SUMMON_OBJECT); + DoCastAOE(SPELL_TEMPER_DUMMY_COMBAT); + me->SetReactState(REACT_AGGRESSIVE); + events.ScheduleEvent(EVENT_HEAT, 8s + 500ms, 0, PHASE_COMBAT); } break; - case EVENT_FORGE_CAST: - DoCast(me, SPELL_FORGE_VISUAL); - events.ScheduleEvent(EVENT_FORGE_CAST, 15s, 0, PHASE_INTRO); + case EVENT_HEAT: + DoCastAOE(SPELL_HEAT); + events.Repeat(10s); + break; + case EVENT_SHATTERING_STOMP: + events.CancelEvent(EVENT_HEAT); + Talk(SAY_SHATTERING_STOMP); + Talk(SAY_ANNOUNCE_SHATTERING_STOMP); + DoCastAOE(SPELL_SHATTERING_STOMP); break; default: break; @@ -280,175 +283,80 @@ struct boss_volkhan : public BossAI return; } - // All the events below happen during the PHASE_NORMAL phase and shouldn't be executed before that - if (!events.IsInPhase(PHASE_NORMAL)) - return; - - // Health check - if (!m_bCanShatterGolem && me->HealthBelowPct(100 - 20 * m_uiHealthAmountModifier)) - { - ++m_uiHealthAmountModifier; - - if (me->IsNonMeleeSpellCast(false)) - me->InterruptNonMeleeSpells(false); - - Talk(SAY_FORGE); - - m_bHasTemper = true; - - m_uiSummonPhase = 1; - } - - switch (m_uiSummonPhase) - { - case 1: - // 1 - Start run to Anvil - Talk(EMOTE_TO_ANVIL); - me->GetMotionMaster()->MovePoint(EVENT_FORGE_CAST, me->GetHomePosition()); - m_uiSummonPhase = 2; // Set Next Phase - break; - case 2: - // 2 - Check if reached Anvil - // This is handled in: void MovementInform(uint32, uint32) override - break; - case 3: - // 3 - Cast Temper on the Anvil - if (Unit* target = GetClosestCreatureWithEntry(me, NPC_VOLKHAN_ANVIL, 1000.0f, true)) - { - me->SetOrientation(2.29f); - DoCast(target, SPELL_TEMPER, false); - DoCast(target, SPELL_TEMPER_DUMMY, false); - } - m_uiDelay_Timer = 1000; // Delay 2 seconds before next phase can begin - m_uiSummonPhase = 4; // Set Next Phase - break; - case 4: - // 4 - Wait for delay to expire - if (m_uiDelay_Timer <= diff) - { - if (Unit* target = SelectTarget(SelectTargetMethod::MaxThreat, 0)) - { - me->SetReactState(REACT_AGGRESSIVE); - me->SetInCombatWith(target); - me->GetMotionMaster()->MoveFollow(target, 0.0f, 0.0f); - } - m_uiSummonPhase = 5; - } - else - m_uiDelay_Timer -= diff; - break; - case 5: - // 5 - Spawn the Golems - if (Creature* creatureTarget = GetClosestCreatureWithEntry(me, NPC_VOLKHAN_ANVIL, 1000.0f, true)) - for (uint8 i = 0; i < MAX_GOLEM; ++i) - me->CastSpell(creatureTarget, SPELL_SUMMON_MOLTEN_GOLEM, true); - - m_bIsStriking = true; - m_uiSummonPhase = 0; // Reset back to Phase 0 for next time - break; - } - DoMeleeAttackIfReady(); } - private: - GuidList m_lGolemGUIDList; - uint32 m_uiHealthAmountModifier; - uint8 GolemsShattered; - uint32 m_uiDelay_Timer; - uint32 m_uiSummonPhase; - - bool m_bHasTemper; - bool m_bIsStriking; - bool m_bCanShatterGolem; +private: + bool _shatteredGolems; + bool _temperingGolems; + uint8 _temperCycles; + uint8 _shatteredGolemsCount; }; -/*###### -## npc_molten_golem -######*/ - -struct npc_molten_golem : public ScriptedAI +struct npc_volkhan_molten_golem : public ScriptedAI { - npc_molten_golem(Creature* creature) : ScriptedAI(creature) + npc_volkhan_molten_golem(Creature* creature) : ScriptedAI(creature) { } + + void JustAppeared() override { - Initialize(); + DoCastSelf(SPELL_COOL_DOWN); + //DoCastSelf(SPELL_STUN_SELF); } - void Initialize() + void JustEngagedWith(Unit* /*who*/) override { - m_bIsFrozen = false; - events.ScheduleEvent(EVENT_BLAST, 20s); - events.ScheduleEvent(EVENT_IMMOLATION, 5s); + _events.ScheduleEvent(EVENT_IMMOLATION_STRIKE, 7s, 12s); } - bool m_bIsFrozen; - - void Reset() override + void EnterEvadeMode(EvadeReason why) override { - Initialize(); + ScriptedAI::EnterEvadeMode(why); + _events.Reset(); } - void AttackStart(Unit* who) override + void JustDied(Unit* /*killer*/) override { - if (me->Attack(who, true)) - { - AddThreat(who, 0.0f); - me->SetInCombatWith(who); - who->SetInCombatWith(me); - - if (!m_bIsFrozen) - me->GetMotionMaster()->MoveChase(who); - } + me->DespawnOrUnsummon(5s); } - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override + void DoAction(int32 action) override { - if (damage >= me->GetHealth()) + switch (action) { - me->UpdateEntry(NPC_BRITTLE_GOLEM); - me->SetHealth(1); - damage = 0; - me->RemoveAllAuras(); - me->AttackStop(); - me->SetReactState(REACT_PASSIVE); // should be replaced by spell 59123 - if (me->IsNonMeleeSpellCast(false)) - me->InterruptNonMeleeSpells(false); - - me->GetMotionMaster()->Clear(); - m_bIsFrozen = true; + case ACTION_SHATTER: + me->RemoveAurasDueToSpell(SPELL_COSMETIC_STUN_IMMUNE_FREEZE_AMNIM); + DoCastAOE(SPELL_SHATTER); + DoCastSelf(SPELL_INSTAKILL_SELF); + break; + default: + break; } } - void SpellHit(WorldObject* /*caster*/, SpellInfo const* spellInfo) override + void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override { - // This is the dummy effect of the spells - if (spellInfo->Id == sSpellMgr->GetSpellIdForDifficulty(SPELL_SHATTER, me)) - if (me->GetEntry() == NPC_BRITTLE_GOLEM) - me->DespawnOrUnsummon(); + // Molten Golems cannot die from foreign damage. They will kill themselves via suicide spell when getting shattered + if (damage >= me->GetHealth() && attacker != me) + damage = me->GetHealth() - 1; } void UpdateAI(uint32 diff) override { - // Return since we have no target or if we are frozen - if (!UpdateVictim() || m_bIsFrozen) + 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_BLAST: - DoCast(me, SPELL_BLAST_WAVE); - events.ScheduleEvent(EVENT_BLAST, 20s); - break; - case EVENT_IMMOLATION: + case EVENT_IMMOLATION_STRIKE: DoCastVictim(SPELL_IMMOLATION_STRIKE); - events.ScheduleEvent(EVENT_BLAST, 5s); + _events.Repeat(5s); break; default: break; @@ -461,8 +369,112 @@ struct npc_molten_golem : public ScriptedAI DoMeleeAttackIfReady(); } - private: - EventMap events; +private: + EventMap _events; +}; + +// 52654, 52238 - Temper +class spell_volkhan_temper_dummy : public SpellScript +{ + PrepareSpellScript(spell_volkhan_temper_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_TEMPER_DUMMY_COMBAT }); + } + + void HandleDummyEffect(SpellEffIndex /*effIndex*/) + { + Unit* target = GetHitUnit(); + if (InstanceScript* instance = target->GetInstanceScript()) + if (GameObject* temperVisual = instance->GetGameObject(DATA_VOLKHAN_TEMPER_VISUAL)) + temperVisual->SendCustomAnim(0); + + if (GetSpellInfo()->Id == SPELL_TEMPER_DUMMY_COMBAT) + { + for (uint8 i = 0; i < 2; ++i) + GetHitUnit()->CastSpell(nullptr, SPELL_SUMMON_MOLTEN_GOLEM); + + if (Unit* caster = GetCaster()) + if (Creature* creatureCaster = caster->ToCreature()) + if (CreatureAI* ai = creatureCaster->AI()) + ai->DoAction(ACTION_GOLEMS_TEMPERED); + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_volkhan_temper_dummy::HandleDummyEffect, EFFECT_0, SPELL_EFFECT_DUMMY); + } +}; + +// 52441 - Cool Down +class spell_volkhan_cool_down : public AuraScript +{ + PrepareAuraScript(spell_volkhan_cool_down); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_COOL_DOWN_SLOW, SPELL_COSMETIC_STUN_IMMUNE_FREEZE_AMNIM }); + } + + void HandlePeriodicDummyEffect(AuraEffect const* /*aurEff*/) + { + Unit* target = GetTarget(); + if (target->GetHealthPct() > 1.f) + { + // This damage part feels weird but there is no trace of spells in sniffs that could do such thing otherwise. + Unit::DealDamage(target, target, CalculatePct(target->GetMaxHealth(), target->GetMap()->IsHeroic() ? 1 : 2)); + int32 bp = 0 - static_cast<int32>(100.f - target->GetHealthPct()); + target->CastSpell(nullptr, SPELL_COOL_DOWN_SLOW, CastSpellExtraArgs().AddSpellBP0(bp)); + } + else + { + target->CastSpell(nullptr, SPELL_COSMETIC_STUN_IMMUNE_FREEZE_AMNIM); + target->RemoveAurasDueToSpell(SPELL_COOL_DOWN_SLOW); + GetAura()->Remove(); + } + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_volkhan_cool_down::HandlePeriodicDummyEffect, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } +}; + +// 59123 Cosmetic - Stun + Immune Permanent (Freeze Anim) +class spell_volkhan_cosmetic_stun_immune_permanent : public AuraScript +{ + PrepareAuraScript(spell_volkhan_cosmetic_stun_immune_permanent); + + void HandleApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (Creature* target = GetTarget()->ToCreature()) + target->UpdateEntry(ENTRY_BRITTLE_GOLEM, nullptr, false); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_volkhan_cosmetic_stun_immune_permanent::HandleApply, EFFECT_0, SPELL_AURA_MOD_STUN, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 52237, 59529 - Shattering Stomp +class spell_volkhan_shattering_stomp : public SpellScript +{ + PrepareSpellScript(spell_volkhan_shattering_stomp); + + void HandleShattering() + { + if (Creature* caster = GetCaster()->ToCreature()) + if (CreatureAI* ai = caster->AI()) + ai->DoAction(ACTION_SHATTER_GOLEMS); + } + + void Register() override + { + AfterCast += SpellCastFn(spell_volkhan_shattering_stomp::HandleShattering); + } }; class achievement_shatter_resistant : public AchievementCriteriaScript @@ -472,13 +484,24 @@ class achievement_shatter_resistant : public AchievementCriteriaScript bool OnCheck(Player* /*source*/, Unit* target) override { - return target && target->GetAI()->GetData(DATA_SHATTER_RESISTANT) < 5; + if (!target || !target->IsCreature()) + return false; + + if (Creature* creature = target->ToCreature()) + if (CreatureAI* ai = creature->AI()) + return ai->GetData(DATA_SHATTER_RESISTANT) < 5; + + return false; } }; void AddSC_boss_volkhan() { RegisterHallsOfLightningCreatureAI(boss_volkhan); - RegisterHallsOfLightningCreatureAI(npc_molten_golem); + RegisterHallsOfLightningCreatureAI(npc_volkhan_molten_golem); + RegisterSpellScript(spell_volkhan_temper_dummy); + RegisterSpellScript(spell_volkhan_cool_down); + RegisterSpellScript(spell_volkhan_cosmetic_stun_immune_permanent); + RegisterSpellScript(spell_volkhan_shattering_stomp); new achievement_shatter_resistant(); } diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/halls_of_lightning.h b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/halls_of_lightning.h index 26df616f30d..594a3086f25 100644 --- a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/halls_of_lightning.h +++ b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/halls_of_lightning.h @@ -33,7 +33,12 @@ enum HOLDataTypes DATA_IONAR = 2, DATA_LOKEN = 3, - // Misc + // Additional Data + /*Volkhan*/ + DATA_VOLKHAN_TEMPER_VISUAL, + DATA_VOLKHANS_ANVIL, + + /*Loken*/ DATA_LOKEN_GLOBE }; @@ -43,16 +48,25 @@ enum HOLCreaturesIds NPC_GENERAL_BJARNGRIM = 28586, NPC_VOLKHAN = 28587, NPC_IONAR = 28546, - NPC_LOKEN = 28923 + NPC_LOKEN = 28923, + + /*Volkhan*/ + NPC_VOLKHANS_ANVIL = 28823, + NPC_MOLTEN_GOLEM = 28695 }; enum HOLGameObjectIds { - GO_BJARNGRIM_DOOR = 191416, - GO_VOLKHAN_DOOR = 191325, - GO_IONAR_DOOR = 191326, - GO_LOKEN_DOOR = 191324, - GO_LOKEN_THRONE = 192654 + GO_BJARNGRIM_DOOR = 191416, + GO_VOLKHAN_DOOR = 191325, + GO_IONAR_DOOR = 191326, + GO_LOKEN_DOOR = 191324, + + /*Volkhan*/ + GO_VOLKHAN_TEMPER_VISUAL = 190858, + + /*Loken*/ + GO_LOKEN_THRONE = 192654 }; template <class AI, class T> diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/instance_halls_of_lightning.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/instance_halls_of_lightning.cpp index 1bea6368f08..1cd269e135a 100644 --- a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/instance_halls_of_lightning.cpp +++ b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/instance_halls_of_lightning.cpp @@ -17,6 +17,7 @@ #include "ScriptMgr.h" #include "Creature.h" +#include "CreatureAI.h" #include "GameObject.h" #include "halls_of_lightning.h" #include "InstanceScript.h" @@ -36,13 +37,15 @@ ObjectData const creatureData[] = { NPC_VOLKHAN, DATA_VOLKHAN }, { NPC_IONAR, DATA_IONAR }, { NPC_LOKEN, DATA_LOKEN }, + { NPC_VOLKHANS_ANVIL, DATA_VOLKHANS_ANVIL }, { 0, 0 } // END }; ObjectData const gameObjectData[] = { - { GO_LOKEN_THRONE, DATA_LOKEN_GLOBE }, - { 0, 0 } // END + { GO_VOLKHAN_TEMPER_VISUAL, DATA_VOLKHAN_TEMPER_VISUAL }, + { GO_LOKEN_THRONE, DATA_LOKEN_GLOBE }, + { 0, 0 } // END }; class instance_halls_of_lightning : public InstanceMapScript @@ -60,6 +63,27 @@ class instance_halls_of_lightning : public InstanceMapScript LoadDoorData(doorData); } + void OnCreatureCreate(Creature* creature) override + { + InstanceScript::OnCreatureCreate(creature); + + switch (creature->GetEntry()) + { + case NPC_MOLTEN_GOLEM: + if (GetBossState(DATA_VOLKHAN) == IN_PROGRESS) + { + if (Creature* volkhan = GetCreature(DATA_VOLKHAN)) + if (CreatureAI* ai = volkhan->AI()) + ai->JustSummoned(creature); + } + else // These golems are summoned via trigger missile so we have to clean them up if they spawned during a wipe/completion + creature->DespawnOrUnsummon(); + break; + default: + break; + } + } + bool SetBossState(uint32 type, EncounterState state) override { if (!InstanceScript::SetBossState(type, state)) |