diff --git a/data/sql/updates/db_world/2025_09_23_00.sql b/data/sql/updates/db_world/2025_09_23_00.sql new file mode 100644 index 000000000..fc0097136 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_23_00.sql @@ -0,0 +1,16 @@ +-- DB update 2025_09_22_03 -> 2025_09_23_00 + +-- Update SmartAIs +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE (`entry` IN (18855, 19643, 21660)); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0) AND (`entryorguid` IN (18855, 19643, 21660)); +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`, `event_param6`, `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 +(18855, 0, 0, 0, 0, 0, 100, 0, 3000, 6000, 3000, 4000, 0, 0, 11, 9053, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Sunfury Magister - In Combat - Cast \'Fireball\''), +(18855, 0, 1, 0, 0, 0, 100, 0, 8000, 12000, 26000, 31000, 0, 0, 11, 35778, 33, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Sunfury Magister - In Combat - Cast \'Bloodcrystal Surge\''), +(18855, 0, 2, 0, 2, 0, 100, 512, 0, 15, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Sunfury Magister - Between 0-15% Health - Flee For Assist'), +(18855, 0, 3, 0, 1, 0, 50, 0, 10000, 20000, 15000, 30000, 0, 0, 11, 34397, 0, 0, 0, 0, 0, 19, 19421, 30, 0, 0, 0, 0, 0, 0, 'Sunfury Magister - Out of Combat - Cast \'Red Beam\''), +(19643, 0, 0, 0, 1, 0, 100, 0, 1000, 1000, 600000, 600000, 0, 0, 11, 35917, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Sunfury Astromancer - Out of Combat - Cast \'Fiery Intellect\''), +(19643, 0, 1, 0, 0, 0, 100, 0, 2000, 3000, 1500, 2000, 0, 30, 11, 38391, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Sunfury Astromancer - In Combat - Cast \'Scorch\''), +(19643, 0, 2, 0, 0, 0, 100, 0, 12000, 16000, 12000, 16000, 0, 30, 11, 35914, 64, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 'Sunfury Astromancer - In Combat - Cast \'Astral Focus\''), +(21660, 0, 0, 0, 0, 0, 100, 0, 0, 0, 2400, 3800, 0, 0, 11, 34447, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Cabal Abjurist - In Combat - Cast \'Arcane Missiles\''), +(21660, 0, 1, 0, 106, 0, 100, 0, 5000, 9000, 13000, 18000, 0, 10, 11, 11831, 64, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Cabal Abjurist - On Hostile in Range - Cast \'Frost Nova\''); diff --git a/data/sql/updates/db_world/2025_09_23_01.sql b/data/sql/updates/db_world/2025_09_23_01.sql new file mode 100644 index 000000000..3d5541747 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_23_01.sql @@ -0,0 +1,19 @@ +-- DB update 2025_09_23_00 -> 2025_09_23_01 +-- +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 27996); +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`, `event_param6`, `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 +(27996, 0, 0, 0, 1, 0, 100, 512, 9000, 9000, 30000, 30000, 0, 0, 1, 0, 0, 0, 0, 0, 0, 21, 10, 0, 0, 0, 0, 0, 0, 0, 'Wyrmrest Vanquisher - Out of Combat - Say Line 0'), +(27996, 0, 1, 0, 2, 0, 100, 513, 0, 33, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 21, 10, 0, 0, 0, 0, 0, 0, 0, 'Wyrmrest Vanquisher - Between 0-33% Health - Say Line 1 (No Repeat)'), +(27996, 0, 3, 0, 54, 0, 100, 512, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Wyrmrest Vanquisher - On Just Summoned - Set Reactstate Passive'), +(27996, 0, 4, 5, 28, 0, 100, 512, 0, 0, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 0, 202, 15, 0, 0, 0, 0, 0, 0, 0, 'Wyrmrest Vanquisher - On Passenger Removed - Move To Random Point'), +(27996, 0, 5, 6, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Wyrmrest Vanquisher - On Passenger Removed - Say Line 2'), +(27996, 0, 6, 7, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 3000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Wyrmrest Vanquisher - On Passenger Removed - Despawn In 3000 ms'), +(27996, 0, 7, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 134, 44795, 2, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Wyrmrest Vanquisher - On Passenger Removed - Invoker Cast \'Parachute\''); + +DELETE FROM `smart_scripts` WHERE `entryorguid` = 2800500 AND `source_type` = 9; +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28005) AND (`source_type` = 0) AND (`id` IN (1)); + +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_dragonblight_devour_ghoul', 'spell_dragonblight_devour_ghoul_periodic'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(50430, 'spell_dragonblight_devour_ghoul'), +(50432, 'spell_dragonblight_devour_ghoul_periodic'); diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 1b6733e50..043fa814b 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 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 healthPct, std::function 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 82f50542b..f3d9e11a5 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 _uniqueTimedEvents; }; +enum HealthCheckStatus +{ + HEALTH_CHECK_PROCESSED, + HEALTH_CHECK_SCHEDULED, + HEALTH_CHECK_PENDING +}; + struct HealthCheckEventData { - HealthCheckEventData(uint8 healthPct, std::function exec, bool valid = true, bool allowedWhileCasting = true) : _healthPct(healthPct), _exec(exec), _valid(valid), _allowedWhileCasting(allowedWhileCasting) { }; + HealthCheckEventData(uint8 healthPct, std::function 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 _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 exec, bool allowedWhileCasting = true); void ScheduleHealthCheckEvent(std::initializer_list healthPct, std::function 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/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 6562c9d27..a77e194bf 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -272,7 +272,7 @@ bool TemporaryThreatModifierEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) Creature::Creature(): Unit(), MovableMapObject(), m_groupLootTimer(0), lootingGroupLowGUID(0), m_lootRecipientGroup(0), m_corpseRemoveTime(0), m_respawnTime(0), m_respawnDelay(300), m_corpseDelay(60), m_wanderDistance(0.0f), m_boundaryCheckTime(2500), m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), - m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), + m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_alreadyCallForHelp(false), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_regenPower(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false), m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0), _isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true) @@ -2486,6 +2486,9 @@ void Creature::CallForHelp(float radius, Unit* target /*= nullptr*/) return; } + if (m_alreadyCallForHelp) // avoid recursive call for help for any reason + return; + Acore::CallOfHelpCreatureInRangeDo u_do(this, target, radius); Acore::CreatureWorker worker(this, u_do); Cell::VisitObjects(this, worker, radius); diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index e9326f0d1..a2c294702 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -268,6 +268,7 @@ public: void DoFleeToGetAssistance(); void CallForHelp(float fRadius, Unit* target = nullptr); void CallAssistance(Unit* target = nullptr); + void SetNoCallForHelp(bool val) { m_alreadyCallForHelp = val; } void SetNoCallAssistance(bool val) { m_AlreadyCallAssistance = val; } void SetNoSearchAssistance(bool val) { m_AlreadySearchedAssistance = val; } bool HasSearchedAssistance() { return m_AlreadySearchedAssistance; } @@ -469,6 +470,7 @@ protected: uint8 m_equipmentId; int8 m_originalEquipmentId; // can be -1 + bool m_alreadyCallForHelp; bool m_AlreadyCallAssistance; bool m_AlreadySearchedAssistance; bool m_regenHealth; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index d9556c77f..d35e8600c 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -1157,7 +1157,11 @@ namespace Acore return; if (u->AI()) + { + u->SetNoCallForHelp(true); // avoid recursive call for help causing stack overflow u->AI()->AttackStart(i_enemy); + u->SetNoCallForHelp(false); + } } private: Unit* const i_funit; diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index e5360809f..9873af325 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -5287,6 +5287,8 @@ void SpellMgr::LoadSpellInfoCorrections() vse->m_flags &= ~VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE; vse = const_cast(sVehicleSeatStore.LookupEntry(4693)); // Siege Engine, Accessory vse->m_flags &= ~VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE; + vse = const_cast(sVehicleSeatStore.LookupEntry(1520)); // Wyrmrest Vanquisher + vse->m_flags |= VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE; // pussywizard: fix z offset for some vehicles: vse = const_cast(sVehicleSeatStore.LookupEntry(6206)); // Marrowgar - Bone Spike diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp index 0f0bb75ae..3cdb5967f 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 diff --git a/src/server/scripts/Northrend/Gundrak/boss_slad_ran.cpp b/src/server/scripts/Northrend/Gundrak/boss_slad_ran.cpp index d9e57fab1..68a22e964 100644 --- a/src/server/scripts/Northrend/Gundrak/boss_slad_ran.cpp +++ b/src/server/scripts/Northrend/Gundrak/boss_slad_ran.cpp @@ -75,21 +75,32 @@ class boss_slad_ran : public CreatureScript public: boss_slad_ran() : CreatureScript("boss_slad_ran") { } - CreatureAI* GetAI(Creature* creature) const override - { - return GetGundrakAI(creature); - } - struct boss_slad_ranAI : public BossAI { - boss_slad_ranAI(Creature* creature) : BossAI(creature, DATA_SLAD_RAN) - { - } + boss_slad_ranAI(Creature* creature) : BossAI(creature, DATA_SLAD_RAN) { } void Reset() override { BossAI::Reset(); _achievement = true; + + ScheduleHealthCheckEvent(90, [&] { + Talk(SAY_SUMMON_SNAKES); + + ScheduleTimedEvent(1s, [&] { + for (uint8 i = MAX_CONSTRICTOR; i < MAX_SUMMONS; ++i) + me->SummonCreature(NPC_SLADRAN_VIPER, SpawnLoc[i], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20 * IN_MILLISECONDS); + }, 8s); + }); + + ScheduleHealthCheckEvent(DUNGEON_MODE(50, 75), [&] { + Talk(SAY_SUMMON_CONSTRICTORS); + + ScheduleTimedEvent(1s, [&] { + for (uint8 i = 0; i < MAX_CONSTRICTOR; ++i) + me->SummonCreature(NPC_SLADRAN_CONSTRICTORS, SpawnLoc[i], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20 * IN_MILLISECONDS); + }, 3s, 5s); + }); } uint32 GetData(uint32 data) const override @@ -110,11 +121,18 @@ public: Talk(SAY_AGGRO); BossAI::JustEngagedWith(who); - events.ScheduleEvent(EVENT_POISON_NOVA, 10s); - events.ScheduleEvent(EVENT_POWERFULL_BITE, 3s); - events.ScheduleEvent(EVENT_VENOM_BOLT, 15s); - events.ScheduleEvent(EVENT_CHECK_HEALTH1, 1s); - events.ScheduleEvent(EVENT_CHECK_HEALTH2, 1s); + ScheduleTimedEvent(10s, [&]{ + Talk(EMOTE_NOVA); + DoCastAOE(SPELL_POISON_NOVA); + }, 15s); + + ScheduleTimedEvent(3s, [&] { + DoCastVictim(SPELL_POWERFULL_BITE); + }, 10s); + + ScheduleTimedEvent(15s, [&] { + DoCastVictim(SPELL_VENOM_BOLT); + }, 10s); } void JustDied(Unit* killer) override @@ -133,72 +151,14 @@ public: } } - void JustSummoned(Creature* summon) override - { - summon->SetInCombatWithZone(); - summons.Summon(summon); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_CHECK_HEALTH1: - if (me->HealthBelowPct(70)) - { - Talk(SAY_SUMMON_SNAKES); - events.ScheduleEvent(EVENT_SUMMON1, 1s); - break; - } - events.ScheduleEvent(EVENT_CHECK_HEALTH1, 1s); - break; - case EVENT_CHECK_HEALTH2: - if (me->HealthBelowPct(50)) - { - Talk(SAY_SUMMON_CONSTRICTORS); - events.ScheduleEvent(EVENT_SUMMON2, 1s); - break; - } - events.ScheduleEvent(EVENT_CHECK_HEALTH2, 1s); - break; - case EVENT_POISON_NOVA: - Talk(EMOTE_NOVA); - me->CastSpell(me, SPELL_POISON_NOVA, false); - events.ScheduleEvent(EVENT_POISON_NOVA, 15s); - break; - case EVENT_POWERFULL_BITE: - me->CastSpell(me->GetVictim(), SPELL_POWERFULL_BITE, false); - events.ScheduleEvent(EVENT_POWERFULL_BITE, 10s); - break; - case EVENT_VENOM_BOLT: - me->CastSpell(me->GetVictim(), SPELL_VENOM_BOLT, false); - events.ScheduleEvent(EVENT_VENOM_BOLT, 10s); - break; - case EVENT_SUMMON1: - for (uint8 i = MAX_CONSTRICTOR; i < MAX_SUMMONS; ++i) - me->SummonCreature(NPC_SLADRAN_VIPER, SpawnLoc[i], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20 * IN_MILLISECONDS); - events.ScheduleEvent(EVENT_SUMMON1, 8s); - break; - case EVENT_SUMMON2: - for (uint8 i = 0; i < MAX_CONSTRICTOR; ++i) - me->SummonCreature(NPC_SLADRAN_CONSTRICTORS, SpawnLoc[i], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20 * IN_MILLISECONDS); - events.ScheduleEvent(EVENT_SUMMON2, 3s, 5s); - break; - } - - DoMeleeAttackIfReady(); - } - private: bool _achievement; }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetGundrakAI(creature); + } }; class spell_sladran_grip_of_sladran_aura : public AuraScript diff --git a/src/server/scripts/Northrend/zone_dragonblight.cpp b/src/server/scripts/Northrend/zone_dragonblight.cpp index e1f50f3cd..52aac2193 100644 --- a/src/server/scripts/Northrend/zone_dragonblight.cpp +++ b/src/server/scripts/Northrend/zone_dragonblight.cpp @@ -2271,6 +2271,66 @@ private: } }; +enum DevourGhoulSpells +{ + SPELL_DEVOUR_GHOUL_RIDE_VEHICLE = 50437, + SPELL_DEVOUR_PERIODIC = 50432, + SPELL_NOURISHMENT = 50443 +}; + +// 50430 - Devour Ghoul +class spell_dragonblight_devour_ghoul: public SpellScript +{ + PrepareSpellScript(spell_dragonblight_devour_ghoul); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DEVOUR_GHOUL_RIDE_VEHICLE }); + } + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + if (GetCaster()) + { + GetHitUnit()->CastSpell(GetCaster(), SPELL_DEVOUR_GHOUL_RIDE_VEHICLE, true); + GetCaster()->CastSpell(GetHitUnit(), SPELL_DEVOUR_PERIODIC, true); + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_dragonblight_devour_ghoul::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +// 50432 - Devour Ghoul +class spell_dragonblight_devour_ghoul_periodic : public AuraScript +{ + PrepareAuraScript(spell_dragonblight_devour_ghoul_periodic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_NOURISHMENT }); + } + + void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_EXPIRE && GetCaster()) + GetCaster()->CastSpell(GetCaster(), SPELL_NOURISHMENT, true); + + if (GetUnitOwner() && GetUnitOwner()->ToCreature()) + { + GetUnitOwner()->ExitVehicle(); + GetUnitOwner()->ToCreature()->DespawnOrUnsummon(2000); + } + } + + void Register() override + { + OnEffectRemove += AuraEffectRemoveFn(spell_dragonblight_devour_ghoul_periodic::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL); + } +}; + void AddSC_dragonblight() { new npc_conversing_with_the_depths_trigger(); @@ -2299,4 +2359,6 @@ void AddSC_dragonblight() RegisterSpellScript(spell_dragonblight_corrosive_spit); RegisterSpellScript(spell_handover_reins); RegisterSpellScript(spell_dragonblight_flame_fury); + RegisterSpellScript(spell_dragonblight_devour_ghoul); + RegisterSpellScript(spell_dragonblight_devour_ghoul_periodic); }