diff options
author | Andrew <47818697+Nyeriah@users.noreply.github.com> | 2025-09-23 05:49:23 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-09-23 10:49:23 +0200 |
commit | 78dea88d5d0e8532f2c926e1558ebb05f8e04894 (patch) | |
tree | 208d717f9c0f3dc5d9ef0f6b286f2571615c29f8 /src/server | |
parent | 7a0b9785bb5ae22a99e269d8f462295bb43aedc4 (diff) |
fix(Scripts/AzjolNerub): Fix Anubarak impale sequence (#22717)
Diffstat (limited to 'src/server')
3 files changed, 125 insertions, 64 deletions
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 1b6733e503..043fa814bd 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -746,27 +746,39 @@ void BossAI::UpdateAI(uint32 diff) DoMeleeAttackIfReady(); } +void BossAI::OnSpellCastFinished(SpellInfo const* spellInfo, SpellFinishReason reason) +{ + ScriptedAI::OnSpellCastFinished(spellInfo, reason); + // Check if any health check events are pending (i.e. waiting for the boss to stop casting. + if (_nextHealthCheck.IsPending() && me->IsInCombat()) + { + _nextHealthCheck.UpdateStatus(HEALTH_CHECK_PROCESSED); + // This must be delayed because creature might still have unit state casting at this point, which might break scripts. + scheduler.Schedule(1s, [this](TaskContext context) + { + if (me->HasUnitState(UNIT_STATE_CASTING)) + context.Repeat(); + else + ProcessHealthCheck(); + }); + } +} + void BossAI::DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask) { ScriptedAI::DamageTaken(attacker, damage, damagetype, damageSchoolMask); - if (_nextHealthCheck._valid) + if (!_nextHealthCheck.HasBeenProcessed()) { - if (!_nextHealthCheck._allowedWhileCasting && me->HasUnitState(UNIT_STATE_CASTING)) - return; - if (me->HealthBelowPctDamaged(_nextHealthCheck._healthPct, damage)) { - _nextHealthCheck._exec(); - _nextHealthCheck._valid = false; - - _healthCheckEvents.remove_if([&](HealthCheckEventData data) -> bool + if (!_nextHealthCheck._allowedWhileCasting && me->HasUnitState(UNIT_STATE_CASTING)) { - return data._healthPct == _nextHealthCheck._healthPct; - }); + _nextHealthCheck.UpdateStatus(HEALTH_CHECK_PENDING); + return; + } - if (!_healthCheckEvents.empty()) - _nextHealthCheck = _healthCheckEvents.front(); + ProcessHealthCheck(); } } } @@ -780,18 +792,32 @@ void BossAI::DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damage */ void BossAI::ScheduleHealthCheckEvent(uint32 healthPct, std::function<void()> exec, bool allowedWhileCasting /*=true*/) { - _healthCheckEvents.push_back(HealthCheckEventData(healthPct, exec, true, allowedWhileCasting)); + _healthCheckEvents.push_back(HealthCheckEventData(healthPct, exec, HEALTH_CHECK_SCHEDULED, allowedWhileCasting)); _nextHealthCheck = _healthCheckEvents.front(); }; void BossAI::ScheduleHealthCheckEvent(std::initializer_list<uint8> healthPct, std::function<void()> exec, bool allowedWhileCasting /*=true*/) { for (auto const& checks : healthPct) - _healthCheckEvents.push_back(HealthCheckEventData(checks, exec, true, allowedWhileCasting)); + _healthCheckEvents.push_back(HealthCheckEventData(checks, exec, HEALTH_CHECK_SCHEDULED, allowedWhileCasting)); _nextHealthCheck = _healthCheckEvents.front(); } +void BossAI::ProcessHealthCheck() +{ + _nextHealthCheck.UpdateStatus(HEALTH_CHECK_PROCESSED); + _nextHealthCheck._exec(); + + _healthCheckEvents.remove_if([&](HealthCheckEventData data) -> bool + { + return data._healthPct == _nextHealthCheck._healthPct; + }); + + if (!_healthCheckEvents.empty()) + _nextHealthCheck = _healthCheckEvents.front(); +} + void BossAI::ScheduleEnrageTimer(uint32 spellId, Milliseconds timer, uint8 textId /*= 0*/) { me->m_Events.AddEventAtOffset([this, spellId, textId] diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 82f50542b3..f3d9e11a51 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -454,14 +454,27 @@ private: std::unordered_set<uint32> _uniqueTimedEvents; }; +enum HealthCheckStatus +{ + HEALTH_CHECK_PROCESSED, + HEALTH_CHECK_SCHEDULED, + HEALTH_CHECK_PENDING +}; + struct HealthCheckEventData { - HealthCheckEventData(uint8 healthPct, std::function<void()> exec, bool valid = true, bool allowedWhileCasting = true) : _healthPct(healthPct), _exec(exec), _valid(valid), _allowedWhileCasting(allowedWhileCasting) { }; + HealthCheckEventData(uint8 healthPct, std::function<void()> exec, uint8 status = HEALTH_CHECK_SCHEDULED, bool allowedWhileCasting = true, Milliseconds Delay = 0s) : _healthPct(healthPct), _exec(exec), _status(status), _allowedWhileCasting(allowedWhileCasting), _delay(Delay) { }; uint8 _healthPct; std::function<void()> _exec; - bool _valid; + uint8 _status; bool _allowedWhileCasting; + Milliseconds _delay; + + [[nodiscard]] bool HasBeenProcessed() const { return _status == HEALTH_CHECK_PROCESSED; }; + [[nodiscard]] bool IsPending() const { return _status == HEALTH_CHECK_PENDING; }; + [[nodiscard]] Milliseconds GetDelay() const { return _delay; }; + void UpdateStatus(uint8 status) { _status = status; }; }; class BossAI : public ScriptedAI @@ -476,6 +489,7 @@ public: bool CanRespawn() override; + void OnSpellCastFinished(SpellInfo const* spell, SpellFinishReason reason) override; void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask) override; void JustSummoned(Creature* summon) override; void SummonedCreatureDespawn(Creature* summon) override; @@ -485,6 +499,7 @@ public: void ScheduleHealthCheckEvent(uint32 healthPct, std::function<void()> exec, bool allowedWhileCasting = true); void ScheduleHealthCheckEvent(std::initializer_list<uint8> healthPct, std::function<void()> exec, bool allowedWhileCasting = true); + void ProcessHealthCheck(); // @brief Casts the spell after the fixed time and says the text id if provided. Timer will run even if the creature is casting or out of combat. // @param spellId The spell to cast. diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp index 0f0bb75aed..3cdb5967fd 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp @@ -56,21 +56,24 @@ enum Misc { ACHIEV_TIMED_START_EVENT = 20381, - EVENT_CHECK_HEALTH_25 = 1, - EVENT_CHECK_HEALTH_50 = 2, - EVENT_CHECK_HEALTH_75 = 3, - EVENT_CARRION_BEETELS = 4, - EVENT_LEECHING_SWARM = 5, - EVENT_IMPALE = 6, - EVENT_POUND = 7, - EVENT_CLOSE_DOORS = 8, - EVENT_EMERGE = 9, - EVENT_SUMMON_VENOMANCER = 10, - EVENT_SUMMON_DARTER = 11, - EVENT_SUMMON_GUARDIAN = 12, - EVENT_SUMMON_ASSASSINS = 13, - EVENT_ENABLE_ROTATE = 14, - EVENT_KILL_TALK = 15 + EVENT_CARRION_BEETELS = 1, + EVENT_LEECHING_SWARM = 2, + EVENT_IMPALE = 3, + EVENT_POUND = 4, + EVENT_CLOSE_DOORS = 5, + EVENT_EMERGE = 6, + EVENT_SUMMON_VENOMANCER = 7, + EVENT_SUMMON_DARTER = 8, + EVENT_SUMMON_GUARDIAN = 9, + EVENT_SUMMON_ASSASSINS = 10, + EVENT_ENABLE_ROTATE = 11, + EVENT_KILL_TALK = 12 +}; + +enum ANAnubarakNpcs +{ + NPC_ANUBAR_GUARDIAN = 29216, + NPC_ANUBAR_VENOMANCER = 29217 }; class boss_anub_arak : public CreatureScript @@ -83,11 +86,10 @@ class boss_anub_arak : public CreatureScript boss_anub_arakAI(Creature* creature) : BossAI(creature, DATA_ANUBARAK_EVENT) { me->m_SightDistance = 120.0f; - intro = false; + _intro = false; + _summonedMinions = false; } - bool intro; - void EnterEvadeMode(EvadeReason why) override { me->DisableRotate(false); @@ -96,9 +98,9 @@ class boss_anub_arak : public CreatureScript void MoveInLineOfSight(Unit* who) override { - if (!intro && who->IsPlayer()) + if (!_intro && who->IsPlayer()) { - intro = true; + _intro = true; Talk(SAY_INTRO); } BossAI::MoveInLineOfSight(who); @@ -129,8 +131,42 @@ class boss_anub_arak : public CreatureScript void Reset() override { BossAI::Reset(); + _summonedMinions = false; me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); + + ScheduleHealthCheckEvent({ 75, 50, 25 }, [&]{ + Talk(SAY_SUBMERGE); + _summonedMinions = false; + DoCastSelf(SPELL_CLEAR_ALL_DEBUFFS, true); + DoCastSelf(SPELL_SUBMERGE, false); + + me->m_Events.AddEventAtOffset([this] { + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + DoCastSelf(SPELL_IMPALE_PERIODIC, true); + }, 2s); + + events.Reset(); + events.ScheduleEvent(EVENT_EMERGE, 60s); + events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 2s); + events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s); + events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 15s); + events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 20s); + events.ScheduleEvent(EVENT_SUMMON_DARTER, 30s); + events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 35s); + }, false); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + if (!me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return; + + if (_summonedMinions && !summons.IsAnyCreatureWithEntryAlive(NPC_ANUBAR_GUARDIAN) && !summons.IsAnyCreatureWithEntryAlive(NPC_ANUBAR_VENOMANCER)) + { + events.Reset(); + events.ScheduleEvent(EVENT_EMERGE, 5s); + } } void JustEngagedWith(Unit* ) override @@ -141,16 +177,13 @@ class boss_anub_arak : public CreatureScript events.ScheduleEvent(EVENT_CARRION_BEETELS, 6500ms); events.ScheduleEvent(EVENT_LEECHING_SWARM, 20s); events.ScheduleEvent(EVENT_POUND, 15s); - events.ScheduleEvent(EVENT_CHECK_HEALTH_75, 1s); - events.ScheduleEvent(EVENT_CHECK_HEALTH_50, 1s); - events.ScheduleEvent(EVENT_CHECK_HEALTH_25, 1s); events.ScheduleEvent(EVENT_CLOSE_DOORS, 5s); } void SummonHelpers(float x, float y, float z, uint32 spellId) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); - me->SummonCreature(spellInfo->Effects[EFFECT_0].MiscValue, x, y, z); + me->SummonCreature(spellInfo->Effects[EFFECT_0].MiscValue, x, y, z, 0.0f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000); } void UpdateAI(uint32 diff) override @@ -159,10 +192,12 @@ class boss_anub_arak : public CreatureScript return; events.Update(diff); + scheduler.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) return; - switch (uint32 eventId = events.ExecuteEvent()) + switch (events.ExecuteEvent()) { case EVENT_CLOSE_DOORS: _JustEngagedWith(); @@ -191,34 +226,14 @@ class boss_anub_arak : public CreatureScript me->RemoveAurasDueToSpell(SPELL_SELF_ROOT); me->DisableRotate(false); break; - case EVENT_CHECK_HEALTH_25: - case EVENT_CHECK_HEALTH_50: - case EVENT_CHECK_HEALTH_75: - if (me->HealthBelowPct(eventId*25)) - { - Talk(SAY_SUBMERGE); - DoCastSelf(SPELL_CLEAR_ALL_DEBUFFS, true); - me->CastSpell(me, SPELL_IMPALE_PERIODIC, true); - me->CastSpell(me, SPELL_SUBMERGE, false); - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); - - events.DelayEvents(46000, 0); - events.ScheduleEvent(EVENT_EMERGE, 45s); - events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 2s); - events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s); - events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 15s); - events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 20s); - events.ScheduleEvent(EVENT_SUMMON_DARTER, 30s); - events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 35s); - break; - } - events.ScheduleEvent(eventId, 500ms); - break; case EVENT_EMERGE: me->CastSpell(me, SPELL_EMERGE, true); me->RemoveAura(SPELL_SUBMERGE); me->RemoveAura(SPELL_IMPALE_PERIODIC); me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); + events.ScheduleEvent(EVENT_CARRION_BEETELS, 6500ms); + events.ScheduleEvent(EVENT_LEECHING_SWARM, 20s); + events.ScheduleEvent(EVENT_POUND, 15s); break; case EVENT_SUMMON_ASSASSINS: SummonHelpers(509.32f, 247.42f, 239.48f, SPELL_SUMMON_ASSASSIN); @@ -232,6 +247,7 @@ class boss_anub_arak : public CreatureScript SummonHelpers(550.34f, 316.00f, 234.30f, SPELL_SUMMON_GUARDIAN); break; case EVENT_SUMMON_VENOMANCER: + _summonedMinions = true; SummonHelpers(550.34f, 316.00f, 234.30f, SPELL_SUMMON_VENOMANCER); break; } @@ -239,6 +255,10 @@ class boss_anub_arak : public CreatureScript if (!me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) DoMeleeAttackIfReady(); } + + private: + bool _intro; + bool _summonedMinions; }; CreatureAI* GetAI(Creature* creature) const override |