diff options
| author | offl <11556157+offl@users.noreply.github.com> | 2025-11-09 16:29:36 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-09 15:29:36 +0100 |
| commit | 0f8a33ca4cabe923c4611e7d0221c8819979edaa (patch) | |
| tree | 2fe6c2b8d96ff6261f9b78939be5904dd06f0366 | |
| parent | 475808d626958ccf22563f1415ae3381d173c062 (diff) | |
Scripts/Utgarde Keep: Rewrite scripts (#31349)
Closes #26822
6 files changed, 709 insertions, 482 deletions
diff --git a/sql/updates/world/3.3.5/2025_11_09_00_world.sql b/sql/updates/world/3.3.5/2025_11_09_00_world.sql new file mode 100644 index 00000000000..08700c7e058 --- /dev/null +++ b/sql/updates/world/3.3.5/2025_11_09_00_world.sql @@ -0,0 +1,47 @@ +-- Ingvar +UPDATE `creature_template` SET `unit_flags` = 33555200, `AIName` = 'SmartAI' WHERE `entry` = 24012; +DELETE FROM `smart_scripts` WHERE `entryorguid` = 24012 AND `source_type` = 0; +INSERT INTO `smart_scripts` (`entryorguid`,`source_type`,`id`,`link`,`event_type`,`event_phase_mask`,`event_chance`,`event_flags`,`event_param1`,`event_param2`,`event_param3`,`event_param4`,`event_param5`,`action_type`,`action_param1`,`action_param2`,`action_param3`,`action_param4`,`action_param5`,`action_param6`,`target_type`,`target_param1`,`target_param2`,`target_param3`,`target_param4`,`target_x`,`target_y`,`target_z`,`target_o`,`comment`) VALUES +(24012,0,0,0,11,0,100,0,0,0,0,0,0,11,42862,0,0,0,0,0,1,0,0,0,0,0,0,0,0,"Ingvar Res Ground Visual - On Spawn - Cast 'Scourge Resurrection'"), +(24012,0,1,0,11,0,100,0,0,0,0,0,0,41,10000,0,0,0,0,0,1,0,0,0,0,0,0,0,0,"Ingvar Res Ground Visual - On Spawn - Delayed Despawn"); + +DELETE FROM `creature_text` WHERE `CreatureID` IN (23954,23980) AND `GroupID` = 3; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(23954,3,0,"%s roars!",41,0,100,0,0,0,14029,0,"Ingvar the Plunderer - EMOTE_ROAR"), +(23980,3,0,"%s roars!",41,0,100,0,0,0,14029,0,"Ingvar the Plunderer - EMOTE_ROAR"); + +DELETE FROM `creature_equip_template` WHERE `CreatureID` = 23954 AND `ID` = 2; +UPDATE `creature_equip_template` SET `ItemID1` = 33177 WHERE `CreatureID` = 23954 AND `ID` = 1; +UPDATE `creature` SET `equipment_id` = 1 WHERE `id` = 23954; + +-- Keleseth +DELETE FROM `creature_summon_groups` WHERE `summonerId` = 23953 AND `summonerType` = 0; +INSERT INTO `creature_summon_groups` (`summonerId`,`summonerType`,`groupId`,`entry`,`position_x`,`position_y`,`position_z`,`orientation`,`summonType`,`summonTime`,`Comment`) VALUES +(23953,0,0,23970,153.91295,260.99866,42.953950,5.777040004730224609,8,0,"Prince Keleseth - Group 0 - Vrykul Skeleton"), +(23953,0,0,23970,148.90604,260.21863,42.953945,5.899212837219238281,8,0,"Prince Keleseth - Group 0 - Vrykul Skeleton"), +(23953,0,0,23970,147.77333,266.23520,42.953945,5.759586334228515625,8,0,"Prince Keleseth - Group 0 - Vrykul Skeleton"), +(23953,0,0,23970,153.47198,266.16130,42.953945,5.619960308074951171,8,0,"Prince Keleseth - Group 0 - Vrykul Skeleton"); + +DELETE FROM `creature_text` WHERE `CreatureID` = 23953; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(23953,0,0,"Your blood is mine!",14,0,0,0,0,13221,22736,0,"Prince Keleseth - SAY_AGGRO"), +(23953,1,0,"Not so fast.",14,0,0,0,0,13222,22734,0,"Prince Keleseth - SAY_FROST_TOMB"), +(23953,2,0,"Darkness waits.",14,0,0,0,0,13223,29591,0,"Prince Keleseth - SAY_SLAY"), +(23953,3,0,"Aranal, ledel! Their fate shall be yours!",14,0,0,0,0,13224,22729,0,"Prince Keleseth - SAY_SUMMON_SKELETONS"), +(23953,4,0,"I join... the night.",14,0,0,0,0,13225,29592,0,"Prince Keleseth - SAY_DEATH"), +(23953,5,0,"%s casts Frost Tomb on $n.",41,0,0,0,0,0,27152,0,"Prince Keleseth - EMOTE_FROST_TOMB"); + +UPDATE `spell_script_names` SET `ScriptName` = 'spell_keleseth_frost_tomb_channel' WHERE `ScriptName` = 'spell_frost_tomb'; + +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_keleseth_frost_tomb_periodic'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(42672, 'spell_keleseth_frost_tomb_periodic'); + +DELETE FROM `creature_text` WHERE `CreatureID` = 23970; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(23970,0,0,"%s rises from the floor!",16,0,100,0,0,0,26607,0,"Vrykul Skeleton - EMOTE_RISES"); + +-- Skarvald +DELETE FROM `creature_loot_template` WHERE `Entry` = 24201 AND `Reference` = 35045; +DELETE FROM `creature_loot_template` WHERE `Entry` = 31656 AND `Item` = 47241; +DELETE FROM `creature_loot_template` WHERE `Entry` = 31656 AND `Reference` = 35049; diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp index 27f3321fabf..b0f0cb822d8 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp @@ -15,12 +15,12 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* ScriptData -SDName: Boss_Ingvar_The_Plunderer -SD%Complete: 95 -SDComment: Blizzlike Timers (just shadow axe summon needs a new timer) -SDCategory: Utgarde Keep -EndScriptData */ +/* + * Combat timers requires to be revisited + * Everything related to Ingvar Throw Dummy requires additional research and checks + since it may be handled wrongly or something may be missing + * Out of Combat events are NYI, should be handled from Proto-Drake Rider's script (DB issue) + */ #include "ScriptMgr.h" #include "InstanceScript.h" @@ -28,146 +28,197 @@ EndScriptData */ #include "ObjectAccessor.h" #include "ScriptedCreature.h" #include "Spell.h" +#include "SpellInfo.h" +#include "SpellMgr.h" #include "SpellScript.h" #include "utgarde_keep.h" -enum Yells +enum IngvarTexts { - // Ingvar (Human/Undead) - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_DEATH = 2, + // Ingvar (Human / Undead) + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_DEATH = 2, + EMOTE_ROAR = 3, // Annhylde The Caller - YELL_RESURRECT = 0 + SAY_RESURRECT = 0 +}; + +enum IngvarSpells +{ + // Human Form + SPELL_CLEAVE = 42724, + SPELL_SMASH = 42669, + SPELL_STAGGERING_ROAR = 42708, + SPELL_ENRAGE = 42705, + + // Undead Form + SPELL_DARK_SMASH = 42723, + SPELL_DREADFUL_ROAR = 42729, + SPELL_WOE_STRIKE = 42730, + SPELL_SHADOW_AXE = 42748, + + // Feign Death & Resurrection + SPELL_CLEAR_ALL_DEBUFFS = 34098, + SPELL_INGVAR_FEIGN_DEATH = 42795, + SPELL_SUMMON_BANSHEE = 42912, + SPELL_ETHEREAL_TELEPORT = 34427, + SPELL_SCOURGE_RESURRECTION_CHANNEL = 42857, + SPELL_SCOURGE_RESURRECTION_VISUAL = 42863, + SPELL_SCOURGE_RESURRECTION_HEAL = 42704, + + // Ingvar Throw Dummy + SPELL_THROW_AXE = 42750, + + // Scripts + SPELL_WOE_STRIKE_EFFECT = 42739 }; -enum Events +enum IngvarEvents { - EVENT_CLEAVE = 1, + EVENT_CLEAVE = 1, EVENT_SMASH, EVENT_STAGGERING_ROAR, EVENT_ENRAGE, + EVENT_SUMMON_BANSHEE, + EVENT_DARK_SMASH, EVENT_DREADFUL_ROAR, EVENT_WOE_STRIKE, EVENT_SHADOW_AXE, - EVENT_JUST_TRANSFORMED, - EVENT_SUMMON_BANSHEE, - EVENT_RESURRECT_1, - EVENT_RESURRECT_2 + EVENT_RESURRECTION_1, + EVENT_RESURRECTION_2, + EVENT_RESURRECTION_3, + EVENT_RESURRECTION_4, + EVENT_RESURRECTION_5, + EVENT_RESURRECTION_6, + EVENT_RESURRECTION_7, + EVENT_RESURRECTION_8, + EVENT_RESURRECTION_9, + EVENT_RESURRECTION_10 }; -enum Phases +enum IngvarActions { - PHASE_HUMAN = 1, - PHASE_UNDEAD, - PHASE_EVENT + ACTION_START_UNDEAD_PHASE = 0, + ACTION_AXE_RETURNS = 1 }; -enum Spells +enum IngvarPoints { - // Ingvar Spells human form - SPELL_CLEAVE = 42724, - SPELL_SMASH = 42669, - SPELL_STAGGERING_ROAR = 42708, - SPELL_ENRAGE = 42705, - - SPELL_INGVAR_FEIGN_DEATH = 42795, - SPELL_SUMMON_BANSHEE = 42912, - SPELL_SCOURG_RESURRECTION = 42863, // Spawn resurrect effect around Ingvar - - // Ingvar Spells undead form - SPELL_DARK_SMASH = 42723, - SPELL_DREADFUL_ROAR = 42729, - SPELL_WOE_STRIKE = 42730, - SPELL_WOE_STRIKE_EFFECT = 42739, - - SPELL_SHADOW_AXE_SUMMON = 42748, - SPELL_SHADOW_AXE_PERIODIC_DAMAGE = 42750, - - // Spells for Annhylde - SPELL_SCOURG_RESURRECTION_HEAL = 42704, // Heal Max + DummyAura - SPELL_SCOURG_RESURRECTION_BEAM = 42857, // Channeling Beam of Annhylde - SPELL_SCOURG_RESURRECTION_DUMMY = 42862, // Some Emote Dummy? - SPELL_INGVAR_TRANSFORM = 42796 + POINT_CALLER_DOWN = 0, + POINT_CALLER_UP = 1, + POINT_AXE_TO_TARGET = 2, + POINT_AXE_TO_OWNER = 3 }; -enum Misc +enum IngvarCreatures { - ACTION_START_PHASE_2 + NPC_INGVAR_UNDEAD = 23980, + NPC_THROW_TARGET = 23996 }; +// 23954 - Ingvar the Plunderer struct boss_ingvar_the_plunderer : public BossAI { - boss_ingvar_the_plunderer(Creature* creature) : BossAI(creature, DATA_INGVAR) { } + boss_ingvar_the_plunderer(Creature* creature) : BossAI(creature, DATA_INGVAR), _isUnkillable(true), _isInTransition(false) { } void Reset() override { if (me->GetEntry() != NPC_INGVAR) me->UpdateEntry(NPC_INGVAR); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_UNINTERACTIBLE); - me->SetImmuneToPC(false); + me->RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE); + me->SetReactState(REACT_AGGRESSIVE); _Reset(); + + _isUnkillable = true; + _isInTransition = false; + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + + Talk(SAY_AGGRO); + + events.ScheduleEvent(EVENT_CLEAVE, 6s, 12s); + events.ScheduleEvent(EVENT_SMASH, 12s, 17s); + events.ScheduleEvent(EVENT_STAGGERING_ROAR, 18s, 21s); + events.ScheduleEvent(EVENT_ENRAGE, 7s, 14s); } void DamageTaken(Unit* /*doneBy*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override { - if (damage >= me->GetHealth() && events.IsInPhase(PHASE_HUMAN)) + if (damage >= me->GetHealth() && _isUnkillable) { - events.SetPhase(PHASE_EVENT); - events.ScheduleEvent(EVENT_SUMMON_BANSHEE, 3s, 0, PHASE_EVENT); - - me->RemoveAllAuras(); - me->StopMoving(); - DoCast(me, SPELL_INGVAR_FEIGN_DEATH, true); + damage = me->GetHealth() - 1; - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_UNINTERACTIBLE); - me->SetImmuneToPC(true, true); + if (_isInTransition) + return; + _isInTransition = true; + me->InterruptNonMeleeSpells(false); Talk(SAY_DEATH); + /// @todo: This should not be called. Clear All Debuffs should remove all debuffs. Does it work? Remove this + me->RemoveAllAuras(); + DoCastSelf(SPELL_CLEAR_ALL_DEBUFFS); + DoCastSelf(SPELL_INGVAR_FEIGN_DEATH); + /// This one is removed manually + me->RemoveAurasDueToSpell(SPELL_ENRAGE); + me->SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE); + me->SetReactState(REACT_PASSIVE); + + events.Reset(); + events.ScheduleEvent(EVENT_SUMMON_BANSHEE, 2400ms); } - - if (events.IsInPhase(PHASE_EVENT)) - damage = 0; } - void DoAction(int32 actionId) override + void OnSpellStart(SpellInfo const* spell) override { - if (actionId == ACTION_START_PHASE_2) - StartZombiePhase(); + if (spell->Id == sSpellMgr->GetSpellIdForDifficulty(SPELL_STAGGERING_ROAR, me) || + spell->Id == sSpellMgr->GetSpellIdForDifficulty(SPELL_DREADFUL_ROAR, me)) + Talk(EMOTE_ROAR); } - void StartZombiePhase() + void OnSpellCast(SpellInfo const* spell) override { - me->RemoveAura(SPELL_INGVAR_FEIGN_DEATH); - DoCast(me, SPELL_INGVAR_TRANSFORM, true); - me->UpdateEntry(NPC_INGVAR_UNDEAD); - events.ScheduleEvent(EVENT_JUST_TRANSFORMED, 500ms, 0, PHASE_EVENT); + if (spell->Id == SPELL_SHADOW_AXE) + SetEquipmentSlots(false, EQUIP_UNEQUIP); } - void JustEngagedWith(Unit* who) override + void DoAction(int32 action) override { - if (events.IsInPhase(PHASE_EVENT) || events.IsInPhase(PHASE_UNDEAD)) // ingvar gets multiple JustEngagedWith calls - return; - BossAI::JustEngagedWith(who); - - Talk(SAY_AGGRO); - events.SetPhase(PHASE_HUMAN); - events.ScheduleEvent(EVENT_CLEAVE, 6s, 12s, 0, PHASE_HUMAN); - events.ScheduleEvent(EVENT_STAGGERING_ROAR, 18s, 21s, 0, PHASE_HUMAN); - events.ScheduleEvent(EVENT_ENRAGE, 7s, 14s, 0, PHASE_HUMAN); - events.ScheduleEvent(EVENT_SMASH, 12s, 17s, 0, PHASE_HUMAN); + switch (action) + { + case ACTION_START_UNDEAD_PHASE: + me->UpdateEntry(NPC_INGVAR_UNDEAD); + me->RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE); + me->SetReactState(REACT_AGGRESSIVE); + Talk(SAY_AGGRO); + DoZoneInCombat(); + + _isUnkillable = false; + + events.ScheduleEvent(EVENT_DARK_SMASH, 14s, 18s); + events.ScheduleEvent(EVENT_DREADFUL_ROAR, 0s); + events.ScheduleEvent(EVENT_WOE_STRIKE, 10s, 14s); + events.ScheduleEvent(EVENT_SHADOW_AXE, 30s); + break; + case ACTION_AXE_RETURNS: + me->LoadEquipment(1, true); + break; + default: + break; + } } - void AttackStart(Unit* who) override + void KilledUnit(Unit* /*who*/) override { - if (events.IsInPhase(PHASE_EVENT)) // prevent ingvar from beginning to attack/chase during transition - return; - BossAI::AttackStart(who); + Talk(SAY_SLAY); } void JustDied(Unit* /*killer*/) override @@ -176,24 +227,9 @@ struct boss_ingvar_the_plunderer : public BossAI Talk(SAY_DEATH); } - void ScheduleSecondPhase() - { - events.SetPhase(PHASE_UNDEAD); - events.ScheduleEvent(EVENT_DARK_SMASH, 14s, 18s, 0, PHASE_UNDEAD); - events.ScheduleEvent(EVENT_DREADFUL_ROAR, 0ms, 0, PHASE_UNDEAD); - events.ScheduleEvent(EVENT_WOE_STRIKE, 10s, 14s, 0, PHASE_UNDEAD); - events.ScheduleEvent(EVENT_SHADOW_AXE, 30s, 0, PHASE_UNDEAD); - } - - void KilledUnit(Unit* who) override - { - if (who->GetTypeId() == TYPEID_PLAYER) - Talk(SAY_SLAY); - } - void UpdateAI(uint32 diff) override { - if (!events.IsInPhase(PHASE_EVENT) && !UpdateVictim()) + if (!UpdateVictim()) return; events.Update(diff); @@ -205,50 +241,46 @@ struct boss_ingvar_the_plunderer : public BossAI { switch (eventId) { - // PHASE ONE + // Human Phase case EVENT_CLEAVE: DoCastVictim(SPELL_CLEAVE); - events.ScheduleEvent(EVENT_CLEAVE, 6s, 12s, 0, PHASE_HUMAN); + events.Repeat(6s, 12s); + break; + case EVENT_SMASH: + DoCastSelf(SPELL_SMASH); + events.Repeat(12s, 16s); break; case EVENT_STAGGERING_ROAR: - DoCast(me, SPELL_STAGGERING_ROAR); - events.ScheduleEvent(EVENT_STAGGERING_ROAR, 18s, 22s, 0, PHASE_HUMAN); + DoCastSelf(SPELL_STAGGERING_ROAR); + events.Repeat(18s, 22s); break; case EVENT_ENRAGE: - DoCast(me, SPELL_ENRAGE); - events.ScheduleEvent(EVENT_ENRAGE, 7s, 14s, 0, PHASE_HUMAN); + DoCastSelf(SPELL_ENRAGE); + events.Repeat(7s, 14s); break; - case EVENT_SMASH: - DoCastAOE(SPELL_SMASH); - events.ScheduleEvent(EVENT_SMASH, 12s, 16s, 0, PHASE_HUMAN); - break; - case EVENT_JUST_TRANSFORMED: - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_UNINTERACTIBLE); - me->SetImmuneToPC(false); - ScheduleSecondPhase(); - Talk(SAY_AGGRO); - DoZoneInCombat(); - return; + + // Transition Phase case EVENT_SUMMON_BANSHEE: - DoCast(me, SPELL_SUMMON_BANSHEE); - return; - // PHASE TWO + DoCastSelf(SPELL_SUMMON_BANSHEE); + break; + + // Undead Phase case EVENT_DARK_SMASH: - DoCastVictim(SPELL_DARK_SMASH); - events.ScheduleEvent(EVENT_DARK_SMASH, 12s, 16s, 0, PHASE_UNDEAD); + DoCastSelf(SPELL_DARK_SMASH); + events.Repeat(12s, 16s); break; case EVENT_DREADFUL_ROAR: - DoCast(me, SPELL_DREADFUL_ROAR); - events.ScheduleEvent(EVENT_DREADFUL_ROAR, 18s, 22s, 0, PHASE_UNDEAD); + DoCastSelf(SPELL_DREADFUL_ROAR); + events.Repeat(18s, 22s); break; case EVENT_WOE_STRIKE: DoCastVictim(SPELL_WOE_STRIKE); - events.ScheduleEvent(EVENT_WOE_STRIKE, 10s, 14s, 0, PHASE_UNDEAD); + events.Repeat(10s, 14s); break; case EVENT_SHADOW_AXE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true)) - DoCast(target, SPELL_SHADOW_AXE_SUMMON); - events.ScheduleEvent(EVENT_SHADOW_AXE, 30s, 0, PHASE_UNDEAD); + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) + DoCast(target, SPELL_SHADOW_AXE); + events.Repeat(30s); break; default: break; @@ -258,27 +290,22 @@ struct boss_ingvar_the_plunderer : public BossAI return; } - if (!events.IsInPhase(PHASE_EVENT)) - DoMeleeAttackIfReady(); + DoMeleeAttackIfReady(); } + +private: + bool _isUnkillable; + bool _isInTransition; }; +// 24068 - Annhylde the Caller struct npc_annhylde_the_caller : public ScriptedAI { - npc_annhylde_the_caller(Creature* creature) : ScriptedAI(creature) - { - x = 0.f; - y = 0.f; - z = 0.f; - _instance = creature->GetInstanceScript(); - } + npc_annhylde_the_caller(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { } - void Reset() override + void JustAppeared() override { - _events.Reset(); - - me->GetPosition(x, y, z); - me->GetMotionMaster()->MovePoint(1, x, y, z - 15.0f); + _events.ScheduleEvent(EVENT_RESURRECTION_1, 0s); } void MovementInform(uint32 type, uint32 id) override @@ -288,28 +315,17 @@ struct npc_annhylde_the_caller : public ScriptedAI switch (id) { - case 1: - Talk(YELL_RESURRECT); - if (Creature* ingvar = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_INGVAR))) - { - ingvar->RemoveAura(SPELL_SUMMON_BANSHEE); - ingvar->CastSpell(ingvar, SPELL_SCOURG_RESURRECTION_DUMMY, true); - DoCast(ingvar, SPELL_SCOURG_RESURRECTION_BEAM); - } - _events.ScheduleEvent(EVENT_RESURRECT_1, 8s); + case POINT_CALLER_DOWN: + _events.ScheduleEvent(EVENT_RESURRECTION_3, 1s); break; - case 2: - me->DespawnOrUnsummon(); + case POINT_CALLER_UP: + _events.ScheduleEvent(EVENT_RESURRECTION_10, 1s); break; default: break; } } - void AttackStart(Unit* /*who*/) override { } - void MoveInLineOfSight(Unit* /*who*/) override { } - void JustEngagedWith(Unit* /*who*/) override { } - void UpdateAI(uint32 diff) override { _events.Update(diff); @@ -318,22 +334,51 @@ struct npc_annhylde_the_caller : public ScriptedAI { switch (eventId) { - case EVENT_RESURRECT_1: + case EVENT_RESURRECTION_1: + DoCastSelf(SPELL_ETHEREAL_TELEPORT); + _events.ScheduleEvent(EVENT_RESURRECTION_2, 2400ms); + break; + case EVENT_RESURRECTION_2: + me->GetMotionMaster()->MovePoint(POINT_CALLER_DOWN, me->GetPositionWithOffset({ 0.0f, 0.0f, -15.0f })); + break; + case EVENT_RESURRECTION_3: + Talk(SAY_RESURRECT); + _events.ScheduleEvent(EVENT_RESURRECTION_4, 2400ms); + break; + case EVENT_RESURRECTION_4: if (Creature* ingvar = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_INGVAR))) - { - ingvar->RemoveAura(SPELL_INGVAR_FEIGN_DEATH); - ingvar->CastSpell(ingvar, SPELL_SCOURG_RESURRECTION_HEAL, false); - } - _events.ScheduleEvent(EVENT_RESURRECT_2, 3s); + ingvar->RemoveAurasDueToSpell(SPELL_SUMMON_BANSHEE); + _events.ScheduleEvent(EVENT_RESURRECTION_5, 1200ms); + break; + case EVENT_RESURRECTION_5: + DoCastSelf(SPELL_SCOURGE_RESURRECTION_CHANNEL); + _events.ScheduleEvent(EVENT_RESURRECTION_6, 2400ms); + break; + case EVENT_RESURRECTION_6: + if (Creature* ingvar = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_INGVAR))) + ingvar->CastSpell(ingvar, SPELL_SCOURGE_RESURRECTION_VISUAL); + _events.ScheduleEvent(EVENT_RESURRECTION_7, 6s); break; - case EVENT_RESURRECT_2: + case EVENT_RESURRECTION_7: if (Creature* ingvar = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_INGVAR))) { - ingvar->RemoveAurasDueToSpell(SPELL_SCOURG_RESURRECTION_DUMMY); - ingvar->AI()->DoAction(ACTION_START_PHASE_2); + ingvar->RemoveAurasDueToSpell(SPELL_INGVAR_FEIGN_DEATH); + ///! HACK: Removing Feign Death changes react state to default + ingvar->SetReactState(REACT_PASSIVE); + ingvar->CastSpell(ingvar, SPELL_SCOURGE_RESURRECTION_HEAL); } - - me->GetMotionMaster()->MovePoint(2, x, y, z + 15.0f); + _events.ScheduleEvent(EVENT_RESURRECTION_8, 3600ms); + break; + case EVENT_RESURRECTION_8: + if (Creature* ingvar = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_INGVAR))) + ingvar->AI()->DoAction(ACTION_START_UNDEAD_PHASE); + _events.ScheduleEvent(EVENT_RESURRECTION_9, 1200ms); + break; + case EVENT_RESURRECTION_9: + me->GetMotionMaster()->MovePoint(POINT_CALLER_UP, me->GetPositionWithOffset({ 0.0f, 0.0f, 15.0f })); + break; + case EVENT_RESURRECTION_10: + me->DespawnOrUnsummon(); break; default: break; @@ -344,34 +389,53 @@ struct npc_annhylde_the_caller : public ScriptedAI private: InstanceScript* _instance; EventMap _events; - float x, y, z; }; +// 23997 - Ingvar Throw Dummy struct npc_ingvar_throw_dummy : public ScriptedAI { - npc_ingvar_throw_dummy(Creature* creature) : ScriptedAI(creature) { } + npc_ingvar_throw_dummy(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { } - void Reset() override + void JustAppeared() override { - if (Creature* target = me->FindNearestCreature(NPC_THROW_TARGET, 200.0f)) + DoCastSelf(SPELL_THROW_AXE); + + _scheduler.Schedule(1s, [this](TaskContext /*task*/) { - float x, y, z; - target->GetPosition(x, y, z); - me->GetMotionMaster()->MoveCharge(x, y, z); - target->DespawnOrUnsummon(); - } - else - me->DespawnOrUnsummon(); + if (Creature* target = me->FindNearestCreature(NPC_THROW_TARGET, 200.0f)) + me->GetMotionMaster()->MovePoint(POINT_AXE_TO_TARGET, target->GetPosition()); + }); + + _scheduler.Schedule(6s, 10s, [this](TaskContext /*task*/) + { + if (Creature* ingvar = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_INGVAR))) + me->GetMotionMaster()->MovePoint(POINT_AXE_TO_OWNER, ingvar->GetPosition()); + }); } void MovementInform(uint32 type, uint32 id) override { - if (type == EFFECT_MOTION_TYPE && id == EVENT_CHARGE) + if (type == POINT_MOTION_TYPE && id == POINT_AXE_TO_OWNER) { - me->CastSpell(me, SPELL_SHADOW_AXE_PERIODIC_DAMAGE, true); - me->DespawnOrUnsummon(10s); + if (Creature* ingvar = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_INGVAR))) + ingvar->AI()->DoAction(ACTION_AXE_RETURNS); + + _scheduler.Schedule(1s, [this](TaskContext /*task*/) + { + SetEquipmentSlots(false, EQUIP_UNEQUIP); + me->DespawnOrUnsummon(); + }); } } + + void UpdateAI(uint32 diff) override + { + _scheduler.Update(diff); + } + +private: + InstanceScript* _instance; + TaskScheduler _scheduler; }; // 42912 - Summon Banshee diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_keleseth.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_keleseth.cpp index 4794101ae14..06248979273 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_keleseth.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_keleseth.cpp @@ -15,157 +15,145 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* ScriptData -SDName: Boss_Prince_Keleseth -SD%Complete: 100 -SDComment: -SDCategory: Utgarde Keep -EndScriptData */ +/* + * Combat timers requires to be revisited + */ #include "ScriptMgr.h" #include "InstanceScript.h" -#include "MotionMaster.h" #include "ObjectAccessor.h" #include "ScriptedCreature.h" -#include "SpellAuras.h" +#include "SpellAuraEffects.h" #include "SpellScript.h" +#include "TemporarySummon.h" #include "utgarde_keep.h" -enum KelsethEncounter +enum KelesethTexts { - SPELL_SHADOWBOLT = 43667, - SPELL_FROST_TOMB = 48400, - SPELL_FROST_TOMB_STUN = 42672, - SPELL_FROST_TOMB_SUMMON = 42714, - - SPELL_SHADOW_FISSURE = 50657, - SPELL_FULL_HEAL = 17683, - SPELL_DECREPIFY = 42702, - SPELL_BONE_ARMOR = 59386, - - NPC_FROSTTOMB = 23965, - NPC_SKELETON = 23970, - - NPC_RUNEMAGE = 23960, - NPC_STRATEGIST = 23956, + // Prince Keleseth + SAY_AGGRO = 0, + SAY_FROST_TOMB = 1, + SAY_SLAY = 2, + SAY_SUMMON_SKELETONS = 3, + SAY_DEATH = 4, + EMOTE_FROST_TOMB = 5, + + // Vrykul Skeleton + EMOTE_RISES = 0 +}; - SAY_START_COMBAT = 1, - SAY_SUMMON_SKELETONS, - SAY_FROST_TOMB, - SAY_FROST_TOMB_EMOTE, - SAY_DEATH, +enum KelesethSpells +{ + // Prince Keleseth + SPELL_SHADOW_BOLT = 43667, + SPELL_FROST_TOMB = 42672, + + // Vrykul Skeleton + SPELL_DECREPIFY = 42702, + SPELL_BONE_ARMOR = 59386, + SPELL_SHADOW_FISSURE = 50657, + SPELL_FULL_HEAL = 17683, + SPELL_INSTAKILL_SELF = 29878, + + // Frost Tomb + SPELL_FROST_TOMB_CHANNEL = 48400, + + // Scripts + SPELL_FROST_TOMB_SUMMON = 42714 +}; - EVENT_SHADOWBOLT = 1, +enum KelesethEvents +{ + // Prince Keleseth + EVENT_SHADOW_BOLT = 1, EVENT_FROST_TOMB, EVENT_SUMMON_SKELETONS, + // Vrykul Skeleton + EVENT_ATTACK, EVENT_DECREPIFY, - EVENT_FULL_HEAL, - EVENT_SHADOW_FISSURE, - EVENT_RESURRECT, - - DATA_ON_THE_ROCKS -}; - -#define SKELETONSPAWN_Z 42.8668f - -float const SkeletonSpawnPoint[1][2] = -{ - {156.2559f, 259.2093f}, + EVENT_BONE_ARMOR, + EVENT_RESURRECT_1, + EVENT_RESURRECT_2 }; -float AttackLoc[3]= {197.636f, 194.046f, 40.8164f}; - -struct npc_frost_tomb : public ScriptedAI +enum KelesethMisc { - npc_frost_tomb(Creature* creature) : ScriptedAI(creature) - { - _instance = creature->GetInstanceScript(); - } - - void IsSummonedBy(WorldObject* summonerWO) override - { - Unit* summoner = summonerWO->ToUnit(); - if (!summoner) - return; - DoCast(summoner, SPELL_FROST_TOMB, true); - } - - void UpdateAI(uint32 /*diff*/) override { } - - void JustDied(Unit* /*killer*/) override - { - if (Creature* keleseth = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_PRINCE_KELESETH))) - keleseth->AI()->SetData(DATA_ON_THE_ROCKS, false); - } - -private: - InstanceScript* _instance; + SUMMON_GROUP_SKELETONS = 0, + DATA_ON_THE_ROCKS = 1, + ACTION_INSTAKILL_SELF = 0, + NPC_SKELETON = 23970 }; +// 23953 - Prince Keleseth struct boss_keleseth : public BossAI { - boss_keleseth(Creature* creature) : BossAI(creature, DATA_PRINCE_KELESETH) - { - Initialize(); - } - - void Initialize() - { - onTheRocks = true; - } + boss_keleseth(Creature* creature) : BossAI(creature, DATA_PRINCE_KELESETH), _onTheRocks(true) { } void Reset() override { _Reset(); - events.ScheduleEvent(EVENT_SHADOWBOLT, 2s, 3s); - events.ScheduleEvent(EVENT_FROST_TOMB, 14s, 19s); - events.ScheduleEvent(EVENT_SUMMON_SKELETONS, 6s); - Initialize(); + _onTheRocks = true; } void JustEngagedWith(Unit* who) override { BossAI::JustEngagedWith(who); - Talk(SAY_START_COMBAT); - if (!who) - return; + Talk(SAY_AGGRO); - std::list<Creature*> guards; - me->GetCreatureListWithEntryInGrid(guards, NPC_RUNEMAGE, 60.0f); - me->GetCreatureListWithEntryInGrid(guards, NPC_STRATEGIST, 60.0f); - if (!guards.empty()) - { - for (std::list<Creature*>::iterator itr = guards.begin(); itr != guards.end(); ++itr) - { - if ((*itr)->IsAlive() && (*itr)->IsWithinLOSInMap(me)) - (*itr)->AI()->AttackStart(who); - } - } + events.ScheduleEvent(EVENT_SHADOW_BOLT, 0s); + events.ScheduleEvent(EVENT_FROST_TOMB, 14s, 19s); + events.ScheduleEvent(EVENT_SUMMON_SKELETONS, 5s); + + /// @todo: Should he really call for help? Check this + me->CallForHelp(50.0f); } - void JustDied(Unit* /*killer*/) override + void KillSkeletons() { - _JustDied(); - Talk(SAY_DEATH); + std::vector<Creature*> skeletons; + GetCreatureListWithEntryInGrid(skeletons, me, NPC_SKELETON, 200.0f); + for (Creature* skeleton : skeletons) + skeleton->AI()->DoAction(ACTION_INSTAKILL_SELF); } + // Do not engage or despawn summons, killed by spells + void JustSummoned(Creature* /*summon*/) override { } + void SetData(uint32 data, uint32 value) override { if (data == DATA_ON_THE_ROCKS) - onTheRocks = value != 0; + _onTheRocks = value != 0; } uint32 GetData(uint32 data) const override { if (data == DATA_ON_THE_ROCKS) - return onTheRocks; + return _onTheRocks; return 0; } + void EnterEvadeMode(EvadeReason why) override + { + KillSkeletons(); + BossAI::EnterEvadeMode(why); + } + + void KilledUnit(Unit* /*victim*/) override + { + Talk(SAY_SLAY); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + KillSkeletons(); + Talk(SAY_DEATH); + } + void UpdateAI(uint32 diff) override { if (!UpdateVictim()) @@ -180,25 +168,22 @@ struct boss_keleseth : public BossAI { switch (eventId) { - case EVENT_SUMMON_SKELETONS: - Talk(SAY_SUMMON_SKELETONS); - SummonSkeletons(); - break; - case EVENT_SHADOWBOLT: - DoCastVictim(SPELL_SHADOWBOLT); - events.ScheduleEvent(EVENT_SHADOWBOLT, 2s, 3s); + case EVENT_SHADOW_BOLT: + DoCastVictim(SPELL_SHADOW_BOLT); + events.Repeat(2s, 3s); break; case EVENT_FROST_TOMB: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true, true, -SPELL_FROST_TOMB)) + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true, true, -SPELL_FROST_TOMB_CHANNEL)) { - Talk(SAY_FROST_TOMB); - Talk(SAY_FROST_TOMB_EMOTE, target); - - DoCast(target, SPELL_FROST_TOMB_STUN, true); - // checked from sniffs - the player casts the spell - target->CastSpell(target, SPELL_FROST_TOMB_SUMMON, true); + Talk(SAY_FROST_TOMB, target); + Talk(EMOTE_FROST_TOMB, target); + DoCast(target, SPELL_FROST_TOMB); } - events.ScheduleEvent(EVENT_FROST_TOMB, 14s, 19s); + events.Repeat(14s, 19s); + break; + case EVENT_SUMMON_SKELETONS: + Talk(SAY_SUMMON_SKELETONS); + me->SummonCreatureGroup(SUMMON_GROUP_SKELETONS); break; default: break; @@ -211,83 +196,115 @@ struct boss_keleseth : public BossAI DoMeleeAttackIfReady(); } - void SummonSkeletons() - { - // I could not found any spell cast for this - for (uint8 i = 0; i < 4; ++i) - me->SummonCreature(NPC_SKELETON, SkeletonSpawnPoint[0][0], SkeletonSpawnPoint[0][1], SKELETONSPAWN_Z, 0); - } - private: - bool onTheRocks; + bool _onTheRocks; }; +// 23970 - Vrykul Skeleton struct npc_vrykul_skeleton : public ScriptedAI { npc_vrykul_skeleton(Creature* creature) : ScriptedAI(creature) { } - void Reset() override + void InitializeAI() override { - events.Reset(); - events.ScheduleEvent(EVENT_DECREPIFY, 4s, 6s); + me->SetCorpseDelay(15, true); } - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override + void JustAppeared() override { - if (damage >= me->GetHealth()) + _events.ScheduleEvent(EVENT_ATTACK, 7s); + } + + void JustEngagedWith(Unit* /*who*/) override + { + _events.ScheduleEvent(EVENT_DECREPIFY, 4s, 6s); + if (IsHeroic()) + _events.ScheduleEvent(EVENT_BONE_ARMOR, 10s, 15s); + } + + void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override + { + // Creature is unkillable by default. But allow to kill self with spell + if (damage >= me->GetHealth() && attacker != me) { - damage = 0; + damage = me->GetHealth() - 1; // There are some issues with pets // they will still attack. I would say it is a PetAI bug if (!me->HasUnitFlag(UNIT_FLAG_UNINTERACTIBLE)) { - // from sniffs + me->SetReactState(REACT_PASSIVE); me->SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE); me->SetStandState(UNIT_STAND_STATE_DEAD); - events.Reset(); - events.ScheduleEvent(EVENT_RESURRECT, 18s, 22s); - - me->GetMotionMaster()->Clear(); - me->GetMotionMaster()->MoveIdle(); + _events.Reset(); + _events.ScheduleEvent(EVENT_RESURRECT_1, 18s, 22s); } } } + void DoAction(int32 action) override + { + if (action == ACTION_INSTAKILL_SELF) + { + /// @todo: Spell doesn't work if creature is in evade mode + DoCastSelf(SPELL_INSTAKILL_SELF, true); + me->KillSelf(); + } + } + void UpdateAI(uint32 diff) override { if (!UpdateVictim()) + { + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ATTACK: + DoZoneInCombat(); + break; + default: + break; + } + } 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_DECREPIFY: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_DECREPIFY)) - DoCast(target, SPELL_DECREPIFY); - events.ScheduleEvent(EVENT_DECREPIFY, 1s, 5s); - break; - case EVENT_RESURRECT: - events.ScheduleEvent(EVENT_FULL_HEAL, 1s); - events.ScheduleEvent(EVENT_SHADOW_FISSURE, 1s); + DoCastVictim(SPELL_DECREPIFY); + _events.Repeat(1s, 5s); break; - case EVENT_FULL_HEAL: - DoCast(me, SPELL_FULL_HEAL, true); + case EVENT_BONE_ARMOR: + /// It is unclear how exactly this ability is used + if (roll_chance_i(50)) + DoCastSelf(SPELL_BONE_ARMOR); + _events.Repeat(20s, 30s); break; - case EVENT_SHADOW_FISSURE: - DoCast(me, SPELL_SHADOW_FISSURE, true); - DoCastAOE(SPELL_BONE_ARMOR, true); + case EVENT_RESURRECT_1: + DoCastSelf(SPELL_SHADOW_FISSURE); + DoCastSelf(SPELL_FULL_HEAL); me->RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE); me->SetStandState(UNIT_STAND_STATE_STAND); - me->GetMotionMaster()->MoveChase(me->GetVictim()); - events.ScheduleEvent(EVENT_DECREPIFY, 4s, 6s); + Talk(EMOTE_RISES); + _events.ScheduleEvent(EVENT_RESURRECT_2, 1s); + break; + case EVENT_RESURRECT_2: + me->SetReactState(REACT_AGGRESSIVE); + _events.ScheduleEvent(EVENT_DECREPIFY, 4s, 6s); + if (IsHeroic()) + _events.ScheduleEvent(EVENT_BONE_ARMOR, 10s, 15s); break; default: break; @@ -297,20 +314,104 @@ struct npc_vrykul_skeleton : public ScriptedAI return; } - if (!me->HasUnitFlag(UNIT_FLAG_UNINTERACTIBLE)) - DoMeleeAttackIfReady(); + DoMeleeAttackIfReady(); } private: - EventMap events; + EventMap _events; +}; + +// 23965 - Frost Tomb +struct npc_frost_tomb : public ScriptedAI +{ + npc_frost_tomb(Creature* creature) : ScriptedAI(creature), _isKilled(false), _instance(creature->GetInstanceScript()) { } + + void InitializeAI() override + { + me->SetReactState(REACT_PASSIVE); + } + + void JustAppeared() override + { + if (TempSummon* summon = me->ToTempSummon()) + if (Unit* summoner = summon->GetSummonerUnit()) + DoCast(summoner, SPELL_FROST_TOMB_CHANNEL); + } + + void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override + { + if (damage >= me->GetHealth()) + { + damage = me->GetHealth() -1; + + if (_isKilled) + return; + + _isKilled = true; + + _scheduler.Schedule(0s, [this](TaskContext task) + { + switch (task.GetRepeatCounter()) + { + case 0: + if (Creature* keleseth = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_PRINCE_KELESETH))) + keleseth->AI()->SetData(DATA_ON_THE_ROCKS, false); + me->InterruptNonMeleeSpells(false); + task.Repeat(1s); + break; + case 1: + me->SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE); + task.Repeat(1s); + break; + case 2: + me->DespawnOrUnsummon(); + break; + default: + break; + } + }); + } + } + + void UpdateAI(uint32 diff) override + { + _scheduler.Update(diff); + } + +private: + bool _isKilled; + TaskScheduler _scheduler; + InstanceScript* _instance; +}; + +// 42672 - Frost Tomb +class spell_keleseth_frost_tomb_periodic : public AuraScript +{ + PrepareAuraScript(spell_keleseth_frost_tomb_periodic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_FROST_TOMB_SUMMON }); + } + + void OnPeriodic(AuraEffect const* aurEff) + { + if (aurEff->GetTickNumber() == 1) + GetTarget()->CastSpell(GetTarget(), SPELL_FROST_TOMB_SUMMON, true); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_keleseth_frost_tomb_periodic::OnPeriodic, EFFECT_1, SPELL_AURA_PERIODIC_DUMMY); + } }; // 48400 - Frost Tomb -class spell_frost_tomb : public AuraScript +class spell_keleseth_frost_tomb_channel : public AuraScript { - PrepareAuraScript(spell_frost_tomb); + PrepareAuraScript(spell_keleseth_frost_tomb_channel); - void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEATH) if (Unit* caster = GetCaster()) @@ -321,7 +422,7 @@ class spell_frost_tomb : public AuraScript void Register() override { - AfterEffectRemove += AuraEffectRemoveFn(spell_frost_tomb::OnRemove, EFFECT_0, SPELL_AURA_MOD_STUN, AURA_EFFECT_HANDLE_REAL); + AfterEffectRemove += AuraEffectRemoveFn(spell_keleseth_frost_tomb_channel::AfterRemove, EFFECT_0, SPELL_AURA_MOD_STUN, AURA_EFFECT_HANDLE_REAL); } }; @@ -332,6 +433,7 @@ class achievement_on_the_rocks : public AchievementCriteriaScript bool OnCheck(Player* /*source*/, Unit* target) override { + // todo: migrate to worldstate 3895 (worldstateexpression 6312) return target && target->GetAI() && target->GetAI()->GetData(DATA_ON_THE_ROCKS); } }; @@ -341,6 +443,7 @@ void AddSC_boss_keleseth() RegisterUtgardeKeepCreatureAI(boss_keleseth); RegisterUtgardeKeepCreatureAI(npc_frost_tomb); RegisterUtgardeKeepCreatureAI(npc_vrykul_skeleton); - RegisterSpellScript(spell_frost_tomb); + RegisterSpellScript(spell_keleseth_frost_tomb_periodic); + RegisterSpellScript(spell_keleseth_frost_tomb_channel); new achievement_on_the_rocks(); } diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_skarvald_dalronn.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_skarvald_dalronn.cpp index eab6e918cc5..86da67aee28 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_skarvald_dalronn.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_skarvald_dalronn.cpp @@ -15,12 +15,9 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* ScriptData -SDName: Boss_Skarvald_Dalronn -SD%Complete: 95 -SDComment: Needs adjustments to blizzlike timers -SDCategory: Utgarde Keep -EndScriptData */ +/* + * Combat timers requires to be revisited + */ #include "ScriptMgr.h" #include "InstanceScript.h" @@ -28,53 +25,67 @@ EndScriptData */ #include "ScriptedCreature.h" #include "utgarde_keep.h" -enum Texts +enum SkarvaldTexts { - // Texts are common for both bosses and their ghosts. - SAY_AGGRO = 0, - SAY_DEATH = 1, // Said once both bosses are dead. - SAY_DIED_FIRST = 2, // Said by the first boss that dies. - SAY_KILL = 3, - SAY_DEATH_RESPONSE = 4 // Said by the boss alive after the first one dies. - + // Texts are common for both bosses. + SAY_AGGRO = 0, + SAY_DEATH = 1, // Said once both bosses are dead. + SAY_DIED_FIRST = 2, // Said by the first boss that dies. + SAY_SLAY = 3, + SAY_DEATH_RESPONSE = 4 // Said by the boss alive after the first one dies. }; -enum Spells +enum SkarvaldSpells { - // Spells of Skarvald and his Ghost - SPELL_CHARGE = 43651, - SPELL_STONE_STRIKE = 48583, - SPELL_ENRAGE = 48193, - SPELL_SUMMON_SKARVALD_GHOST = 48613, - - // Spells of Dalronn and his Ghost - SPELL_SHADOW_BOLT = 43649, - SPELL_SUMMON_SKELETONS = 52611, - SPELL_DEBILITATE = 43650, - SPELL_SUMMON_DALRONN_GHOST = 48612, + // Skarvald the Constructor + SPELL_CHARGE = 43651, + SPELL_STONE_STRIKE = 48583, + SPELL_ENRAGE = 48193, + SPELL_SUMMON_SKARVALD_GHOST = 48613, + + // Dalronn the Controller + SPELL_SHADOW_BOLT = 43649, + SPELL_SUMMON_SKELETONS = 52611, + SPELL_DEBILITATE = 43650, + SPELL_SUMMON_DALRONN_GHOST = 48612, + + // Ghosts + SPELL_GHOST_VISUAL = 22650, + SPELL_PERIODIC_HEAL = 48591, + + // Shared + SPELL_PERMANENT_FEIGN_DEATH = 29266, + SPELL_QUIET_SUICIDE = 3617 }; -enum Events +enum SkarvaldEvents { // Skarvald the Constructor - EVENT_SKARVALD_CHARGE = 1, + EVENT_SKARVALD_CHARGE = 1, EVENT_STONE_STRIKE, + EVENT_ENRAGE, // Dalronn the Controller EVENT_SHADOW_BOLT, EVENT_DEBILITATE, EVENT_SUMMON_SKELETONS, - EVENT_DELAYED_AGGRO_SAY, // Dalronn's SAY_AGGRO is delayed so it doesn't overlap Skarvald's one. + EVENT_DELAYED_AGGRO_SAY, + + // Common + EVENT_DEATH_RESPONSE, + EVENT_FEIGN_DEATH +}; - // Common event to both bosses. - // Delays SAY_DEATH_RESPONSE so it doesn't overlap with the SAY_DIED_FIRST from the boss that has just died. - EVENT_DEATH_RESPONSE +enum SkarvaldActions +{ + ACTION_OTHER_FEIGNS_DEATH = 1, + ACTION_CLEANUP_AND_DIE = 2 }; -enum Actions +enum SkarvaldCreatures { - ACTION_OTHER_JUST_DIED = 1, - ACTION_DESPAWN_SUMMONS = 2 // Only needed to clear off the ghosts when the second boss dies. + NPC_DALRONN_GHOST = 27389, + NPC_SKARVALD_GHOST = 27390 }; class SkarvaldChargePredicate @@ -91,23 +102,37 @@ class SkarvaldChargePredicate Unit* _me; }; -struct generic_boss_controllerAI : public BossAI +struct ControllerBaseAI : public BossAI { - generic_boss_controllerAI(Creature* creature) : BossAI(creature, DATA_SKARVALD_DALRONN) + ControllerBaseAI(Creature* creature) : BossAI(creature, DATA_SKARVALD_DALRONN) { OtherBossData = 0; IsInGhostForm = me->GetEntry() == NPC_SKARVALD_GHOST || me->GetEntry() == NPC_DALRONN_GHOST; + OtherFeignsDeath = false; + IsAboutToFeignDeath = false; + } + + void JustAppeared() override + { + if (!IsInGhostForm) + return; + + DoCastSelf(SPELL_GHOST_VISUAL); + DoCastSelf(SPELL_PERIODIC_HEAL); + + DoZoneInCombat(); } void Reset() override { - if (IsInGhostForm) + if (!IsInGhostForm) { - // Call this here since ghosts aren't set in combat as they spawn. - DoZoneInCombat(me); - } - else _Reset(); + OtherFeignsDeath = false; + IsAboutToFeignDeath = false; + me->RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE); + me->SetReactState(REACT_AGGRESSIVE); + } } void JustEngagedWith(Unit* who) override @@ -116,81 +141,108 @@ struct generic_boss_controllerAI : public BossAI BossAI::JustEngagedWith(who); } - void JustDied(Unit* /*killer*/) override + void DamageTaken(Unit* who, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override { - if (Creature* otherBoss = ObjectAccessor::GetCreature(*me, instance->GetGuidData(OtherBossData))) + if (OtherFeignsDeath) + return; + + if (damage >= me->GetHealth() && who != me) { - if (otherBoss->IsAlive()) - { - Talk(SAY_DIED_FIRST); - me->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE); - otherBoss->AI()->DoAction(ACTION_OTHER_JUST_DIED); - DoCast(me, OtherBossData == DATA_DALRONN ? SPELL_SUMMON_SKARVALD_GHOST : SPELL_SUMMON_DALRONN_GHOST, true); - } - else + damage = me->GetHealth() -1; + + if (!IsAboutToFeignDeath) { - Talk(SAY_DEATH); - otherBoss->AI()->DoAction(ACTION_DESPAWN_SUMMONS); - _JustDied(); + IsAboutToFeignDeath = true; + + if (Creature* otherBoss = ObjectAccessor::GetCreature(*me, instance->GetGuidData(OtherBossData))) + otherBoss->AI()->DoAction(ACTION_OTHER_FEIGNS_DEATH); + + events.Reset(); + events.ScheduleEvent(EVENT_FEIGN_DEATH, 0s); } } } - void DoAction(int32 actionId) override + void DoAction(int32 action) override { - switch (actionId) + switch (action) { - case ACTION_OTHER_JUST_DIED: - events.ScheduleEvent(EVENT_DEATH_RESPONSE, 2s); + case ACTION_OTHER_FEIGNS_DEATH: + events.ScheduleEvent(EVENT_DEATH_RESPONSE, 4s); + OtherFeignsDeath = true; break; - case ACTION_DESPAWN_SUMMONS: + case ACTION_CLEANUP_AND_DIE: summons.DespawnAll(); + DoCastSelf(SPELL_QUIET_SUICIDE, true); + me->RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE); break; default: break; } } - void ExecuteEvent(uint32 eventId) override + void KilledUnit(Unit* who) override { - if (eventId == EVENT_DEATH_RESPONSE) - Talk(SAY_DEATH_RESPONSE); + if (!IsInGhostForm && who->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_SLAY); } - void KilledUnit(Unit* who) override + void JustDied(Unit* /*killer*/) override { - if (!IsInGhostForm && who->GetTypeId() == TYPEID_PLAYER) - Talk(SAY_KILL); + if (OtherFeignsDeath) + { + Talk(SAY_DEATH); + _JustDied(); + + if (Creature* otherBoss = ObjectAccessor::GetCreature(*me, instance->GetGuidData(OtherBossData))) + otherBoss->AI()->DoAction(ACTION_CLEANUP_AND_DIE); + } + } + + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_DEATH_RESPONSE: + Talk(SAY_DEATH_RESPONSE); + break; + case EVENT_FEIGN_DEATH: + Talk(SAY_DIED_FIRST); + DoCastSelf(OtherBossData == DATA_DALRONN ? SPELL_SUMMON_SKARVALD_GHOST : SPELL_SUMMON_DALRONN_GHOST, true); + DoCastSelf(SPELL_PERMANENT_FEIGN_DEATH); + me->SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE); + me->SetReactState(REACT_PASSIVE); + break; + default: + break; + } } - protected: - uint32 OtherBossData; - bool IsInGhostForm; +protected: + uint32 OtherBossData; + bool IsInGhostForm; + bool OtherFeignsDeath; + bool IsAboutToFeignDeath; }; -struct boss_skarvald_the_constructor : public generic_boss_controllerAI +// 24200, 27390 - Skarvald the Constructor +struct boss_skarvald_the_constructor : public ControllerBaseAI { - boss_skarvald_the_constructor(Creature* creature) : generic_boss_controllerAI(creature) + boss_skarvald_the_constructor(Creature* creature) : ControllerBaseAI(creature) { OtherBossData = DATA_DALRONN; - Enraged = false; - } - - void Reset() override - { - Enraged = false; - generic_boss_controllerAI::Reset(); } void JustEngagedWith(Unit* who) override { - generic_boss_controllerAI::JustEngagedWith(who); + ControllerBaseAI::JustEngagedWith(who); if (!IsInGhostForm) Talk(SAY_AGGRO); events.ScheduleEvent(EVENT_SKARVALD_CHARGE, 5s); events.ScheduleEvent(EVENT_STONE_STRIKE, 10s); + events.ScheduleEvent(EVENT_ENRAGE, 10s, 15s); } void ExecuteEvent(uint32 eventId) override @@ -200,49 +252,42 @@ struct boss_skarvald_the_constructor : public generic_boss_controllerAI case EVENT_SKARVALD_CHARGE: if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, SkarvaldChargePredicate(me))) DoCast(target, SPELL_CHARGE); - events.ScheduleEvent(EVENT_CHARGE, 5s, 10s); + events.Repeat(5s, 10s); break; case EVENT_STONE_STRIKE: DoCastVictim(SPELL_STONE_STRIKE); - events.ScheduleEvent(EVENT_STONE_STRIKE, 5s, 10s); + events.Repeat(5s, 10s); + break; + case EVENT_ENRAGE: + DoCastSelf(SPELL_ENRAGE); + events.Repeat(20s, 30s); break; default: - generic_boss_controllerAI::ExecuteEvent(eventId); + ControllerBaseAI::ExecuteEvent(eventId); break; } } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override - { - if (!Enraged && !IsInGhostForm && me->HealthBelowPctDamaged(15, damage)) - { - Enraged = true; - DoCast(me, SPELL_ENRAGE); - } - } - private: - bool Enraged; }; -struct boss_dalronn_the_controller : public generic_boss_controllerAI +// 24201, 27389 - Dalronn the Controller +struct boss_dalronn_the_controller : public ControllerBaseAI { - boss_dalronn_the_controller(Creature* creature) : generic_boss_controllerAI(creature) + boss_dalronn_the_controller(Creature* creature) : ControllerBaseAI(creature) { OtherBossData = DATA_SKARVALD; } void JustEngagedWith(Unit* who) override { - generic_boss_controllerAI::JustEngagedWith(who); + ControllerBaseAI::JustEngagedWith(who); events.ScheduleEvent(EVENT_SHADOW_BOLT, 1s); events.ScheduleEvent(EVENT_DEBILITATE, 5s); - - if (!IsInGhostForm) - events.ScheduleEvent(EVENT_DELAYED_AGGRO_SAY, 5s); - if (IsHeroic()) events.ScheduleEvent(EVENT_SUMMON_SKELETONS, 10s); + + if (!IsInGhostForm) + events.ScheduleEvent(EVENT_DELAYED_AGGRO_SAY, 6s); } void ExecuteEvent(uint32 eventId) override @@ -252,22 +297,22 @@ struct boss_dalronn_the_controller : public generic_boss_controllerAI case EVENT_SHADOW_BOLT: if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true)) DoCast(target, SPELL_SHADOW_BOLT); - events.ScheduleEvent(EVENT_SHADOW_BOLT, 2100ms); //give a 100ms pause to try cast other spells + events.Repeat(2s, 4s); break; case EVENT_DEBILITATE: if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 50.0f, true)) DoCast(target, SPELL_DEBILITATE); - events.ScheduleEvent(EVENT_DEBILITATE, 5s, 10s); + events.Repeat(5s, 10s); break; case EVENT_SUMMON_SKELETONS: - DoCast(me, SPELL_SUMMON_SKELETONS); - events.ScheduleEvent(EVENT_SUMMON_SKELETONS, 10s, 30s); + DoCastSelf(SPELL_SUMMON_SKELETONS); + events.Repeat(10s, 30s); break; case EVENT_DELAYED_AGGRO_SAY: Talk(SAY_AGGRO); break; default: - generic_boss_controllerAI::ExecuteEvent(eventId); + ControllerBaseAI::ExecuteEvent(eventId); break; } } diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/instance_utgarde_keep.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/instance_utgarde_keep.cpp index fe305deb4cd..28cbb5c79cc 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/instance_utgarde_keep.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/instance_utgarde_keep.cpp @@ -28,13 +28,6 @@ DoorData const doorData[] = { 0, 0, DOOR_TYPE_ROOM } // END }; -MinionData const minionData[] = -{ - { NPC_SKARVALD, DATA_SKARVALD_DALRONN }, - { NPC_DALRONN, DATA_SKARVALD_DALRONN }, - { 0, 0 } -}; - class instance_utgarde_keep : public InstanceMapScript { public: @@ -47,7 +40,6 @@ class instance_utgarde_keep : public InstanceMapScript SetHeaders(DataHeader); SetBossNumber(EncounterCount); LoadDoorData(doorData); - LoadMinionData(minionData); } void OnCreatureCreate(Creature* creature) override @@ -59,11 +51,9 @@ class instance_utgarde_keep : public InstanceMapScript break; case NPC_SKARVALD: SkarvaldGUID = creature->GetGUID(); - AddMinion(creature, true); break; case NPC_DALRONN: DalronnGUID = creature->GetGUID(); - AddMinion(creature, true); break; case NPC_INGVAR: IngvarGUID = creature->GetGUID(); @@ -73,19 +63,6 @@ class instance_utgarde_keep : public InstanceMapScript } } - void OnCreatureRemove(Creature* creature) override - { - switch (creature->GetEntry()) - { - case NPC_SKARVALD: - case NPC_DALRONN: - AddMinion(creature, false); - break; - default: - break; - } - } - void OnGameObjectCreate(GameObject* go) override { InstanceScript::OnGameObjectCreate(go); diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.h b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.h index 386eb7c1bbf..8721dc1ef52 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.h +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.h @@ -46,16 +46,7 @@ enum UKCreatureIds NPC_PRINCE_KELESETH = 23953, NPC_SKARVALD = 24200, NPC_DALRONN = 24201, - NPC_INGVAR = 23954, - - // Skarvald - Dalronn - NPC_DALRONN_GHOST = 27389, - NPC_SKARVALD_GHOST = 27390, - - // Ingvar the Plunderer - NPC_INGVAR_UNDEAD = 23980, - NPC_THROW_TARGET = 23996, - NPC_ANNHYLDE_THE_CALLER = 24068 + NPC_INGVAR = 23954 }; enum UKGameObjectIds |
