aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoroffl <11556157+offl@users.noreply.github.com>2025-11-09 16:29:36 +0200
committerGitHub <noreply@github.com>2025-11-09 15:29:36 +0100
commit0f8a33ca4cabe923c4611e7d0221c8819979edaa (patch)
tree2fe6c2b8d96ff6261f9b78939be5904dd06f0366
parent475808d626958ccf22563f1415ae3381d173c062 (diff)
Scripts/Utgarde Keep: Rewrite scripts (#31349)
Closes #26822
-rw-r--r--sql/updates/world/3.3.5/2025_11_09_00_world.sql47
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp434
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_keleseth.cpp421
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_skarvald_dalronn.cpp255
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/instance_utgarde_keep.cpp23
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.h11
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