summaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorAndrew <47818697+Nyeriah@users.noreply.github.com>2025-09-23 05:49:23 -0300
committerGitHub <noreply@github.com>2025-09-23 10:49:23 +0200
commit78dea88d5d0e8532f2c926e1558ebb05f8e04894 (patch)
tree208d717f9c0f3dc5d9ef0f6b286f2571615c29f8 /src/server
parent7a0b9785bb5ae22a99e269d8f462295bb43aedc4 (diff)
fix(Scripts/AzjolNerub): Fix Anubarak impale sequence (#22717)
Diffstat (limited to 'src/server')
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.cpp54
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.h19
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp116
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