From 479848ec5d44727926fce14886d5a4615b748016 Mon Sep 17 00:00:00 2001 From: Crow Date: Fri, 11 Sep 2026 18:57:18 -0500 Subject: [PATCH] fix(Scripts/MagtheridonsLair): anchor the release countdown to the Channeler pull (#27600) Co-authored-by: Claude Opus 5 --- .../MagtheridonsLair/boss_magtheridon.cpp | 118 +++++++++++++----- .../instance_magtheridons_lair.cpp | 22 +++- .../MagtheridonsLair/magtheridons_lair.h | 6 +- 3 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp index f97519f04f..51077a5217 100644 --- a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp @@ -72,22 +72,25 @@ enum Groups struct boss_magtheridon : public BossAI { - boss_magtheridon(Creature* creature) : BossAI(creature, DATA_MAGTHERIDON) + boss_magtheridon(Creature* creature) : BossAI(creature, DATA_MAGTHERIDON), + _castingQuake(false), _magReleased(false), _currentPhase(0) { } void Reset() override { - BossAI::Reset(); + // State first: BossAI::Reset() calls back into this AI through the instance. _currentPhase = 0; _castingQuake = false; _magReleased = false; + + BossAI::Reset(); _interruptScheduler.CancelAll(); scheduler.Schedule(90s, [this](TaskContext context) { - if (!me->IsEngaged()) - { + // Engaged from the Channeler pull, so key the idle taunt on the release, not on combat. + if (!_magReleased) Talk(SAY_TAUNT); - } + context.Repeat(90s); }); DoCastSelf(SPELL_SHADOW_CAGE, true); @@ -166,6 +169,7 @@ struct boss_magtheridon : public BossAI context.Repeat(56300ms, 64300ms); }).Schedule(55650ms, [this](TaskContext context) { + Talk(SAY_EMOTE_NOVA); DoCastSelf(SPELL_BLAST_NOVA); scheduler.DelayAll(10s); context.Repeat(54350ms, 55400ms); @@ -175,27 +179,31 @@ struct boss_magtheridon : public BossAI }); } - void DoAction(int32 action) override + // Both release paths land here: the 2 minute timer and the last Channeler dying. + void ReleaseMagtheridon() { - if (action == ACTION_RELEASE_MAGTHERIDON) - { - if (_magReleased) - return; + if (_magReleased) + return; - Talk(SAY_EMOTE_FREE); - Talk(SAY_FREE); - scheduler.CancelGroup(GROUP_EARLY_RELEASE_CHECK); //cancel regular countdown - _magReleased = true; - scheduler.Schedule(3s, [this](TaskContext) - { - ScheduleCombatEvents(); - }); - } - else if (action == ACTION_BANISH_SELF) + _magReleased = true; + Talk(SAY_EMOTE_FREE); + Talk(SAY_FREE); + scheduler.Schedule(3s, [this](TaskContext /*context*/) { - Talk(SAY_BANISH); - me->CastSpell(me, SPELL_SHADOW_CAGE_STUN, true); - } + ScheduleCombatEvents(); + }); + } + + // Caged, he keeps UNIT_FLAG_IMMUNE_TO_PC, so every threat reference from the instance's + // SetInCombatWithZone() lands offline (ThreatReference::ShouldBeOffline) and the threat manager + // never engages him. Engage off the combat reference itself, so the encounter anchors to the + // Channeler pull and not to his release. + void JustEnteredCombat(Unit* who) override + { + if (IsEngaged()) + return; + + EngagementStart(who); } void JustEngagedWith(Unit* who) override @@ -208,15 +216,67 @@ struct boss_magtheridon : public BossAI Talk(SAY_EMOTE_NEARLY); }).Schedule(120s, GROUP_EARLY_RELEASE_CHECK, [this](TaskContext /*context*/) { - Talk(SAY_EMOTE_FREE); - Talk(SAY_FREE); - _magReleased = true; - }).Schedule(123s, GROUP_EARLY_RELEASE_CHECK, [this](TaskContext /*context*/) - { - ScheduleCombatEvents(); + ReleaseMagtheridon(); }); } + // Caged, he is engaged but never fights, so a wipe gets here through his own UpdateVictim() or + // through the instance, whichever creature updates first. BossAI's evade would trip + // CREATURE_FLAG_EXTRA_HARD_RESET and despawn him for 20s in front of the raid. Reset in place + // instead. Released, the normal boss evade applies. + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override + { + if (_magReleased) + { + BossAI::EnterEvadeMode(why); + return; + } + + // Also covers the re-entry from Reset() via instance->SetBossState(NOT_STARTED): + // EngagementOver() already ran. + if (!IsEngaged()) + return; + + me->CombatStop(true); + // BossAI::_Reset() bails while engaged, so end the engagement first. Reset() then drops the + // countdown through scheduler.CancelAll(). + EngagementOver(); + // He never walks home, so clear what BossAI::_JustReachedHome() would have. + me->setActive(false); + Reset(); + } + + // Read by the instance: IsImmuneToPC() lags the release by the 3s until ScheduleCombatEvents(). + uint32 GetData(uint32 type) const override + { + return type == DATA_MAGTHERIDON_RELEASED ? uint32(_magReleased) : 0; + } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_RELEASE_MAGTHERIDON: + // Channelers died before the timer ran out. Drop the rest of it, or the 60s task + // still calls him nearly free after he is free. + scheduler.CancelGroup(GROUP_EARLY_RELEASE_CHECK); + ReleaseMagtheridon(); + break; + case ACTION_BANISH_SELF: + Talk(SAY_BANISH); + DoCastSelf(SPELL_SHADOW_CAGE_STUN, true); + break; + case ACTION_RESET_ENCOUNTER: + // Wipe signal from the instance. UpdateVictim() catches most wipes on its own, but + // not a survivor outside the room still holding him in combat. Freed, he fights on. + if (!_magReleased) + EnterEvadeMode(EVADE_REASON_OTHER); + break; + default: + break; + } + } + void UpdateAI(uint32 diff) override { scheduler.Update(diff); diff --git a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/instance_magtheridons_lair.cpp b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/instance_magtheridons_lair.cpp index 13e44cc26c..3548eb9725 100644 --- a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/instance_magtheridons_lair.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/instance_magtheridons_lair.cpp @@ -106,9 +106,17 @@ public: void OnCreatureEvade(Creature* creature) override { - // Phase-1 wipe signal: Mag is ImmuneToPC so BossAI evade does not fire; a Channeler evade is the trigger. - if (creature->GetEntry() == NPC_HELLFIRE_CHANNELER && GetBossState(DATA_MAGTHERIDON) == IN_PROGRESS) - SetBossState(DATA_MAGTHERIDON, NOT_STARTED); + if (creature->GetEntry() != NPC_HELLFIRE_CHANNELER || GetBossState(DATA_MAGTHERIDON) != IN_PROGRESS) + return; + + // Only a Channeler-phase wipe resets the encounter. Past the 2 minute auto-release he is + // loose with Channelers still up, and a reset there would disable the Manticron Cubes and + // open the door mid-fight. + if (Creature* magtheridon = instance->GetCreature(_magtheridonGUID)) + if (magtheridon->AI()->GetData(DATA_MAGTHERIDON_RELEASED)) + return; + + SetBossState(DATA_MAGTHERIDON, NOT_STARTED); } void OnGameObjectCreate(GameObject* go) override @@ -191,6 +199,10 @@ public: for (ObjectGuid const& guid : _burningAbyssalsSet) if (Creature* abyssal = instance->GetCreature(guid)) abyssal->DespawnOrUnsummon(); + + // Reset a still-caged Magtheridon: he is engaged from the Channeler pull on. + if (Creature* magtheridon = instance->GetCreature(_magtheridonGUID)) + magtheridon->AI()->DoAction(ACTION_RESET_ENCOUNTER); } } } @@ -202,7 +214,9 @@ public: switch (type) { case DATA_CHANNELER_COMBAT: - // Force the encounter start: Mag is ImmuneToPC so SetInCombatWithZone alone may miss JustEngagedWith. + // Start the encounter on the Channeler pull. The combat references this creates + // engage Magtheridon (boss_magtheridon::JustEnteredCombat), which anchors his + // release countdown here and not to when players can first hit him. if (GetBossState(DATA_MAGTHERIDON) != IN_PROGRESS) { SetBossState(DATA_MAGTHERIDON, IN_PROGRESS); diff --git a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/magtheridons_lair.h b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/magtheridons_lair.h index d3e9caa143..537d3d7f68 100644 --- a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/magtheridons_lair.h +++ b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/magtheridons_lair.h @@ -30,7 +30,8 @@ enum DataTypes DATA_CHANNELER_COMBAT = 10, DATA_ACTIVATE_CUBES = 11, - DATA_COLLAPSE = 12 + DATA_COLLAPSE = 12, + DATA_MAGTHERIDON_RELEASED = 13 }; enum NpcIds @@ -46,7 +47,8 @@ enum NpcIds enum MagtheridonActions { ACTION_RELEASE_MAGTHERIDON = 1, - ACTION_BANISH_SELF = 2 + ACTION_BANISH_SELF = 2, + ACTION_RESET_ENCOUNTER = 3 }; enum GoIds