From 9268ca9784dc229f0dcd63ea2e21dfd867b5f000 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:27:00 -0300 Subject: [PATCH 001/132] fix(Scripts/EoE): fix Vortex only hitting 8 players (#25119) Co-authored-by: Claude Opus 4.6 --- .../Nexus/EyeOfEternity/boss_malygos.cpp | 13 +++----- .../instance_eye_of_eternity.cpp | 31 +++++++------------ 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp b/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp index 0829623ba..df57c3368 100644 --- a/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp +++ b/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp @@ -1232,18 +1232,15 @@ class spell_malygos_vortex_visual : public AuraScript if (!caster) return; - for (auto const* ref : caster->GetThreatMgr().GetUnsortedThreatList()) + if (InstanceScript* instance = caster->GetInstanceScript()) { - if (Player* player = ref->GetVictim()->ToPlayer()) + if (Creature* trigger = ObjectAccessor::GetCreature(*caster, instance->GetGuidData(DATA_VORTEX_TRIGGER))) { - if (player->IsGameMaster()) - continue; - - if (InstanceScript* instance =caster->GetInstanceScript()) + caster->GetMap()->DoForAllPlayers([&](Player* player) { - if (Creature* trigger =ObjectAccessor::GetCreature(*caster, instance->GetGuidData(DATA_VORTEX_TRIGGER))) + if (player->IsAlive() && !player->IsGameMaster()) trigger->CastSpell(player, SPELL_VORTEX_TELEPORT, true); - } + }); } } diff --git a/src/server/scripts/Northrend/Nexus/EyeOfEternity/instance_eye_of_eternity.cpp b/src/server/scripts/Northrend/Nexus/EyeOfEternity/instance_eye_of_eternity.cpp index 4cec1ceb0..17de6107e 100644 --- a/src/server/scripts/Northrend/Nexus/EyeOfEternity/instance_eye_of_eternity.cpp +++ b/src/server/scripts/Northrend/Nexus/EyeOfEternity/instance_eye_of_eternity.cpp @@ -72,31 +72,22 @@ struct instance_eye_of_eternity : public InstanceScript void VortexHandling() { - Creature* malygos = GetCreature(DATA_MALYGOS); - if (!malygos) + if (_vortexTriggers.empty()) return; - for (ObjectGuid const& guid : _vortexTriggers) + size_t triggerIndex = 0; + size_t triggerCount = _vortexTriggers.size(); + + instance->DoForAllPlayers([&](Player* player) { - uint8 counter = 0; - if (Creature* trigger = instance->GetCreature(guid)) - { - for (auto* ref : malygos->GetThreatMgr().GetUnsortedThreatList()) - { - if (counter >= 5) - break; + if (!player->IsAlive() || player->IsGameMaster()) + return; - if (Player* player = ref->GetVictim()->ToPlayer()) - { - if (player->IsGameMaster() || player->HasAura(SPELL_VORTEX_4)) - continue; + if (Creature* trigger = instance->GetCreature(_vortexTriggers[triggerIndex])) + player->CastSpell(trigger, SPELL_VORTEX_4, true); - player->CastSpell(trigger, SPELL_VORTEX_4, true); - counter++; - } - } - } - } + triggerIndex = (triggerIndex + 1) % triggerCount; + }); } void OnPlayerEnter(Player* player) override From 4955ecbd8c25540fac7ac350eb19c972711085e4 Mon Sep 17 00:00:00 2001 From: sogladev Date: Fri, 20 Mar 2026 23:52:38 +0100 Subject: [PATCH 002/132] fix(Scripts/VioletHold): VioletHold globules no longer inherit boss speed (#25143) --- src/server/scripts/Northrend/VioletHold/boss_ichoron.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/scripts/Northrend/VioletHold/boss_ichoron.cpp b/src/server/scripts/Northrend/VioletHold/boss_ichoron.cpp index 1c21394b4..1cbd12df6 100644 --- a/src/server/scripts/Northrend/VioletHold/boss_ichoron.cpp +++ b/src/server/scripts/Northrend/VioletHold/boss_ichoron.cpp @@ -244,7 +244,7 @@ public: { pSummoned->SetSpeed(MOVE_RUN, 0.3f); pSummoned->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); - pSummoned->GetMotionMaster()->MoveFollow(me, 0, 0); + pSummoned->GetMotionMaster()->MoveFollow(me, 0, 0, MOTION_SLOT_ACTIVE, false, false); me->CastSpell(pSummoned, SPELL_CREATE_GLOBULE_VISUAL, true); // triggered should ignore los globules.Summon(pSummoned); if (pInstance) From 67918db7a0e2e382c00a3be325f8762d22f9914f Mon Sep 17 00:00:00 2001 From: sogladev Date: Sat, 21 Mar 2026 02:04:33 +0100 Subject: [PATCH 003/132] fix(Core/Unit): Improve vehicle exit safety checks and fallback logic (#24612) --- src/server/game/Entities/Unit/Unit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 565b350cd..8712c7514 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -15714,6 +15714,12 @@ void Unit::_ExitVehicle(Position const* exitPosition) pos.RelocateOffset({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO }); else if (seatAddon->ExitParameter == VehicleExitParameters::VehicleExitParamDest) pos.Relocate({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO }); + + bool isInLoS = GetMap()->isInLineOfSight(GetPositionX(), GetPositionY(), GetPositionZ(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), GetPhaseMask(), LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing); + float floorZ = GetMapHeight(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); + + if (!isInLoS || pos.GetPositionZ() < floorZ) + pos = vehicleBase->GetPosition(); } } From 1d7c5e4b71a8cd86818be1364413438688b2f6a8 Mon Sep 17 00:00:00 2001 From: Takenbacon Date: Fri, 20 Mar 2026 18:04:52 -0700 Subject: [PATCH 004/132] fix(Core/Cinematics): Cinematic camera improvements (#25097) --- .../game/AI/SmartScripts/SmartScript.cpp | 2 +- .../game/Entities/GameObject/GameObject.cpp | 34 +++++- .../game/Entities/GameObject/GameObject.h | 8 +- src/server/game/Entities/Object/Object.cpp | 17 ++- src/server/game/Entities/Object/Object.h | 1 + .../game/Entities/Player/CinematicMgr.cpp | 110 +++++++++--------- .../game/Entities/Player/CinematicMgr.h | 36 +++--- src/server/game/Entities/Player/Player.cpp | 22 ++-- src/server/game/Entities/Player/Player.h | 6 +- .../game/Entities/Player/PlayerUpdates.cpp | 14 +-- src/server/game/Entities/Unit/Unit.cpp | 4 +- src/server/game/Grids/Cells/Cell.h | 1 + src/server/game/Grids/Cells/CellImpl.h | 16 +-- .../game/Grids/Notifiers/GridNotifiers.cpp | 2 +- .../game/Grids/Notifiers/GridNotifiers.h | 4 +- src/server/game/Handlers/CharacterHandler.cpp | 4 +- src/server/game/Handlers/MiscHandler.cpp | 6 +- src/server/game/Server/Protocol/Opcodes.cpp | 4 +- src/server/game/Spells/Spell.cpp | 2 +- src/server/scripts/Commands/cs_debug.cpp | 2 +- 20 files changed, 164 insertions(+), 131 deletions(-) diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index d126ff9d8..82d9512f9 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -2957,7 +2957,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!IsPlayer(target)) continue; - target->ToPlayer()->SendCinematicStart(e.action.cinematic.entry); + target->ToPlayer()->GetCinematicMgr().StartCinematic(e.action.cinematic.entry); } break; } diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 32e450030..7885b3bfc 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -1703,7 +1703,7 @@ void GameObject::Use(Unit* user) Player* player = user->ToPlayer(); if (info->camera.cinematicId) - player->SendCinematicStart(info->camera.cinematicId); + player->GetCinematicMgr().StartCinematic(info->camera.cinematicId); if (info->camera.eventID) { @@ -2142,7 +2142,32 @@ void GameObject::SendCustomAnim(uint32 anim) SendMessageToSet(&data, true); } -bool GameObject::IsInRange(float x, float y, float z, float radius) const +bool GameObject::IsInRange2d(float x, float y, float radius) const +{ + GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(m_goInfo->displayId); + if (!info) + return IsWithinDist2d(x, y, radius); + + float sinA = std::sin(GetOrientation()); + float cosA = cos(GetOrientation()); + float dx = x - GetPositionX(); + float dy = y - GetPositionY(); + float dist = std::sqrt(dx * dx + dy * dy); + //! Check if the distance between the 2 objects is 0, can happen if both objects are on the same position. + //! The code below this check wont crash if dist is 0 because 0/0 in float operations is valid, and returns infinite + if (G3D::fuzzyEq(dist, 0.0f)) + return true; + + float scale = GetObjectScale(); + float sinB = dx / dist; + float cosB = dy / dist; + dx = dist * (cosA * cosB + sinA * sinB); + dy = dist * (cosA * sinB - sinA * cosB); + return dx < (info->maxX * scale) + radius && dx >(info->minX * scale) - radius + && dy < (info->maxY * scale) + radius && dy >(info->minY * scale) - radius; +} + +bool GameObject::IsInRange3d(float x, float y, float z, float radius) const { GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(m_goInfo->displayId); if (!info) @@ -2217,6 +2242,11 @@ void GameObject::UpdatePackedRotation() m_packedRotation = z | (y << 21) | (x << 42); } +bool GameObject::IsWithinSightRange(Position const& pos, float dist) const +{ + return IsInRange2d(pos.GetPositionX(), pos.GetPositionY(), dist); +} + void GameObject::SetWorldRotation(G3D::Quat const& rot) { G3D::Quat rotation = rot; diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index efcb66249..8c4a943e0 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -284,7 +284,8 @@ public: void CastSpell(Unit* target, uint32 spell); void SendCustomAnim(uint32 anim); - [[nodiscard]] bool IsInRange(float x, float y, float z, float radius) const; + bool IsInRange2d(float x, float y, float radius) const; + bool IsInRange3d(float x, float y, float z, float radius) const; void ModifyHealth(int32 change, Unit* attackerOrHealer = nullptr, uint32 spellId = 0); void SetDestructibleBuildingModifyState(bool allow) { m_allowModifyDestructibleBuilding = allow; } @@ -418,8 +419,11 @@ private: { //! Following check does check 3d distance dist2compare += obj->GetObjectSize(); - return IsInRange(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), dist2compare); + return IsInRange3d(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), dist2compare); } + + bool IsWithinSightRange(Position const& pos, float dist) const override; + GameObjectAI* m_AI; bool m_saveStateOnDb = false; diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 98e5b890a..9cd58f197 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1362,6 +1362,12 @@ bool WorldObject::IsWithinDist2d(const Position* pos, float dist) const return IsInDist2d(pos, dist + GetObjectSize()); } +// Visibility always uses 2d checks, factors in self-object size already. Gameobjects will override this for custom calc +bool WorldObject::IsWithinSightRange(Position const& pos, float dist) const +{ + return IsInDist2d(&pos, dist + GetObjectSize()); +} + // use only if you will sure about placing both object at same map bool WorldObject::IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D, bool incOwnRadius, bool incTargetRadius) const { @@ -1674,7 +1680,7 @@ float WorldObject::GetGridActivationRange() const { if (ToPlayer()) { - if (ToPlayer()->GetCinematicMgr()->IsOnCinematic()) + if (ToPlayer()->GetCinematicMgr().IsOnCinematic()) { return DEFAULT_VISIBILITY_INSTANCE; } @@ -1725,7 +1731,7 @@ float WorldObject::GetSightRange(WorldObject const* target) const return VISIBILITY_DIST_WINTERGRASP; else if (target->IsVisibilityOverridden()) return target->GetVisibilityOverrideDistance(); - else if (ToPlayer()->GetCinematicMgr()->IsOnCinematic()) + else if (ToPlayer()->GetCinematicMgr().IsOnCinematic()) return DEFAULT_VISIBILITY_INSTANCE; else return GetMap()->GetVisibilityRange(); @@ -1794,10 +1800,10 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo if (distanceCheck) { bool corpseCheck = false; - WorldObject const* viewpoint = this; + Position const* sightPosition = this; if (Player const* thisPlayer = ToPlayer()) { - viewpoint = thisPlayer->GetSeer(); + sightPosition = &thisPlayer->GetSightPosition(); if (Creature const* creature = obj->ToCreature()) { @@ -1843,8 +1849,7 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo return false; } - // Xinef: check reversely obj vs viewpoint, object could be a gameObject which overrides _IsWithinDist function to include gameobject size - if (!corpseCheck && !viewpoint->IsWithinDist(obj, GetSightRange(obj), false)) + if (!corpseCheck && !obj->IsWithinSightRange(*sightPosition, GetSightRange(obj))) return false; } diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index d2da4b523..4a635175d 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -543,6 +543,7 @@ public: bool IsWithinDist3d(const Position* pos, float dist) const; [[nodiscard]] bool IsWithinDist2d(float x, float y, float dist) const; bool IsWithinDist2d(const Position* pos, float dist) const; + virtual bool IsWithinSightRange(Position const& pos, float dist) const; // use only if you will sure about placing both object at same map bool IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D = true, bool incOwnRadius = true, bool incTargetRadius = true) const; bool IsWithinDistInMap(WorldObject const* obj, float dist2compare, bool is3D = true, bool incOwnRadius = true, bool incTargetRadius = true) const; diff --git a/src/server/game/Entities/Player/CinematicMgr.cpp b/src/server/game/Entities/Player/CinematicMgr.cpp index d8091d918..1cce91ed2 100644 --- a/src/server/game/Entities/Player/CinematicMgr.cpp +++ b/src/server/game/Entities/Player/CinematicMgr.cpp @@ -17,92 +17,84 @@ #include "CinematicMgr.h" #include "M2Stores.h" -#include "MotionMaster.h" #include "Player.h" -CinematicMgr::CinematicMgr(Player* playerref) +CinematicMgr::CinematicMgr(Player& player) : _player(player) { - player = playerref; - m_cinematicDiff = 0; - m_lastCinematicCheck = 0; - m_activeCinematicCameraId = 0; - m_cinematicLength = 0; - m_cinematicCamera = nullptr; - m_remoteSightPosition = Position(0.0f, 0.0f, 0.0f); - m_CinematicObject = nullptr; + _cinematicDiff = 0; + _activeCinematicCameraId = 0; + _cinematicCamera = nullptr; + _remoteSightPosition = Position(0.0f, 0.0f, 0.0f); + _cinematicUpdateTimer.SetInterval(CINEMATIC_UPDATEDIFF); } -CinematicMgr::~CinematicMgr() +void CinematicMgr::StartCinematic(uint32 const cinematicSequenceId) { - if (m_cinematicCamera && m_activeCinematicCameraId) - EndCinematic(); + _player.SendCinematicStart(cinematicSequenceId); + if (CinematicSequencesEntry const* sequence = sCinematicSequencesStore.LookupEntry(cinematicSequenceId)) + SetActiveCinematicCamera(sequence->cinematicCamera); } -void CinematicMgr::BeginCinematic() +void CinematicMgr::StartCinematicCamera() { // Sanity check for active camera set - if (m_activeCinematicCameraId == 0) + if (_activeCinematicCameraId == 0) return; - if (std::vector const* flyByCameras = GetFlyByCameras(m_activeCinematicCameraId)) + if (std::vector const* flyByCameras = GetFlyByCameras(_activeCinematicCameraId)) { // Initialize diff, and set camera - m_cinematicDiff = 0; - m_cinematicCamera = flyByCameras; + _cinematicDiff = 0; + _cinematicUpdateTimer.Reset(); + _cinematicCamera = flyByCameras; - auto camitr = m_cinematicCamera->begin(); - if (camitr != m_cinematicCamera->end()) + auto camitr = _cinematicCamera->begin(); + if (camitr != _cinematicCamera->end()) { Position const& pos = camitr->locations; if (!pos.IsPositionValid()) return; - player->GetMap()->LoadGrid(pos.GetPositionX(), pos.GetPositionY()); - m_CinematicObject = player->SummonCreature(VISUAL_WAYPOINT, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 120000); - if (m_CinematicObject) - { - m_CinematicObject->setActive(true); - player->SetViewpoint(m_CinematicObject, true); - } - - // Get cinematic length - m_cinematicLength = flyByCameras->back().timeStamp; + _player.GetMap()->LoadGridsInRange(pos, MAX_VISIBILITY_DISTANCE); + _remoteSightPosition.Relocate(pos); + _player.UpdateVisibilityForPlayer(); } } } void CinematicMgr::EndCinematic() { - if (m_activeCinematicCameraId == 0) + if (_activeCinematicCameraId == 0) return; - m_cinematicDiff = 0; - m_cinematicCamera = nullptr; - m_activeCinematicCameraId = 0; - if (m_CinematicObject) - { - if (WorldObject* vpObject = player->GetViewpoint()) - if (vpObject == m_CinematicObject) - player->SetViewpoint(m_CinematicObject, false); - - m_CinematicObject->AddObjectToRemoveList(); - } + _cinematicDiff = 0; + _cinematicUpdateTimer.Reset(); + _cinematicCamera = nullptr; + _activeCinematicCameraId = 0; + _player.UpdateVisibilityForPlayer(); } -void CinematicMgr::UpdateCinematicLocation(uint32 /*diff*/) +void CinematicMgr::UpdateCinematic(uint32 const diff) { - if (m_activeCinematicCameraId == 0 || !m_cinematicCamera || m_cinematicCamera->size() == 0) + if (_activeCinematicCameraId == 0 || !_cinematicCamera || _cinematicCamera->empty()) return; + _cinematicDiff += diff; + _cinematicUpdateTimer.Update(diff); + if (!_cinematicUpdateTimer.Passed()) + return; + + _cinematicUpdateTimer.Reset(); + Position lastPosition; uint32 lastTimestamp = 0; Position nextPosition; uint32 nextTimestamp = 0; // Obtain direction of travel - for (FlyByCamera cam : *m_cinematicCamera) + for (FlyByCamera const& cam : *_cinematicCamera) { - if (cam.timeStamp > m_cinematicDiff) + if (cam.timeStamp > _cinematicDiff) { nextPosition.Relocate(cam.locations); nextTimestamp = cam.timeStamp; @@ -113,26 +105,25 @@ void CinematicMgr::UpdateCinematicLocation(uint32 /*diff*/) } float angle = lastPosition.GetAbsoluteAngle(&nextPosition); angle -= lastPosition.GetOrientation(); - if (angle < 0) - angle += 2 * float(M_PI); + angle = Position::NormalizeOrientation(angle); // Look for position around 2 second ahead of us. - int32 workDiff = m_cinematicDiff; + int32 workDiff = _cinematicDiff; // Modify result based on camera direction (Humans for example, have the camera point behind) workDiff += static_cast(float(CINEMATIC_LOOKAHEAD) * cos(angle)); // Get an iterator to the last entry in the cameras, to make sure we don't go beyond the end - auto endItr = m_cinematicCamera->rbegin(); - if (endItr != m_cinematicCamera->rend() && workDiff > static_cast(endItr->timeStamp)) + auto endItr = _cinematicCamera->rbegin(); + if (endItr != _cinematicCamera->rend() && workDiff > static_cast(endItr->timeStamp)) workDiff = endItr->timeStamp; // Never try to go back in time before the start of cinematic! if (workDiff < 0) - workDiff = m_cinematicDiff; + workDiff = _cinematicDiff; // Obtain the previous and next waypoint based on timestamp - for (FlyByCamera cam : *m_cinematicCamera) + for (FlyByCamera const& cam : *_cinematicCamera) { if (static_cast(cam.timeStamp) >= workDiff) { @@ -157,12 +148,15 @@ void CinematicMgr::UpdateCinematicLocation(uint32 /*diff*/) Position interPosition(lastPosition.m_positionX + (xDiff * (float(interDiff) / float(timeDiff))), lastPosition.m_positionY + (yDiff * (float(interDiff) / float(timeDiff))), lastPosition.m_positionZ + (zDiff * (float(interDiff) / float(timeDiff)))); - // Advance (at speed) to this position. The remote sight object is used - // to send update information to player in cinematic - if (m_CinematicObject && interPosition.IsPositionValid()) - m_CinematicObject->MonsterMoveWithSpeed(interPosition.m_positionX, interPosition.m_positionY, interPosition.m_positionZ, 500.0f); + // Advance _remoteSightPosition to new position + if (interPosition.IsPositionValid()) + { + _player.GetMap()->LoadGridsInRange(interPosition, MAX_VISIBILITY_DISTANCE); + _remoteSightPosition.Relocate(interPosition); + _player.UpdateVisibilityForPlayer(); + } // If we never received an end packet 10 seconds after the final timestamp then force an end - if (m_cinematicDiff > m_cinematicLength + 10 * IN_MILLISECONDS) + if (_cinematicDiff > _cinematicCamera->back().timeStamp + 10 * IN_MILLISECONDS) EndCinematic(); } diff --git a/src/server/game/Entities/Player/CinematicMgr.h b/src/server/game/Entities/Player/CinematicMgr.h index 1dffe9957..3409854d0 100644 --- a/src/server/game/Entities/Player/CinematicMgr.h +++ b/src/server/game/Entities/Player/CinematicMgr.h @@ -20,7 +20,7 @@ #include "Define.h" #include "Position.h" -#include "TemporarySummon.h" +#include "Timer.h" #include constexpr auto CINEMATIC_UPDATEDIFF = 500; @@ -31,31 +31,27 @@ struct FlyByCamera; class AC_GAME_API CinematicMgr { - friend class Player; public: - explicit CinematicMgr(Player* playerref); - ~CinematicMgr(); + explicit CinematicMgr(Player& player); + ~CinematicMgr() = default; // Cinematic camera data and remote sight functions - uint32 GetActiveCinematicCamera() const { return m_activeCinematicCameraId; } - void SetActiveCinematicCamera(uint32 cinematicCameraId = 0) { m_activeCinematicCameraId = cinematicCameraId; } - bool IsOnCinematic() const { return (m_cinematicCamera != nullptr); } - void BeginCinematic(); + void StartCinematic(uint32 const cinematicSequenceId); + uint32 GetActiveCinematicCamera() const { return _activeCinematicCameraId; } + void SetActiveCinematicCamera(uint32 cinematicCameraId = 0) { _activeCinematicCameraId = cinematicCameraId; } + bool IsOnCinematic() const { return (_cinematicCamera != nullptr); } + void StartCinematicCamera(); void EndCinematic(); - void UpdateCinematicLocation(uint32 diff); + void UpdateCinematic(uint32 const diff); + Position const& GetRemoteSightPosition() const { return _remoteSightPosition; } private: - // Remote location information - Player* player; - -protected: - uint32 m_cinematicDiff; - uint32 m_lastCinematicCheck; - uint32 m_activeCinematicCameraId; - uint32 m_cinematicLength; - std::vector const* m_cinematicCamera; - Position m_remoteSightPosition; - TempSummon* m_CinematicObject; + Player& _player; + uint32 _cinematicDiff; + uint32 _activeCinematicCameraId; + std::vector const* _cinematicCamera; + Position _remoteSightPosition; + IntervalTimer _cinematicUpdateTimer; }; #endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 8f1ac6058..44e6a734e 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -154,7 +154,7 @@ static uint32 copseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 }; #ifdef _MSC_VER #pragma warning(disable:4355) #endif -Player::Player(WorldSession* session): Unit(), m_mover(this) +Player::Player(WorldSession* session): Unit(), m_mover(this), _cinematicMgr(*this) { #ifdef _MSC_VER #pragma warning(default:4355) @@ -395,8 +395,6 @@ Player::Player(WorldSession* session): Unit(), m_mover(this) m_creationTime = 0s; - _cinematicMgr = new CinematicMgr(this); - m_achievementMgr = new AchievementMgr(this); m_reputationMgr = new ReputationMgr(this); @@ -462,7 +460,6 @@ Player::~Player() delete m_runes; delete m_achievementMgr; delete m_reputationMgr; - delete _cinematicMgr; sWorldSessionMgr->DecreasePlayerCount(); @@ -5688,10 +5685,6 @@ void Player::SendCinematicStart(uint32 CinematicSequenceId) const WorldPacket data(SMSG_TRIGGER_CINEMATIC, 4); data << uint32(CinematicSequenceId); SendDirectMessage(&data); - if (CinematicSequencesEntry const* sequence = sCinematicSequencesStore.LookupEntry(CinematicSequenceId)) - { - _cinematicMgr->SetActiveCinematicCamera(sequence->cinematicCamera); - } } void Player::SendMovieStart(uint32 MovieId) @@ -13201,6 +13194,14 @@ WorldObject* Player::GetViewpoint() const return nullptr; } +Position const& Player::GetSightPosition() const +{ + if (_cinematicMgr.IsOnCinematic()) + return _cinematicMgr.GetRemoteSightPosition(); + + return *m_seer; +} + bool Player::CanUseBattlegroundObject(GameObject* gameobject) const { // It is possible to call this method will a nullptr pointer, only skipping faction check. @@ -16270,7 +16271,10 @@ bool Player::IsWorldObjectOutOfSightRange(WorldObject const* target) const } // Check if out of range - return !m_seer->IsWithinDist(target, GetSightRange(target), false); + if (!target->IsWithinSightRange(GetSightPosition(), GetSightRange(target))) + return true; + + return false; } std::string Player::GetPlayerName() diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 7d1ad43d6..45c5dc553 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1393,7 +1393,8 @@ public: [[nodiscard]] TradeData* GetTradeData() const { return m_trade; } void TradeCancel(bool sendback, TradeStatus status = TRADE_STATUS_TRADE_CANCELED); - CinematicMgr* GetCinematicMgr() const { return _cinematicMgr; } + CinematicMgr& GetCinematicMgr() { return _cinematicMgr; } + CinematicMgr const& GetCinematicMgr() const { return _cinematicMgr; } void UpdateEnchantTime(uint32 time); void UpdateSoulboundTradeItems(); @@ -2384,6 +2385,7 @@ public: WorldObject* GetSeer() const { return m_seer; } void SetViewpoint(WorldObject* target, bool apply); [[nodiscard]] WorldObject* GetViewpoint() const; + Position const& GetSightPosition() const; void StopCastingCharm(Aura* except = nullptr); void StopCastingBindSight(Aura* except = nullptr); @@ -2978,7 +2980,7 @@ private: Item* _StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool update); Item* _LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint32 timeDiff, Field* fields); - CinematicMgr* _cinematicMgr; + CinematicMgr _cinematicMgr; typedef GuidSet RefundableItemsSet; RefundableItemsSet m_refundableItems; diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index b276e461d..ce2fde051 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -68,14 +68,8 @@ void Player::Update(uint32 p_time) m_nextMailDelivereTime = time_t(0); } - // Update cinematic location, if 500ms have passed and we're doing a - // cinematic now. - _cinematicMgr->m_cinematicDiff += p_time; - if (_cinematicMgr->m_cinematicCamera && _cinematicMgr->m_activeCinematicCameraId && GetMSTimeDiffToNow(_cinematicMgr->m_lastCinematicCheck) > CINEMATIC_UPDATEDIFF) - { - _cinematicMgr->m_lastCinematicCheck = getMSTime(); - _cinematicMgr->UpdateCinematicLocation(p_time); - } + // Update cinematic camera (if needed) + _cinematicMgr.UpdateCinematic(p_time); // used to implement delayed far teleports SetMustDelayTeleport(true); @@ -1608,8 +1602,8 @@ void Player::UpdateVisibilityForPlayer(bool mapChange) m_seer = this; Acore::VisibleNotifier notifier(*this, mapChange); - Cell::VisitObjects(m_seer, notifier, GetSightRange()); - Cell::VisitFarVisibleObjects(m_seer, notifier, VISIBILITY_DISTANCE_GIGANTIC); + Cell::VisitObjects(GetSightPosition().GetPositionX(), GetSightPosition().GetPositionY(), GetMap(), notifier, GetSightRange()); + Cell::VisitFarVisibleObjects(GetSightPosition().GetPositionX(), GetSightPosition().GetPositionY(), GetMap(), notifier, VISIBILITY_DISTANCE_GIGANTIC); notifier.SendToSelf(); if (mapChange) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 8712c7514..391bb12e3 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -16325,8 +16325,8 @@ void Unit::ExecuteDelayedUnitRelocationEvent() GetMap()->LoadGridsInRange(*player, MAX_VISIBILITY_DISTANCE); Acore::PlayerRelocationNotifier notifier(*player); - Cell::VisitObjects(viewPoint, notifier, player->GetSightRange()); - Cell::VisitFarVisibleObjects(viewPoint, notifier, VISIBILITY_DISTANCE_GIGANTIC); + Cell::VisitObjects(player->GetSightPosition().GetPositionX(), player->GetSightPosition().GetPositionY(), player->GetMap(), notifier, player->GetSightRange()); + Cell::VisitFarVisibleObjects(player->GetSightPosition().GetPositionX(), player->GetSightPosition().GetPositionY(), player->GetMap(), notifier, VISIBILITY_DISTANCE_GIGANTIC); notifier.SendToSelf(); this->AddToNotify(NOTIFY_AI_RELOCATION); diff --git a/src/server/game/Grids/Cells/Cell.h b/src/server/game/Grids/Cells/Cell.h index 72097e7e7..d077a19c6 100644 --- a/src/server/game/Grids/Cells/Cell.h +++ b/src/server/game/Grids/Cells/Cell.h @@ -107,6 +107,7 @@ struct Cell template static void VisitObjects(float x, float y, Map* map, T& visitor, float radius); template static void VisitFarVisibleObjects(WorldObject const* obj, T& visitor, float radius); + template static void VisitFarVisibleObjects(float x, float y, Map* map, T& visitor, float radius); private: template void VisitCircle(TypeContainerVisitor&, Map&, CellCoord const&, CellCoord const&) const; diff --git a/src/server/game/Grids/Cells/CellImpl.h b/src/server/game/Grids/Cells/CellImpl.h index 4bc1a66ed..3fe62948d 100644 --- a/src/server/game/Grids/Cells/CellImpl.h +++ b/src/server/game/Grids/Cells/CellImpl.h @@ -164,11 +164,7 @@ inline void Cell::VisitCircle(TypeContainerVisitor& visitor, Map& template inline void Cell::VisitObjects(WorldObject const* center_obj, T& visitor, float radius) { - CellCoord p(Acore::ComputeCellCoord(center_obj->GetPositionX(), center_obj->GetPositionY())); - Cell cell(p); - - TypeContainerVisitor gnotifier(visitor); - cell.Visit(p, gnotifier, *center_obj->GetMap(), *center_obj, radius); + VisitObjects(center_obj->GetPositionX(), center_obj->GetPositionY(), center_obj->GetMap(), visitor, radius); } template @@ -184,11 +180,17 @@ inline void Cell::VisitObjects(float x, float y, Map* map, T& visitor, float rad template inline void Cell::VisitFarVisibleObjects(WorldObject const* center_obj, T& visitor, float radius) { - CellCoord p(Acore::ComputeCellCoord(center_obj->GetPositionX(), center_obj->GetPositionY())); + VisitFarVisibleObjects(center_obj->GetPositionX(), center_obj->GetPositionY(), center_obj->GetMap(), visitor, radius); +} + +template +inline void Cell::VisitFarVisibleObjects(float x, float y, Map* map, T& visitor, float radius) +{ + CellCoord p(Acore::ComputeCellCoord(x, y)); Cell cell(p); TypeContainerVisitor gnotifier(visitor); - cell.Visit(p, gnotifier, *center_obj->GetMap(), *center_obj, radius); + cell.Visit(p, gnotifier, *map, x, y, radius); } #endif diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.cpp b/src/server/game/Grids/Notifiers/GridNotifiers.cpp index 36ed17744..28f87f286 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.cpp +++ b/src/server/game/Grids/Notifiers/GridNotifiers.cpp @@ -192,7 +192,7 @@ void MessageDistDeliverer::Visit(VisiblePlayersMap const& m) for (auto const& kvPair : m) { Player const* target = kvPair.second; - if (i_distSq != 0.0f && target->m_seer->GetExactDist2dSq(i_source) > i_distSq) + if (i_distSq != 0.0f && target->GetSightPosition().GetExactDist2dSq(i_source) > i_distSq) continue; // @todo: Might not need this check anymore diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index e3271e305..838ed4048 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -1384,7 +1384,7 @@ namespace Acore AnyPlayerExactPositionInGameObjectRangeCheck(GameObject const* go, float range) : _go(go), _range(range) {} bool operator()(Player* u) { - if (!_go->IsInRange(u->GetPositionX(), u->GetPositionY(), u->GetPositionZ(), _range)) + if (!_go->IsInRange3d(u->GetPositionX(), u->GetPositionY(), u->GetPositionZ(), _range)) return false; return true; @@ -1604,7 +1604,7 @@ namespace Acore bool operator() (GameObject* go) { if (!entry || (go->GetGOInfo() && go->GetGOInfo()->entry == entry)) - return go->IsInRange(x, y, z, range); + return go->IsInRange3d(x, y, z, range); else return false; } private: diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 2a605bcdb..d1f558025 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -872,9 +872,9 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) if (ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(pCurrChar->getClass())) { if (cEntry->CinematicSequence) - pCurrChar->SendCinematicStart(cEntry->CinematicSequence); + pCurrChar->GetCinematicMgr().StartCinematic(cEntry->CinematicSequence); else if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(pCurrChar->getRace())) - pCurrChar->SendCinematicStart(rEntry->CinematicSequence); + pCurrChar->GetCinematicMgr().StartCinematic(rEntry->CinematicSequence); // send new char string if not empty std::string_view newCharString = sWorld->getStringConfig(CONFIG_NEW_CHAR_STRING); diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index c90c3ff0e..bfe454add 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -947,14 +947,14 @@ void WorldSession::HandleSetActionButtonOpcode(WorldPacket& recv_data) void WorldSession::HandleCompleteCinematic(WorldPacket& /*recv_data*/) { - // If player has sight bound to visual waypoint NPC we should remove it - GetPlayer()->GetCinematicMgr()->EndCinematic(); + // End the current cinematic and restore the normal player view + GetPlayer()->GetCinematicMgr().EndCinematic(); } void WorldSession::HandleNextCinematicCamera(WorldPacket& /*recv_data*/) { // Sent by client when cinematic actually begun. So we begin the server side process - GetPlayer()->GetCinematicMgr()->BeginCinematic(); + GetPlayer()->GetCinematicMgr().StartCinematicCamera(); } void WorldSession::HandleSetActionBarToggles(WorldPacket& recv_data) diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 2e85d484f..55113a806 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -379,8 +379,8 @@ void OpcodeTable::Initialize() /*0x0F8*/ DEFINE_HANDLER(CMSG_TRIGGER_CINEMATIC_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); /*0x0F9*/ DEFINE_HANDLER(CMSG_OPENING_CINEMATIC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); /*0x0FA*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRIGGER_CINEMATIC, STATUS_NEVER); - /*0x0FB*/ DEFINE_HANDLER(CMSG_NEXT_CINEMATIC_CAMERA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNextCinematicCamera ); - /*0x0FC*/ DEFINE_HANDLER(CMSG_COMPLETE_CINEMATIC, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCompleteCinematic ); + /*0x0FB*/ DEFINE_HANDLER(CMSG_NEXT_CINEMATIC_CAMERA, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleNextCinematicCamera ); + /*0x0FC*/ DEFINE_HANDLER(CMSG_COMPLETE_CINEMATIC, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleCompleteCinematic ); /*0x0FD*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_TUTORIAL_FLAGS, STATUS_NEVER); /*0x0FE*/ DEFINE_HANDLER(CMSG_TUTORIAL_FLAG, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialFlag ); /*0x0FF*/ DEFINE_HANDLER(CMSG_TUTORIAL_CLEAR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialClear ); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 63e2c5c0b..34163da51 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -9069,7 +9069,7 @@ namespace Acore { if (target->IsGameObject()) { - if (!target->ToGameObject()->IsInRange(_position->GetPositionX(), _position->GetPositionY(), _position->GetPositionZ(), _range)) + if (!target->ToGameObject()->IsInRange3d(_position->GetPositionX(), _position->GetPositionY(), _position->GetPositionZ(), _range)) return false; } else if (!target->IsWithinDist3d(_position, _range)) diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index cd1fc6702..156c0373a 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -137,7 +137,7 @@ public: handler->PSendSysMessage("{} waypoints dumped", flyByCameras->size()); } - handler->GetPlayer()->SendCinematicStart(cinematicId); + handler->GetPlayer()->GetCinematicMgr().StartCinematic(cinematicId); return true; } From d78bce0a32a28cf0d71e261d778b52991bfcf64b Mon Sep 17 00:00:00 2001 From: sogladev Date: Sat, 21 Mar 2026 02:39:18 +0100 Subject: [PATCH 005/132] refactor(Scripts/Northrend): use emplace_back instead of push_back (#25142) --- .../HallsOfReflection/halls_of_reflection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp index 43547fcf0..bf20e8d99 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp @@ -1822,9 +1822,9 @@ public: { me->SetSpeed(MOVE_RUN, 9.0f / 7.0f); Movement::PointsArray path; - path.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); + path.emplace_back(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); for (uint8 i = 0; i <= 2; ++i) - path.push_back(G3D::Vector3(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ())); + path.emplace_back(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()); me->GetMotionMaster()->MoveSplinePath(&path); } break; @@ -1834,9 +1834,9 @@ public: Talk(SAY_LK_WINTER); me->CastSpell(me, SPELL_REMORSELESS_WINTER, true); Movement::PointsArray path; - path.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); + path.emplace_back(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); for (uint8 i = 3; i < PATH_WP_COUNT - 1; ++i) - path.push_back(G3D::Vector3(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ())); + path.emplace_back(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()); me->GetMotionMaster()->MoveSplinePath(&path); me->GetMotionMaster()->propagateSpeedChange(); events.ScheduleEvent(EVENT_LK_SUMMON, 1s); @@ -1986,9 +1986,9 @@ public: ++currentStopPoint; me->SetWalk(false); Movement::PointsArray path; - path.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); + path.emplace_back(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); for (uint8 i = WP_STOP[currentStopPoint - 1] + (currentStopPoint == 1 ? 0 : 1); i <= WP_STOP[currentStopPoint]; ++i) - path.push_back(G3D::Vector3(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ())); + path.emplace_back(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()); me->GetMotionMaster()->MoveSplinePath(&path, FORCED_MOVEMENT_RUN); } From 556c01fc91d59fecf7ce44039968484555921e62 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:42:30 -0500 Subject: [PATCH 006/132] fix(DB/Spell): Add internal cooldown to Spirit Burn proc (#25129) Co-authored-by: blinkysc --- data/sql/updates/pending_db_world/rev_1773945980134291128.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1773945980134291128.sql diff --git a/data/sql/updates/pending_db_world/rev_1773945980134291128.sql b/data/sql/updates/pending_db_world/rev_1773945980134291128.sql new file mode 100644 index 000000000..6de026bd8 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1773945980134291128.sql @@ -0,0 +1,3 @@ +-- Spirit Burn (54647) - Add 10s internal cooldown +DELETE FROM `spell_proc` WHERE `SpellId` = 54647; +INSERT INTO `spell_proc` (`SpellId`, `Cooldown`) VALUES (54647, 8000); From 82a22cd021da24700af3a7a6ffb99781023c1ebf Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:43:19 -0500 Subject: [PATCH 007/132] fix(Core/Arena): Make personal rating season-aware on team join (#24755) Co-authored-by: blinkysc --- src/server/game/Battlegrounds/ArenaTeam.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/game/Battlegrounds/ArenaTeam.cpp b/src/server/game/Battlegrounds/ArenaTeam.cpp index 720270d92..8e32b3c99 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.cpp +++ b/src/server/game/Battlegrounds/ArenaTeam.cpp @@ -132,6 +132,8 @@ bool ArenaTeam::AddMember(ObjectGuid playerGuid) if (sWorld->getIntConfig(CONFIG_ARENA_START_PERSONAL_RATING) > 0) personalRating = sWorld->getIntConfig(CONFIG_ARENA_START_PERSONAL_RATING); + else if (sArenaSeasonMgr->GetCurrentSeason() < 6) + personalRating = 1500; else if (GetRating() >= 1000) personalRating = 1000; From 4250768a2312a124a416036b5f3e736849763dd3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 01:43:47 +0000 Subject: [PATCH 008/132] chore(DB): import pending files Referenced commit(s): 556c01fc91d59fecf7ce44039968484555921e62 --- .../rev_1773945980134291128.sql => db_world/2026_03_21_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1773945980134291128.sql => db_world/2026_03_21_00.sql} (79%) diff --git a/data/sql/updates/pending_db_world/rev_1773945980134291128.sql b/data/sql/updates/db_world/2026_03_21_00.sql similarity index 79% rename from data/sql/updates/pending_db_world/rev_1773945980134291128.sql rename to data/sql/updates/db_world/2026_03_21_00.sql index 6de026bd8..6a24761e2 100644 --- a/data/sql/updates/pending_db_world/rev_1773945980134291128.sql +++ b/data/sql/updates/db_world/2026_03_21_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_20_01 -> 2026_03_21_00 -- Spirit Burn (54647) - Add 10s internal cooldown DELETE FROM `spell_proc` WHERE `SpellId` = 54647; INSERT INTO `spell_proc` (`SpellId`, `Cooldown`) VALUES (54647, 8000); From d17aac161f389e389b00c4bc0f682ba92eac6434 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:44:38 -0500 Subject: [PATCH 009/132] fix(Core/Movement): Only update home position for patrolling NPCs (#24080) Co-authored-by: blinkysc Co-authored-by: sudlud --- src/server/game/Handlers/ItemHandler.cpp | 6 +++++- src/server/game/Handlers/NPCHandler.cpp | 6 +++++- src/server/game/Handlers/QuestHandler.cpp | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index e6e51086a..c2cc245b6 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -874,7 +874,11 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry) // Stop the npc if moving if (uint32 pause = vendor->GetMovementTemplate().GetInteractionPauseTimer()) vendor->PauseMovement(pause); - vendor->SetHomePosition(vendor->GetPosition()); + + // Update home position for patrolling NPCs only (prevents drift for stationary NPCs) + if (vendor->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE || + vendor->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + vendor->SetHomePosition(vendor->GetPosition()); SetCurrentVendor(vendorEntry); diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 8323cde9e..b355b1bcf 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -162,7 +162,11 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData) // Stop the npc if moving if (uint32 pause = unit->GetMovementTemplate().GetInteractionPauseTimer()) unit->PauseMovement(pause); - unit->SetHomePosition(unit->GetPosition()); + + // Update home position for patrolling NPCs only (prevents drift for stationary NPCs) + if (unit->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE || + unit->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + unit->SetHomePosition(unit->GetPosition()); // If spiritguide, no need for gossip menu, just put player into resurrect queue if (unit->IsSpiritGuide()) diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp index 25ebc3d32..33c4aeeda 100644 --- a/src/server/game/Handlers/QuestHandler.cpp +++ b/src/server/game/Handlers/QuestHandler.cpp @@ -96,7 +96,11 @@ void WorldSession::HandleQuestgiverHelloOpcode(WorldPacket& recvData) // Stop the npc if moving if (uint32 pause = creature->GetMovementTemplate().GetInteractionPauseTimer()) creature->PauseMovement(pause); - creature->SetHomePosition(creature->GetPosition()); + + // Update home position for patrolling NPCs only (prevents drift for stationary NPCs) + if (creature->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE || + creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + creature->SetHomePosition(creature->GetPosition()); if (sScriptMgr->OnGossipHello(_player, creature)) return; From 710d4986bdd90784297e084270af011290cec2a3 Mon Sep 17 00:00:00 2001 From: Victor Godoy <44324742+xDevICCI@users.noreply.github.com> Date: Sat, 21 Mar 2026 00:05:54 -0300 Subject: [PATCH 010/132] feat(Scripts/Commands): Add online session time to pinfo command (#24388) Co-authored-by: victor Co-authored-by: sogladev Co-authored-by: Claude Opus 4.6 Co-authored-by: Andrew <47818697+Nyeriah@users.noreply.github.com> --- .../updates/pending_db_world/rev_1768236150653871465.sql | 4 ++++ src/server/game/Miscellaneous/Language.h | 1 + src/server/scripts/Commands/cs_misc.cpp | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1768236150653871465.sql diff --git a/data/sql/updates/pending_db_world/rev_1768236150653871465.sql b/data/sql/updates/pending_db_world/rev_1768236150653871465.sql new file mode 100644 index 000000000..1f3eb455e --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1768236150653871465.sql @@ -0,0 +1,4 @@ +-- Add language string for pinfo online time +DELETE FROM `acore_string` WHERE `entry` = 35410; +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(35410, '¦ Online for: {}', '¦ 온라인 시간: {}', '¦ Online depuis : {}', '¦ Online seit: {}', '¦ 在线时长:{}', '¦ 線上時間:{}', '¦ Conectado durante: {}', '¦ Conectado durante: {}', '¦ Онлайн: {}'); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 70ff2a0ff..4482dcb7b 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -815,6 +815,7 @@ enum AcoreStrings LANG_NO_REASON = 35407, LANG_UNAUTHORIZED = 35408, LANG_PINFO_CHR_MAP_WITH_AREA = 35409, + LANG_PINFO_CHR_ONLINETIME = 35410, LANG_CHARACTER_GENDER_MALE = 855, LANG_CHARACTER_GENDER_FEMALE = 856, diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index cd543488a..bfa883c25 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -2418,6 +2418,13 @@ public: // Output XX. LANG_PINFO_CHR_PLAYEDTIME handler->PSendSysMessage(LANG_PINFO_CHR_PLAYEDTIME, (secsToTimeString(totalPlayerTime, true))); + // Output XXI. LANG_PINFO_CHR_ONLINETIME (only for online players) + if (playerTarget) + { + uint32 onlineTime = uint32(GameTime::GetGameTime().count() - playerTarget->m_logintime); + handler->PSendSysMessage(LANG_PINFO_CHR_ONLINETIME, secsToTimeString(onlineTime, true)); + } + // Mail Data - an own query, because it may or may not be useful. // SQL: "SELECT SUM(CASE WHEN (checked & 1) THEN 1 ELSE 0 END) AS 'readmail', COUNT(*) AS 'totalmail' FROM mail WHERE `receiver` = ?" CharacterDatabasePreparedStatement* mailQuery = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PINFO_MAILS); From 7e984aa1de790d51b1fec63ce181eb3d70dd028e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 03:07:41 +0000 Subject: [PATCH 011/132] chore(DB): import pending files Referenced commit(s): 710d4986bdd90784297e084270af011290cec2a3 --- .../rev_1768236150653871465.sql => db_world/2026_03_21_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1768236150653871465.sql => db_world/2026_03_21_01.sql} (92%) diff --git a/data/sql/updates/pending_db_world/rev_1768236150653871465.sql b/data/sql/updates/db_world/2026_03_21_01.sql similarity index 92% rename from data/sql/updates/pending_db_world/rev_1768236150653871465.sql rename to data/sql/updates/db_world/2026_03_21_01.sql index 1f3eb455e..a7ee80b76 100644 --- a/data/sql/updates/pending_db_world/rev_1768236150653871465.sql +++ b/data/sql/updates/db_world/2026_03_21_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_21_00 -> 2026_03_21_01 -- Add language string for pinfo online time DELETE FROM `acore_string` WHERE `entry` = 35410; INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES From f301aaea53796ff51a5c0f9efbf3bc85742cc94d Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sat, 21 Mar 2026 00:59:38 -0300 Subject: [PATCH 012/132] =?UTF-8?q?fix(Core/Spells):=20Spells=20that=20don?= =?UTF-8?q?'t=20initiate=20combat=20shouldn't=20affect=20=E2=80=A6=20(#251?= =?UTF-8?q?46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Opus 4.6 --- src/server/game/Spells/Spell.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 34163da51..a61cb33c7 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2887,16 +2887,9 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) if (!m_triggeredByAuraSpell.spellInfo || m_damage || (!(m_triggeredByAuraSpell.spellInfo->Effects[m_triggeredByAuraSpell.effectIndex].TriggerSpell == m_spellInfo->Id) && !(m_triggeredByAuraSpell.spellInfo->IsAuraEffectEqual(m_spellInfo)))) m_caster->AtTargetAttacked(effectUnit, !(m_spellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT) || m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS))); - // Unsure if there are more spells that are not supposed to stop enemy from - // regenerating HP from food, so for now it stays as an ID. - const uint32 SPELL_PREMEDITATION = 14183; - if (m_spellInfo->Id != SPELL_PREMEDITATION) - { - if (!effectUnit->IsStandState()) - { - effectUnit->SetStandState(UNIT_STAND_STATE_STAND); - } - } + // Spells that don't initiate combat shouldn't affect stand state (e.g. Premeditation, Storm Giant/Felreaver Passive) + if (m_spellInfo->HasInitialAggro() && !effectUnit->IsStandState()) + effectUnit->SetStandState(UNIT_STAND_STATE_STAND); } } From 51ee56d1e28516a849da7860d9ef776fc3110912 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sat, 21 Mar 2026 00:59:56 -0300 Subject: [PATCH 013/132] fix(Core/Movement): skip TriggerAlert for creatures immune to players (#25147) Co-authored-by: Claude Opus 4.6 --- src/server/game/AI/CreatureAI.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index fbe681109..3d1ae51cb 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -211,6 +211,9 @@ void CreatureAI::TriggerAlert(Unit const* who) const // If this unit isn't an NPC, is already distracted, is in combat, is confused, stunned or fleeing, do nothing if (!me->IsCreature() || me->IsEngaged() || me->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED)) return; + // If the creature is immune to players, it should not be alerted by them + if (me->IsImmuneToPC()) + return; // Only alert for hostiles! if (me->IsCivilian() || me->HasReactState(REACT_PASSIVE) || !me->IsHostileTo(who) || !me->_IsTargetAcceptable(who)) return; From a975a8368b6bbe06498a91e02c026715717866c7 Mon Sep 17 00:00:00 2001 From: sogladev Date: Sat, 21 Mar 2026 13:24:10 +0100 Subject: [PATCH 014/132] fix(DB/Trainer): Shattrath Leatherworking and Engineering bookshelves (#25130) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- data/sql/updates/pending_db_world/rev_1773957059802281730.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1773957059802281730.sql diff --git a/data/sql/updates/pending_db_world/rev_1773957059802281730.sql b/data/sql/updates/pending_db_world/rev_1773957059802281730.sql new file mode 100644 index 000000000..81133e39d --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1773957059802281730.sql @@ -0,0 +1,4 @@ +-- +-- swap trainers of Engineering (33611) and Leatherworking (33612) bookshelf creature entries +UPDATE `creature_default_trainer` SET `TrainerId` = 91 WHERE (`CreatureId` = 33611); +UPDATE `creature_default_trainer` SET `TrainerId` = 62 WHERE (`CreatureId` = 33612); From ac50be7f8d71cdc9a992da25df6cda1cbdec1495 Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sat, 21 Mar 2026 12:24:58 +0000 Subject: [PATCH 015/132] =?UTF-8?q?fix(Core/Loot):=20Fix=20Master=20Loot?= =?UTF-8?q?=20visibility=20and=20assignment=20for=20quest=20i=E2=80=A6=20(?= =?UTF-8?q?#24828)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sogla Co-authored-by: Ryan Turner <16946913+TheSCREWEDSoftware@users.noreply.github.com> --- src/server/game/Loot/LootMgr.cpp | 84 ++++++-------------------------- 1 file changed, 16 insertions(+), 68 deletions(-) diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index 74e8b7575..af65a38b9 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -418,83 +418,34 @@ bool LootItem::AllowedForPlayer(Player const* player, ObjectGuid source) const { ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(itemid); if (!pProto) - { return false; - } if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_LOOT, itemid, nullptr)) - { return false; - } - bool isMasterLooter = player->GetGroup() && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID(); - bool itemVisibleForMasterLooter = !needs_quest && (!follow_loot_rules || !is_underthreshold); - - // DB conditions check if (!sConditionMgr->IsObjectMeetToConditions(const_cast(player), conditions)) - { - // Master Looter can see conditioned recipes - if (isMasterLooter && itemVisibleForMasterLooter) - { - if (pProto->HasFlag(ITEM_FLAG_HIDE_UNUSABLE_RECIPE) || (pProto->Class == ITEM_CLASS_RECIPE && pProto->Bonding == BIND_WHEN_PICKED_UP && pProto->Spells[1].SpellId != 0)) - { - return true; - } - } - return false; - } // not show loot for not own team if (pProto->HasFlag2(ITEM_FLAG2_FACTION_HORDE) && player->GetTeamId(true) != TEAM_HORDE) - { return false; - } if (pProto->HasFlag2(ITEM_FLAG2_FACTION_ALLIANCE) && player->GetTeamId(true) != TEAM_ALLIANCE) - { return false; - } - // Master looter can see all items even if the character can't loot them - if (isMasterLooter && itemVisibleForMasterLooter) - { - return true; - } - - // Don't allow loot for players without profession or those who already know the recipe + // profession / recipe checks if (pProto->HasFlag(ITEM_FLAG_HIDE_UNUSABLE_RECIPE) && (!player->HasSkill(pProto->RequiredSkill) || player->HasSpell(pProto->Spells[1].SpellId))) - { return false; - } - // Don't allow to loot soulbound recipes that the player has already learned if (pProto->Class == ITEM_CLASS_RECIPE && pProto->Bonding == BIND_WHEN_PICKED_UP && pProto->Spells[1].SpellId != 0 && player->HasSpell(pProto->Spells[1].SpellId)) - { return false; - } // check quest requirements - if (!pProto->HasFlagCu(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS)) - { - // Don't drop quest items if the player is missing the relevant quest - if (needs_quest && !player->HasQuestForItem(itemid)) - return false; - - // for items that start quests - if (pProto->StartQuest) - { - // Don't drop the item if the player has already finished the quest OR player already has the item in their inventory, and that item is unique OR the player has not finished a prerequisite quest - uint32 prevQuestId = sObjectMgr->GetQuestTemplate(pProto->StartQuest) ? sObjectMgr->GetQuestTemplate(pProto->StartQuest)->GetPrevQuestId() : 0; - if (player->GetQuestStatus(pProto->StartQuest) != QUEST_STATUS_NONE || (player->HasItemCount(itemid, pProto->MaxCount) && pProto->MaxCount) || (prevQuestId && !player->GetQuestRewardStatus(prevQuestId))) - return false; - } - } + if (needs_quest && !pProto->HasFlagCu(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && !player->HasQuestForItem(itemid)) + return false; if (!sScriptMgr->OnAllowedForPlayerLootCheck(player, source)) - { return false; - } return true; } @@ -691,7 +642,7 @@ QuestItemList* Loot::FillQuestLoot(Player* player) QuestItemList* ql = new QuestItemList(); - Player* lootOwner = (roundRobinPlayer) ? ObjectAccessor::FindPlayer(roundRobinPlayer) : player; + bool isMasterLooter = player->GetGroup() && player->GetGroup()->GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID(); for (uint8 i = 0; i < quest_items.size(); ++i) { @@ -699,32 +650,29 @@ QuestItemList* Loot::FillQuestLoot(Player* player) sScriptMgr->OnPlayerBeforeFillQuestLootItem(player, item); - // Quest item is not free for all and is already assigned to another player - // or player doesn't need it - if (item.is_blocked || !item.AllowedForPlayer(player, sourceWorldObjectGUID)) - { - continue; - } + bool allowed = item.AllowedForPlayer(player, sourceWorldObjectGUID); - // Player is not the loot owner, and loot owner still needs this quest item - if (!item.freeforall && - lootOwner && lootOwner != player && - item.AllowedForPlayer(lootOwner, sourceWorldObjectGUID)) - { + if (!allowed && !isMasterLooter) continue; - } ql->push_back(QuestItem(i)); - ++unlootedCount; - if (!item.freeforall) + // Only add "allowed looter" if you are actually allowed to loot. + if (allowed) { - item.is_blocked = true; + item.AddAllowedLooter(player); + + if (!item.is_counted) + { + ++unlootedCount; + item.is_counted = true; + } } if (items.size() + ql->size() == MAX_NR_LOOT_ITEMS) break; } + if (ql->empty()) { delete ql; From b90c917b49cdbe70d351b80ff5e32619bf28f637 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 12:25:15 +0000 Subject: [PATCH 016/132] chore(DB): import pending files Referenced commit(s): a975a8368b6bbe06498a91e02c026715717866c7 --- .../rev_1773957059802281730.sql => db_world/2026_03_21_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1773957059802281730.sql => db_world/2026_03_21_02.sql} (85%) diff --git a/data/sql/updates/pending_db_world/rev_1773957059802281730.sql b/data/sql/updates/db_world/2026_03_21_02.sql similarity index 85% rename from data/sql/updates/pending_db_world/rev_1773957059802281730.sql rename to data/sql/updates/db_world/2026_03_21_02.sql index 81133e39d..ec20ef557 100644 --- a/data/sql/updates/pending_db_world/rev_1773957059802281730.sql +++ b/data/sql/updates/db_world/2026_03_21_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_21_01 -> 2026_03_21_02 -- -- swap trainers of Engineering (33611) and Leatherworking (33612) bookshelf creature entries UPDATE `creature_default_trainer` SET `TrainerId` = 91 WHERE (`CreatureId` = 33611); From e6054ec7ea04e71caa0077c3eea29c3cfad12a59 Mon Sep 17 00:00:00 2001 From: Kitzunu <24550914+Kitzunu@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:28:24 +0100 Subject: [PATCH 017/132] refactor(Script/Command): learn spell (#24319) Co-authored-by: Treeston <14020072+Treeston@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../rev_1767486855333599800.sql | 8 ++ src/server/game/Entities/Creature/Trainer.cpp | 2 +- src/server/game/Entities/Creature/Trainer.h | 2 +- src/server/game/Globals/ObjectMgr.cpp | 14 ++- src/server/game/Globals/ObjectMgr.h | 2 + src/server/scripts/Commands/cs_learn.cpp | 86 ++++++++++--------- 6 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1767486855333599800.sql diff --git a/data/sql/updates/pending_db_world/rev_1767486855333599800.sql b/data/sql/updates/pending_db_world/rev_1767486855333599800.sql new file mode 100644 index 000000000..66224d0b5 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1767486855333599800.sql @@ -0,0 +1,8 @@ +-- +DELETE FROM `command` WHERE `name`='learn all my quest'; +DELETE FROM `command` WHERE `name`='learn all my trainer'; +DELETE FROM `command` WHERE `name`='learn all my spells'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('learn all my quest', 2, 'Syntax: .learn all my quest Learn all spells rewarded from quest for your class.'), +('learn all my trainer', 2, 'Syntax: .learn all my trainer Learn all spells taught by trainers for your class.'); +UPDATE `command` SET `help` = 'Syntax: .learn all my class Learn all spells (trainer, talent, and quest rewards) for your class.' WHERE `name`='learn all my class'; diff --git a/src/server/game/Entities/Creature/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp index c9d3d193c..58b61aabb 100644 --- a/src/server/game/Entities/Creature/Trainer.cpp +++ b/src/server/game/Entities/Creature/Trainer.cpp @@ -130,7 +130,7 @@ namespace Trainer return nullptr; } - bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell) + bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell) const { SpellState state = GetSpellState(player, trainerSpell); if (state != SpellState::Available) diff --git a/src/server/game/Entities/Creature/Trainer.h b/src/server/game/Entities/Creature/Trainer.h index 66de1154d..49e405603 100644 --- a/src/server/game/Entities/Creature/Trainer.h +++ b/src/server/game/Entities/Creature/Trainer.h @@ -70,7 +70,7 @@ namespace Trainer [[nodiscard]] Spell const* GetSpell(uint32 spellId) const; [[nodiscard]] std::vector const& GetSpells() const { return _spells; } void SendSpells(Creature* npc, Player* player, LocaleConstant locale) const; - bool CanTeachSpell(Player const* player, Spell const* trainerSpell); + bool CanTeachSpell(Player const* player, Spell const* trainerSpell) const; void TeachSpell(Creature* npc, Player* player, uint32 spellId); [[nodiscard]] Type GetTrainerType() const { return _type; } diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index b3bcd7fac..fe9ccbce6 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -9719,6 +9719,7 @@ void ObjectMgr::LoadTrainers() // For reload case _trainers.clear(); + _classTrainers.clear(); std::unordered_map> spellsByTrainer; if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell")) @@ -9795,7 +9796,18 @@ void ObjectMgr::LoadTrainers() spellsByTrainer.erase(spellsItr); } - _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells))); + auto [it, isNew] = _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells))); + ASSERT(isNew); + if (trainerType == Trainer::Type::Class) + { + if (!requirement || requirement >= MAX_CLASSES) + LOG_ERROR("sql.sql", "Table `trainer` has invalid class requirement for trainer {}, ignoring"); + else + { + uint8 classId = static_cast(requirement); + _classTrainers[classId].push_back(&it->second); + } + } } while (trainersResult->NextRow()); } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index d0b93880a..1c6185a50 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -1418,6 +1418,7 @@ public: bool DeleteGameTele(std::string_view name); Trainer::Trainer* GetTrainer(uint32 creatureId); + std::vector const& GetClassTrainers(uint8 classId) const { return _classTrainers.at(classId); } [[nodiscard]] VendorItemData const* GetNpcVendorItemList(uint32 entry) const { @@ -1666,6 +1667,7 @@ private: CacheVendorItemContainer _cacheVendorItemStore; std::unordered_map _trainers; + std::unordered_map> _classTrainers; std::unordered_map _creatureDefaultTrainers; std::set _difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate diff --git a/src/server/scripts/Commands/cs_learn.cpp b/src/server/scripts/Commands/cs_learn.cpp index 1d1825fa8..1b5cc4b89 100644 --- a/src/server/scripts/Commands/cs_learn.cpp +++ b/src/server/scripts/Commands/cs_learn.cpp @@ -37,8 +37,9 @@ public: { { "class", HandleLearnAllMyClassCommand, SEC_GAMEMASTER, Console::No }, { "pettalents", HandleLearnAllMyPetTalentsCommand, SEC_GAMEMASTER, Console::No }, - { "spells", HandleLearnAllMySpellsCommand, SEC_GAMEMASTER, Console::No }, - { "talents", HandleLearnAllMyTalentsCommand, SEC_GAMEMASTER, Console::No } + { "trainer", HandleLearnAllMyTrainerSpellsCommand, SEC_GAMEMASTER, Console::No }, + { "talents", HandleLearnAllMyTalentsCommand, SEC_GAMEMASTER, Console::No }, + { "quest", HandleLearnAllMyQuestSpells, SEC_GAMEMASTER, Console::No } }; static ChatCommandTable learnAllCommandTable = @@ -98,52 +99,57 @@ public: static bool HandleLearnAllMyClassCommand(ChatHandler* handler) { - HandleLearnAllMySpellsCommand(handler); + HandleLearnAllMyTrainerSpellsCommand(handler); HandleLearnAllMyTalentsCommand(handler); + HandleLearnAllMyQuestSpells(handler); return true; } - static bool HandleLearnAllMySpellsCommand(ChatHandler* handler) + static bool HandleLearnAllMyQuestSpells(ChatHandler* handler) { - ChrClassesEntry const* classEntry = sChrClassesStore.LookupEntry(handler->GetSession()->GetPlayer()->getClass()); - if (!classEntry) - return true; - uint32 family = classEntry->spellfamily; - - for (uint32 i = 0; i < sSkillLineAbilityStore.GetNumRows(); ++i) + Player* player = handler->GetPlayer(); + for (auto const& questPair : sObjectMgr->GetQuestTemplates()) { - SkillLineAbilityEntry const* entry = sSkillLineAbilityStore.LookupEntry(i); - if (!entry) - continue; - - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(entry->Spell); - if (!spellInfo) - continue; - - // skip server-side/triggered spells - if (spellInfo->SpellLevel == 0) - continue; - - // skip wrong class/race skills - if (!handler->GetSession()->GetPlayer()->IsSpellFitByClassAndRace(spellInfo->Id)) - continue; - - // skip other spell families - if (spellInfo->SpellFamilyName != family) - continue; - - // skip spells with first rank learned as talent (and all talents then also) - uint32 firstRank = sSpellMgr->GetFirstSpellInChain(spellInfo->Id); - if (GetTalentSpellCost(firstRank) > 0) - continue; - - // skip broken spells - if (!SpellMgr::IsSpellValid(spellInfo)) - continue; - - handler->GetSession()->GetPlayer()->learnSpell(spellInfo->Id); + Quest const* quest = questPair.second; + if (quest->GetRequiredClasses() && player->SatisfyQuestClass(quest, false)) + player->learnQuestRewardedSpells(quest); } + return true; + } + + static bool HandleLearnAllMyTrainerSpellsCommand(ChatHandler* handler) + { + if (!sChrClassesStore.LookupEntry(handler->GetSession()->GetPlayer()->getClass())) + return true; + + Player* player = handler->GetPlayer(); + std::vector const& trainers = sObjectMgr->GetClassTrainers(player->getClass()); + + bool hadNew; + do + { + hadNew = false; + for (Trainer::Trainer const* trainer : trainers) + { + if (!trainer->IsTrainerValidForPlayer(player)) + continue; + + for (Trainer::Spell const& trainerSpell : trainer->GetSpells()) + { + if (!trainer->CanTeachSpell(player, &trainerSpell)) + continue; + + if (trainerSpell.IsCastable()) + player->CastSpell(player, trainerSpell.SpellId, true); + else + player->learnSpell(trainerSpell.SpellId, false); + + hadNew = true; + } + } + } while (hadNew); + handler->SendSysMessage(LANG_COMMAND_LEARN_CLASS_SPELLS); return true; } From 0f7340257540e6bed1080e051854492fd48eac92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 12:29:36 +0000 Subject: [PATCH 018/132] chore(DB): import pending files Referenced commit(s): e6054ec7ea04e71caa0077c3eea29c3cfad12a59 --- .../rev_1767486855333599800.sql => db_world/2026_03_21_03.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1767486855333599800.sql => db_world/2026_03_21_03.sql} (93%) diff --git a/data/sql/updates/pending_db_world/rev_1767486855333599800.sql b/data/sql/updates/db_world/2026_03_21_03.sql similarity index 93% rename from data/sql/updates/pending_db_world/rev_1767486855333599800.sql rename to data/sql/updates/db_world/2026_03_21_03.sql index 66224d0b5..15b58f5cc 100644 --- a/data/sql/updates/pending_db_world/rev_1767486855333599800.sql +++ b/data/sql/updates/db_world/2026_03_21_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_21_02 -> 2026_03_21_03 -- DELETE FROM `command` WHERE `name`='learn all my quest'; DELETE FROM `command` WHERE `name`='learn all my trainer'; From 9840d54ef4b5f8d61c3856515fd6daa00143a5fe Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sat, 21 Mar 2026 07:37:20 -0500 Subject: [PATCH 019/132] fix(Core/Combat): Fire OnPlayerLeaveCombat hook from CombatManager exit path (#25137) Co-authored-by: blinkysc --- src/server/game/Combat/CombatManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp index 64ba173e7..60bcd77be 100644 --- a/src/server/game/Combat/CombatManager.cpp +++ b/src/server/game/Combat/CombatManager.cpp @@ -427,6 +427,9 @@ bool CombatManager::UpdateOwnerCombatState() const _owner->AtExitCombat(); if (!_owner->IsCreature()) _owner->AtDisengage(); + + if (Player* player = _owner->ToPlayer()) + sScriptMgr->OnPlayerLeaveCombat(player); } if (Unit* master = _owner->GetCharmerOrOwner()) From c6125db63dec79b3951dff018caad0560e73686a Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sat, 21 Mar 2026 08:58:03 -0500 Subject: [PATCH 020/132] fix(agents): Tone down copilot (#25159) Co-authored-by: blinkysc --- .github/agents/pr-reviewer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/agents/pr-reviewer.md b/.github/agents/pr-reviewer.md index 0c7ee6571..830c3533f 100644 --- a/.github/agents/pr-reviewer.md +++ b/.github/agents/pr-reviewer.md @@ -109,7 +109,7 @@ Based on CLAUDE.md, always verify: - Be constructive and educational - Reference specific sections of CLAUDE.md, C++ Code Standards, or SQL Standards when applicable -- Suggest specific fixes with code examples when possible +- Suggest specific fixes with code examples if necessary - Highlight both issues and good practices - For C++ issues, cite specific rule from C++ Code Standards wiki - For SQL issues, cite specific rule from SQL Standards wiki From a7f61ed86a647e48e267113c95b87a48d767442d Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sat, 21 Mar 2026 12:37:02 -0300 Subject: [PATCH 021/132] fix(Core/Spells): Prevent Flexweave Underlay use on ground (#25158) Co-authored-by: Claude Opus 4.6 --- .../rev_flexweave_underlay_script.sql | 2 ++ src/server/scripts/Spells/spell_item.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_flexweave_underlay_script.sql diff --git a/data/sql/updates/pending_db_world/rev_flexweave_underlay_script.sql b/data/sql/updates/pending_db_world/rev_flexweave_underlay_script.sql new file mode 100644 index 000000000..03f85741e --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_flexweave_underlay_script.sql @@ -0,0 +1,2 @@ +DELETE FROM `spell_script_names` WHERE `spell_id` = 55002; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (55002, 'spell_item_flexweave_underlay'); diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index a94ee8382..4917351e8 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -3565,6 +3565,24 @@ class spell_item_rocket_boots : public SpellScript } }; +// 55002 - Flexweave Underlay +class spell_item_flexweave_underlay : public SpellScript +{ + PrepareSpellScript(spell_item_flexweave_underlay); + + SpellCastResult CheckCast() + { + if (GetCaster()->IsFlying() || GetCaster()->IsFalling()) + return SPELL_CAST_OK; + return SPELL_FAILED_NOT_FLYING; + } + + void Register() override + { + OnCheckCast += SpellCheckCastFn(spell_item_flexweave_underlay::CheckCast); + } +}; + class spell_item_healing_injector : public SpellScript { PrepareSpellScript(spell_item_healing_injector); @@ -6327,6 +6345,7 @@ void AddSC_item_spell_scripts() RegisterSpellScript(spell_item_deathbringers_will_normal); RegisterSpellScript(spell_item_deathbringers_will_heroic); RegisterSpellScript(spell_item_discerning_eye_beast_dummy); + RegisterSpellScript(spell_item_flexweave_underlay); RegisterSpellScript(spell_item_frozen_shadoweave); RegisterSpellScript(spell_item_healing_touch_refund); RegisterSpellScript(spell_item_heartpierce); From a361a10a0199b313cef568c366b46b700c0ad5ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 15:38:07 +0000 Subject: [PATCH 022/132] chore(DB): import pending files Referenced commit(s): a7f61ed86a647e48e267113c95b87a48d767442d --- .../2026_03_21_04.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_flexweave_underlay_script.sql => db_world/2026_03_21_04.sql} (79%) diff --git a/data/sql/updates/pending_db_world/rev_flexweave_underlay_script.sql b/data/sql/updates/db_world/2026_03_21_04.sql similarity index 79% rename from data/sql/updates/pending_db_world/rev_flexweave_underlay_script.sql rename to data/sql/updates/db_world/2026_03_21_04.sql index 03f85741e..39717e0a7 100644 --- a/data/sql/updates/pending_db_world/rev_flexweave_underlay_script.sql +++ b/data/sql/updates/db_world/2026_03_21_04.sql @@ -1,2 +1,3 @@ +-- DB update 2026_03_21_03 -> 2026_03_21_04 DELETE FROM `spell_script_names` WHERE `spell_id` = 55002; INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (55002, 'spell_item_flexweave_underlay'); From bf4c181b59b49cbb42b1578b80371d1d2d37c765 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:05:57 -0500 Subject: [PATCH 023/132] fix(Core/Combat): Fix guardians not attacking after threat system port (#25157) Co-authored-by: blinkysc --- src/server/game/AI/CreatureAI.cpp | 13 +++++++++---- src/server/game/Entities/Unit/Unit.cpp | 17 ++++++++++------- src/server/scripts/Pet/pet_dk.cpp | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 3d1ae51cb..b2535a446 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -239,14 +239,16 @@ void CreatureAI::EnterEvadeMode(EvadeReason why) { if (Unit* owner = me->GetCharmerOrOwner()) { + // Owned creatures (pets/guardians) follow their owner — clear evade state + // so they can re-enter combat immediately via CanBeginCombat + me->ClearUnitState(UNIT_STATE_EVADE); me->GetMotionMaster()->Clear(false); me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE); } else { // Required to prevent attacking creatures that are evading and cause them to reenter combat - // Does not apply to MoveFollow - me->AddUnitState(UNIT_STATE_EVADE); + // Does not apply to MoveFollow — UNIT_STATE_EVADE is already set from _EnterEvadeMode me->GetMotionMaster()->MoveTargetedHome(); } } @@ -381,8 +383,11 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/) return false; } - // Set evade state early to prevent recursion when CombatStop triggers JustExitedCombat - // This will be cleared in MoveTargetedHome for pets/guardians following their owner + // Set evade state early to prevent recursion: CombatStop below purges combat + // refs, which triggers JustExitedCombat -> EnterEvadeMode -> _EnterEvadeMode. + // The IsInEvadeMode() check above will catch it. + // EnterEvadeMode will clear this for owned creatures (pets/guardians) that + // use MoveFollow instead of MoveTargetedHome. me->AddUnitState(UNIT_STATE_EVADE); // don't remove vehicle auras, passengers aren't supposed to drop off the vehicle diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 391bb12e3..7d8cb92c0 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -7333,19 +7333,22 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) // set position before any AI calls/assistance //if (IsCreature()) // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); - if (creature && !IsControlledByPlayer()) + if (creature) { EngageWithTarget(victim); - creature->SendAIReaction(AI_REACTION_HOSTILE); + if (!IsControlledByPlayer()) + { + creature->SendAIReaction(AI_REACTION_HOSTILE); - /// @todo: Implement aggro range, detection range and assistance range templates - if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE))) - creature->CallAssistance(); + /// @todo: Implement aggro range, detection range and assistance range templates + if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE))) + creature->CallAssistance(); - creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD)); + creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD)); - SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + } } // delay offhand weapon attack by 50% of the base attack time diff --git a/src/server/scripts/Pet/pet_dk.cpp b/src/server/scripts/Pet/pet_dk.cpp index db6ef1361..e86aadd7a 100644 --- a/src/server/scripts/Pet/pet_dk.cpp +++ b/src/server/scripts/Pet/pet_dk.cpp @@ -306,6 +306,8 @@ struct npc_pet_dk_army_of_the_dead : public AggressorAI { npc_pet_dk_army_of_the_dead(Creature* creature) : AggressorAI(creature) { } + // Restrict MoveInLineOfSight aggro to targets already fighting our owner, + // so ghouls don't pull extra packs on their own. bool CanAIAttack(Unit const* target) const override { if (!target) @@ -315,6 +317,26 @@ struct npc_pet_dk_army_of_the_dead : public AggressorAI return false; return AggressorAI::CanAIAttack(target); } + + // Owner started attacking a target — engage immediately. + // We bypass OnOwnerCombatInteraction because CanStartAttack -> CanAIAttack + // may reject the target before combat refs are established. + void OwnerAttacked(Unit* target) override + { + if (!target || !me->IsAlive() || me->HasReactState(REACT_PASSIVE)) + return; + if (me->IsValidAttackTarget(target)) + AttackStart(target); + } + + // Owner was attacked — help defend. + void OwnerAttackedBy(Unit* attacker) override + { + if (!attacker || !me->IsAlive() || me->HasReactState(REACT_PASSIVE)) + return; + if (me->IsValidAttackTarget(attacker)) + AttackStart(attacker); + } }; struct npc_pet_dk_dancing_rune_weapon : public NullCreatureAI From d8d19b61d34a6bf8f7a6a3e1ad5f47c397b81dc4 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sat, 21 Mar 2026 15:27:04 -0300 Subject: [PATCH 024/132] =?UTF-8?q?refactor(Core/Battlefield):=20replace?= =?UTF-8?q?=20manual=20timers=20with=20TaskScheduler=20=E2=80=A6=20(#25083?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Opus 4.6 --- .../apps/worldserver/worldserver.conf.dist | 9 +++ src/server/game/Battlefield/Battlefield.cpp | 80 +++++++++---------- src/server/game/Battlefield/Battlefield.h | 14 +++- .../game/Battlefield/Zones/BattlefieldWG.cpp | 57 +++++++------ .../game/Battlefield/Zones/BattlefieldWG.h | 2 - src/server/game/World/WorldConfig.cpp | 2 + src/server/game/World/WorldConfig.h | 1 + 7 files changed, 87 insertions(+), 78 deletions(-) diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index e59978780..2e62d2829 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -3627,6 +3627,15 @@ Wintergrasp.NoBattleTimer = 150 Wintergrasp.CrashRestartTimer = 10 +# +# Wintergrasp.SkipBattleSessionCount +# Description: When the peace timer expires, if the number of active sessions +# exceeds this value, a new war will not start. +# Set to 0 to disable this check. +# Default: 3500 + +Wintergrasp.SkipBattleSessionCount = 3500 + # ################################################################################################### diff --git a/src/server/game/Battlefield/Battlefield.cpp b/src/server/game/Battlefield/Battlefield.cpp index b9d9d43e8..2edfa3fa4 100644 --- a/src/server/game/Battlefield/Battlefield.cpp +++ b/src/server/game/Battlefield/Battlefield.cpp @@ -31,6 +31,7 @@ #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Transport.h" +#include "World.h" #include "WorldPacket.h" #include "WorldSessionMgr.h" @@ -57,9 +58,6 @@ Battlefield::Battlefield() : NoWarBattleTime(0), RestartAfterCrash(0), TimeForAcceptInvite(20), - KickDontAcceptTimer(1000), - KickAfkPlayersTimer(1000), - LastResurrectTimer(RESURRECTION_INTERVAL), StartGroupingTimer(0), StartGrouping(false) { @@ -147,7 +145,11 @@ bool Battlefield::Update(uint32 diff) { if (Timer <= diff) { - if (!IsEnabled() || (!IsWarTime() && sWorldSessionMgr->GetActiveSessionCount() > 3500)) // if WG is disabled or there is more than 3500 connections, switch automatically + uint32 sessionLimit = sWorld->getIntConfig(CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT); + bool tooManySessions = sessionLimit && !IsWarTime() + && sWorldSessionMgr->GetActiveSessionCount() > sessionLimit; + + if (!IsEnabled() || tooManySessions) { Active = true; EndBattle(false); @@ -174,52 +176,16 @@ bool Battlefield::Update(uint32 diff) SendUpdateWorldStates(); } + _scheduler.Update(diff); + bool objectiveChanged = false; if (IsWarTime()) { - if (KickAfkPlayersTimer <= diff) - { - KickAfkPlayersTimer = 20000; - KickAfkPlayers(); - } - else - KickAfkPlayersTimer -= diff; - - // Kick players who chose not to accept invitation to the battle - if (KickDontAcceptTimer <= diff) - { - time_t now = GameTime::GetGameTime().count(); - for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) - for (PlayerTimerMap::value_type const& pair : InvitedPlayers[team]) - if (pair.second <= now) - KickPlayerFromBattlefield(pair.first); - - InvitePlayersInZoneToWar(); - for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) - for (PlayerTimerMap::value_type const& pair : PlayersWillBeKick[team]) - if (pair.second <= now) - KickPlayerFromBattlefield(pair.first); - - KickDontAcceptTimer = 5000; - } - else - KickDontAcceptTimer -= diff; - for (BfCapturePoint* cp : CapturePoints) if (cp->Update(diff)) objectiveChanged = true; } - if (LastResurrectTimer <= diff) - { - for (BfGraveyard* gy : GraveyardList) - if (gy) - gy->Resurrect(); - LastResurrectTimer = RESURRECTION_INTERVAL; - } - else - LastResurrectTimer -= diff; - return objectiveChanged; } @@ -342,6 +308,30 @@ void Battlefield::StartBattle() Timer = BattleTime; Active = true; + // Schedule war-only periodic timers + _scheduler.Schedule(1s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context) + { + KickAfkPlayers(); + context.Repeat(20s); + }); + + _scheduler.Schedule(1s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context) + { + time_t now = GameTime::GetGameTime().count(); + for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) + for (PlayerTimerMap::value_type const& pair : InvitedPlayers[team]) + if (pair.second <= now) + KickPlayerFromBattlefield(pair.first); + + InvitePlayersInZoneToWar(); + for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) + for (PlayerTimerMap::value_type const& pair : PlayersWillBeKick[team]) + if (pair.second <= now) + KickPlayerFromBattlefield(pair.first); + + context.Repeat(5s); + }); + InvitePlayersInZoneToWar(); InvitePlayersInQueueToWar(); @@ -359,6 +349,8 @@ void Battlefield::EndBattle(bool endByTimer) Active = false; + _scheduler.CancelGroup(BATTLEFIELD_TIMER_GROUP_WAR); + StartGrouping = false; if (!endByTimer) @@ -626,7 +618,9 @@ void Battlefield::RemovePlayerFromResurrectQueue(ObjectGuid playerGuid) void Battlefield::SendAreaSpiritHealerQueryOpcode(Player* player, ObjectGuid const& guid) { WorldPacket data(SMSG_AREA_SPIRIT_HEALER_TIME, 12); - uint32 time = LastResurrectTimer; // resurrect every 30 seconds + Milliseconds remaining = _scheduler.GetNextGroupOccurrence(BATTLEFIELD_TIMER_GROUP_RESURRECT); + uint32 time = static_cast(std::clamp(remaining, + Milliseconds::zero(), Milliseconds(RESURRECTION_INTERVAL)).count()); data << guid << time; ASSERT(player); diff --git a/src/server/game/Battlefield/Battlefield.h b/src/server/game/Battlefield/Battlefield.h index ad2a3394c..b3b45cb42 100644 --- a/src/server/game/Battlefield/Battlefield.h +++ b/src/server/game/Battlefield/Battlefield.h @@ -22,6 +22,7 @@ #include "GameObject.h" #include "ObjectAccessor.h" #include "SharedDefines.h" +#include "TaskScheduler.h" #include "ZoneScript.h" enum BattlefieldTypes @@ -55,6 +56,13 @@ enum BattlefieldSounds constexpr auto BATTLEFIELD_OBJECTIVE_UPDATE_INTERVAL = 1000; +enum BattlefieldTimerGroups +{ + BATTLEFIELD_TIMER_GROUP_RESURRECT = 1, + BATTLEFIELD_TIMER_GROUP_WAR = 2, + BATTLEFIELD_TIMER_GROUP_SAVE = 3, +}; + const uint32 BattlefieldFactions[PVP_TEAMS_COUNT] = { 1732, // Alliance @@ -401,18 +409,16 @@ protected: uint32 NoWarBattleTime; // Time between two battles uint32 RestartAfterCrash; // Delay to restart Wintergrasp if the server crashed during a running battle uint32 TimeForAcceptInvite; - uint32 KickDontAcceptTimer; WorldLocation KickPosition; // Position where players are teleported if they switch to afk during the battle or if they don't accept invitation - uint32 KickAfkPlayersTimer; // Timer for check Afk in war - // Graveyard variables GraveyardVect GraveyardList; // Vector which contains the different GY of the battle - uint32 LastResurrectTimer; // Timer for resurrect player every 30 sec uint32 StartGroupingTimer; // Timer for invite players in area 15 minutes before start battle bool StartGrouping; // bool for knowing if all players in area have been invited + TaskScheduler _scheduler; + GuidUnorderedSet Groups[PVP_TEAMS_COUNT]; // Contains different raid groups std::vector Data64; diff --git a/src/server/game/Battlefield/Zones/BattlefieldWG.cpp b/src/server/game/Battlefield/Zones/BattlefieldWG.cpp index 2478162d7..f0f9ee4fc 100644 --- a/src/server/game/Battlefield/Zones/BattlefieldWG.cpp +++ b/src/server/game/Battlefield/Zones/BattlefieldWG.cpp @@ -77,8 +77,6 @@ bool BattlefieldWG::SetupBattlefield() Data32.resize(BATTLEFIELD_WG_DATA_MAX); - SaveTimer = 60000; - // Init Graveyards SetGraveyardNumber(BATTLEFIELD_WG_GRAVEYARD_MAX); @@ -189,37 +187,31 @@ bool BattlefieldWG::SetupBattlefield() } UpdateCounterVehicle(true); + + // Schedule always-running periodic timers + _scheduler.Schedule(Milliseconds(RESURRECTION_INTERVAL), + BATTLEFIELD_TIMER_GROUP_RESURRECT, [this](TaskContext context) + { + for (BfGraveyard* gy : GraveyardList) + if (gy) + gy->Resurrect(); + context.Repeat(); + }); + + _scheduler.Schedule(60s, BATTLEFIELD_TIMER_GROUP_SAVE, [this](TaskContext context) + { + sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_ACTIVE, Active); + sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_DEFENDER, DefenderTeam); + sWorldState->setWorldState(ClockWorldState[0], Timer); + context.Repeat(); + }); + return true; } bool BattlefieldWG::Update(uint32 diff) { - bool result = Battlefield::Update(diff); - if (SaveTimer <= diff) - { - sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_ACTIVE, Active); - sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_DEFENDER, DefenderTeam); - sWorldState->setWorldState(ClockWorldState[0], Timer); - SaveTimer = 60 * IN_MILLISECONDS; - } - else - SaveTimer -= diff; - - // Update Tenacity - if (IsWarTime()) - { - if (TenacityUpdateTimer <= diff) - { - TenacityUpdateTimer = 10000; - if (!UpdateTenacityList.empty()) - UpdateTenacity(); - UpdateTenacityList.clear(); - } - else - TenacityUpdateTimer -= diff; - } - - return result; + return Battlefield::Update(diff); } void BattlefieldWG::OnBattleStart() @@ -293,7 +285,14 @@ void BattlefieldWG::OnBattleStart() // Xinef: reset tenacity counter TenacityStack = 0; - TenacityUpdateTimer = 20000; + + _scheduler.Schedule(20s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context) + { + if (!UpdateTenacityList.empty()) + UpdateTenacity(); + UpdateTenacityList.clear(); + context.Repeat(10s); + }); if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE)) ChatHandler(nullptr).SendWorldText(BATTLEFIELD_WG_WORLD_START_MESSAGE); diff --git a/src/server/game/Battlefield/Zones/BattlefieldWG.h b/src/server/game/Battlefield/Zones/BattlefieldWG.h index 58d39e55f..8ea7d3ef3 100644 --- a/src/server/game/Battlefield/Zones/BattlefieldWG.h +++ b/src/server/game/Battlefield/Zones/BattlefieldWG.h @@ -449,8 +449,6 @@ protected: GuidUnorderedSet UpdateTenacityList; int32 TenacityStack; - uint32 TenacityUpdateTimer; - uint32 SaveTimer; ObjectGuid TitansRelic; }; diff --git a/src/server/game/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp index b30587974..d754951d3 100644 --- a/src/server/game/World/WorldConfig.cpp +++ b/src/server/game/World/WorldConfig.cpp @@ -591,6 +591,8 @@ void WorldConfig::BuildConfigCache() SetConfigValue(CONFIG_WINTERGRASP_NOBATTLETIME, "Wintergrasp.NoBattleTimer", 150); SetConfigValue(CONFIG_WINTERGRASP_RESTART_AFTER_CRASH, "Wintergrasp.CrashRestartTimer", 10); + SetConfigValue(CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT, "Wintergrasp.SkipBattleSessionCount", 3500); + SetConfigValue(CONFIG_BIRTHDAY_TIME, "BirthdayTime", 1222964635); SetConfigValue(CONFIG_MINIGOB_MANABONK, "Minigob.Manabonk.Enable", true); diff --git a/src/server/game/World/WorldConfig.h b/src/server/game/World/WorldConfig.h index 0e4480af2..6356e4111 100644 --- a/src/server/game/World/WorldConfig.h +++ b/src/server/game/World/WorldConfig.h @@ -327,6 +327,7 @@ enum ServerConfigs CONFIG_WINTERGRASP_BATTLETIME, CONFIG_WINTERGRASP_NOBATTLETIME, CONFIG_WINTERGRASP_RESTART_AFTER_CRASH, + CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT, CONFIG_PACKET_SPOOF_BANMODE, CONFIG_PACKET_SPOOF_BANDURATION, CONFIG_WARDEN_CLIENT_RESPONSE_DELAY, From 6d83465ad23eebffcbca58a6ed2d8e2e96ad70e9 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sat, 21 Mar 2026 17:09:02 -0300 Subject: [PATCH 025/132] fix(Core/Taxi): Use original team for flight path lookups (#25165) Co-authored-by: Claude Opus 4.6 --- src/server/game/Entities/Player/Player.cpp | 2 +- src/server/game/Globals/ObjectMgr.cpp | 5 +++++ src/server/game/Globals/ObjectMgr.h | 1 + src/server/game/Handlers/TaxiHandler.cpp | 6 +++--- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 44e6a734e..faa3b5053 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -10441,7 +10441,7 @@ void Player::SendTaxiNodeStatusMultiple() if (!creature->HasNpcFlag(UNIT_NPC_FLAG_FLIGHTMASTER)) return; - uint32 nearestNode = sObjectMgr->GetNearestTaxiNode(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(), creature->GetMapId(), GetTeamId()); + uint32 nearestNode = sObjectMgr->GetNearestTaxiNode(*creature, GetTeamId(true)); if (!nearestNode) return; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index fe9ccbce6..c3d639022 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -7160,6 +7160,11 @@ void ObjectMgr::LoadAreaTriggerScripts() LOG_INFO("server.loading", " "); } +uint32 ObjectMgr::GetNearestTaxiNode(WorldLocation const& loc, uint32 teamId) +{ + return GetNearestTaxiNode(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), loc.GetMapId(), teamId); +} + uint32 ObjectMgr::GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 teamId) { bool found = false; diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 1c6185a50..cbf2ff4e8 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -811,6 +811,7 @@ public: void GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, PlayerLevelInfo* info) const; uint32 GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 teamId); + uint32 GetNearestTaxiNode(WorldLocation const& loc, uint32 teamId); void GetTaxiPath(uint32 source, uint32 destination, uint32& path, uint32& cost); uint32 GetTaxiMountDisplayId(uint32 id, TeamId teamId, bool allowed_alt_team = false); diff --git a/src/server/game/Handlers/TaxiHandler.cpp b/src/server/game/Handlers/TaxiHandler.cpp index 4fd089b6b..c4f21afed 100644 --- a/src/server/game/Handlers/TaxiHandler.cpp +++ b/src/server/game/Handlers/TaxiHandler.cpp @@ -43,7 +43,7 @@ void WorldSession::SendTaxiStatus(ObjectGuid guid) } // find taxi node - uint32 nearest = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), player->GetTeamId()); + uint32 nearest = sObjectMgr->GetNearestTaxiNode(*unit, player->GetTeamId(true)); if (!nearest) { return; @@ -84,7 +84,7 @@ void WorldSession::HandleTaxiQueryAvailableNodes(WorldPacket& recvData) void WorldSession::SendTaxiMenu(Creature* unit) { // find current node - uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), GetPlayer()->GetTeamId()); + uint32 curloc = sObjectMgr->GetNearestTaxiNode(*unit, GetPlayer()->GetTeamId(true)); if (curloc == 0) return; @@ -127,7 +127,7 @@ void WorldSession::SendDoFlight(uint32 mountDisplayId, uint32 path, uint32 pathN bool WorldSession::SendLearnNewTaxiNode(Creature* unit) { // find current node - uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), GetPlayer()->GetTeamId()); + uint32 curloc = sObjectMgr->GetNearestTaxiNode(*unit, GetPlayer()->GetTeamId(true)); if (curloc == 0) return true; // `true` send to avoid WorldSession::SendTaxiMenu call with one more curlock seartch with same false result. From 749a31c3fa3998755a297da2e2df01b72759967e Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sat, 21 Mar 2026 20:54:29 -0500 Subject: [PATCH 026/132] fix(Core/Spells): Don't remove item-dependent auras on disarm/weapon swap (#25170) Co-authored-by: blinkysc Co-authored-by: ariel- --- src/server/game/Entities/Player/Player.cpp | 41 +++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index faa3b5053..3da3b0f1b 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -12499,21 +12499,52 @@ bool Player::HasItemFitToSpellRequirements(SpellInfo const* spellInfo, Item cons if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i)) if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) return true; + + // Keep active non-passive auras (e.g. Bladestorm) when disarmed + if (!spellInfo->IsPassive()) + { + bool hasWeaponInSlot = false; + for (uint8 i = EQUIPMENT_SLOT_MAINHAND; i < EQUIPMENT_SLOT_TABARD; ++i) + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) + { + hasWeaponInSlot = true; + break; + } + + if (hasWeaponInSlot) + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (spellInfo->Effects[i].IsAura()) + return true; + } + break; } case ITEM_CLASS_ARMOR: { + // most used check: shield only + if (spellInfo->EquippedItemSubClassMask & (1 << ITEM_SUBCLASS_ARMOR_SHIELD)) + { + if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND)) + if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) + return true; + + // Keep active non-passive auras (e.g. Shield Wall) when disarmed + Item* offhand = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (!spellInfo->IsPassive() && offhand && offhand != ignoreItem) + { + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (spellInfo->Effects[i].IsAura()) + return true; + } + } + // tabard not have dependent spells for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_MAINHAND; ++i) if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i)) if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) return true; - // shields can be equipped to offhand slot - if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND)) - if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) - return true; - // ranged slot can have some armor subclasses if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) From a4136cc8b9d97aa0d160c4f78193e00f67884503 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sat, 21 Mar 2026 21:35:24 -0500 Subject: [PATCH 027/132] fix(Core/Scripts): Fix Ebon Gargoyle infinite evade loop (#25173) Co-authored-by: blinkysc --- src/server/scripts/Pet/pet_dk.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/server/scripts/Pet/pet_dk.cpp b/src/server/scripts/Pet/pet_dk.cpp index e86aadd7a..f3d561f37 100644 --- a/src/server/scripts/Pet/pet_dk.cpp +++ b/src/server/scripts/Pet/pet_dk.cpp @@ -70,6 +70,25 @@ struct npc_pet_dk_ebon_gargoyle : ScriptedAI } } + void JustExitedCombat() override + { + EngagementOver(); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + if (!_EnterEvadeMode()) + return; + + me->ClearUnitState(UNIT_STATE_EVADE); + + if (Unit* owner = me->GetOwner()) + { + me->GetMotionMaster()->Clear(false); + me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE); + } + } + void InitializeAI() override { ScriptedAI::InitializeAI(); From 70278b5d99840ce8feeded624ee381be2efc1663 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:11:13 -0300 Subject: [PATCH 028/132] fix(Scripts/Oculus): preserve warrior stances when boarding drakes (#25149) Co-authored-by: Claude Opus 4.6 --- src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp b/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp index 0faf90823..392425bbb 100644 --- a/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp +++ b/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp @@ -817,7 +817,8 @@ class spell_oculus_rider_aura : public AuraScript _drakeGUID = drake->GetGUID(); caster->AddAura(SPELL_DRAKE_FLAG_VISUAL, caster); caster->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - caster->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); + if (caster->IsInDisallowedMountForm()) + caster->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); drake->CastSpell(drake, SPELL_SOAR_TRIGGER); if (drake->GetEntry() == NPC_RUBY_DRAKE) drake->CastSpell(drake, SPELL_RUBY_EVASIVE_AURA); From 44912f0e2aa2178ecbc57363ac8617941cbfe5c9 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:17:17 -0300 Subject: [PATCH 029/132] choreCore/Scripts): Remove unused includes from scripts (#25179) Co-authored-by: Claude Opus 4.6 (1M context) --- src/server/scripts/Commands/cs_cheat.cpp | 1 - src/server/scripts/Commands/cs_disable.cpp | 1 - src/server/scripts/Commands/cs_gear.cpp | 1 - src/server/scripts/Commands/cs_honor.cpp | 1 - src/server/scripts/Commands/cs_inventory.cpp | 1 - src/server/scripts/Events/winter_veil.cpp | 1 - src/server/scripts/OutdoorPvP/OutdoorPvPHP.cpp | 1 - src/server/scripts/OutdoorPvP/OutdoorPvPSI.cpp | 1 - src/server/scripts/OutdoorPvP/OutdoorPvPTF.cpp | 1 - src/server/scripts/Spells/spell_dk.cpp | 1 - src/server/scripts/Spells/spell_druid.cpp | 1 - src/server/scripts/Spells/spell_generic.cpp | 1 - src/server/scripts/Spells/spell_hunter.cpp | 1 - src/server/scripts/Spells/spell_item.cpp | 1 - src/server/scripts/Spells/spell_mage.cpp | 1 - src/server/scripts/Spells/spell_priest.cpp | 1 - src/server/scripts/Spells/spell_quest.cpp | 1 - src/server/scripts/Spells/spell_rogue.cpp | 1 - src/server/scripts/Spells/spell_shaman.cpp | 1 - src/server/scripts/Spells/spell_warlock.cpp | 1 - src/server/scripts/Spells/spell_warrior.cpp | 1 - src/server/scripts/World/go_scripts.cpp | 1 - src/server/scripts/World/item_scripts.cpp | 1 - src/server/scripts/World/server_mail.cpp | 1 - 24 files changed, 24 deletions(-) diff --git a/src/server/scripts/Commands/cs_cheat.cpp b/src/server/scripts/Commands/cs_cheat.cpp index 84d22411e..2b067ab78 100644 --- a/src/server/scripts/Commands/cs_cheat.cpp +++ b/src/server/scripts/Commands/cs_cheat.cpp @@ -19,7 +19,6 @@ #include "CommandScript.h" #include "Language.h" #include "Player.h" -#include "WorldSession.h" using namespace Acore::ChatCommands; diff --git a/src/server/scripts/Commands/cs_disable.cpp b/src/server/scripts/Commands/cs_disable.cpp index f38e387d3..2846f59a4 100644 --- a/src/server/scripts/Commands/cs_disable.cpp +++ b/src/server/scripts/Commands/cs_disable.cpp @@ -21,7 +21,6 @@ #include "Language.h" #include "ObjectMgr.h" #include "OutdoorPvP.h" -#include "Player.h" #include "SpellMgr.h" using namespace Acore::ChatCommands; diff --git a/src/server/scripts/Commands/cs_gear.cpp b/src/server/scripts/Commands/cs_gear.cpp index 1916359d7..b67fc456d 100644 --- a/src/server/scripts/Commands/cs_gear.cpp +++ b/src/server/scripts/Commands/cs_gear.cpp @@ -19,7 +19,6 @@ #include "CommandScript.h" #include "Language.h" #include "Player.h" -#include "WorldSession.h" using namespace Acore::ChatCommands; diff --git a/src/server/scripts/Commands/cs_honor.cpp b/src/server/scripts/Commands/cs_honor.cpp index 32cbb26a2..af83d0b9f 100644 --- a/src/server/scripts/Commands/cs_honor.cpp +++ b/src/server/scripts/Commands/cs_honor.cpp @@ -19,7 +19,6 @@ #include "CommandScript.h" #include "Language.h" #include "Player.h" -#include "WorldSession.h" using namespace Acore::ChatCommands; diff --git a/src/server/scripts/Commands/cs_inventory.cpp b/src/server/scripts/Commands/cs_inventory.cpp index 50490bb62..43fae2e91 100644 --- a/src/server/scripts/Commands/cs_inventory.cpp +++ b/src/server/scripts/Commands/cs_inventory.cpp @@ -19,7 +19,6 @@ #include "CommandScript.h" #include "Language.h" #include "Player.h" -#include "WorldSession.h" constexpr std::array bagSpecsToString = { diff --git a/src/server/scripts/Events/winter_veil.cpp b/src/server/scripts/Events/winter_veil.cpp index 32c848550..ffb82280e 100644 --- a/src/server/scripts/Events/winter_veil.cpp +++ b/src/server/scripts/Events/winter_veil.cpp @@ -15,7 +15,6 @@ * with this program. If not, see . */ -#include "CreatureScript.h" #include "GameEventMgr.h" #include "Player.h" #include "ScriptedCreature.h" diff --git a/src/server/scripts/OutdoorPvP/OutdoorPvPHP.cpp b/src/server/scripts/OutdoorPvP/OutdoorPvPHP.cpp index 66ac94100..f472847eb 100644 --- a/src/server/scripts/OutdoorPvP/OutdoorPvPHP.cpp +++ b/src/server/scripts/OutdoorPvP/OutdoorPvPHP.cpp @@ -16,7 +16,6 @@ */ #include "OutdoorPvPHP.h" -#include "CreatureScript.h" #include "Language.h" #include "MapMgr.h" #include "ObjectMgr.h" diff --git a/src/server/scripts/OutdoorPvP/OutdoorPvPSI.cpp b/src/server/scripts/OutdoorPvP/OutdoorPvPSI.cpp index 33e2fb9fa..bbd72a023 100644 --- a/src/server/scripts/OutdoorPvP/OutdoorPvPSI.cpp +++ b/src/server/scripts/OutdoorPvP/OutdoorPvPSI.cpp @@ -16,7 +16,6 @@ */ #include "OutdoorPvPSI.h" -#include "CreatureScript.h" #include "GameObject.h" #include "Language.h" #include "MapMgr.h" diff --git a/src/server/scripts/OutdoorPvP/OutdoorPvPTF.cpp b/src/server/scripts/OutdoorPvP/OutdoorPvPTF.cpp index 77bf4b4fa..ab781a85d 100644 --- a/src/server/scripts/OutdoorPvP/OutdoorPvPTF.cpp +++ b/src/server/scripts/OutdoorPvP/OutdoorPvPTF.cpp @@ -16,7 +16,6 @@ */ #include "OutdoorPvPTF.h" -#include "CreatureScript.h" #include "GameTime.h" #include "Language.h" #include "MapMgr.h" diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index ae2d2c517..442139783 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -16,7 +16,6 @@ */ #include "AreaDefines.h" -#include "CreatureScript.h" #include "PetDefines.h" #include "Player.h" #include "SpellAuraEffects.h" diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp index fea4d869e..0670da786 100644 --- a/src/server/scripts/Spells/spell_druid.cpp +++ b/src/server/scripts/Spells/spell_druid.cpp @@ -16,7 +16,6 @@ */ #include "Containers.h" -#include "CreatureScript.h" #include "GameTime.h" #include "GridNotifiers.h" #include "Player.h" diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index 7b72cf252..8dff3bba9 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -23,7 +23,6 @@ #include "Cell.h" #include "CellImpl.h" #include "Chat.h" -#include "CreatureScript.h" #include "GameTime.h" #include "GridNotifiers.h" #include "Group.h" diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index ed9556de2..a7147d328 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -17,7 +17,6 @@ #include "Cell.h" #include "CellImpl.h" -#include "CreatureScript.h" #include "GridNotifiers.h" #include "Pet.h" #include "SpellAuraEffects.h" diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index 4917351e8..764af2b9a 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -17,7 +17,6 @@ #include "AreaDefines.h" #include "Battleground.h" -#include "CreatureScript.h" #include "ObjectMgr.h" #include "Pet.h" #include "Player.h" diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index f69a43210..f6d5f240a 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -15,7 +15,6 @@ * with this program. If not, see . */ -#include "CreatureScript.h" #include "Pet.h" #include "Player.h" #include "SpellAuraEffects.h" diff --git a/src/server/scripts/Spells/spell_priest.cpp b/src/server/scripts/Spells/spell_priest.cpp index bcd9673e4..4ab3a1611 100644 --- a/src/server/scripts/Spells/spell_priest.cpp +++ b/src/server/scripts/Spells/spell_priest.cpp @@ -15,7 +15,6 @@ * with this program. If not, see . */ -#include "CreatureScript.h" #include "GridNotifiers.h" #include "Player.h" #include "SpellAuraEffects.h" diff --git a/src/server/scripts/Spells/spell_quest.cpp b/src/server/scripts/Spells/spell_quest.cpp index 2ec1a3891..bbf9bb52e 100644 --- a/src/server/scripts/Spells/spell_quest.cpp +++ b/src/server/scripts/Spells/spell_quest.cpp @@ -16,7 +16,6 @@ */ #include "CellImpl.h" -#include "CreatureScript.h" #include "CreatureTextMgr.h" #include "GridNotifiers.h" #include "MapMgr.h" diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp index 5d53b04d0..1d01f3028 100644 --- a/src/server/scripts/Spells/spell_rogue.cpp +++ b/src/server/scripts/Spells/spell_rogue.cpp @@ -17,7 +17,6 @@ #include "AreaDefines.h" #include "CellImpl.h" -#include "CreatureScript.h" #include "GameTime.h" #include "GridNotifiers.h" #include "SpellAuraEffects.h" diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp index cb7da23d9..1a9c545a5 100644 --- a/src/server/scripts/Spells/spell_shaman.cpp +++ b/src/server/scripts/Spells/spell_shaman.cpp @@ -15,7 +15,6 @@ * with this program. If not, see . */ -#include "CreatureScript.h" #include "GridNotifiers.h" #include "ObjectAccessor.h" #include "Player.h" diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp index b7b09b350..c3410d0da 100644 --- a/src/server/scripts/Spells/spell_warlock.cpp +++ b/src/server/scripts/Spells/spell_warlock.cpp @@ -16,7 +16,6 @@ */ #include "AreaDefines.h" -#include "CreatureScript.h" #include "Pet.h" #include "PetDefines.h" #include "Player.h" diff --git a/src/server/scripts/Spells/spell_warrior.cpp b/src/server/scripts/Spells/spell_warrior.cpp index b6a96008f..b6a4f53e1 100644 --- a/src/server/scripts/Spells/spell_warrior.cpp +++ b/src/server/scripts/Spells/spell_warrior.cpp @@ -15,7 +15,6 @@ * with this program. If not, see . */ -#include "CreatureScript.h" #include "Player.h" #include "SpellAuraEffects.h" #include "SpellInfo.h" diff --git a/src/server/scripts/World/go_scripts.cpp b/src/server/scripts/World/go_scripts.cpp index 9242761b4..902193daf 100644 --- a/src/server/scripts/World/go_scripts.cpp +++ b/src/server/scripts/World/go_scripts.cpp @@ -18,7 +18,6 @@ #include "AreaDefines.h" #include "CellImpl.h" #include "Chat.h" -#include "CreatureScript.h" #include "GameEventMgr.h" #include "GameObjectAI.h" #include "GameObjectScript.h" diff --git a/src/server/scripts/World/item_scripts.cpp b/src/server/scripts/World/item_scripts.cpp index 81b2cbb8d..c01767c2d 100644 --- a/src/server/scripts/World/item_scripts.cpp +++ b/src/server/scripts/World/item_scripts.cpp @@ -16,7 +16,6 @@ */ #include "AreaDefines.h" -#include "CreatureScript.h" #include "ItemScript.h" #include "Player.h" #include "ScriptedCreature.h" diff --git a/src/server/scripts/World/server_mail.cpp b/src/server/scripts/World/server_mail.cpp index 1a8d418a4..3a40f7492 100644 --- a/src/server/scripts/World/server_mail.cpp +++ b/src/server/scripts/World/server_mail.cpp @@ -15,7 +15,6 @@ * with this program. If not, see . */ -#include "CreatureScript.h" #include "Player.h" #include "PlayerScript.h" #include "QueryResult.h" From bdb6ebf1ef20ff75754e856dbe7ef2d5c9688299 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 09:21:58 -0500 Subject: [PATCH 030/132] fix(Scripts/Stormpeaks): Add Oathbound Warder pet abilities (#24062) Co-authored-by: blinkysc Co-authored-by: Gultask <100873791+Gultask@users.noreply.github.com> Co-authored-by: sudlud --- .../rev_1765066149291553835.sql | 9 +++++ .../scripts/Northrend/zone_storm_peaks.cpp | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1765066149291553835.sql diff --git a/data/sql/updates/pending_db_world/rev_1765066149291553835.sql b/data/sql/updates/pending_db_world/rev_1765066149291553835.sql new file mode 100644 index 000000000..14915bbd1 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1765066149291553835.sql @@ -0,0 +1,9 @@ +-- +UPDATE `creature_template` SET `ScriptName` = 'npc_oathbound_warder' WHERE `entry` = 30270; + +DELETE FROM `creature_template_spell` WHERE `CreatureID` = 30270; +INSERT INTO `creature_template_spell` (`CreatureID`, `Index`, `Spell`, `VerifiedBuild`) VALUES +(30270, 0, 56491, 0), +(30270, 1, 56425, 0), +(30270, 2, 56451, 0), +(30270, 3, 56506, 0); diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp index 4b4b17728..7f3c5873a 100644 --- a/src/server/scripts/Northrend/zone_storm_peaks.cpp +++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp @@ -20,6 +20,7 @@ #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" +#include "SpellMgr.h" #include "ScriptedEscortAI.h" #include "ScriptedGossip.h" #include "SpellAuraEffects.h" @@ -1340,9 +1341,46 @@ class spell_eject_passenger_wild_wyrm : public SpellScript } }; +struct npc_oathbound_warder : public ScriptedAI +{ + npc_oathbound_warder(Creature* creature) : ScriptedAI(creature) { } + + void AttackStart(Unit* /*who*/) override { } + void JustEngagedWith(Unit* /*who*/) override { } + void UpdateAI(uint32 /*diff*/) override { } // Need so AI doesn't stop casting when hit in combat + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + me->SetReactState(REACT_PASSIVE); + + CharmInfo* charmInfo = me->GetCharmInfo(); + if (!charmInfo) + return; + + charmInfo->InitEmptyActionBar(false); + + uint32 slot = 0; + for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i) + { + uint32 spellId = me->m_spells[i]; + if (!spellId) + continue; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (spellInfo && spellInfo->IsPassive()) + me->CastSpell(me, spellInfo, true); + + charmInfo->SetActionBar(6 + slot, spellId, ACT_PASSIVE); + ++slot; + } + } +}; + void AddSC_storm_peaks() { RegisterCreatureAI(npc_frosthound); + RegisterCreatureAI(npc_oathbound_warder); new npc_iron_watcher(); new npc_time_lost_proto_drake(); RegisterSpellScript(spell_q13007_iron_colossus); From 9e6fcb827f1ae71ac985bbf4d8f92478b2284f25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Mar 2026 14:23:04 +0000 Subject: [PATCH 031/132] chore(DB): import pending files Referenced commit(s): bdb6ebf1ef20ff75754e856dbe7ef2d5c9688299 --- .../rev_1765066149291553835.sql => db_world/2026_03_22_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1765066149291553835.sql => db_world/2026_03_22_00.sql} (88%) diff --git a/data/sql/updates/pending_db_world/rev_1765066149291553835.sql b/data/sql/updates/db_world/2026_03_22_00.sql similarity index 88% rename from data/sql/updates/pending_db_world/rev_1765066149291553835.sql rename to data/sql/updates/db_world/2026_03_22_00.sql index 14915bbd1..c53290bce 100644 --- a/data/sql/updates/pending_db_world/rev_1765066149291553835.sql +++ b/data/sql/updates/db_world/2026_03_22_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_21_04 -> 2026_03_22_00 -- UPDATE `creature_template` SET `ScriptName` = 'npc_oathbound_warder' WHERE `entry` = 30270; From a5b0cee7793bbc44ae1db795f349de2edec7253c Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 09:28:42 -0500 Subject: [PATCH 032/132] fix(Core/Spells): Enter combat at spell launch for delayed spells (#25167) Co-authored-by: blinkysc Co-authored-by: Treeston --- src/server/game/Spells/Spell.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index a61cb33c7..a901be115 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2593,6 +2593,9 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) m_spellAura = nullptr; // Set aura to null for every target-make sure that pointer is not used for unit without aura applied + if (m_originalCaster && missInfo != SPELL_MISS_EVADE && !m_originalCaster->IsFriendlyTo(effectUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || effectUnit->IsEngaged())) + effectUnit->SetInCombatWith(m_originalCaster); + PrepareScriptHitHandlers(); CallScriptBeforeHitHandlers(missInfo); @@ -8289,6 +8292,13 @@ void Spell::HandleLaunchPhase() } } + if (m_originalCaster && target.missCondition != SPELL_MISS_EVADE) + { + Unit* targetUnit = m_caster->GetGUID() == target.targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target.targetGUID); + if (targetUnit && !m_originalCaster->IsFriendlyTo(targetUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || targetUnit->IsEngaged())) + m_originalCaster->SetInCombatWith(targetUnit, true); + } + DoAllEffectOnLaunchTarget(target, multiplier); } From f554cde330de66f111f3fa3e6e28c66776bc500e Mon Sep 17 00:00:00 2001 From: Localhost <40755539+r0m1ntik@users.noreply.github.com> Date: Sun, 22 Mar 2026 15:54:14 +0100 Subject: [PATCH 033/132] =?UTF-8?q?fix(Core/Map):=20Ensure=20grid=20is=20v?= =?UTF-8?q?alid=20before=20unloading=20to=20prevent=20potential=20?= =?UTF-8?q?=E2=80=A6=20(#24621)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roman BADANIN Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/server/game/Maps/Map.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 63aad5ebb..27d0b8fed 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -1037,9 +1037,10 @@ void Map::UnloadAll() for (GridRefMgr::iterator i = GridRefMgr::begin(); i != GridRefMgr::end();) { - MapGridType& grid(*i->GetSource()); + MapGridType* grid = i->GetSource(); ++i; - UnloadGrid(grid); // deletes the grid and removes it from the GridRefMgr + if (grid) + UnloadGrid(*grid); } // pussywizard: crashfix, some npc can be left on transport (not a default passenger) From 4e21de8d86d5ec1092be589fd7be8ac5afe4f658 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:28:48 -0500 Subject: [PATCH 034/132] fix(Core/Spells): Invert disarm aura-keep check for weapons (#25175) Co-authored-by: blinkysc --- src/server/game/Entities/Player/Player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 3da3b0f1b..fb3af1eb4 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -12512,7 +12512,7 @@ bool Player::HasItemFitToSpellRequirements(SpellInfo const* spellInfo, Item cons break; } - if (hasWeaponInSlot) + if (!hasWeaponInSlot) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (spellInfo->Effects[i].IsAura()) return true; From 11d4d01e7fa2e08ac33f120bac9ffd0eba3ccb97 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:50:06 -0300 Subject: [PATCH 035/132] fix(Scripts/Ulduar): spawn keepers at Observation Ring on defeat (#25144) Co-authored-by: Claude Opus 4.6 --- .../Ulduar/Ulduar/boss_yoggsaron.cpp | 15 +++--- .../Ulduar/Ulduar/instance_ulduar.cpp | 52 +++++++++++++++++-- .../scripts/Northrend/Ulduar/Ulduar/ulduar.h | 6 +++ 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp index 3e458823e..f0f759e74 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp @@ -285,7 +285,6 @@ const Position KeepersPos[4] = const uint32 TABLE_KEEPER_ENTRY[4] = {NPC_FREYA_KEEPER, NPC_HODIR_KEEPER, NPC_MIMIRON_KEEPER, NPC_THORIM_KEEPER}; const uint32 TABLE_GOSSIP_ENTRY[4] = {NPC_FREYA_GOSSIP, NPC_HODIR_GOSSIP, NPC_MIMIRON_GOSSIP, NPC_THORIM_GOSSIP}; -const uint32 TABLE_KEEPER_TYPE[4] = {BOSS_FREYA, BOSS_HODIR, BOSS_MIMIRON, BOSS_THORIM}; static LocationsXY yoggPortalLoc[] = { @@ -505,8 +504,17 @@ struct boss_yoggsaron_sara : public ScriptedAI void DespawnGossipKeepers() { + static uint32 const gossipData[] = + { + DATA_FREYA_GOSSIP, DATA_HODIR_GOSSIP, + DATA_MIMIRON_GOSSIP, DATA_THORIM_GOSSIP + }; for (uint8 i = KEEPER_FREYA; i <= KEEPER_THORIM; i++) + { summons.DespawnEntry(TABLE_GOSSIP_ENTRY[i]); + if (Creature* keeper = m_pInstance->GetCreature(gossipData[i])) + keeper->DespawnOrUnsummon(); + } } void UpdateKeeperSpawns() @@ -518,11 +526,6 @@ struct boss_yoggsaron_sara : public ScriptedAI if (!summons.HasEntry(TABLE_KEEPER_ENTRY[i])) me->SummonCreature(TABLE_KEEPER_ENTRY[i], KeepersPos[i]); } - else if (m_pInstance->GetData(TABLE_KEEPER_TYPE[i]) == DONE) - { - if (!summons.HasEntry(TABLE_GOSSIP_ENTRY[i])) - me->SummonCreature(TABLE_GOSSIP_ENTRY[i], GossipKeepersPos[i]); - } } } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp index 7d4b0d1d3..cd691e25b 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp @@ -39,6 +39,32 @@ DoorData const doorData[] = { 0, 0, DOOR_TYPE_ROOM } }; +// Observation Ring keeper positions, indexed by KEEPER_* constants +static Position const ObservationRingKeepersPos[4] = +{ + {1945.6823f, 33.342014f, 411.44083f, 5.270895f}, // Freya + {1945.7609f, -81.52171f, 411.4407f, 1.029744f}, // Hodir + {2028.7656f, 17.42014f, 411.44458f, 3.857178f}, // Mimiron + {2028.8219f, -65.73573f, 411.44257f, 2.460914f} // Thorim +}; + +static uint32 const ObservationRingKeeperEntry[4] = +{ + NPC_FREYA_GOSSIP, NPC_HODIR_GOSSIP, + NPC_MIMIRON_GOSSIP, NPC_THORIM_GOSSIP +}; + +static uint32 const ObservationRingKeeperData[4] = +{ + DATA_FREYA_GOSSIP, DATA_HODIR_GOSSIP, + DATA_MIMIRON_GOSSIP, DATA_THORIM_GOSSIP +}; + +static uint32 const ObservationRingKeeperBoss[4] = +{ + BOSS_FREYA, BOSS_HODIR, BOSS_MIMIRON, BOSS_THORIM +}; + ObjectData const creatureData[] = { { NPC_LEVIATHAN, BOSS_LEVIATHAN }, @@ -69,6 +95,11 @@ ObjectData const creatureData[] = // Yogg-Saron helpers { NPC_SARA, DATA_SARA }, { NPC_BRAIN_OF_YOGG_SARON, DATA_BRAIN_OF_YOGG_SARON }, + // Observation Ring Keepers + { NPC_FREYA_GOSSIP, DATA_FREYA_GOSSIP }, + { NPC_HODIR_GOSSIP, DATA_HODIR_GOSSIP }, + { NPC_MIMIRON_GOSSIP, DATA_MIMIRON_GOSSIP }, + { NPC_THORIM_GOSSIP, DATA_THORIM_GOSSIP }, // Algalon helpers { NPC_BRANN_BRONZBEARD_ALG, DATA_BRANN_BRONZEBEARD_ALG }, { NPC_BRANN_BASE_CAMP, DATA_BRANN_BASE_CAMP }, @@ -173,7 +204,6 @@ public: // Shared EventMap _events; bool m_mimironTramUsed; - ObjectGuid m_keepersGossipGUID[4]; void Initialize() override { @@ -227,6 +257,15 @@ public: } } + // Spawn Observation Ring keepers for defeated bosses + for (uint8 i = KEEPER_FREYA; i <= KEEPER_THORIM; ++i) + if (IsBossDone(ObservationRingKeeperBoss[i]) + && !(m_watchersMask & (1 << i)) + && !GetObjectGuid(ObservationRingKeeperData[i])) + instance->SummonCreature( + ObservationRingKeeperEntry[i], + ObservationRingKeepersPos[i]); + if (!GetObjectGuid(BOSS_ALGALON) && m_algalonTimer && (m_algalonTimer <= 60 || m_algalonTimer == TIMER_ALGALON_TO_SUMMON)) { TempSummon* algalon = instance->SummonCreature(NPC_ALGALON, AlgalonLandPos); @@ -303,7 +342,7 @@ public: case BOSS_HODIR: case BOSS_THORIM: case BOSS_FREYA: - if (GetBossState(BOSS_MIMIRON) == DONE && GetBossState(BOSS_FREYA) == DONE && GetBossState(BOSS_HODIR) == DONE && GetBossState(BOSS_THORIM) == DONE) + if (AllBossesDone({BOSS_MIMIRON, BOSS_FREYA, BOSS_HODIR, BOSS_THORIM})) { scheduler.Schedule(45s, [this](TaskContext /*context*/) { @@ -317,6 +356,13 @@ public: } if (type == BOSS_HODIR && state == DONE) setChestsLootable(BOSS_HODIR); + if (state == DONE) + { + uint8 keeperIdx = type - BOSS_FREYA; + instance->SummonCreature( + ObservationRingKeeperEntry[keeperIdx], + ObservationRingKeepersPos[keeperIdx]); + } break; case BOSS_ALGALON: if (GameObject* go = GetGameObject(DATA_SIGILDOOR_03)) @@ -563,7 +609,7 @@ public: OpenIfDone(BOSS_ASSEMBLY, gameObject, GO_STATE_ACTIVE); break; case GO_KEEPERS_GATE: - if (GetBossState(BOSS_MIMIRON) == DONE && GetBossState(BOSS_FREYA) == DONE && GetBossState(BOSS_HODIR) == DONE && GetBossState(BOSS_THORIM) == DONE) + if (AllBossesDone({BOSS_MIMIRON, BOSS_FREYA, BOSS_HODIR, BOSS_THORIM})) gameObject->RemoveGameObjectFlag(GO_FLAG_LOCKED); break; // Mimiron, Hodir, Vezax diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h index 5307e6758..9883e2ea6 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h @@ -143,6 +143,12 @@ enum UlduarData DATA_BRANN_MEMOTESAY = 801, DATA_BRANN_EASY_MODE = 802, DATA_BRANN_BASE_CAMP = 803, + + // Observation Ring Keepers + DATA_FREYA_GOSSIP = 810, + DATA_HODIR_GOSSIP = 811, + DATA_MIMIRON_GOSSIP = 812, + DATA_THORIM_GOSSIP = 813, }; enum UlduarNPCs From 94faf55a86a572177b1a33e4fd57bedeaed5f0d8 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:51:35 -0300 Subject: [PATCH 036/132] refactor(Scripts/HoR): modernize Halls of Reflection scripts (#25176) Co-authored-by: Claude Opus 4.6 (1M context) --- src/server/game/Instances/InstanceScript.cpp | 5 + src/server/game/Instances/InstanceScript.h | 16 +- .../HallsOfReflection/boss_falric.cpp | 262 +- .../HallsOfReflection/boss_marwyn.cpp | 268 +- .../HallsOfReflection/halls_of_reflection.cpp | 2405 ++++++++--------- .../HallsOfReflection/halls_of_reflection.h | 20 + .../instance_halls_of_reflection.cpp | 2019 +++++++------- 7 files changed, 2363 insertions(+), 2632 deletions(-) diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 5b72b9464..94b367e61 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -134,6 +134,11 @@ void InstanceScript::HandleGameObject(ObjectGuid GUID, bool open, GameObject* go } } +void InstanceScript::HandleGameObject(uint32 type, bool open) +{ + HandleGameObject(ObjectGuid::Empty, open, GetGameObject(type)); +} + bool InstanceScript::IsEncounterInProgress() const { for (std::vector::const_iterator itr = bosses.begin(); itr != bosses.end(); ++itr) diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index b10db66e9..ba05588ee 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -193,11 +193,21 @@ public: //Called when a player enters/leaves water bodies. virtual void OnPlayerInWaterStateUpdate(Player* /*player*/, bool /*inWater*/) {} - //Handle open / close objects - //use HandleGameObject(ObjectGuid::Empty, boolen, GO); in OnObjectCreate in instance scripts - //use HandleGameObject(GUID, boolen, nullptr); in any other script + /** + * @brief Open or close a GameObject by GUID. + * @param guid The GUID of the GameObject. Pass ObjectGuid::Empty when providing the go pointer directly. + * @param open true to open (GO_STATE_ACTIVE), false to close (GO_STATE_READY). + * @param go Optional pointer to the GameObject. If nullptr, the object is looked up by GUID. + */ void HandleGameObject(ObjectGuid guid, bool open, GameObject* go = nullptr); + /** + * @brief Open or close a GameObject registered via LoadObjectData. + * @param type The ObjectData type constant (e.g. GO_FRONT_DOOR) used in the gameObjectData array. + * @param open true to open (GO_STATE_ACTIVE), false to close (GO_STATE_READY). + */ + void HandleGameObject(uint32 type, bool open); + //change active state of doors or buttons void DoUseDoorOrButton(ObjectGuid guid, uint32 withRestoreTime = 0, bool useAlternativeState = false); diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_falric.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_falric.cpp index 12772dced..699581697 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_falric.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_falric.cpp @@ -16,173 +16,151 @@ */ #include "CreatureScript.h" -#include "halls_of_reflection.h" #include "ScriptedCreature.h" #include "SpellMgr.h" +#include "halls_of_reflection.h" enum Yells { - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_DEATH = 2, - SAY_IMPENDING_DESPAIR = 3, - SAY_DEFILING_HORROR = 4, + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_DEATH = 2, + SAY_IMPENDING_DESPAIR = 3, + SAY_DEFILING_HORROR = 4, }; enum Spells { - SPELL_QUIVERING_STRIKE = 72422, - SPELL_IMPENDING_DESPAIR = 72426, - SPELL_DEFILING_HORROR = 72435, + SPELL_QUIVERING_STRIKE = 72422, + SPELL_IMPENDING_DESPAIR = 72426, + SPELL_DEFILING_HORROR = 72435, }; enum Events { - EVENT_NONE, - EVENT_QUIVERING_STRIKE, + EVENT_QUIVERING_STRIKE = 1, EVENT_IMPENDING_DESPAIR, EVENT_DEFILING_HORROR, EVENT_UNROOT, }; -const uint32 hopelessnessId[3] = { 72395, 72396, 72397 }; +constexpr uint32 hopelessnessId[3] = { 72395, 72396, 72397 }; -class boss_falric : public CreatureScript +struct boss_falric : public BossAI { -public: - boss_falric() : CreatureScript("boss_falric") { } + boss_falric(Creature* creature) : BossAI(creature, DATA_FALRIC) { } - struct boss_falricAI : public ScriptedAI + void Reset() override { - boss_falricAI(Creature* creature) : ScriptedAI(creature) - { - pInstance = creature->GetInstanceScript(); - } - - InstanceScript* pInstance; - EventMap events; - uint8 uiHopelessnessCount; - uint16 startFightTimer; - - void Reset() override - { - startFightTimer = 0; - uiHopelessnessCount = 0; - me->SetImmuneToAll(true); - me->SetControlled(false, UNIT_STATE_ROOT); - events.Reset(); - if (pInstance) - pInstance->SetData(DATA_FALRIC, NOT_STARTED); - } - - void JustEngagedWith(Unit* /*who*/) override - { - me->SetImmuneToAll(false); - - events.ScheduleEvent(EVENT_QUIVERING_STRIKE, 5s); - events.ScheduleEvent(EVENT_IMPENDING_DESPAIR, 11s); - events.ScheduleEvent(EVENT_DEFILING_HORROR, 20s); - } - - void DoAction(int32 a) override - { - if (a == 1) - { - Talk(SAY_AGGRO); - startFightTimer = 8000; - } - } - - void UpdateAI(uint32 diff) override - { - if (startFightTimer) - { - if (startFightTimer <= diff) - { - startFightTimer = 0; - me->SetInCombatWithZone(); - } - else - startFightTimer -= diff; - } - - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_QUIVERING_STRIKE: - me->CastSpell(me->GetVictim(), SPELL_QUIVERING_STRIKE, false); - events.ScheduleEvent(EVENT_QUIVERING_STRIKE, 5s); - break; - case EVENT_IMPENDING_DESPAIR: - if (Unit* target = SelectTargetFromPlayerList(45.0f, 0, true)) - { - Talk(SAY_IMPENDING_DESPAIR); - me->CastSpell(target, SPELL_IMPENDING_DESPAIR, false); - } - events.ScheduleEvent(EVENT_IMPENDING_DESPAIR, 12s); - break; - case EVENT_DEFILING_HORROR: - Talk(SAY_DEFILING_HORROR); - me->CastSpell((Unit*)nullptr, SPELL_DEFILING_HORROR, false); - me->SetControlled(true, UNIT_STATE_ROOT); - events.DelayEventsToMax(5s, 0); - events.ScheduleEvent(EVENT_UNROOT, 4s); - events.ScheduleEvent(EVENT_DEFILING_HORROR, 20s); - break; - case EVENT_UNROOT: - me->SetControlled(false, UNIT_STATE_ROOT); - break; - } - - if ((uiHopelessnessCount == 0 && HealthBelowPct(67)) || (uiHopelessnessCount == 1 && HealthBelowPct(34)) || (uiHopelessnessCount == 2 && HealthBelowPct(11))) - { - if (uiHopelessnessCount) - me->RemoveOwnedAura(sSpellMgr->GetSpellIdForDifficulty(hopelessnessId[uiHopelessnessCount - 1], me)); - - me->CastSpell((Unit*)nullptr, hopelessnessId[uiHopelessnessCount], true); - ++uiHopelessnessCount; - } - - if (!me->HasUnitState(UNIT_STATE_ROOT)) - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_DEATH); - if (pInstance) - pInstance->SetData(DATA_FALRIC, DONE); - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer()) - Talk(SAY_SLAY); - } - - void EnterEvadeMode(EvadeReason why) override - { - me->SetControlled(false, UNIT_STATE_ROOT); - ScriptedAI::EnterEvadeMode(why); - if (startFightTimer) - Reset(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); + BossAI::Reset(); + _hopelessnessCount = 0; + _startingFight = false; + me->SetImmuneToAll(true); + me->SetControlled(false, UNIT_STATE_ROOT); } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetImmuneToAll(false); + + events.ScheduleEvent(EVENT_QUIVERING_STRIKE, 5s); + events.ScheduleEvent(EVENT_IMPENDING_DESPAIR, 11s); + events.ScheduleEvent(EVENT_DEFILING_HORROR, 20s); + } + + void DoAction(int32 action) override + { + if (action == 1) + { + Talk(SAY_AGGRO); + _startingFight = true; + me->m_Events.AddEventAtOffset([this]() + { + _startingFight = false; + me->SetInCombatWithZone(); + }, 8s); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_QUIVERING_STRIKE: + DoCastVictim(SPELL_QUIVERING_STRIKE); + events.Repeat(5s); + break; + case EVENT_IMPENDING_DESPAIR: + if (Unit* target = SelectTargetFromPlayerList(45.0f, 0, true)) + { + Talk(SAY_IMPENDING_DESPAIR); + DoCast(target, SPELL_IMPENDING_DESPAIR); + } + events.Repeat(12s); + break; + case EVENT_DEFILING_HORROR: + Talk(SAY_DEFILING_HORROR); + DoCastAOE(SPELL_DEFILING_HORROR); + me->SetControlled(true, UNIT_STATE_ROOT); + events.DelayEventsToMax(5s, 0); + events.ScheduleEvent(EVENT_UNROOT, 4s); + events.Repeat(20s); + break; + case EVENT_UNROOT: + me->SetControlled(false, UNIT_STATE_ROOT); + break; + } + + if ((_hopelessnessCount == 0 && HealthBelowPct(67)) + || (_hopelessnessCount == 1 && HealthBelowPct(34)) + || (_hopelessnessCount == 2 && HealthBelowPct(11))) + { + if (_hopelessnessCount) + me->RemoveOwnedAura(sSpellMgr->GetSpellIdForDifficulty(hopelessnessId[_hopelessnessCount - 1], me)); + + DoCastAOE(hopelessnessId[_hopelessnessCount], true); + ++_hopelessnessCount; + } + + if (!me->HasUnitState(UNIT_STATE_ROOT)) + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer()) + Talk(SAY_SLAY); + } + + void EnterEvadeMode(EvadeReason why) override + { + me->SetControlled(false, UNIT_STATE_ROOT); + BossAI::EnterEvadeMode(why); + if (_startingFight) + Reset(); + } + +private: + uint8 _hopelessnessCount{}; + bool _startingFight{}; }; void AddSC_boss_falric() { - new boss_falric(); + RegisterHallsOfReflectionCreatureAI(boss_falric); } diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_marwyn.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_marwyn.cpp index 119dcef19..2f4b0784f 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_marwyn.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/boss_marwyn.cpp @@ -16,160 +16,136 @@ */ #include "CreatureScript.h" -#include "SpellScriptLoader.h" -#include "halls_of_reflection.h" #include "ScriptedCreature.h" #include "SpellAuraEffects.h" #include "SpellScript.h" +#include "SpellScriptLoader.h" +#include "halls_of_reflection.h" enum Yells { - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_DEATH = 2, - SAY_CORRUPTED_FLESH = 3, - SAY_CORRUPTED_WELL = 4, + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_DEATH = 2, + SAY_CORRUPTED_FLESH = 3, + SAY_CORRUPTED_WELL = 4, }; enum Spells { - SPELL_OBLITERATE = 72360, - SPELL_WELL_OF_CORRUPTION = 72362, - SPELL_CORRUPTED_FLESH = 72363, - SPELL_SHARED_SUFFERING = 72368, + SPELL_OBLITERATE = 72360, + SPELL_WELL_OF_CORRUPTION = 72362, + SPELL_CORRUPTED_FLESH = 72363, + SPELL_SHARED_SUFFERING = 72368, }; enum Events { - EVENT_NONE, - EVENT_OBLITERATE, + EVENT_OBLITERATE = 1, EVENT_WELL_OF_CORRUPTION, EVENT_CORRUPTED_FLESH, EVENT_SHARED_SUFFERING, }; -class boss_marwyn : public CreatureScript +struct boss_marwyn : public BossAI { -public: - boss_marwyn() : CreatureScript("boss_marwyn") { } + boss_marwyn(Creature* creature) : BossAI(creature, DATA_MARWYN) { } - struct boss_marwynAI : public ScriptedAI + void Reset() override { - boss_marwynAI(Creature* creature) : ScriptedAI(creature) + BossAI::Reset(); + _startingFight = false; + me->SetImmuneToAll(true); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetImmuneToAll(false); + + events.ScheduleEvent(EVENT_OBLITERATE, 15s); + events.ScheduleEvent(EVENT_WELL_OF_CORRUPTION, 13s); + events.ScheduleEvent(EVENT_CORRUPTED_FLESH, 20s); + events.ScheduleEvent(EVENT_SHARED_SUFFERING, 5s); + } + + void DoAction(int32 action) override + { + if (action == 1) { - pInstance = creature->GetInstanceScript(); - } - - InstanceScript* pInstance; - EventMap events; - uint16 startFightTimer; - - void Reset() override - { - startFightTimer = 0; - me->SetImmuneToAll(true); - events.Reset(); - if (pInstance) - pInstance->SetData(DATA_MARWYN, NOT_STARTED); - } - - void JustEngagedWith(Unit* /*who*/) override - { - me->SetImmuneToAll(false); - - events.ScheduleEvent(EVENT_OBLITERATE, 15s); - events.ScheduleEvent(EVENT_WELL_OF_CORRUPTION, 13s); - events.ScheduleEvent(EVENT_CORRUPTED_FLESH, 20s); - events.ScheduleEvent(EVENT_SHARED_SUFFERING, 5s); - } - - void DoAction(int32 a) override - { - if (a == 1) + Talk(SAY_AGGRO); + _startingFight = true; + me->m_Events.AddEventAtOffset([this]() { - Talk(SAY_AGGRO); - startFightTimer = 8000; - } + _startingFight = false; + me->SetInCombatWithZone(); + }, 8s); } + } - void UpdateAI(uint32 diff) override + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) { - if (startFightTimer) - { - if (startFightTimer <= diff) + case EVENT_OBLITERATE: + if (me->IsWithinMeleeRange(me->GetVictim())) { - startFightTimer = 0; - me->SetInCombatWithZone(); + DoCastVictim(SPELL_OBLITERATE); + events.Repeat(15s); } else - startFightTimer -= diff; - } - - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_OBLITERATE: - if (me->IsWithinMeleeRange(me->GetVictim())) - { - me->CastSpell(me->GetVictim(), SPELL_OBLITERATE, false); - events.ScheduleEvent(EVENT_OBLITERATE, 15s); - } - else - events.ScheduleEvent(EVENT_OBLITERATE, 3s); - break; - case EVENT_WELL_OF_CORRUPTION: - Talk(SAY_CORRUPTED_WELL); - if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) - me->CastSpell(target, SPELL_WELL_OF_CORRUPTION, false); - events.ScheduleEvent(EVENT_WELL_OF_CORRUPTION, 13s); - break; - case EVENT_CORRUPTED_FLESH: - Talk(SAY_CORRUPTED_FLESH); - me->CastSpell((Unit*)nullptr, SPELL_CORRUPTED_FLESH, false); - events.ScheduleEvent(EVENT_CORRUPTED_FLESH, 20s); - break; - case EVENT_SHARED_SUFFERING: - if (Unit* target = SelectTargetFromPlayerList(200.0f, 0, true)) - me->CastSpell(target, SPELL_SHARED_SUFFERING, true); - events.ScheduleEvent(EVENT_SHARED_SUFFERING, 15s); - break; - } - - DoMeleeAttackIfReady(); + events.Repeat(3s); + break; + case EVENT_WELL_OF_CORRUPTION: + Talk(SAY_CORRUPTED_WELL); + if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) + DoCast(target, SPELL_WELL_OF_CORRUPTION); + events.Repeat(13s); + break; + case EVENT_CORRUPTED_FLESH: + Talk(SAY_CORRUPTED_FLESH); + DoCastAOE(SPELL_CORRUPTED_FLESH); + events.Repeat(20s); + break; + case EVENT_SHARED_SUFFERING: + if (Unit* target = SelectTargetFromPlayerList(200.0f, 0, true)) + DoCast(target, SPELL_SHARED_SUFFERING, true); + events.Repeat(15s); + break; } - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_DEATH); - if (pInstance) - pInstance->SetData(DATA_MARWYN, DONE); - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer()) - Talk(SAY_SLAY); - } - - void EnterEvadeMode(EvadeReason why) override - { - ScriptedAI::EnterEvadeMode(why); - if (startFightTimer) - Reset(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); + DoMeleeAttackIfReady(); } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer()) + Talk(SAY_SLAY); + } + + void EnterEvadeMode(EvadeReason why) override + { + BossAI::EnterEvadeMode(why); + if (_startingFight) + Reset(); + } + +private: + bool _startingFight{}; }; enum SharedSufferingAura @@ -186,25 +162,37 @@ class spell_hor_shared_suffering_aura : public AuraScript return ValidateSpellInfo({ SPELL_SHARED_SUFFERING_DAMAGE }); } - void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) { - if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_ENEMY_SPELL) // dispelled - if (Unit* caster = GetCaster()) - if (Map* map = caster->FindMap()) - if (Aura* a = aurEff->GetBase()) - { - uint32 count = 0; - uint32 ticks = 0; - uint32 dmgPerTick = a->GetSpellInfo()->Effects[0].BasePoints; - Map::PlayerList const& pl = map->GetPlayers(); - for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - if (Player* p = itr->GetSource()) - if (p->IsAlive()) - ++count; - ticks = (a->GetDuration() / int32(a->GetSpellInfo()->Effects[0].Amplitude)) + 1; - int32 dmg = (ticks * dmgPerTick) / count; - caster->CastCustomSpell(GetTarget(), SPELL_SHARED_SUFFERING_DAMAGE, nullptr, &dmg, nullptr, true); - } + if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_ENEMY_SPELL) + return; + + Unit* caster = GetCaster(); + if (!caster) + return; + + Map* map = caster->FindMap(); + if (!map) + return; + + Aura* aura = aurEff->GetBase(); + if (!aura) + return; + + uint32 count = 0; + uint32 dmgPerTick = aura->GetSpellInfo()->Effects[0].BasePoints; + Map::PlayerList const& pl = map->GetPlayers(); + for (auto itr = pl.begin(); itr != pl.end(); ++itr) + if (Player* p = itr->GetSource()) + if (p->IsAlive()) + ++count; + + if (!count) + return; + + uint32 ticks = (aura->GetDuration() / int32(aura->GetSpellInfo()->Effects[0].Amplitude)) + 1; + int32 dmg = (ticks * dmgPerTick) / count; + caster->CastCustomSpell(GetTarget(), SPELL_SHARED_SUFFERING_DAMAGE, nullptr, &dmg, nullptr, true); } void Register() override @@ -215,6 +203,6 @@ class spell_hor_shared_suffering_aura : public AuraScript void AddSC_boss_marwyn() { - new boss_marwyn(); + RegisterHallsOfReflectionCreatureAI(boss_marwyn); RegisterSpellScript(spell_hor_shared_suffering_aura); } diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp index bf20e8d99..266550e9f 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp @@ -155,16 +155,12 @@ public: { QuestStatus status = player->GetQuestStatus(creature->GetEntry() == NPC_SYLVANAS_PART1 ? QUEST_DELIVRANCE_FROM_THE_PIT_H2 : QUEST_DELIVRANCE_FROM_THE_PIT_A2); if (status == QUEST_STATUS_COMPLETE || status == QUEST_STATUS_REWARDED) - { - AddGossipItemFor(player, creature->GetEntry() == NPC_SYLVANAS_PART1 ? GOSSIP_MENU_SYLVANAS : GOSISP_MENU_JAINA, GOSSIP_OPTION_START, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - } + AddGossipItemFor(player, creature->GetEntry() == NPC_SYLVANAS_PART1 ? GOSSIP_MENU_SYLVANAS : GOSISP_MENU_JAINA, GOSSIP_OPTION_START, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); // once last quest is completed, she offers this shortcut of the starting event status = player->GetQuestStatus(creature->GetEntry() == NPC_SYLVANAS_PART1 ? QUEST_WRATH_OF_THE_LICH_KING_H2 : QUEST_WRATH_OF_THE_LICH_KING_A2); if (status == QUEST_STATUS_COMPLETE || status == QUEST_STATUS_REWARDED) - { AddGossipItemFor(player, creature->GetEntry() == NPC_SYLVANAS_PART1 ? GOSSIP_MENU_SYLVANAS : GOSISP_MENU_JAINA, GOSSIP_OPTION_START_SKIP, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - } } SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID()); @@ -190,13 +186,13 @@ public: ClearGossipMenuFor(player); switch (uiAction) { - case GOSSIP_ACTION_INFO_DEF+1: + case GOSSIP_ACTION_INFO_DEF + 1: CloseGossipMenuFor(player); if (creature->AI()) creature->AI()->DoAction(ACTION_START_INTRO); creature->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); break; - case GOSSIP_ACTION_INFO_DEF+2: + case GOSSIP_ACTION_INFO_DEF + 2: CloseGossipMenuFor(player); if (creature->AI()) creature->AI()->DoAction(ACTION_SKIP_INTRO); @@ -216,13 +212,13 @@ public: { npc_hor_leaderAI(Creature* creature) : NullCreatureAI(creature) { - pInstance = me->GetInstanceScript(); - if (!pInstance) + instance = me->GetInstanceScript(); + if (!instance) me->IsAIEnabled = false; - first = (pInstance && !pInstance->GetData(DATA_INTRO)); + first = (instance && !instance->GetPersistentData(PERSISTENT_DATA_INTRO)); } - InstanceScript* pInstance; + InstanceScript* instance; EventMap events; bool first; bool shortver; @@ -262,7 +258,7 @@ public: switch (events.ExecuteEvent()) { case EVENT_PRE_INTRO_1: - if (pInstance) + if (instance) { me->SetSheath(SHEATH_STATE_MELEE); @@ -272,10 +268,8 @@ public: break; case EVENT_PRE_INTRO_2: if (me->GetEntry() == NPC_JAINA_PART1) - { Talk(SAY_JAINA_INTRO_1); - } - me->GetMotionMaster()->MovePoint(0, SpawnPos); + me->GetMotionMaster()->MovePoint(0, SpawnPos); break; case EVENT_TALK_LEADER_1: me->SetSheath(SHEATH_STATE_UNARMED); @@ -292,19 +286,16 @@ public: shortver = false; me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); me->GetMotionMaster()->MovePoint(0, MoveThronePos); - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) { - pLoralen->LoadEquipment(true); - pLoralen->SetVisible(true); - pLoralen->SetWalk(true); - pLoralen->GetMotionMaster()->MovePoint(0, LoralenMidleFollowPos); - + loralen->LoadEquipment(true); + loralen->SetVisible(true); + loralen->SetWalk(true); + loralen->GetMotionMaster()->MovePoint(0, LoralenMidleFollowPos); } // Begining of intro is differents between factions as the speech sequence and timers are differents. if (me->GetEntry() == NPC_JAINA_PART1) - { events.ScheduleEvent(EVENT_INTRO_A2_1, 10s); - } else { events.ScheduleEvent(EVENT_INTRO_H2_2, 9s); @@ -317,10 +308,8 @@ public: shortver = true; me->SetUInt32Value(UNIT_NPC_EMOTESTATE, (me->GetEntry() == NPC_JAINA_PART1 ? EMOTE_STATE_READY2H : EMOTE_STATE_READY1H)); me->GetMotionMaster()->MovePoint(0, MoveThronePos); - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) - { - pLoralen->GetMotionMaster()->MovePoint(0, LoralenFollowPos); - } + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) + loralen->GetMotionMaster()->MovePoint(0, LoralenFollowPos); events.ScheduleEvent(EVENT_INTRO_LK_1, 0ms); break; @@ -334,25 +323,25 @@ public: events.ScheduleEvent(EVENT_INTRO_A2_3, 10s); break; case EVENT_INTRO_A2_3: - pInstance->HandleGameObject(pInstance->GetGuidData(GO_FROSTMOURNE), true); - me->CastSpell(me, SPELL_FROSTMOURNE_SPAWN_SOUND, true); - //me->CastSpell(me, SPELL_ARCANE_CAST_VISUAL, false);// the sniff is missing from the alliance + instance->HandleGameObject(GO_FROSTMOURNE, true); + DoCastSelf(SPELL_FROSTMOURNE_SPAWN_SOUND, true); + //DoCastSelf(SPELL_ARCANE_CAST_VISUAL);// the sniff is missing from the alliance events.ScheduleEvent(EVENT_INTRO_A2_4, 10s); break; case EVENT_INTRO_A2_4: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - pUther->SetVisible(true); - if (Aura* a = pUther->AddAura(SPELL_SHADOWMOURNE_VISUAL, pUther)) + uther->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + uther->SetVisible(true); + if (Aura* a = uther->AddAura(SPELL_SHADOWMOURNE_VISUAL, uther)) a->SetDuration(8000); } events.ScheduleEvent(EVENT_INTRO_A2_5, 2s); break; case EVENT_INTRO_A2_5: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_1); + uther->AI()->Talk(SAY_UTHER_INTRO_A2_1); events.ScheduleEvent(EVENT_INTRO_A2_6, 3s); } break; @@ -361,10 +350,8 @@ public: events.ScheduleEvent(EVENT_INTRO_A2_7, 6s); break; case EVENT_INTRO_A2_7: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) - { - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_2); - } + if (Creature* uther = instance->GetCreature(NPC_UTHER)) + uther->AI()->Talk(SAY_UTHER_INTRO_A2_2); events.ScheduleEvent(EVENT_INTRO_A2_8, 6500ms); break; case EVENT_INTRO_A2_8: @@ -372,10 +359,8 @@ public: events.ScheduleEvent(EVENT_INTRO_A2_9, 2s); break; case EVENT_INTRO_A2_9: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) - { - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_3); - } + if (Creature* uther = instance->GetCreature(NPC_UTHER)) + uther->AI()->Talk(SAY_UTHER_INTRO_A2_3); events.ScheduleEvent(EVENT_INTRO_A2_10, 9s); break; case EVENT_INTRO_A2_10: @@ -383,10 +368,8 @@ public: events.ScheduleEvent(EVENT_INTRO_A2_11, 5s); break; case EVENT_INTRO_A2_11: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) - { - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_4); - } + if (Creature* uther = instance->GetCreature(NPC_UTHER)) + uther->AI()->Talk(SAY_UTHER_INTRO_A2_4); events.ScheduleEvent(EVENT_INTRO_A2_12, 11s); break; case EVENT_INTRO_A2_12: @@ -394,10 +377,8 @@ public: events.ScheduleEvent(EVENT_INTRO_A2_13, 4s); break; case EVENT_INTRO_A2_13: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) - { - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_5); - } + if (Creature* uther = instance->GetCreature(NPC_UTHER)) + uther->AI()->Talk(SAY_UTHER_INTRO_A2_5); events.ScheduleEvent(EVENT_INTRO_A2_14, 12s + 500ms); break; case EVENT_INTRO_A2_14: @@ -405,15 +386,13 @@ public: events.ScheduleEvent(EVENT_INTRO_A2_15, 10s); break; case EVENT_INTRO_A2_15: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) - { - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_6); - } + if (Creature* uther = instance->GetCreature(NPC_UTHER)) + uther->AI()->Talk(SAY_UTHER_INTRO_A2_6); events.ScheduleEvent(EVENT_INTRO_A2_16, 24s); break; case EVENT_INTRO_A2_16: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_7); + if (Creature* uther = instance->GetCreature(NPC_UTHER)) + uther->AI()->Talk(SAY_UTHER_INTRO_A2_7); events.ScheduleEvent(EVENT_INTRO_A2_17, 4s); break; case EVENT_INTRO_A2_17: @@ -421,10 +400,10 @@ public: events.ScheduleEvent(EVENT_INTRO_A2_18, 2s); break; case EVENT_INTRO_A2_18: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_8); + uther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + uther->AI()->Talk(SAY_UTHER_INTRO_A2_8); } events.ScheduleEvent(EVENT_INTRO_A2_19, 11s); break; @@ -435,17 +414,13 @@ public: // H2 Intro Events case EVENT_LORALEN_MOVE_1: - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) - { - pLoralen->GetMotionMaster()->MovePoint(0, LoralenFollowPos); - } + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) + loralen->GetMotionMaster()->MovePoint(0, LoralenFollowPos); break; case EVENT_LORALEN_MOVE_2: - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) - { - pLoralen->HandleEmoteCommand(EMOTE_ONESHOT_KNEEL); - } + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) + loralen->HandleEmoteCommand(EMOTE_ONESHOT_KNEEL); break; case EVENT_INTRO_H2_2: @@ -462,13 +437,13 @@ public: break; case EVENT_INTRO_H2_3: - me->CastSpell(me, SPELL_SUMMON_SOULS, false); + DoCastSelf(SPELL_SUMMON_SOULS); events.ScheduleEvent(EVENT_INTRO_H2_3_1, 5s); break; case EVENT_INTRO_H2_3_1: - me->CastSpell(me, SPELL_FROSTMOURNE_SPAWN_SOUND, false); - pInstance->HandleGameObject(pInstance->GetGuidData(GO_FROSTMOURNE), true); + DoCastSelf(SPELL_FROSTMOURNE_SPAWN_SOUND); + instance->HandleGameObject(GO_FROSTMOURNE, true); events.ScheduleEvent(EVENT_INTRO_H2_3_2, 3s); break; @@ -477,22 +452,20 @@ public: events.ScheduleEvent(EVENT_INTRO_H2_4, 2s); break; case EVENT_INTRO_H2_4: - if (Creature* pUther = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->SetVisible(true); - if (Aura* a = pUther->AddAura(SPELL_SHADOWMOURNE_VISUAL, pUther)) - { + uther->SetVisible(true); + if (Aura* a = uther->AddAura(SPELL_SHADOWMOURNE_VISUAL, uther)) a->SetDuration(8000); - } } events.ScheduleEvent(EVENT_INTRO_H2_5, 7s); break; case EVENT_INTRO_H2_5: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pUther->AI()->Talk(SAY_UTHER_INTRO_H2_1); + uther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + uther->AI()->Talk(SAY_UTHER_INTRO_H2_1); } events.ScheduleEvent(EVENT_INTRO_H2_6, 11s); break; @@ -502,10 +475,10 @@ public: events.ScheduleEvent(EVENT_INTRO_H2_7, 2s + 500ms); break; case EVENT_INTRO_H2_7: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pUther->AI()->Talk(SAY_UTHER_INTRO_H2_2); + uther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + uther->AI()->Talk(SAY_UTHER_INTRO_H2_2); } events.ScheduleEvent(EVENT_INTRO_H2_8, 9s);// break; @@ -515,10 +488,10 @@ public: events.ScheduleEvent(EVENT_INTRO_H2_9, 5s); break; case EVENT_INTRO_H2_9: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pUther->AI()->Talk(SAY_UTHER_INTRO_H2_3); + uther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + uther->AI()->Talk(SAY_UTHER_INTRO_H2_3); } events.ScheduleEvent(EVENT_INTRO_H2_10, 20s); break; @@ -528,10 +501,10 @@ public: events.ScheduleEvent(EVENT_INTRO_H2_11, 3s); break; case EVENT_INTRO_H2_11: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pUther->AI()->Talk(SAY_UTHER_INTRO_H2_4); + uther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + uther->AI()->Talk(SAY_UTHER_INTRO_H2_4); } events.ScheduleEvent(EVENT_INTRO_H2_12, 21s + 500ms); break; @@ -541,19 +514,18 @@ public: events.ScheduleEvent(EVENT_INTRO_H2_13, 3s + 500ms); break; case EVENT_INTRO_H2_13: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pUther->AI()->Talk(SAY_UTHER_INTRO_H2_5); - + uther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + uther->AI()->Talk(SAY_UTHER_INTRO_H2_5); } events.ScheduleEvent(EVENT_INTRO_H2_14, 12s + 500ms); break; case EVENT_INTRO_H2_14: - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pUther->AI()->Talk(SAY_UTHER_INTRO_H2_6); + uther->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + uther->AI()->Talk(SAY_UTHER_INTRO_H2_6); } events.ScheduleEvent(EVENT_INTRO_H2_15, 8s); break; @@ -565,25 +537,25 @@ public: // Remaining Intro Events common for both faction case EVENT_INTRO_LK_1: - if (Creature* pLichKing = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_EVENT))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) { - pInstance->HandleGameObject(pInstance->GetGuidData(GO_ARTHAS_DOOR), true); - pLichKing->SetVisible(true); + instance->HandleGameObject(GO_ARTHAS_DOOR, true); + lichKing->SetVisible(true); - pLichKing->GetMotionMaster()->MovePoint(0, LichKingMoveMidlelThronePos, FORCED_MOVEMENT_NONE, 0.f, false); + lichKing->GetMotionMaster()->MovePoint(0, LichKingMoveMidlelThronePos, FORCED_MOVEMENT_NONE, 0.f, false); } if (!shortver) - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->HandleEmoteCommand(EMOTE_ONESHOT_EXCLAMATION); + uther->HandleEmoteCommand(EMOTE_ONESHOT_EXCLAMATION); if (me->GetEntry() == NPC_JAINA_PART1) - pUther->AI()->Talk(SAY_UTHER_INTRO_A2_9); + uther->AI()->Talk(SAY_UTHER_INTRO_A2_9); else - pUther->AI()->Talk(SAY_UTHER_INTRO_H2_7); + uther->AI()->Talk(SAY_UTHER_INTRO_H2_7); } events.ScheduleEvent(EVENT_INTRO_LK_1_1, 9s); @@ -593,102 +565,100 @@ public: break; case EVENT_INTRO_LK_1_1: - if (Creature* pLichKing = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_LICH_KING_EVENT))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) { - pLichKing->HandleEmoteCommand(EMOTE_ONESHOT_EXCLAMATION); - pLichKing->AI()->Talk(SAY_LK_INTRO_1); + lichKing->HandleEmoteCommand(EMOTE_ONESHOT_EXCLAMATION); + lichKing->AI()->Talk(SAY_LK_INTRO_1); } break; case EVENT_INTRO_LK_1_2: if (!shortver) - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_COWER); - pUther->SetFacingTo(0.89f); + uther->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_COWER); + uther->SetFacingTo(0.89f); } break; case EVENT_INTRO_LK_1_3: - pInstance->HandleGameObject(pInstance->GetGuidData(GO_ARTHAS_DOOR), false); + instance->HandleGameObject(GO_ARTHAS_DOOR, false); break; case EVENT_INTRO_LK_2: if (!shortver) - if (Creature* pLichKing = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_LICH_KING_EVENT))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) { - pLichKing->SetVisible(true); - pLichKing->GetMotionMaster()->MovePoint(0, LichKingMoveThronePos, FORCED_MOVEMENT_NONE, 0.f, false); + lichKing->SetVisible(true); + lichKing->GetMotionMaster()->MovePoint(0, LichKingMoveThronePos, FORCED_MOVEMENT_NONE, 0.f, false); } events.ScheduleEvent(EVENT_INTRO_LK_2_1, 1s); break; case EVENT_INTRO_LK_2_1: if (!shortver) - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) + if (Creature* uther = instance->GetCreature(NPC_UTHER)) { - pUther->SendPlaySpellVisual(SPELL_UTHER_DESPAWN); - pUther->CastSpell(pUther, SPELL_UTHER_DESPAWN, true); + uther->SendPlaySpellVisual(SPELL_UTHER_DESPAWN); + uther->CastSpell(uther, SPELL_UTHER_DESPAWN, true); } events.ScheduleEvent(EVENT_INTRO_LK_3, 2s); break; case EVENT_INTRO_LK_3: if (!shortver) - if (Creature* pUther = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_UTHER))) - { - pUther->SetVisible(false); - } + if (Creature* uther = instance->GetCreature(NPC_UTHER)) + uther->SetVisible(false); events.ScheduleEvent(EVENT_INTRO_LK_4, 6s); break; case EVENT_INTRO_LK_4: - if (Creature* pLichKing = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_LICH_KING_EVENT))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) { - pLichKing->HandleEmoteCommand(EMOTE_ONESHOT_QUESTION); - pLichKing->AI()->Talk(SAY_LK_INTRO_2); + lichKing->HandleEmoteCommand(EMOTE_ONESHOT_QUESTION); + lichKing->AI()->Talk(SAY_LK_INTRO_2); } events.ScheduleEvent(EVENT_INTRO_LK_4_2, 10s); break; case EVENT_INTRO_LK_4_2: - if (Creature* pLichKing = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_LICH_KING_EVENT))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) { - pLichKing->LoadEquipment(1, true); - pLichKing->SendMovementFlagUpdate(); - pLichKing->CastSpell(pLichKing, SPELL_FROSTMOURNE_EQUIP, false); - pInstance->HandleGameObject(pInstance->GetGuidData(GO_FROSTMOURNE), false); + lichKing->LoadEquipment(1, true); + lichKing->SendMovementFlagUpdate(); + lichKing->CastSpell(lichKing, SPELL_FROSTMOURNE_EQUIP); + instance->HandleGameObject(GO_FROSTMOURNE, false); events.ScheduleEvent(EVENT_INTRO_LK_4_3, 1750ms); } events.ScheduleEvent(EVENT_INTRO_LK_5, 6s); break; case EVENT_INTRO_LK_4_3: - if (GameObject* go = pInstance->instance->GetGameObject(pInstance->GetGuidData(GO_FROSTMOURNE))) + if (GameObject* go = instance->GetGameObject(GO_FROSTMOURNE)) go->SetPhaseMask(2, true); break; case EVENT_INTRO_LK_5: - if (Creature* pFalric = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_FALRIC))) + if (Creature* falric = instance->GetCreature(DATA_FALRIC)) { - pFalric->UpdatePosition(5274.9f, 2039.2f, 709.319f, 5.4619f, true); - pFalric->StopMovingOnCurrentPos(); - pFalric->SetVisible(true); - if (pFalric->IsAlive()) + falric->UpdatePosition(5274.9f, 2039.2f, 709.319f, 5.4619f, true); + falric->StopMovingOnCurrentPos(); + falric->SetVisible(true); + if (falric->IsAlive()) { - pFalric->GetMotionMaster()->MovePoint(0, FalricMovePos); - if (Aura* a = pFalric->AddAura(SPELL_SHADOWMOURNE_VISUAL, pFalric)) + falric->GetMotionMaster()->MovePoint(0, FalricMovePos); + if (Aura* a = falric->AddAura(SPELL_SHADOWMOURNE_VISUAL, falric)) a->SetDuration(8000); } } - if (Creature* pMarwyn = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_MARWYN))) + if (Creature* marwyn = instance->GetCreature(DATA_MARWYN)) { - pMarwyn->UpdatePosition(5343.77f, 1973.86f, 709.319f, 2.35173f, true); - pMarwyn->StopMovingOnCurrentPos(); - pMarwyn->SetVisible(true); - if (pMarwyn->IsAlive()) + marwyn->UpdatePosition(5343.77f, 1973.86f, 709.319f, 2.35173f, true); + marwyn->StopMovingOnCurrentPos(); + marwyn->SetVisible(true); + if (marwyn->IsAlive()) { - pMarwyn->GetMotionMaster()->MovePoint(0, MarwynMovePos); - if (Aura* a = pMarwyn->AddAura(SPELL_SHADOWMOURNE_VISUAL, pMarwyn)) + marwyn->GetMotionMaster()->MovePoint(0, MarwynMovePos); + if (Aura* a = marwyn->AddAura(SPELL_SHADOWMOURNE_VISUAL, marwyn)) a->SetDuration(8000); } } @@ -699,57 +669,55 @@ public: break; case EVENT_INTRO_LK_5_1: - if (Creature* pLichKing = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_LICH_KING_EVENT))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) { - pLichKing->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - pLichKing->AI()->Talk(SAY_LK_INTRO_3); + lichKing->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + lichKing->AI()->Talk(SAY_LK_INTRO_3); } break; case EVENT_INTRO_LK_5_2: - if (Creature* pLichKing = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_LICH_KING_EVENT))) - { - pLichKing->GetMotionMaster()->MovePoint(0, LichKingMoveAwayPos, FORCED_MOVEMENT_NONE, 0.f, false); - } + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) + lichKing->GetMotionMaster()->MovePoint(0, LichKingMoveAwayPos, FORCED_MOVEMENT_NONE, 0.f, false); break; case EVENT_INTRO_LK_5_3: me->SetSpeed(MOVE_RUN, 1.6); me->SetSheath(SHEATH_STATE_MELEE); me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_READY1H); - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) { - pLoralen->SetSheath(SHEATH_STATE_MELEE); - pLoralen->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_READY1H); - pLoralen->SetWalk(false); - pLoralen->LoadEquipment(true); + loralen->SetSheath(SHEATH_STATE_MELEE); + loralen->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_READY1H); + loralen->SetWalk(false); + loralen->LoadEquipment(true); } break; case EVENT_INTRO_LK_6: - if (Creature* pFalric = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_FALRIC))) + if (Creature* falric = instance->GetCreature(DATA_FALRIC)) { - pFalric->HandleEmoteCommand(EMOTE_ONESHOT_BOW); - pFalric->AI()->Talk(SAY_FALRIC_INTRO_1); + falric->HandleEmoteCommand(EMOTE_ONESHOT_BOW); + falric->AI()->Talk(SAY_FALRIC_INTRO_1); } events.ScheduleEvent(EVENT_INTRO_LK_7, 2s); break; case EVENT_INTRO_LK_7: - if (Creature* pMarwyn = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_MARWYN))) + if (Creature* marwyn = instance->GetCreature(DATA_MARWYN)) { - pMarwyn->HandleEmoteCommand(EMOTE_ONESHOT_BOW); - pMarwyn->AI()->Talk(SAY_MARWYN_INTRO_1); + marwyn->HandleEmoteCommand(EMOTE_ONESHOT_BOW); + marwyn->AI()->Talk(SAY_MARWYN_INTRO_1); } events.ScheduleEvent(EVENT_INTRO_LK_8, 3s); break; case EVENT_INTRO_LK_8: - if (Creature* pFalric = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_FALRIC))) + if (Creature* falric = instance->GetCreature(DATA_FALRIC)) { - pFalric->AI()->Talk(SAY_FALRIC_INTRO_2); - pInstance->SetData(ACTION_SHOW_TRASH, 1); - pInstance->HandleGameObject(pInstance->GetGuidData(GO_ARTHAS_DOOR), true); + falric->AI()->Talk(SAY_FALRIC_INTRO_2); + instance->SetData(ACTION_SHOW_TRASH, 1); + instance->HandleGameObject(GO_ARTHAS_DOOR, true); } events.ScheduleEvent(EVENT_INTRO_LK_9, 6s); break; @@ -765,39 +733,28 @@ public: me->HandleEmoteCommand(EMOTE_ONESHOT_EXCLAMATION); me->GetMotionMaster()->MovePoint(0, LichKingMoveAwayPos, FORCED_MOVEMENT_NONE, 0.f, false); } - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) - { - pLoralen->GetMotionMaster()->MovePoint(0, LoralenFollowLk1, FORCED_MOVEMENT_NONE, 0.f, false); - } + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) + loralen->GetMotionMaster()->MovePoint(0, LoralenFollowLk1, FORCED_MOVEMENT_NONE, 0.f, false); events.ScheduleEvent(EVENT_INTRO_LK_10, 1s + 500ms); break; case EVENT_INTRO_LK_10: - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) - { - pLoralen->GetMotionMaster()->MovePoint(0, LoralenFollowLk2, FORCED_MOVEMENT_NONE, 0.f, false); - - } + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) + loralen->GetMotionMaster()->MovePoint(0, LoralenFollowLk2, FORCED_MOVEMENT_NONE, 0.f, false); events.ScheduleEvent(EVENT_INTRO_LK_11, 2s); break; case EVENT_INTRO_LK_11: - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) - { - pLoralen->GetMotionMaster()->MovePoint(0, LoralenFollowLk3, FORCED_MOVEMENT_NONE, 0.f, false); - } + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) + loralen->GetMotionMaster()->MovePoint(0, LoralenFollowLk3, FORCED_MOVEMENT_NONE, 0.f, false); events.ScheduleEvent(EVENT_INTRO_LK_12, 5s + 500ms); break; case EVENT_INTRO_LK_12: - if (Creature* pLichKing = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_LICH_KING_EVENT))) - { - pLichKing->SetVisible(false); - } - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) - { - pLoralen->GetMotionMaster()->MovePoint(0, LoralenFollowLkFinal, FORCED_MOVEMENT_NONE, 0.f, false); - } + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_EVENT)) + lichKing->SetVisible(false); + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) + loralen->GetMotionMaster()->MovePoint(0, LoralenFollowLkFinal, FORCED_MOVEMENT_NONE, 0.f, false); events.ScheduleEvent(EVENT_INTRO_LK_13, 2s); break; case EVENT_INTRO_LK_13: @@ -806,19 +763,19 @@ public: break; case EVENT_INTRO_END: - pInstance->HandleGameObject(pInstance->GetGuidData(GO_ARTHAS_DOOR), false); - pInstance->HandleGameObject(pInstance->GetGuidData(GO_FRONT_DOOR), false); + instance->HandleGameObject(GO_ARTHAS_DOOR, false); + instance->HandleGameObject(GO_FRONT_DOOR, false); events.ScheduleEvent(EVENT_INTRO_END_SET, 3s); break; case EVENT_INTRO_END_SET: - if (Creature* pLoralen = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_DARK_RANGER_LORALEN))) + if (Creature* loralen = instance->GetCreature(NPC_DARK_RANGER_LORALEN)) { - pLoralen->UpdatePosition(5369.71289f, 2083.6330f, 707.695129f, 0.188739f, true); - pLoralen->StopMovingOnCurrentPos(); - pLoralen->SetVisible(true); - pLoralen->KillSelf(pLoralen); + loralen->UpdatePosition(5369.71289f, 2083.6330f, 707.695129f, 0.188739f, true); + loralen->StopMovingOnCurrentPos(); + loralen->SetVisible(true); + loralen->KillSelf(loralen); } - pInstance->SetData(DATA_INTRO, DONE); + instance->SetData(DATA_INTRO, DONE); break; } } @@ -859,6 +816,21 @@ enum TrashSpells SPELL_CURSED_ARROW = 72222, SPELL_FROST_TRAP = 72215, SPELL_ICE_SHOT = 72268, + + // Phantom Mage (clone visual) + SPELL_HALLUCINATION_CLONE = 72343, + + // Raging Ghoul + SPELL_LEAP = 70150, + + // Risen Witch Doctor + SPELL_CURSE_OF_DOOM = 70144, + SPELL_SHADOW_BOLT_WD = 70080, + SPELL_SHADOW_BOLT_VOLLEY = 70145, + + // Lumbering Abomination + SPELL_VOMIT_SPRAY = 70176, + SPELL_CLEAVE = 40505, }; enum TrashEvents @@ -896,704 +868,591 @@ enum TrashEvents EVENT_ICE_SHOT, }; -class npc_ghostly_priest : public CreatureScript +struct npc_ghostly_priest: public ScriptedAI { -public: - npc_ghostly_priest() : CreatureScript("npc_ghostly_priest") { } + npc_ghostly_priest(Creature* creature) : ScriptedAI(creature) {} - CreatureAI* GetAI(Creature* creature) const override + void DoAction(int32 action) override { - return GetHallsOfReflectionAI(creature); + if (action == 1) + { + me->SetInCombatWithZone(); + if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) + AttackStart(target); + } } - struct npc_ghostly_priestAI: public ScriptedAI + void Reset() override { - npc_ghostly_priestAI(Creature* c) : ScriptedAI(c) {} + events.Reset(); + } - EventMap events; + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 5s); + events.ScheduleEvent(EVENT_CIRCLE_OF_DESTRUCTION, 8s); + events.ScheduleEvent(EVENT_COWER_IN_FEAR, 10s); + events.ScheduleEvent(EVENT_DARK_MENDING, 8s); + } - void DoAction(int32 a) override + void JustDied(Unit* /*killer*/) override + { + if (!urand(0,9)) + Talk(SAY_WAVE_DEATH); + } + + void AttackStart(Unit* who) override + { + if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return; + ScriptedAI::AttackStart(who); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + switch (events.ExecuteEvent()) { - if (a == 1) - { - me->SetInCombatWithZone(); - if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) - AttackStart(target); - } - } - - void Reset() override - { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 5s); - events.ScheduleEvent(EVENT_CIRCLE_OF_DESTRUCTION, 8s); - events.ScheduleEvent(EVENT_COWER_IN_FEAR, 10s); - events.ScheduleEvent(EVENT_DARK_MENDING, 8s); - } - - void JustDied(Unit* /*killer*/) override - { - if (!urand(0,9)) + case EVENT_SHADOW_WORD_PAIN: + if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) + DoCast(target, SPELL_SHADOW_WORD_PAIN); + events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 5s); + break; + case EVENT_CIRCLE_OF_DESTRUCTION: + if (Unit* target = SelectTargetFromPlayerList(10.0f, 0, true)) + DoCast(target, SPELL_CIRCLE_OF_DESTRUCTION); + events.ScheduleEvent(EVENT_CIRCLE_OF_DESTRUCTION, 12s); + break; + case EVENT_COWER_IN_FEAR: + if (Unit* target = SelectTargetFromPlayerList(20.0f, 0, true)) + DoCast(target, SPELL_COWER_IN_FEAR); + events.ScheduleEvent(EVENT_COWER_IN_FEAR, 10s); + break; + case EVENT_DARK_MENDING: + if (Unit* target = DoSelectLowestHpFriendly(35.0f, DUNGEON_MODE(20000, 35000))) { - Talk(SAY_WAVE_DEATH); + DoCast(target, SPELL_DARK_MENDING); + events.ScheduleEvent(EVENT_DARK_MENDING, 6s); } + else + events.ScheduleEvent(EVENT_DARK_MENDING, 3s); + break; } - void AttackStart(Unit* who) override - { - if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) - return; - ScriptedAI::AttackStart(who); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_SHADOW_WORD_PAIN: - if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) - me->CastSpell(target, SPELL_SHADOW_WORD_PAIN, false); - events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 5s); - break; - case EVENT_CIRCLE_OF_DESTRUCTION: - if (Unit* target = SelectTargetFromPlayerList(10.0f, 0, true)) - me->CastSpell(target, SPELL_CIRCLE_OF_DESTRUCTION, false); - events.ScheduleEvent(EVENT_CIRCLE_OF_DESTRUCTION, 12s); - break; - case EVENT_COWER_IN_FEAR: - if (Unit* target = SelectTargetFromPlayerList(20.0f, 0, true)) - me->CastSpell(target, SPELL_COWER_IN_FEAR, false); - events.ScheduleEvent(EVENT_COWER_IN_FEAR, 10s); - break; - case EVENT_DARK_MENDING: - if (Unit* target = DoSelectLowestHpFriendly(35.0f, DUNGEON_MODE(20000, 35000))) - { - me->CastSpell(target, SPELL_DARK_MENDING, false); - events.ScheduleEvent(EVENT_DARK_MENDING, 6s); - } - else - events.ScheduleEvent(EVENT_DARK_MENDING, 3s); - break; - } - - DoMeleeAttackIfReady(); - } - - void EnterEvadeMode(EvadeReason why) override - { - ScriptedAI::EnterEvadeMode(why); - if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) - { - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToAll(false); - } - } - }; -}; - -class npc_phantom_mage : public CreatureScript -{ -public: - npc_phantom_mage() : CreatureScript("npc_phantom_mage") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); + DoMeleeAttackIfReady(); } - struct npc_phantom_mageAI: public ScriptedAI + void EnterEvadeMode(EvadeReason why) override { - npc_phantom_mageAI(Creature* c) : ScriptedAI(c) {} - - EventMap events; - - void DoAction(int32 a) override + ScriptedAI::EnterEvadeMode(why); + if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) { - if (a == 1) - { + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToAll(false); + } + } +}; + +struct npc_phantom_mage: public ScriptedAI +{ + npc_phantom_mage(Creature* creature) : ScriptedAI(creature) {} + + void DoAction(int32 action) override + { + if (action == 1) + { + me->SetInCombatWithZone(); + if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) + AttackStart(target); + } + } + + void Reset() override + { + events.Reset(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_FIREBALL, 3s); + events.ScheduleEvent(EVENT_FLAMESTRIKE, 6s); + events.ScheduleEvent(EVENT_FROSTBOLT, 9s); + events.ScheduleEvent(EVENT_CHAINS_OF_ICE, 12s); + events.ScheduleEvent(EVENT_HALLUCINATION, 40s); + } + + void JustDied(Unit* /*killer*/) override + { + if (!urand(0,9)) + Talk(SAY_WAVE_DEATH); + } + + void AttackStart(Unit* who) override + { + if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return; + ScriptedAI::AttackStart(who); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_FIREBALL: + DoCastVictim(SPELL_FIREBALL); + events.ScheduleEvent(EVENT_FIREBALL, 6s); + break; + case EVENT_FLAMESTRIKE: + DoCastVictim(SPELL_FLAMESTRIKE); + events.ScheduleEvent(EVENT_FLAMESTRIKE, 15s); + break; + case EVENT_FROSTBOLT: + if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) + DoCast(target, SPELL_FROSTBOLT); + events.ScheduleEvent(EVENT_FROSTBOLT, 9s); + break; + case EVENT_CHAINS_OF_ICE: + if (Unit* target = SelectTargetFromPlayerList(100.0f, 0, true)) + DoCast(target, SPELL_CHAINS_OF_ICE); + events.ScheduleEvent(EVENT_CHAINS_OF_ICE, 12s); + break; + case EVENT_HALLUCINATION: + //DoCastSelf(SPELL_HALLUCINATION); + me->SummonCreature(NPC_PHANTOM_HALLUCINATION, *me, TEMPSUMMON_TIMED_DESPAWN, 30000); + DoCastSelf(SPELL_HALLUCINATION_CLONE, true); + break; + } + + DoMeleeAttackIfReady(); + } + + void EnterEvadeMode(EvadeReason why) override + { + ScriptedAI::EnterEvadeMode(why); + if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) + { + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToAll(false); + } + } +}; + +struct npc_phantom_hallucination : public npc_phantom_mage +{ + npc_phantom_hallucination(Creature* creature) : npc_phantom_mage(creature) + { + numOfUpd = 2; + } + + uint8 numOfUpd; + + void JustDied(Unit* /*who*/) override + { + DoCastAOE(SPELL_HALLUCINATION_2); + } + + void UpdateAI(uint32 diff) override + { + if (numOfUpd) + { + if (--numOfUpd == 0) me->SetInCombatWithZone(); - if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) - AttackStart(target); - } + return; } - void Reset() override - { - events.Reset(); - } + npc_phantom_mage::UpdateAI(diff); + } - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(EVENT_FIREBALL, 3s); - events.ScheduleEvent(EVENT_FLAMESTRIKE, 6s); - events.ScheduleEvent(EVENT_FROSTBOLT, 9s); - events.ScheduleEvent(EVENT_CHAINS_OF_ICE, 12s); - events.ScheduleEvent(EVENT_HALLUCINATION, 40s); - } + void EnterEvadeMode(EvadeReason why) override + { + if (numOfUpd) + return; - void JustDied(Unit* /*killer*/) override + ScriptedAI::EnterEvadeMode(why); + if (me->IsSummon()) + me->ToTempSummon()->DespawnOrUnsummon(1ms); + } +}; + +struct npc_shadowy_mercenary: public ScriptedAI +{ + npc_shadowy_mercenary(Creature* creature) : ScriptedAI(creature) {} + + void DoAction(int32 action) override + { + if (action == 1) { - if (!urand(0,9)) + me->SetInCombatWithZone(); + if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) + AttackStart(target); + } + } + + void Reset() override + { + events.Reset(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_SHADOW_STEP, 4s); + events.ScheduleEvent(EVENT_DEADLY_POISON, 6s); + events.ScheduleEvent(EVENT_ENVENOMED_DAGGER_THROW, 10s); + events.ScheduleEvent(EVENT_KIDNEY_SHOT, 5s); + } + + void JustDied(Unit* /*killer*/) override + { + if (!urand(0,9)) + Talk(SAY_WAVE_DEATH); + } + + void AttackStart(Unit* who) override + { + if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return; + ScriptedAI::AttackStart(who); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_SHADOW_STEP: + if (Unit* target = SelectTargetFromPlayerList(100.0f, 0, true)) { - Talk(SAY_WAVE_DEATH); - } - } - - void AttackStart(Unit* who) override - { - if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) - return; - ScriptedAI::AttackStart(who); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_FIREBALL: - me->CastSpell(me->GetVictim(), SPELL_FIREBALL, false); - events.ScheduleEvent(EVENT_FIREBALL, 6s); - break; - case EVENT_FLAMESTRIKE: - me->CastSpell(me->GetVictim(), SPELL_FLAMESTRIKE, false); - events.ScheduleEvent(EVENT_FLAMESTRIKE, 15s); - break; - case EVENT_FROSTBOLT: - if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) - me->CastSpell(target, SPELL_FROSTBOLT, false); - events.ScheduleEvent(EVENT_FROSTBOLT, 9s); - break; - case EVENT_CHAINS_OF_ICE: - if (Unit* target = SelectTargetFromPlayerList(100.0f, 0, true)) - me->CastSpell(target, SPELL_CHAINS_OF_ICE, false); - events.ScheduleEvent(EVENT_CHAINS_OF_ICE, 12s); - break; - case EVENT_HALLUCINATION: - //me->CastSpell(me, SPELL_HALLUCINATION, false); - me->SummonCreature(NPC_PHANTOM_HALLUCINATION, *me, TEMPSUMMON_TIMED_DESPAWN, 30000); - me->CastSpell(me, SPELL_HALLUCINATION + 1, true); - break; - } - - DoMeleeAttackIfReady(); - } - - void EnterEvadeMode(EvadeReason why) override - { - ScriptedAI::EnterEvadeMode(why); - if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) - { - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToAll(false); - } - } - }; -}; - -class npc_phantom_hallucination : public CreatureScript -{ -public: - npc_phantom_hallucination() : CreatureScript("npc_phantom_hallucination") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); - } - - struct npc_phantom_hallucinationAI : public npc_phantom_mage::npc_phantom_mageAI - { - npc_phantom_hallucinationAI(Creature* c) : npc_phantom_mage::npc_phantom_mageAI(c) - { - numOfUpd = 2; - } - - uint8 numOfUpd; - - void JustDied(Unit* /*who*/) override - { - me->CastSpell((Unit*)nullptr, SPELL_HALLUCINATION_2, false); - } - - void UpdateAI(uint32 diff) override - { - if (numOfUpd) - { - if (--numOfUpd == 0) - me->SetInCombatWithZone(); - return; - } - - npc_phantom_mageAI::UpdateAI(diff); - } - - void EnterEvadeMode(EvadeReason why) override - { - if (numOfUpd) - return; - - ScriptedAI::EnterEvadeMode(why); - if (me->IsSummon()) - me->ToTempSummon()->DespawnOrUnsummon(1ms); - } - }; -}; - -class npc_shadowy_mercenary : public CreatureScript -{ -public: - npc_shadowy_mercenary() : CreatureScript("npc_shadowy_mercenary") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); - } - - struct npc_shadowy_mercenaryAI: public ScriptedAI - { - npc_shadowy_mercenaryAI(Creature* c) : ScriptedAI(c) {} - - EventMap events; - - void DoAction(int32 a) override - { - if (a == 1) - { - me->SetInCombatWithZone(); - if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) + DoResetThreatList(); + me->AddThreat(target, 5000.0f); AttackStart(target); - } - } - - void Reset() override - { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(EVENT_SHADOW_STEP, 4s); - events.ScheduleEvent(EVENT_DEADLY_POISON, 6s); - events.ScheduleEvent(EVENT_ENVENOMED_DAGGER_THROW, 10s); - events.ScheduleEvent(EVENT_KIDNEY_SHOT, 5s); - } - - void JustDied(Unit* /*killer*/) override - { - if (!urand(0,9)) - { - Talk(SAY_WAVE_DEATH); + DoCast(target, SPELL_SHADOW_STEP); } + events.ScheduleEvent(EVENT_SHADOW_STEP, 20s); + break; + case EVENT_DEADLY_POISON: + DoCastVictim(SPELL_DEADLY_POISON); + events.ScheduleEvent(EVENT_DEADLY_POISON, 4s); + break; + case EVENT_ENVENOMED_DAGGER_THROW: + if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) + DoCast(target, SPELL_ENVENOMED_DAGGER_THROW); + events.ScheduleEvent(EVENT_ENVENOMED_DAGGER_THROW, 10s); + break; + case EVENT_KIDNEY_SHOT: + DoCastVictim(SPELL_KIDNEY_SHOT); + events.ScheduleEvent(EVENT_KIDNEY_SHOT, 10s); + break; } - void AttackStart(Unit* who) override - { - if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) - return; - ScriptedAI::AttackStart(who); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_SHADOW_STEP: - if (Unit* target = SelectTargetFromPlayerList(100.0f, 0, true)) - { - DoResetThreatList(); - me->AddThreat(target, 5000.0f); - AttackStart(target); - me->CastSpell(target, SPELL_SHADOW_STEP, false); - } - events.ScheduleEvent(EVENT_SHADOW_STEP, 20s); - break; - case EVENT_DEADLY_POISON: - me->CastSpell(me->GetVictim(), SPELL_DEADLY_POISON, false); - events.ScheduleEvent(EVENT_DEADLY_POISON, 4s); - break; - case EVENT_ENVENOMED_DAGGER_THROW: - if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) - me->CastSpell(target, SPELL_ENVENOMED_DAGGER_THROW, false); - events.ScheduleEvent(EVENT_ENVENOMED_DAGGER_THROW, 10s); - break; - case EVENT_KIDNEY_SHOT: - me->CastSpell(me->GetVictim(), SPELL_KIDNEY_SHOT, false); - events.ScheduleEvent(EVENT_KIDNEY_SHOT, 10s); - break; - } - - DoMeleeAttackIfReady(); - } - - void EnterEvadeMode(EvadeReason why) override - { - ScriptedAI::EnterEvadeMode(why); - if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) - { - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToAll(false); - } - } - }; -}; - -class npc_spectral_footman : public CreatureScript -{ -public: - npc_spectral_footman() : CreatureScript("npc_spectral_footman") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); + DoMeleeAttackIfReady(); } - struct npc_spectral_footmanAI: public ScriptedAI + void EnterEvadeMode(EvadeReason why) override { - npc_spectral_footmanAI(Creature* c) : ScriptedAI(c) {} - - EventMap events; - - void DoAction(int32 a) override + ScriptedAI::EnterEvadeMode(why); + if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) { - if (a == 1) - { - me->SetInCombatWithZone(); - if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) - AttackStart(target); - } + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToAll(false); } - - void Reset() override - { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(EVENT_SPECTRAL_STRIKE, 5s); - events.ScheduleEvent(EVENT_SHIELD_BASH, 6s); - events.ScheduleEvent(EVENT_TORTURED_ENRAGE, 15s); - } - - void JustDied(Unit* /*killer*/) override - { - if (!urand(0,9)) - { - Talk(SAY_WAVE_DEATH); - } - } - - void AttackStart(Unit* who) override - { - if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) - return; - ScriptedAI::AttackStart(who); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_SPECTRAL_STRIKE: - me->CastSpell(me->GetVictim(), SPELL_SPECTRAL_STRIKE, false); - events.ScheduleEvent(EVENT_SPECTRAL_STRIKE, 5s); - break; - case EVENT_SHIELD_BASH: - me->CastSpell(me->GetVictim(), SPELL_SHIELD_BASH, false); - events.ScheduleEvent(EVENT_SHIELD_BASH, 6s); - break; - case EVENT_TORTURED_ENRAGE: - me->CastSpell(me, SPELL_TORTURED_ENRAGE, false); - events.ScheduleEvent(EVENT_TORTURED_ENRAGE, 15s); - break; - } - - DoMeleeAttackIfReady(); - } - - void EnterEvadeMode(EvadeReason why) override - { - ScriptedAI::EnterEvadeMode(why); - if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) - { - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToAll(false); - } - } - }; -}; - -class npc_tortured_rifleman : public CreatureScript -{ -public: - npc_tortured_rifleman() : CreatureScript("npc_tortured_rifleman") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); - } - - struct npc_tortured_riflemanAI : public ScriptedAI - { - npc_tortured_riflemanAI(Creature* c) : ScriptedAI(c) {} - - EventMap events; - - void DoAction(int32 a) override - { - if (a == 1) - { - me->SetInCombatWithZone(); - if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) - AttackStart(target); - } - } - - void Reset() override - { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(EVENT_CURSED_ARROW, 10s); - events.ScheduleEvent(EVENT_FROST_TRAP, 15s); - events.ScheduleEvent(EVENT_ICE_SHOT, 15s); - } - - void JustDied(Unit* /*killer*/) override - { - if (!urand(0,9)) - { - Talk(SAY_WAVE_DEATH); - } - } - - void AttackStart(Unit* who) override - { - if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) - return; - ScriptedAI::AttackStart(who); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_CURSED_ARROW: - me->CastSpell(me->GetVictim(), SPELL_CURSED_ARROW, false); - events.ScheduleEvent(EVENT_CURSED_ARROW, 10s); - break; - case EVENT_FROST_TRAP: - me->CastSpell((Unit*)nullptr, SPELL_FROST_TRAP, false); - events.ScheduleEvent(EVENT_FROST_TRAP, 30s); - break; - case EVENT_ICE_SHOT: - if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) - me->CastSpell(target, SPELL_ICE_SHOT, false); - events.ScheduleEvent(EVENT_ICE_SHOT, 8s); - break; - } - - DoSpellAttackIfReady(SPELL_SHOOT); - } - - void EnterEvadeMode(EvadeReason why) override - { - ScriptedAI::EnterEvadeMode(why); - if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) - { - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToAll(false); - } - } - }; -}; - -class boss_frostsworn_general : public CreatureScript -{ -public: - boss_frostsworn_general() : CreatureScript("boss_frostsworn_general") { } - - struct boss_frostsworn_generalAI : public ScriptedAI - { - boss_frostsworn_generalAI(Creature* creature) : ScriptedAI(creature) - { - pInstance = creature->GetInstanceScript(); - } - - InstanceScript* pInstance; - EventMap events; - - void Reset() override - { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - Talk(SAY_FROSTSWORN_GENERAL_AGGRO); - events.ScheduleEvent(EVENT_ACTIVATE_REFLECTIONS, 8s); - events.ScheduleEvent(EVENT_THROW_SHIELD, 6s); - pInstance->SetData(ACTION_SPIRITUAL_REFLECTIONS_COPY, 1); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - Position p = me->GetHomePosition(); - if (me->GetExactDist(&p) > 30.0f) - { - EnterEvadeMode(EVADE_REASON_OTHER); - return; - } - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_ACTIVATE_REFLECTIONS: - me->CastSpell((Unit*)nullptr, SPELL_SUMMON_REFLECTIONS_DUMMY, false); - pInstance->SetData(ACTION_SPIRITUAL_REFLECTIONS_ACTIVATE, 1); - break; - case EVENT_THROW_SHIELD: - if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) - me->CastSpell(target, SPELL_THROW_SHIELD, false); - events.ScheduleEvent(EVENT_THROW_SHIELD, 10s); - break; - } - - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_FROSTSWORN_GENERAL_DEATH); - if (pInstance) - pInstance->SetData(DATA_FROSTSWORN_GENERAL, DONE); - } - - void EnterEvadeMode(EvadeReason why) override - { - pInstance->SetData(ACTION_SPIRITUAL_REFLECTIONS_HIDE, 1); - ScriptedAI::EnterEvadeMode(why); - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfReflectionAI(creature); } }; -class npc_hor_spiritual_reflection : public CreatureScript +struct npc_spectral_footman: public ScriptedAI { -public: - npc_hor_spiritual_reflection() : CreatureScript("npc_hor_spiritual_reflection") { } + npc_spectral_footman(Creature* creature) : ScriptedAI(creature) {} - struct npc_hor_spiritual_reflectionAI : public ScriptedAI + void DoAction(int32 action) override { - npc_hor_spiritual_reflectionAI(Creature* creature) : ScriptedAI(creature) + if (action == 1) { + me->SetInCombatWithZone(); + if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) + AttackStart(target); } + } - EventMap events; - - void Reset() override - { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(EVENT_BALEFUL_STRIKE, 4s, 7s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_BALEFUL_STRIKE: - me->CastSpell(me->GetVictim(), SPELL_BALEFUL_STRIKE, false); - events.ScheduleEvent(EVENT_BALEFUL_STRIKE, 4s, 7s); - break; - } - - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - me->CastSpell((Unit*)nullptr, SPELL_SPIRIT_BURST, false); - } - - void EnterEvadeMode(EvadeReason why) override - { - me->UpdatePosition(me->GetHomePosition(), true); - ScriptedAI::EnterEvadeMode(why); - me->SetVisible(false); - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetHallsOfReflectionAI(creature); + events.Reset(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_SPECTRAL_STRIKE, 5s); + events.ScheduleEvent(EVENT_SHIELD_BASH, 6s); + events.ScheduleEvent(EVENT_TORTURED_ENRAGE, 15s); + } + + void JustDied(Unit* /*killer*/) override + { + if (!urand(0,9)) + Talk(SAY_WAVE_DEATH); + } + + void AttackStart(Unit* who) override + { + if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return; + ScriptedAI::AttackStart(who); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_SPECTRAL_STRIKE: + DoCastVictim(SPELL_SPECTRAL_STRIKE); + events.ScheduleEvent(EVENT_SPECTRAL_STRIKE, 5s); + break; + case EVENT_SHIELD_BASH: + DoCastVictim(SPELL_SHIELD_BASH); + events.ScheduleEvent(EVENT_SHIELD_BASH, 6s); + break; + case EVENT_TORTURED_ENRAGE: + DoCastSelf(SPELL_TORTURED_ENRAGE); + events.ScheduleEvent(EVENT_TORTURED_ENRAGE, 15s); + break; + } + + DoMeleeAttackIfReady(); + } + + void EnterEvadeMode(EvadeReason why) override + { + ScriptedAI::EnterEvadeMode(why); + if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) + { + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToAll(false); + } + } +}; + +struct npc_tortured_rifleman : public ScriptedAI +{ + npc_tortured_rifleman(Creature* creature) : ScriptedAI(creature) {} + + void DoAction(int32 action) override + { + if (action == 1) + { + me->SetInCombatWithZone(); + if (Unit* target = SelectTarget(SelectTargetMethod::MaxDistance, 0, 0.0f, true)) + AttackStart(target); + } + } + + void Reset() override + { + events.Reset(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_CURSED_ARROW, 10s); + events.ScheduleEvent(EVENT_FROST_TRAP, 15s); + events.ScheduleEvent(EVENT_ICE_SHOT, 15s); + } + + void JustDied(Unit* /*killer*/) override + { + if (!urand(0,9)) + Talk(SAY_WAVE_DEATH); + } + + void AttackStart(Unit* who) override + { + if (!me->IsVisible() || me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return; + ScriptedAI::AttackStart(who); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_CURSED_ARROW: + DoCastVictim(SPELL_CURSED_ARROW); + events.ScheduleEvent(EVENT_CURSED_ARROW, 10s); + break; + case EVENT_FROST_TRAP: + DoCastAOE(SPELL_FROST_TRAP); + events.ScheduleEvent(EVENT_FROST_TRAP, 30s); + break; + case EVENT_ICE_SHOT: + if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) + DoCast(target, SPELL_ICE_SHOT); + events.ScheduleEvent(EVENT_ICE_SHOT, 8s); + break; + } + + DoSpellAttackIfReady(SPELL_SHOOT); + } + + void EnterEvadeMode(EvadeReason why) override + { + ScriptedAI::EnterEvadeMode(why); + if (me->GetInstanceScript()->GetData(DATA_WAVE_NUMBER)) + { + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToAll(false); + } + } +}; + +struct boss_frostsworn_general : public ScriptedAI +{ + boss_frostsworn_general(Creature* creature) : ScriptedAI(creature) + { + instance = creature->GetInstanceScript(); + } + + InstanceScript* instance; + + void Reset() override + { + events.Reset(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + Talk(SAY_FROSTSWORN_GENERAL_AGGRO); + events.ScheduleEvent(EVENT_ACTIVATE_REFLECTIONS, 8s); + events.ScheduleEvent(EVENT_THROW_SHIELD, 6s); + instance->SetData(ACTION_SPIRITUAL_REFLECTIONS_COPY, 1); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + Position p = me->GetHomePosition(); + if (me->GetExactDist(&p) > 30.0f) + { + EnterEvadeMode(EVADE_REASON_OTHER); + return; + } + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_ACTIVATE_REFLECTIONS: + DoCastAOE(SPELL_SUMMON_REFLECTIONS_DUMMY); + instance->SetData(ACTION_SPIRITUAL_REFLECTIONS_ACTIVATE, 1); + break; + case EVENT_THROW_SHIELD: + if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true)) + DoCast(target, SPELL_THROW_SHIELD); + events.ScheduleEvent(EVENT_THROW_SHIELD, 10s); + break; + } + + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* /*killer*/) override + { + Talk(SAY_FROSTSWORN_GENERAL_DEATH); + if (instance) + instance->SetData(DATA_FROSTSWORN_GENERAL, DONE); + } + + void EnterEvadeMode(EvadeReason why) override + { + instance->SetData(ACTION_SPIRITUAL_REFLECTIONS_HIDE, 1); + ScriptedAI::EnterEvadeMode(why); + } +}; + +struct npc_hor_spiritual_reflection : public ScriptedAI +{ + npc_hor_spiritual_reflection(Creature* creature) : ScriptedAI(creature) {} + + void Reset() override + { + events.Reset(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_BALEFUL_STRIKE, 4s, 7s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_BALEFUL_STRIKE: + DoCastVictim(SPELL_BALEFUL_STRIKE); + events.ScheduleEvent(EVENT_BALEFUL_STRIKE, 4s, 7s); + break; + } + + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* /*killer*/) override + { + DoCastAOE(SPELL_SPIRIT_BURST); + } + + void EnterEvadeMode(EvadeReason why) override + { + me->UpdatePosition(me->GetHomePosition(), true); + ScriptedAI::EnterEvadeMode(why); + me->SetVisible(false); } }; @@ -1611,7 +1470,7 @@ public: if (!inst) return false; - if (inst->GetData(DATA_FROSTSWORN_GENERAL) && !inst->GetData(DATA_LK_INTRO)) + if (inst->GetPersistentData(PERSISTENT_DATA_FROSTSWORN_GENERAL) && !inst->GetPersistentData(PERSISTENT_DATA_LK_INTRO)) inst->SetData(DATA_LK_INTRO, DONE); return false; @@ -1646,261 +1505,250 @@ enum eFightEvents EVENT_DECREASE_REQ_COUNT_BY_100, }; -class npc_hor_lich_king : public CreatureScript +struct npc_hor_lich_king : public NullCreatureAI { -public: - npc_hor_lich_king() : CreatureScript("npc_hor_lich_king") { } - - CreatureAI* GetAI(Creature* creature) const override + npc_hor_lich_king(Creature* creature) : NullCreatureAI(creature), summons(me) { - return GetHallsOfReflectionAI(creature); + instance = me->GetInstanceScript(); + if (!instance) + me->IsAIEnabled = false; } - struct npc_hor_lich_kingAI : public NullCreatureAI + InstanceScript* instance; + EventMap events; + SummonList summons; + uint8 currentWall; + uint8 reqKillCount; + uint8 div2; + + void Reset() override { - npc_hor_lich_kingAI(Creature* creature) : NullCreatureAI(creature), summons(me) + currentWall = 0; + reqKillCount = 0; + events.Reset(); + events.RescheduleEvent(EVENT_LK_CHECK_COMBAT, 1s); + } + void DoAction(int32 action) override + { + if (action == ACTION_START_LK_FIGHT_REAL) + events.ScheduleEvent(EVENT_LK_START_FOLLOWING, 1500ms); + else if ((action == ACTION_INFORM_TRASH_DIED && reqKillCount) || action == ACTION_CHECK_TRASH_DIED) { - pInstance = me->GetInstanceScript(); - if (!pInstance) - me->IsAIEnabled = false; - } - - InstanceScript* pInstance; - EventMap events; - SummonList summons; - uint8 currentWall; - uint8 reqKillCount; - uint8 div2; - - void Reset() override - { - currentWall = 0; - reqKillCount = 0; - events.Reset(); - events.RescheduleEvent(EVENT_LK_CHECK_COMBAT, 1s); - } - void DoAction(int32 a) override - { - if (a == ACTION_START_LK_FIGHT_REAL) - events.ScheduleEvent(EVENT_LK_START_FOLLOWING, 1500ms); - else if ((a == ACTION_INFORM_TRASH_DIED && reqKillCount) || a == ACTION_CHECK_TRASH_DIED) + if ((action == ACTION_CHECK_TRASH_DIED && reqKillCount == 0) || (action == ACTION_INFORM_TRASH_DIED && (--reqKillCount) == 0)) { - if ((a == ACTION_CHECK_TRASH_DIED && reqKillCount == 0) || (a == ACTION_INFORM_TRASH_DIED && (--reqKillCount) == 0)) + events.CancelEvent(EVENT_DECREASE_REQ_COUNT_BY_100); // just in case, magic happens sometimes + ++currentWall; + instance->SetData(ACTION_DELETE_ICE_WALL, 1); + if (currentWall <= 3) { - events.CancelEvent(EVENT_DECREASE_REQ_COUNT_BY_100); // just in case, magic happens sometimes - ++currentWall; - pInstance->SetData(ACTION_DELETE_ICE_WALL, 1); - if (currentWall <= 3) + events.ScheduleEvent(EVENT_LK_SUMMON_NEXT_ICE_WALL, 1s); + events.ScheduleEvent(EVENT_LK_SUMMON, currentWall == 3 ? 11s : 7500ms); + } + else + me->RemoveAura(SPELL_REMORSELESS_WINTER); + if (Creature* c = instance->GetCreature(NPC_SYLVANAS_PART2)) + c->AI()->DoAction(ACTION_INFORM_WALL_DESTROYED); + } + } + } + + void MovementInform(uint32 type, uint32 /*id*/) override + { + // Xinef: dont use 0, it is no longer the last point + // Xinef: if type is escort and spline is finalized, it means that we reached last point from the path + if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized()) + { + if (currentWall == 0) + { + Talk(SAY_LK_IW_1); + me->SetOrientation(4.15f); + if (Creature* icewall = instance->instance->GetCreature(instance->GetGuidData(NPC_ICE_WALL_TARGET))) + { + me->SetFacingToObject(icewall); + me->CastSpell(icewall, SPELL_SUMMON_ICE_WALL); + events.ScheduleEvent(EVENT_LK_REMORSELESS_WINTER, 4s); + } + } + else if (currentWall == 4) + { + Talk(SAY_LK_NOWHERE_TO_RUN); + instance->SetData(DATA_LICH_KING, DONE); + } + } + } + + void JustSummoned(Creature* s) override + { + + ++reqKillCount; + if (events.HasTimeUntilEvent(EVENT_DECREASE_REQ_COUNT_BY_100)) + events.RescheduleEvent(EVENT_DECREASE_REQ_COUNT_BY_100, 10s); + summons.Summon(s); + s->SetHomePosition(PathWaypoints[WP_STOP[currentWall + 1]]); + s->GetMotionMaster()->MovePoint(0, PathWaypoints[WP_STOP[currentWall + 1]]); + s->SetInCombatWithZone(); + if (Unit* target = s->SelectNearestPlayer(350.0f)) + { + s->AddThreat(target, 1000.0f); + s->AI()->AttackStart(target); + } + s->SetHomePosition(PathWaypoints[WP_STOP[currentWall + 1]]); + } + + void SummonedCreatureDespawn(Creature* s) override + { + summons.Despawn(s); + } + + void SpellHitTarget(Unit* target, SpellInfo const* spell) override + { + if (target && target->IsAlive() && spell->Id == SPELL_LICH_KING_ZAP_PLAYER) + Unit::DealDamage(me, target, 10000); + } + + void UpdateAI(uint32 diff) override + { + if (me->HealthBelowPct(70)) + me->SetHealth(me->GetMaxHealth() * 3 / 4); + + events.Update(diff); + + if (me->IsNonMeleeSpellCast(false, true, true)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_LK_CHECK_COMBAT: + if (me->isActiveObject()) // during fight + { + if (Creature* leader = instance->GetCreature(NPC_SYLVANAS_PART2)) + if (leader->IsAlive() && leader->GetPositionX() < 5575.0f && me->GetExactDist2d(leader) <= 12.5f && !leader->HasAura(SPELL_HARVEST_SOUL) && me->HasAura(SPELL_REMORSELESS_WINTER)) + { + me->GetMotionMaster()->MovementExpired(); + me->StopMoving(); + reqKillCount = 255; + leader->InterruptNonMeleeSpells(true); + me->CastSpell(leader, SPELL_HARVEST_SOUL); + events.ScheduleEvent(EVENT_LK_KILL_LEADER, 3s); + events.ScheduleEvent(EVENT_LK_CHECK_COMBAT, 1s); + break; + } + if (instance->instance->HavePlayers()) { - events.ScheduleEvent(EVENT_LK_SUMMON_NEXT_ICE_WALL, 1s); - events.ScheduleEvent(EVENT_LK_SUMMON, currentWall == 3 ? 11s : 7500ms); + me->SetInCombatWithZone(); + ++div2; + if (div2 > 1) + { + div2 = 0; + if (me->HasAura(SPELL_REMORSELESS_WINTER)) // prevent going behind him during fight + { + Map::PlayerList const& pl = instance->instance->GetPlayers(); + for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) + if (Player* p = itr->GetSource()) + if (!p->IsGameMaster() && p->IsAlive() && (p->GetPositionX() - me->GetPositionX() + p->GetPositionY() - me->GetPositionY()) > 20.0f) + me->CastSpell(p, SPELL_LICH_KING_ZAP_PLAYER, true); + } + } } else - me->RemoveAura(SPELL_REMORSELESS_WINTER); - if (Creature* c = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_SYLVANAS_PART2))) - c->AI()->DoAction(ACTION_INFORM_WALL_DESTROYED); + { + summons.DespawnAll(); + instance->SetData(ACTION_STOP_LK_FIGHT, 1); + } } - } - } - - void MovementInform(uint32 type, uint32 /*id*/) override - { - // Xinef: dont use 0, it is no longer the last point - // Xinef: if type is escort and spline is finalized, it means that we reached last point from the path - if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized()) - { - if (currentWall == 0) + events.ScheduleEvent(EVENT_LK_CHECK_COMBAT, 1s); + break; + case EVENT_LK_KILL_LEADER: + if (Creature* leader = instance->GetCreature(NPC_SYLVANAS_PART2)) { - Talk(SAY_LK_IW_1); - me->SetOrientation(4.15f); - if (Creature* icewall = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_ICE_WALL_TARGET))) - { - me->SetFacingToObject(icewall); - me->CastSpell(icewall, SPELL_SUMMON_ICE_WALL, false); - events.ScheduleEvent(EVENT_LK_REMORSELESS_WINTER, 4s); - } + leader->CastSpell(leader, SPELL_HOR_SUICIDE, true); + Unit::Kill(me, leader); + me->InterruptNonMeleeSpells(true); + DoCastAOE(SPELL_FURY_OF_FROSTMOURNE); } - else if (currentWall == 4) + break; + case EVENT_LK_START_FOLLOWING: { - Talk(SAY_LK_NOWHERE_TO_RUN); - pInstance->SetData(DATA_LICH_KING, DONE); + me->SetSpeed(MOVE_RUN, 9.0f / 7.0f); + Movement::PointsArray path; + path.emplace_back(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); + for (uint8 i = 0; i <= 2; ++i) + path.emplace_back(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()); + me->GetMotionMaster()->MoveSplinePath(&path); } - } + break; + case EVENT_LK_REMORSELESS_WINTER: + { + me->SetSpeed(MOVE_RUN, me->GetCreatureTemplate()->speed_run); + Talk(SAY_LK_WINTER); + DoCastSelf(SPELL_REMORSELESS_WINTER, true); + Movement::PointsArray path; + path.emplace_back(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); + for (uint8 i = 3; i < PATH_WP_COUNT - 1; ++i) + path.emplace_back(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()); + me->GetMotionMaster()->MoveSplinePath(&path); + me->GetMotionMaster()->propagateSpeedChange(); + events.ScheduleEvent(EVENT_LK_SUMMON, 1s); + } + break; + case EVENT_LK_SUMMON: + switch (currentWall) + { + case 0: + events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 0ms); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 4s); + break; + case 1: + events.ScheduleEvent(EVENT_LK_SUMMON_LA, 0ms); + events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 100ms); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 4s); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 5100ms); + break; + case 2: + events.ScheduleEvent(EVENT_LK_SUMMON_LA, 0ms); + events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 100ms); + events.ScheduleEvent(EVENT_LK_SUMMON_LA, 4s); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 4100ms); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 5200ms); + break; + case 3: + events.ScheduleEvent(EVENT_LK_SUMMON_LA, 0ms); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 100ms); + events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 1200ms); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 5300ms); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 6400ms); + events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 12s + 500ms); + events.ScheduleEvent(EVENT_LK_SUMMON_LA, 16s + 500ms); + events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 16s + 600ms); + events.ScheduleEvent(EVENT_LK_SUMMON_LA, 17s + 700ms); + break; + } + if (currentWall <= 3) + { + reqKillCount = 100; + events.RescheduleEvent(EVENT_DECREASE_REQ_COUNT_BY_100, 10s); + } + break; + case EVENT_DECREASE_REQ_COUNT_BY_100: + reqKillCount = (reqKillCount <= 100 ? 0 : reqKillCount - 100); + DoAction(ACTION_CHECK_TRASH_DIED); + break; + case EVENT_LK_SUMMON_GHOULS: + DoCastAOE(SPELL_SUMMON_RAGING_GHOULS); + break; + case EVENT_LK_SUMMON_RWD: + DoCastAOE(SPELL_SUMMON_RISEN_WITCH_DOCTOR); + break; + case EVENT_LK_SUMMON_LA: + DoCastAOE(SPELL_SUMMON_LUMBERING_ABOMINATION); + break; + case EVENT_LK_SUMMON_NEXT_ICE_WALL: + Talk(SAY_LK_IW_1 + currentWall); + if (Creature* c = instance->instance->GetCreature(instance->GetGuidData(NPC_ICE_WALL_TARGET + currentWall))) + me->CastSpell(c, SPELL_SUMMON_ICE_WALL); + break; } - - void JustSummoned(Creature* s) override - { - - ++reqKillCount; - if (events.HasTimeUntilEvent(EVENT_DECREASE_REQ_COUNT_BY_100)) - events.RescheduleEvent(EVENT_DECREASE_REQ_COUNT_BY_100, 10s); - summons.Summon(s); - s->SetHomePosition(PathWaypoints[WP_STOP[currentWall + 1]]); - s->GetMotionMaster()->MovePoint(0, PathWaypoints[WP_STOP[currentWall + 1]]); - s->SetInCombatWithZone(); - if (Unit* target = s->SelectNearestPlayer(350.0f)) - { - s->AddThreat(target, 1000.0f); - s->AI()->AttackStart(target); - } - s->SetHomePosition(PathWaypoints[WP_STOP[currentWall + 1]]); - } - - void SummonedCreatureDespawn(Creature* s) override - { - summons.Despawn(s); - } - - void SpellHitTarget(Unit* target, SpellInfo const* spell) override - { - if (target && target->IsAlive() && spell->Id == SPELL_LICH_KING_ZAP_PLAYER) - Unit::DealDamage(me, target, 10000); - } - - void UpdateAI(uint32 diff) override - { - if (me->HealthBelowPct(70)) - me->SetHealth(me->GetMaxHealth() * 3 / 4); - - events.Update(diff); - - if (me->IsNonMeleeSpellCast(false, true, true)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_LK_CHECK_COMBAT: - if (me->isActiveObject()) // during fight - { - if (Creature* leader = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_SYLVANAS_PART2))) - if (leader->IsAlive() && leader->GetPositionX() < 5575.0f && me->GetExactDist2d(leader) <= 12.5f && !leader->HasAura(SPELL_HARVEST_SOUL) && me->HasAura(SPELL_REMORSELESS_WINTER)) - { - me->GetMotionMaster()->MovementExpired(); - me->StopMoving(); - reqKillCount = 255; - leader->InterruptNonMeleeSpells(true); - me->CastSpell(leader, SPELL_HARVEST_SOUL, false); - events.ScheduleEvent(EVENT_LK_KILL_LEADER, 3s); - events.ScheduleEvent(EVENT_LK_CHECK_COMBAT, 1s); - break; - } - if (pInstance->instance->HavePlayers()) - { - me->SetInCombatWithZone(); - ++div2; - if (div2 > 1) - { - div2 = 0; - if (me->HasAura(SPELL_REMORSELESS_WINTER)) // prevent going behind him during fight - { - Map::PlayerList const& pl = pInstance->instance->GetPlayers(); - for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - if (Player* p = itr->GetSource()) - if (!p->IsGameMaster() && p->IsAlive() && (p->GetPositionX() - me->GetPositionX() + p->GetPositionY() - me->GetPositionY()) > 20.0f) - me->CastSpell(p, SPELL_LICH_KING_ZAP_PLAYER, true); - } - } - } - else - { - summons.DespawnAll(); - pInstance->SetData(ACTION_STOP_LK_FIGHT, 1); - } - } - events.ScheduleEvent(EVENT_LK_CHECK_COMBAT, 1s); - break; - case EVENT_LK_KILL_LEADER: - if (Creature* leader = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_SYLVANAS_PART2))) - { - leader->CastSpell(leader, SPELL_HOR_SUICIDE, true); - Unit::Kill(me, leader); - me->InterruptNonMeleeSpells(true); - me->CastSpell((Unit*)nullptr, SPELL_FURY_OF_FROSTMOURNE, false); - } - break; - case EVENT_LK_START_FOLLOWING: - { - me->SetSpeed(MOVE_RUN, 9.0f / 7.0f); - Movement::PointsArray path; - path.emplace_back(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); - for (uint8 i = 0; i <= 2; ++i) - path.emplace_back(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()); - me->GetMotionMaster()->MoveSplinePath(&path); - } - break; - case EVENT_LK_REMORSELESS_WINTER: - { - me->SetSpeed(MOVE_RUN, me->GetCreatureTemplate()->speed_run); - Talk(SAY_LK_WINTER); - me->CastSpell(me, SPELL_REMORSELESS_WINTER, true); - Movement::PointsArray path; - path.emplace_back(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); - for (uint8 i = 3; i < PATH_WP_COUNT - 1; ++i) - path.emplace_back(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()); - me->GetMotionMaster()->MoveSplinePath(&path); - me->GetMotionMaster()->propagateSpeedChange(); - events.ScheduleEvent(EVENT_LK_SUMMON, 1s); - } - break; - case EVENT_LK_SUMMON: - switch (currentWall) - { - case 0: - events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 0ms); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 4s); - break; - case 1: - events.ScheduleEvent(EVENT_LK_SUMMON_LA, 0ms); - events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 100ms); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 4s); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 5100ms); - break; - case 2: - events.ScheduleEvent(EVENT_LK_SUMMON_LA, 0ms); - events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 100ms); - events.ScheduleEvent(EVENT_LK_SUMMON_LA, 4s); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 4100ms); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 5200ms); - break; - case 3: - events.ScheduleEvent(EVENT_LK_SUMMON_LA, 0ms); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 100ms); - events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 1200ms); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 5300ms); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 6400ms); - events.ScheduleEvent(EVENT_LK_SUMMON_GHOULS, 12s + 500ms); - events.ScheduleEvent(EVENT_LK_SUMMON_LA, 16s + 500ms); - events.ScheduleEvent(EVENT_LK_SUMMON_RWD, 16s + 600ms); - events.ScheduleEvent(EVENT_LK_SUMMON_LA, 17s + 700ms); - break; - } - if (currentWall <= 3) - { - reqKillCount = 100; - events.RescheduleEvent(EVENT_DECREASE_REQ_COUNT_BY_100, 10s); - } - break; - case EVENT_DECREASE_REQ_COUNT_BY_100: - reqKillCount = (reqKillCount <= 100 ? 0 : reqKillCount - 100); - DoAction(ACTION_CHECK_TRASH_DIED); - break; - case EVENT_LK_SUMMON_GHOULS: - me->CastSpell((Unit*)nullptr, SPELL_SUMMON_RAGING_GHOULS, false); - break; - case EVENT_LK_SUMMON_RWD: - me->CastSpell((Unit*)nullptr, SPELL_SUMMON_RISEN_WITCH_DOCTOR, false); - break; - case EVENT_LK_SUMMON_LA: - me->CastSpell((Unit*)nullptr, SPELL_SUMMON_LUMBERING_ABOMINATION, false); - break; - case EVENT_LK_SUMMON_NEXT_ICE_WALL: - Talk(SAY_LK_IW_1 + currentWall); - if (Creature* c = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_ICE_WALL_TARGET + currentWall))) - me->CastSpell(c, SPELL_SUMMON_ICE_WALL, false); - break; - } - } - }; + } }; class npc_hor_leader_second : public CreatureScript @@ -1915,8 +1763,8 @@ public: ClearGossipMenuFor(player); - if (InstanceScript* pInstance = creature->GetInstanceScript()) - if (!pInstance->GetData(DATA_LICH_KING)) + if (InstanceScript* instance = creature->GetInstanceScript()) + if (instance->GetBossState(DATA_LICH_KING) != DONE) { creature->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); creature->AI()->DoAction(ACTION_START_LK_FIGHT_REAL); @@ -1935,13 +1783,13 @@ public: { npc_hor_leader_secondAI(Creature* creature) : NullCreatureAI(creature) { - pInstance = me->GetInstanceScript(); - if (!pInstance) + instance = me->GetInstanceScript(); + if (!instance) me->IsAIEnabled = false; } - InstanceScript* pInstance; + InstanceScript* instance; EventMap events; uint8 currentStopPoint; @@ -2004,63 +1852,57 @@ public: switch (events.ExecuteEvent()) { case EVENT_LK_SAY_AGGRO: - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) { - me->Attack(lkboss, true), - lkboss->AI()->Talk(me->GetEntry() == NPC_JAINA_PART2 ? SAY_LK_AGGRO_ALLY : SAY_LK_AGGRO_HORDE); + me->Attack(lichKing, true), + lichKing->AI()->Talk(me->GetEntry() == NPC_JAINA_PART2 ? SAY_LK_AGGRO_ALLY : SAY_LK_AGGRO_HORDE); } break; case EVENT_LK_BATTLE_1: - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) - { - lkboss->CastSpell(lkboss, SPELL_SOUL_REAPER, false); - } + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) + lichKing->CastSpell(lichKing, SPELL_SOUL_REAPER); break; case EVENT_LK_BATTLE_2: //horda - if (Creature* leader = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_SYLVANAS_PART2))) - { + if (Creature* leader = instance->GetCreature(NPC_SYLVANAS_PART2)) leader->CastSpell(leader, SPELL_EVASION, true); - } - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) + lichKing->SetFacingToObject(me); + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) { - lkboss->SetFacingToObject(me); - } - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) - { - lkboss->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_ATTACK2HTIGHT); - me->SetFacingToObject(lkboss); + lichKing->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_ATTACK2HTIGHT); + me->SetFacingToObject(lichKing); } break; case EVENT_JAINA_IMMOBILIZE_LK: - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) { - lkboss->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY_SPELL_OMNI); + lichKing->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY_SPELL_OMNI); me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - me->CastSpell(lkboss, SPELL_JAINA_ICE_PRISON, false); + me->CastSpell(lichKing, SPELL_JAINA_ICE_PRISON); events.ScheduleEvent(EVENT_SAY_LEAVE, 5s); } break; case EVENT_SYLVANAS_IMMOBILIZE_JUMP: - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) { me->AttackStop(); me->SetSheath(SHEATH_STATE_MELEE); - lkboss->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY_SPELL_OMNI); - lkboss->CastSpell(me, SPELL_BLIDING_RETREAT, false); + lichKing->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY_SPELL_OMNI); + lichKing->CastSpell(me, SPELL_BLIDING_RETREAT); me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - me->KnockbackFrom(lkboss->GetPositionX(), lkboss->GetPositionY(), 34.3f, 4.0f); + me->KnockbackFrom(lichKing->GetPositionX(), lichKing->GetPositionY(), 34.3f, 4.0f); events.ScheduleEvent(EVENT_SYLVANAS_DARK_BINDING, 2s +500ms); } break; case EVENT_SYLVANAS_DARK_BINDING: - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) - me->CastSpell(lkboss, SPELL_SYLVANAS_DARK_BINDING, false); + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) + me->CastSpell(lichKing, SPELL_SYLVANAS_DARK_BINDING); events.ScheduleEvent(EVENT_SAY_LEAVE, 2s); break; case EVENT_SAY_LEAVE: { - Map::PlayerList const& pl = pInstance->instance->GetPlayers(); + Map::PlayerList const& pl = instance->instance->GetPlayers(); for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) if (Player* p = itr->GetSource()) p->KilledMonsterCredit(me->GetEntry()); //for quest @@ -2079,15 +1921,15 @@ public: break; case EVENT_START_RUN: { - pInstance->SetData(ACTION_START_LK_FIGHT, 1); + instance->SetData(ACTION_START_LK_FIGHT, 1); me->setActive(true); MoveToNextStopPoint(); - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) { - lkboss->setActive(true); - lkboss->SetInCombatWithZone(); - lkboss->RemoveAura(me->GetEntry() == NPC_JAINA_PART2 ? SPELL_JAINA_ICE_PRISON : SPELL_SYLVANAS_DARK_BINDING); - lkboss->AI()->DoAction(ACTION_START_LK_FIGHT_REAL); + lichKing->setActive(true); + lichKing->SetInCombatWithZone(); + lichKing->RemoveAura(me->GetEntry() == NPC_JAINA_PART2 ? SPELL_JAINA_ICE_PRISON : SPELL_SYLVANAS_DARK_BINDING); + lichKing->AI()->DoAction(ACTION_START_LK_FIGHT_REAL); } } break; @@ -2114,7 +1956,7 @@ public: } Talk(textId); if (currentStopPoint <= 4) - me->CastSpell((Unit*)nullptr, (me->GetEntry() == NPC_JAINA_PART2 ? SPELL_DESTROY_WALL_JAINA : SPELL_DESTROY_WALL_SYLVANAS), false); + DoCastAOE(me->GetEntry() == NPC_JAINA_PART2 ? SPELL_DESTROY_WALL_JAINA : SPELL_DESTROY_WALL_SYLVANAS); else { me->SetFacingTo(PathWaypoints[PATH_WP_COUNT - 1].GetOrientation()); @@ -2131,192 +1973,149 @@ public: }; }; -class npc_hor_raging_ghoul : public CreatureScript +struct npc_hor_raging_ghoul : public ScriptedAI { -public: - npc_hor_raging_ghoul() : CreatureScript("npc_hor_raging_ghoul") { } + npc_hor_raging_ghoul(Creature* creature) : ScriptedAI(creature) {} - struct npc_hor_raging_ghoulAI : public ScriptedAI + bool leaped; + + void Reset() override { - npc_hor_raging_ghoulAI(Creature* creature) : ScriptedAI(creature) {} + leaped = false; + } - bool leaped; - - void Reset() override - { - leaped = false; - } - - void UpdateAI(uint32 /*diff*/) override - { - if (!UpdateVictim()) - return; - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - if (!leaped) - if (Unit* target = me->SelectNearestPlayer(30.0f)) - { - AttackStart(target); - me->CastSpell(target, 70150, false); - leaped = true; - } - - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - me->SetCorpseDelay(10); - if (InstanceScript* pInstance = me->GetInstanceScript()) - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) - lkboss->AI()->DoAction(ACTION_INFORM_TRASH_DIED); - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void UpdateAI(uint32 /*diff*/) override { - return GetHallsOfReflectionAI(creature); + if (!UpdateVictim()) + return; + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + if (!leaped) + if (Unit* target = me->SelectNearestPlayer(30.0f)) + { + AttackStart(target); + DoCast(target, SPELL_LEAP); + leaped = true; + } + + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* /*killer*/) override + { + me->SetCorpseDelay(10); + if (InstanceScript* instance = me->GetInstanceScript()) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) + lichKing->AI()->DoAction(ACTION_INFORM_TRASH_DIED); } }; -class npc_hor_risen_witch_doctor : public CreatureScript +struct npc_hor_risen_witch_doctor : public ScriptedAI { -public: - npc_hor_risen_witch_doctor() : CreatureScript("npc_hor_risen_witch_doctor") { } + npc_hor_risen_witch_doctor(Creature* creature) : ScriptedAI(creature) {} - struct npc_hor_risen_witch_doctorAI : public ScriptedAI + void Reset() override { - npc_hor_risen_witch_doctorAI(Creature* creature) : ScriptedAI(creature) {} + events.Reset(); + } - EventMap events; - - void Reset() override - { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(1, 10s); - events.ScheduleEvent(2, 4500ms); - events.ScheduleEvent(3, 9s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - switch (events.ExecuteEvent()) - { - case 1: - if (Unit* target = SelectTargetFromPlayerList(30.0f, 0, true)) - me->CastSpell(target, 70144, false); - events.ScheduleEvent(1, 12s); - break; - case 2: - me->CastSpell(me->GetVictim(), 70080, false); - events.ScheduleEvent(2, 4500ms); - break; - case 3: - if (SelectTargetFromPlayerList(30.0f, 0, true)) - me->CastSpell(me->GetVictim(), 70145, false); - events.ScheduleEvent(3, 9s); - break; - } - - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - me->SetCorpseDelay(10); - if (InstanceScript* pInstance = me->GetInstanceScript()) - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) - { - lkboss->AI()->DoAction(ACTION_INFORM_TRASH_DIED); - } - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void JustEngagedWith(Unit* /*who*/) override { - return GetHallsOfReflectionAI(creature); + events.ScheduleEvent(1, 10s); + events.ScheduleEvent(2, 4500ms); + events.ScheduleEvent(3, 9s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + switch (events.ExecuteEvent()) + { + case 1: + if (Unit* target = SelectTargetFromPlayerList(30.0f, 0, true)) + DoCast(target, SPELL_CURSE_OF_DOOM); + events.ScheduleEvent(1, 12s); + break; + case 2: + DoCastVictim(SPELL_SHADOW_BOLT_WD); + events.ScheduleEvent(2, 4500ms); + break; + case 3: + if (SelectTargetFromPlayerList(30.0f, 0, true)) + DoCastVictim(SPELL_SHADOW_BOLT_VOLLEY); + events.ScheduleEvent(3, 9s); + break; + } + + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* /*killer*/) override + { + me->SetCorpseDelay(10); + if (InstanceScript* instance = me->GetInstanceScript()) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) + lichKing->AI()->DoAction(ACTION_INFORM_TRASH_DIED); } }; -class npc_hor_lumbering_abomination : public CreatureScript +struct npc_hor_lumbering_abomination : public ScriptedAI { -public: - npc_hor_lumbering_abomination() : CreatureScript("npc_hor_lumbering_abomination") { } + npc_hor_lumbering_abomination(Creature* creature) : ScriptedAI(creature) {} - struct npc_hor_lumbering_abominationAI : public ScriptedAI + void Reset() override { - npc_hor_lumbering_abominationAI(Creature* creature) : ScriptedAI(creature) {} + events.Reset(); + } - EventMap events; + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(1, 5s); + } - void Reset() override + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) + return; + + switch (events.ExecuteEvent()) { - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(1, 5s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasFearAura() || me->IsCharmed() || me->isFrozen() || me->HasUnitState(UNIT_STATE_STUNNED) || me->HasUnitState(UNIT_STATE_CONFUSED)) - return; - - switch (events.ExecuteEvent()) - { - case 1: - if (me->IsWithinMeleeRange(me->GetVictim())) - { - me->CastSpell(me->GetVictim(), 70176, false); - events.ScheduleEvent(1, 18s); - } - else - events.ScheduleEvent(1, 3s); - break; - } - - if (me->GetVictim() && me->isAttackReady() && me->IsWithinMeleeRange(me->GetVictim()) && !me->HasUnitState(UNIT_STATE_CASTING)) - { - me->CastSpell(me->GetVictim(), 40505, false); - } - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - me->SetCorpseDelay(10); - if (InstanceScript* pInstance = me->GetInstanceScript()) - if (Creature* lkboss = pInstance->instance->GetCreature(pInstance->GetGuidData(NPC_LICH_KING_BOSS))) + case 1: + if (me->IsWithinMeleeRange(me->GetVictim())) { - lkboss->AI()->DoAction(ACTION_INFORM_TRASH_DIED); + DoCastVictim(SPELL_VOMIT_SPRAY); + events.ScheduleEvent(1, 18s); } + else + events.ScheduleEvent(1, 3s); + break; } - }; - CreatureAI* GetAI(Creature* creature) const override + if (me->GetVictim() && me->isAttackReady() && me->IsWithinMeleeRange(me->GetVictim()) && !me->HasUnitState(UNIT_STATE_CASTING)) + DoCastVictim(SPELL_CLEAVE); + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* /*killer*/) override { - return GetHallsOfReflectionAI(creature); + me->SetCorpseDelay(10); + if (InstanceScript* instance = me->GetInstanceScript()) + if (Creature* lichKing = instance->GetCreature(NPC_LICH_KING_BOSS)) + lichKing->AI()->DoAction(ACTION_INFORM_TRASH_DIED); } }; @@ -2339,9 +2138,7 @@ class spell_hor_gunship_cannon_fire_aura : public AuraScript PreventDefaultAction(); if (Unit* caster = GetCaster()) if (Creature* creature = caster->SummonCreature(WORLD_TRIGGER, CannonFirePos[caster->GetEntry() == NPC_JAINA_PART2 ? 0 : 1][urand(0, 2)], TEMPSUMMON_TIMED_DESPAWN, 1)) - { creature->CastSpell((Unit*)nullptr, SPELL_GUNSHIP_CANNON_FIRE, true); - } } void Register() override @@ -2359,7 +2156,7 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*areaTrigger*/) override { - if (player->HasAura(70013)) + if (player->HasAura(SPELL_QUELDELAR_COMPULSION)) if (InstanceScript* instance = player->GetInstanceScript()) instance->SetData(DATA_BATTERED_HILT, 2); return true; @@ -2373,13 +2170,13 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*areaTrigger*/) override { - if (player->HasAura(70013)) + if (player->HasAura(SPELL_QUELDELAR_COMPULSION)) if (InstanceScript* instance = player->GetInstanceScript()) { uint32 bhd = instance->GetData(DATA_BATTERED_HILT); if (bhd != BHSF_STARTED) return true; - player->CastSpell(player, 70698, true); + player->CastSpell(player, SPELL_QUELDELAR_WILL, true); player->DestroyItemCount(49766, 1, true); instance->SetData(DATA_BATTERED_HILT, 3); } @@ -2391,22 +2188,22 @@ void AddSC_halls_of_reflection() { new npc_hor_leader(); - new npc_ghostly_priest(); - new npc_phantom_mage(); - new npc_phantom_hallucination(); - new npc_shadowy_mercenary(); - new npc_spectral_footman(); - new npc_tortured_rifleman(); + RegisterHallsOfReflectionCreatureAI(npc_ghostly_priest); + RegisterHallsOfReflectionCreatureAI(npc_phantom_mage); + RegisterHallsOfReflectionCreatureAI(npc_phantom_hallucination); + RegisterHallsOfReflectionCreatureAI(npc_shadowy_mercenary); + RegisterHallsOfReflectionCreatureAI(npc_spectral_footman); + RegisterHallsOfReflectionCreatureAI(npc_tortured_rifleman); - new boss_frostsworn_general(); - new npc_hor_spiritual_reflection(); + RegisterHallsOfReflectionCreatureAI(boss_frostsworn_general); + RegisterHallsOfReflectionCreatureAI(npc_hor_spiritual_reflection); new at_hor_shadow_throne(); - new npc_hor_lich_king(); + RegisterHallsOfReflectionCreatureAI(npc_hor_lich_king); new npc_hor_leader_second(); - new npc_hor_raging_ghoul(); - new npc_hor_risen_witch_doctor(); - new npc_hor_lumbering_abomination(); + RegisterHallsOfReflectionCreatureAI(npc_hor_raging_ghoul); + RegisterHallsOfReflectionCreatureAI(npc_hor_risen_witch_doctor); + RegisterHallsOfReflectionCreatureAI(npc_hor_lumbering_abomination); RegisterSpellScript(spell_hor_gunship_cannon_fire_aura); new at_hor_battered_hilt_start(); diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h index fb5a722c1..dfd4c93c5 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h @@ -44,6 +44,7 @@ enum Data ACTION_DELETE_ICE_WALL, DATA_WAVE_NUMBER, DATA_LK_BATTLE,// in progress + DATA_SHIP_CAPTAIN, }; enum Creatures @@ -108,6 +109,17 @@ enum BatteredHiltStatusFlags BHSF_FINISHED = 4, }; +enum HoRPersistentData +{ + PERSISTENT_DATA_INTRO, + PERSISTENT_DATA_FROSTSWORN_GENERAL, + PERSISTENT_DATA_LK_INTRO, + PERSISTENT_DATA_BATTERED_HILT, + PERSISTENT_DATA_COUNT +}; + +constexpr uint8 MAX_SPIRITUAL_REFLECTIONS = 5; +constexpr uint8 MAX_ICE_WALL_TARGETS = 4; #define NUM_OF_TRASH 34 #define MAX_DIST_FROM_CENTER_IN_COMBAT 70.5f #define MAX_DIST_FROM_CENTER_TO_START 40.0f @@ -291,6 +303,11 @@ enum hMisc // Battered Hilt - Summon Quel'Delar SPELL_SUMMON_EVIL_QUEL = 69966, + SPELL_QUEL_DELAR_HATRED = 70300, + SPELL_FROSTMOURNE_ALTAR_GLOW = 70720, + SPELL_UTHER_HOLY_LIGHT_VISUAL = 73036, + SPELL_QUELDELAR_COMPULSION = 70013, + SPELL_QUELDELAR_WILL = 70698, }; const uint32 allowedCompositions[8][5] = @@ -390,4 +407,7 @@ inline AI* GetHallsOfReflectionAI(T* obj) return GetInstanceAI(obj, HallsOfReflectionScriptName); } +#define RegisterHallsOfReflectionCreatureAI(ai_name) \ + RegisterCreatureAIWithFactory(ai_name, GetHallsOfReflectionAI) + #endif diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp index 261006135..a1bd4554e 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp @@ -46,12 +46,10 @@ public: case 3: _owner.CastSpell((Unit*)nullptr, SPELL_SUMMON_EVIL_QUEL, true); _owner.AI()->Talk(SAY_BATTERED_HILT_REALIZE); - if (InstanceScript* instance = _owner.GetInstanceScript()) - instance->SetData(DATA_BATTERED_HILT, 4); + if (InstanceScript* inst = _owner.GetInstanceScript()) + inst->SetData(DATA_BATTERED_HILT, 4); if (Creature* quel = _owner.FindNearestCreature(NPC_QUEL_DELAR, 50)) - { quel->AI()->Talk(EMOTE_QUEL_SPAWN); - } _owner.m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(_owner, 4), 3500ms); break; case 4: @@ -64,26 +62,22 @@ public: _owner.SetStandState(UNIT_STAND_STATE_KNEEL); break; case 6: - if (InstanceScript* instance = _owner.GetInstanceScript()) - instance->SetData(DATA_BATTERED_HILT, 6); + if (InstanceScript* inst = _owner.GetInstanceScript()) + inst->SetData(DATA_BATTERED_HILT, 6); _owner.m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(_owner, 7), 2s); break; case 7: - if (InstanceScript* instance = _owner.GetInstanceScript()) - { - instance->SetData(DATA_BATTERED_HILT, 7); - } + if (InstanceScript* inst = _owner.GetInstanceScript()) + inst->SetData(DATA_BATTERED_HILT, 7); if (Creature* quel = _owner.FindNearestCreature(NPC_QUEL_DELAR, 50)) - { quel->AI()->Talk(EMOTE_QUEL_PREPARE); - } _owner.m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(_owner, 8), 4s); break; case 8: _owner.SetReactState(REACT_AGGRESSIVE); _owner.SetImmuneToAll(false); - if (InstanceScript* instance = _owner.GetInstanceScript()) - instance->SetData(DATA_BATTERED_HILT, 8); + if (InstanceScript* inst = _owner.GetInstanceScript()) + inst->SetData(DATA_BATTERED_HILT, 8); break; case 9: _owner.AI()->Talk(SAY_BATTERED_HILT_OUTRO1); @@ -102,22 +96,22 @@ public: _owner.m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(_owner, _eventId + 1), 5s); break; case 13: - _owner.CastSpell((Unit*)nullptr, 73036, true); + _owner.CastSpell((Unit*)nullptr, SPELL_UTHER_HOLY_LIGHT_VISUAL, true); _owner.m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(_owner, _eventId + 1), 3s); break; case 14: - { - Position homePos = _owner.GetHomePosition(); - _owner.SetReactState(REACT_PASSIVE); - _owner.SetImmuneToAll(true); - _owner.SetVisible(false); - _owner.UpdatePosition(homePos.GetPositionX(), homePos.GetPositionY(), homePos.GetPositionZ(), homePos.GetOrientation(), true); - _owner.StopMovingOnCurrentPos(); - _owner.GetMotionMaster()->Clear(); - if (InstanceScript* instance = _owner.GetInstanceScript()) - instance->SetData(DATA_BATTERED_HILT, 9); - } + { + Position homePos = _owner.GetHomePosition(); + _owner.SetReactState(REACT_PASSIVE); + _owner.SetImmuneToAll(true); + _owner.SetVisible(false); + _owner.UpdatePosition(homePos.GetPositionX(), homePos.GetPositionY(), homePos.GetPositionZ(), homePos.GetOrientation(), true); + _owner.StopMovingOnCurrentPos(); + _owner.GetMotionMaster()->Clear(); + if (InstanceScript* inst = _owner.GetInstanceScript()) + inst->SetData(DATA_BATTERED_HILT, 9); break; + } } return true; } @@ -127,1073 +121,1012 @@ private: uint8 _eventId; }; -class instance_halls_of_reflection : public InstanceMapScript +ObjectData const creatureData[] = +{ + { NPC_FALRIC, DATA_FALRIC }, + { NPC_MARWYN, DATA_MARWYN }, + { NPC_SYLVANAS_PART1, NPC_SYLVANAS_PART1 }, + { NPC_DARK_RANGER_LORALEN, NPC_DARK_RANGER_LORALEN }, + { NPC_UTHER, NPC_UTHER }, + { NPC_LICH_KING_EVENT, NPC_LICH_KING_EVENT }, + { NPC_FROSTSWORN_GENERAL, NPC_FROSTSWORN_GENERAL }, + { NPC_LICH_KING_BOSS, NPC_LICH_KING_BOSS }, + { NPC_SYLVANAS_PART2, NPC_SYLVANAS_PART2 }, + { NPC_ALTAR_BUNNY, NPC_ALTAR_BUNNY }, + { NPC_QUEL_DELAR, NPC_QUEL_DELAR }, + { NPC_HIGH_CAPTAIN_JUSTIN_BARLETT, DATA_SHIP_CAPTAIN }, + { NPC_SKY_REAVER_KORM_BLACKSKAR, DATA_SHIP_CAPTAIN }, + { 0, 0 } +}; + +ObjectData const gameObjectData[] = +{ + { GO_FROSTMOURNE, GO_FROSTMOURNE }, + { GO_FROSTMOURNE_ALTAR, GO_FROSTMOURNE_ALTAR }, + { GO_FRONT_DOOR, GO_FRONT_DOOR }, + { GO_ARTHAS_DOOR, GO_ARTHAS_DOOR }, + { GO_CAVE_IN, GO_CAVE_IN }, + { GO_DOOR_BEFORE_THRONE, GO_DOOR_BEFORE_THRONE }, + { GO_DOOR_AFTER_THRONE, GO_DOOR_AFTER_THRONE }, + { GO_ICE_WALL, GO_ICE_WALL }, + { 0, 0 } +}; + +class instance_halls_of_reflection : public InstanceScript { public: - instance_halls_of_reflection() : InstanceMapScript("instance_halls_of_reflection", MAP_HALLS_OF_REFLECTION) { } - - InstanceScript* GetInstanceScript(InstanceMap* pMap) const override + instance_halls_of_reflection(Map* map) : InstanceScript(map) { - return new instance_halls_of_reflection_InstanceMapScript(pMap); + SetHeaders(DataHeader); + SetBossNumber(MAX_ENCOUNTER); + SetPersistentDataCount(PERSISTENT_DATA_COUNT); + LoadObjectData(creatureData, gameObjectData); } - struct instance_halls_of_reflection_InstanceMapScript : public InstanceScript + void Initialize() override { - instance_halls_of_reflection_InstanceMapScript(Map* pMap) : InstanceScript(pMap) + std::fill(std::begin(_trashActive), std::end(_trashActive), false); + _trashCounter = 0; + std::memset(&_chosenComposition, 0, sizeof(_chosenComposition)); + _waveNumber = 0; + _nextWaveTimer = 0; + _playerCheckTimer = 5000; + _waveResumeTimer = 0; + _waveResumeStep = 0; + _falricPhaseComplete = false; + _remainingTrashKills = 0; + _isLichKingFightActive = false; + _batteredHiltStatus = 0; + _outroTimer = 0; + _outroStep = 0; + _transport = nullptr; + } + + bool IsEncounterInProgress() const override + { + return (instance->HavePlayers() && _waveNumber) || _isLichKingFightActive; + } + + void OnCreatureCreate(Creature* creature) override + { + InstanceScript::OnCreatureCreate(creature); + + switch (creature->GetEntry()) { - SetHeaders(DataHeader); - }; - - uint32 EncounterMask; - ObjectGuid NPC_FalricGUID; - ObjectGuid NPC_MarwynGUID; - ObjectGuid NPC_LichKingIntroGUID; - ObjectGuid NPC_LeaderIntroGUID; - ObjectGuid NPC_GuardGUID; - ObjectGuid NPC_UtherGUID; - ObjectGuid NPC_LichKingGUID; - ObjectGuid NPC_LeaderGUID; - ObjectGuid NPC_IceWallTargetGUID[4]; - ObjectGuid NPC_AltarBunnyGUID; - ObjectGuid NPC_QuelDelarGUID; - ObjectGuid NPC_ShipCaptainGUID; - ObjectGuid GO_FrostmourneGUID; - ObjectGuid GO_FrostmourneAltarGUID; - ObjectGuid GO_FrontDoorGUID; - ObjectGuid GO_ArthasDoorGUID; - ObjectGuid GO_CaveInGUID; - ObjectGuid GO_DoorBeforeThroneGUID; - ObjectGuid GO_DoorAfterThroneGUID; - ObjectGuid GO_IceWallGUID; - - ObjectGuid NPC_TrashGUID[NUM_OF_TRASH]; - bool TrashActive[NUM_OF_TRASH]; - uint8 TrashCounter; - uint32 chosenComposition[8][5]; - uint8 WaveNumber; - uint32 NextWaveTimer; - uint16 CheckPlayersTimer; - uint16 ResumeFirstEventTimer; - uint8 ResumeFirstEventStep; - bool bFinished5Waves; - uint8 reqKillCount; - bool IsDuringLKFight; - uint32 BatteredHiltStatus; - - ObjectGuid NPC_FrostswornGeneralGUID; - ObjectGuid NPC_SpiritualReflectionGUID[5]; - - uint32 outroTimer; - uint8 outroStep; - MotionTransport* T1; - - void Initialize() override - { - EncounterMask = 0; - memset(&TrashActive, 0, sizeof(TrashActive)); - TrashCounter = 0; - memset(&chosenComposition, 0, sizeof(chosenComposition)); - WaveNumber = 0; - NextWaveTimer = 0; - CheckPlayersTimer = 5000; - ResumeFirstEventTimer = 0; - ResumeFirstEventStep = 0; - bFinished5Waves = false; - reqKillCount = 0; - IsDuringLKFight = false; - BatteredHiltStatus = 0; - - outroTimer = 0; - outroStep = 0; - T1 = nullptr; - } - - bool IsEncounterInProgress() const override - { - return (instance->HavePlayers() && WaveNumber) || IsDuringLKFight; // during LK fight npcs are active and will unset this variable - } - - void OnCreatureCreate(Creature* creature) override - { - switch (creature->GetEntry()) - { - case NPC_SYLVANAS_PART1: + case NPC_SYLVANAS_PART1: + creature->SetVisible(false); + creature->SetSpeed(MOVE_RUN, 1.1); + if (GetTeamIdInInstance() == TEAM_ALLIANCE) + creature->UpdateEntry(NPC_JAINA_PART1); + creature->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); + creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + break; + case NPC_DARK_RANGER_LORALEN: + creature->SetVisible(false); + if (GetTeamIdInInstance() == TEAM_ALLIANCE) + creature->UpdateEntry(NPC_ARCHMAGE_KORELN); + break; + case NPC_UTHER: + creature->SetVisible(false); + creature->SetReactState(REACT_PASSIVE); + break; + case NPC_LICH_KING_EVENT: + creature->SetVisible(false); + creature->SetReactState(REACT_PASSIVE); + creature->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + break; + case NPC_FALRIC: + creature->SetVisible(false); + creature->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + break; + case NPC_MARWYN: + creature->SetVisible(false); + creature->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + break; + case NPC_WAVE_MERCENARY: + case NPC_WAVE_FOOTMAN: + case NPC_WAVE_RIFLEMAN: + case NPC_WAVE_PRIEST: + case NPC_WAVE_MAGE: + if (_trashCounter < NUM_OF_TRASH) + _trashGUID[_trashCounter++] = creature->GetGUID(); + if (GetBossState(DATA_MARWYN) != DONE && !creature->IsAlive()) + creature->Respawn(); + creature->SetVisible(false); + break; + case NPC_FROSTSWORN_GENERAL: + if (GetBossState(DATA_MARWYN) != DONE) + { creature->SetVisible(false); - creature->SetSpeed(MOVE_RUN, 1.1); - NPC_LeaderIntroGUID = creature->GetGUID(); - if (GetTeamIdInInstance() == TEAM_ALLIANCE) - creature->UpdateEntry(NPC_JAINA_PART1); + creature->SetReactState(REACT_PASSIVE); + } + break; + case NPC_SPIRITUAL_REFLECTION: + for (uint8 i = 0; i < MAX_SPIRITUAL_REFLECTIONS; ++i) + if (!_spiritualReflectionGUID[i]) + { + _spiritualReflectionGUID[i] = creature->GetGUID(); + break; + } + creature->SetVisible(false); + break; + case NPC_LICH_KING_BOSS: + if (!GetPersistentData(PERSISTENT_DATA_FROSTSWORN_GENERAL)) + creature->SetVisible(false); + if (!GetPersistentData(PERSISTENT_DATA_LK_INTRO)) + { + creature->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_ATTACK2HTIGHT); + creature->CastSpell(creature, SPELL_SOUL_REAPER, true); + } + else if (GetBossState(DATA_LICH_KING) != DONE) + creature->AddAura(GetTeamIdInInstance() == TEAM_ALLIANCE ? SPELL_JAINA_ICE_PRISON : SPELL_SYLVANAS_DARK_BINDING, creature); + else + creature->SetVisible(false); + + creature->SetHealth((creature->GetMaxHealth() * 3) / 4); + creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + break; + case NPC_SYLVANAS_PART2: + if (!creature->IsAlive()) + creature->Respawn(); + creature->SetWalk(false); + creature->SetSheath(SHEATH_STATE_MELEE); + if (GetTeamIdInInstance() == TEAM_ALLIANCE) + creature->UpdateEntry(NPC_JAINA_PART2); + creature->SetHealth(creature->GetMaxHealth() / 20); + if (!GetPersistentData(PERSISTENT_DATA_FROSTSWORN_GENERAL)) + creature->SetVisible(false); + if (!GetPersistentData(PERSISTENT_DATA_LK_INTRO)) + { + creature->SetSheath(SHEATH_STATE_MELEE); + creature->SetUInt32Value(UNIT_NPC_EMOTESTATE, GetTeamIdInInstance() == TEAM_ALLIANCE ? EMOTE_ONESHOT_ATTACK2HTIGHT : EMOTE_ONESHOT_ATTACK1H); creature->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); - creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + creature->CastSpell(creature, GetTeamIdInInstance() == TEAM_ALLIANCE ? SPELL_JAINA_ICE_BARRIER : SPELL_SYLVANAS_CLOAK_OF_DARKNESS, true); + } + else if (GetBossState(DATA_LICH_KING) != DONE) + { + creature->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); + creature->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + creature->UpdatePosition(LeaderEscapePos, true); + creature->StopMovingOnCurrentPos(); + } + else + { + creature->UpdatePosition(PathWaypoints[PATH_WP_COUNT - 1], true); + creature->StopMovingOnCurrentPos(); + } + creature->SetSheath(SHEATH_STATE_MELEE); + creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + break; + case NPC_ICE_WALL_TARGET: + if (creature->GetPositionX() > 5525.0f) + _iceWallTargetGUID[0] = creature->GetGUID(); + else if (creature->GetPositionX() > 5475.0f) + _iceWallTargetGUID[1] = creature->GetGUID(); + else if (creature->GetPositionX() > 5400.0f) + _iceWallTargetGUID[2] = creature->GetGUID(); + else + _iceWallTargetGUID[3] = creature->GetGUID(); + break; + case NPC_QUEL_DELAR: + creature->SetReactState(REACT_PASSIVE); + break; + } + } + + void OnGameObjectCreate(GameObject* go) override + { + switch (go->GetEntry()) + { + case GO_FROSTMOURNE: + HandleGameObject(ObjectGuid::Empty, false, go); + if (GetPersistentData(PERSISTENT_DATA_INTRO)) + go->SetPhaseMask(2, true); + break; + case GO_FRONT_DOOR: + HandleGameObject(ObjectGuid::Empty, true, go); + break; + case GO_ARTHAS_DOOR: + HandleGameObject(ObjectGuid::Empty, GetBossState(DATA_MARWYN) == DONE, go); + break; + } + + InstanceScript::OnGameObjectCreate(go); + } + + bool SetBossState(uint32 type, EncounterState state) override + { + if (!InstanceScript::SetBossState(type, state)) + return false; + + switch (type) + { + case DATA_FALRIC: + if (_waveNumber) + { + if (state == NOT_STARTED) + HandleWaveWipe(); + else if (state == DONE) + _nextWaveTimer = 60000; + } + break; + case DATA_MARWYN: + if (_waveNumber) + { + if (state == NOT_STARTED) + HandleWaveWipe(); + else if (state == DONE) + { + HandleGameObject(GO_FRONT_DOOR, true); + HandleGameObject(GO_ARTHAS_DOOR, true); + if (Creature* general = GetCreature(NPC_FROSTSWORN_GENERAL)) + { + general->SetVisible(true); + general->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + general->SetReactState(REACT_AGGRESSIVE); + general->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE); + } + _waveNumber = 0; + DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVES_ENABLED, 0); + + instance->DoForAllPlayers([](Player* player) + { + player->CastSpell(player, player->GetTeamId() == TEAM_ALLIANCE ? SPELL_HOR_START_QUEST_ALLY : SPELL_HOR_START_QUEST_HORDE, true); + }); + } + } + break; + case DATA_LICH_KING: + if (state == DONE) + { + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + leader->setActive(false); + if (Creature* lichKing = GetCreature(NPC_LICH_KING_BOSS)) + lichKing->setActive(false); + _isLichKingFightActive = false; + _outroStep = 1; + _outroTimer = 0; + } + break; + } + + return true; + } + + void SetData(uint32 type, uint32 data) override + { + switch (type) + { + case DATA_INTRO: + StorePersistentData(PERSISTENT_DATA_INTRO, 1); + StartNextWave(); + break; + case ACTION_SHOW_TRASH: + RandomizeCompositionsAndShow(); + break; + case DATA_FROSTSWORN_GENERAL: + StorePersistentData(PERSISTENT_DATA_FROSTSWORN_GENERAL, 1); + if (data == DONE) + { + if (Creature* lichKing = GetCreature(NPC_LICH_KING_BOSS)) + lichKing->SetVisible(true); + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + leader->SetVisible(true); + } + break; + case ACTION_SPIRITUAL_REFLECTIONS_COPY: + { + uint8 reflectionIdx = 0; + instance->DoForAllPlayers([&](Player* player) + { + if (reflectionIdx >= MAX_SPIRITUAL_REFLECTIONS || !player->IsAlive() || player->IsGameMaster()) + return; + Creature* reflection = instance->GetCreature(_spiritualReflectionGUID[reflectionIdx++]); + if (!reflection) + return; + if (!reflection->IsAlive()) + reflection->Respawn(); + reflection->SetCanFly(true); + reflection->SetDisableGravity(true); + reflection->SetVisible(true); + + Item* weapon = player->GetWeaponForAttack(BASE_ATTACK); + reflection->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID, weapon ? weapon->GetEntry() : 0); + weapon = player->GetWeaponForAttack(OFF_ATTACK); + reflection->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, weapon ? weapon->GetEntry() : 0); + weapon = player->GetWeaponForAttack(RANGED_ATTACK); + reflection->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, weapon ? weapon->GetEntry() : 0); + player->CastSpell(reflection, SPELL_HOR_CLONE, true); + player->CastSpell(reflection, SPELL_HOR_CLONE_NAME, true); + }); + break; + } + case ACTION_SPIRITUAL_REFLECTIONS_ACTIVATE: + { + Creature* general = GetCreature(NPC_FROSTSWORN_GENERAL); + if (!general) break; - case NPC_DARK_RANGER_LORALEN: - creature->SetVisible(false); - NPC_GuardGUID = creature->GetGUID(); - if (GetTeamIdInInstance() == TEAM_ALLIANCE) - creature->UpdateEntry(NPC_ARCHMAGE_KORELN); - break; - case NPC_UTHER: - creature->SetVisible(false); - NPC_UtherGUID = creature->GetGUID(); - creature->SetReactState(REACT_PASSIVE); - break; - case NPC_LICH_KING_EVENT: - creature->SetVisible(false); - NPC_LichKingIntroGUID = creature->GetGUID(); - creature->SetReactState(REACT_PASSIVE); - creature->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); - break; - case NPC_FALRIC: - creature->SetVisible(false); - NPC_FalricGUID = creature->GetGUID(); - creature->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); - break; - case NPC_MARWYN: - creature->SetVisible(false); - NPC_MarwynGUID = creature->GetGUID(); - creature->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + for (uint8 i = 0; i < MAX_SPIRITUAL_REFLECTIONS; ++i) + if (Creature* reflection = instance->GetCreature(_spiritualReflectionGUID[i])) + if (reflection->IsVisible()) + { + reflection->SetInCombatWithZone(); + reflection->SetCanFly(false); + reflection->SetDisableGravity(false); + reflection->GetMotionMaster()->MoveJump(general->GetPositionX(), general->GetPositionY(), general->GetPositionZ(), 20.0f, 10.0f); + } + break; + } + case ACTION_SPIRITUAL_REFLECTIONS_HIDE: + for (uint8 i = 0; i < MAX_SPIRITUAL_REFLECTIONS; ++i) + if (Creature* reflection = instance->GetCreature(_spiritualReflectionGUID[i])) + reflection->AI()->EnterEvadeMode(); + break; + case DATA_LK_INTRO: + StorePersistentData(PERSISTENT_DATA_LK_INTRO, 1); + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + leader->AI()->DoAction(ACTION_START_INTRO); + break; + case ACTION_START_LK_FIGHT: + _isLichKingFightActive = true; + DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_RETREATING_TIMED_EVENT); + DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_RETREATING_TIMED_EVENT); + break; + case ACTION_STOP_LK_FIGHT: + if (!_isLichKingFightActive) break; + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + { + if (!leader->IsAlive()) + { + leader->Respawn(); + if (GetTeamIdInInstance() == TEAM_ALLIANCE) + leader->UpdateEntry(NPC_JAINA_PART2); + } + leader->GetThreatMgr().ClearAllThreat(); + leader->CombatStop(true); + leader->InterruptNonMeleeSpells(true); + leader->GetMotionMaster()->Clear(); + leader->GetMotionMaster()->MoveIdle(); + leader->UpdatePosition(LeaderEscapePos, true); + leader->StopMovingOnCurrentPos(); + leader->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); + leader->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + leader->SetHealth(leader->GetMaxHealth() / 20); + leader->AI()->Reset(); + leader->setActive(false); + leader->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + leader->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + } + if (Creature* lichKing = GetCreature(NPC_LICH_KING_BOSS)) + { + lichKing->GetThreatMgr().ClearAllThreat(); + lichKing->CombatStop(true); + lichKing->InterruptNonMeleeSpells(true); + lichKing->GetMotionMaster()->Clear(); + lichKing->GetMotionMaster()->MoveIdle(); + lichKing->UpdatePosition(lichKing->GetHomePosition(), true); + lichKing->StopMovingOnCurrentPos(); + lichKing->RemoveAllAuras(); + lichKing->AddAura(GetTeamIdInInstance() == TEAM_ALLIANCE ? SPELL_JAINA_ICE_PRISON : SPELL_SYLVANAS_DARK_BINDING, lichKing); + lichKing->AI()->Reset(); + lichKing->setActive(false); + lichKing->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + lichKing->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + lichKing->SetSpeed(MOVE_RUN, lichKing->GetCreatureTemplate()->speed_run); + } + _isLichKingFightActive = false; + _outroTimer = 0; + _outroStep = 0; + [[fallthrough]]; + case ACTION_DELETE_ICE_WALL: + HandleGameObject(GO_ICE_WALL, true); + break; + case DATA_LICH_KING: + SetBossState(DATA_LICH_KING, static_cast(data)); + break; + case DATA_BATTERED_HILT: + { + if (GetPersistentData(PERSISTENT_DATA_BATTERED_HILT)) + return; + + switch (data) + { + case 1: + StorePersistentData(PERSISTENT_DATA_BATTERED_HILT, 1); + break; + case 2: + if (_batteredHiltStatus) + break; + _batteredHiltStatus |= BHSF_STARTED; + if (Creature* bunny = GetCreature(NPC_ALTAR_BUNNY)) + bunny->CastSpell(bunny, SPELL_FROSTMOURNE_ALTAR_GLOW, true); + if (Creature* uther = GetCreature(NPC_UTHER)) + uther->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*uther, 1), 3s); + break; + case 3: + if (!(_batteredHiltStatus & BHSF_STARTED) || (_batteredHiltStatus & BHSF_THROWN)) + break; + _batteredHiltStatus |= BHSF_THROWN; + if (Creature* uther = GetCreature(NPC_UTHER)) + uther->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*uther, 3), 5500ms); + break; + case 4: + if (Creature* quelDelar = GetCreature(NPC_QUEL_DELAR)) + { + quelDelar->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + quelDelar->SetSpeed(MOVE_RUN, 2.5f); + } + break; + case 5: + if (Creature* uther = GetCreature(NPC_UTHER)) + uther->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*uther, 6), 3s); + break; + case 6: + if (Creature* quelDelar = GetCreature(NPC_QUEL_DELAR)) + { + quelDelar->SetSpeed(MOVE_RUN, quelDelar->GetCreatureTemplate()->speed_run); + quelDelar->GetMotionMaster()->MoveLand(0, quelDelar->GetPositionX(), quelDelar->GetPositionY(), 707.70f, 7.0f); + } + break; + case 7: + if (Creature* quelDelar = GetCreature(NPC_QUEL_DELAR)) + { + quelDelar->SetReactState(REACT_AGGRESSIVE); + quelDelar->SetImmuneToAll(false); + quelDelar->RemoveAurasDueToSpell(SPELL_QUEL_DELAR_HATRED); + } + break; + case 8: + if (Creature* quelDelar = GetCreature(NPC_QUEL_DELAR)) + quelDelar->SetInCombatWithZone(); + break; + case 9: + StorePersistentData(PERSISTENT_DATA_BATTERED_HILT, 1); + _batteredHiltStatus |= BHSF_FINISHED; + break; + } + return; + } + } + } + + uint32 GetData(uint32 type) const override + { + switch (type) + { + case DATA_WAVE_NUMBER: + return static_cast(_waveNumber); + case DATA_BATTERED_HILT: + return _batteredHiltStatus; + } + + return 0; + } + + ObjectGuid GetGuidData(uint32 type) const override + { + switch (type) + { + case NPC_ICE_WALL_TARGET: + case NPC_ICE_WALL_TARGET + 1: + case NPC_ICE_WALL_TARGET + 2: + case NPC_ICE_WALL_TARGET + 3: + return _iceWallTargetGUID[type - NPC_ICE_WALL_TARGET]; + default: + return GetObjectGuid(type); + } + } + + void OnUnitDeath(Unit* unit) override + { + if (_waveNumber && _remainingTrashKills) + { + switch (unit->GetEntry()) + { case NPC_WAVE_MERCENARY: case NPC_WAVE_FOOTMAN: case NPC_WAVE_RIFLEMAN: case NPC_WAVE_PRIEST: case NPC_WAVE_MAGE: - if (TrashCounter < NUM_OF_TRASH) - NPC_TrashGUID[TrashCounter++] = creature->GetGUID(); - if (!(EncounterMask & (1 << DATA_MARWYN)) && !creature->IsAlive()) - creature->Respawn(); - creature->SetVisible(false); - break; - case NPC_FROSTSWORN_GENERAL: - if (!(EncounterMask & (1 << DATA_MARWYN))) - { - creature->SetVisible(false); - creature->SetReactState(REACT_PASSIVE); - } - NPC_FrostswornGeneralGUID = creature->GetGUID(); - break; - case NPC_SPIRITUAL_REFLECTION: - for (uint8 i = 0; i < 5; ++i) - if (!NPC_SpiritualReflectionGUID[i]) - { - NPC_SpiritualReflectionGUID[i] = creature->GetGUID(); - break; - } - creature->SetVisible(false); - break; - case NPC_LICH_KING_BOSS: - if (!(EncounterMask & (1 << DATA_FROSTSWORN_GENERAL))) - creature->SetVisible(false); - if (!(EncounterMask & (1 << DATA_LK_INTRO))) - { - creature->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_ATTACK2HTIGHT); //the fight cannot be in the form of an emote, it is causing bugs - creature->CastSpell(creature, SPELL_SOUL_REAPER, true); - } - else if (!(EncounterMask & (1 << DATA_LICH_KING))) - creature->AddAura(GetTeamIdInInstance() == TEAM_ALLIANCE ? SPELL_JAINA_ICE_PRISON : SPELL_SYLVANAS_DARK_BINDING, creature); - else - creature->SetVisible(false); - - NPC_LichKingGUID = creature->GetGUID(); - creature->SetHealth((creature->GetMaxHealth() * 3) / 4); - creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); - break; - case NPC_SYLVANAS_PART2: - if (!creature->IsAlive()) - creature->Respawn(); - NPC_LeaderGUID = creature->GetGUID(); - creature->SetWalk(false); - creature->SetSheath(SHEATH_STATE_MELEE); - if (GetTeamIdInInstance() == TEAM_ALLIANCE) - creature->UpdateEntry(NPC_JAINA_PART2); - creature->SetWalk(false); - creature->SetHealth(creature->GetMaxHealth() / 20); - if (!(EncounterMask & (1 << DATA_FROSTSWORN_GENERAL))) - creature->SetVisible(false); - if (!(EncounterMask & (1 << DATA_LK_INTRO))) - { - creature->SetSheath(SHEATH_STATE_MELEE); - creature->SetUInt32Value(UNIT_NPC_EMOTESTATE, GetTeamIdInInstance() == TEAM_ALLIANCE ? EMOTE_ONESHOT_ATTACK2HTIGHT : EMOTE_ONESHOT_ATTACK1H); //the fight cannot be in the form of an emote, it is causing bugs. - creature->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); - creature->CastSpell(creature, GetTeamIdInInstance() == TEAM_ALLIANCE ? SPELL_JAINA_ICE_BARRIER : SPELL_SYLVANAS_CLOAK_OF_DARKNESS, true); - } - else if (!(EncounterMask & (1 << DATA_LICH_KING))) - { - creature->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); - creature->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); - creature->UpdatePosition(LeaderEscapePos, true); - creature->StopMovingOnCurrentPos(); - } - else - { - creature->UpdatePosition(PathWaypoints[PATH_WP_COUNT - 1], true); - creature->StopMovingOnCurrentPos(); - } - creature->SetSheath(SHEATH_STATE_MELEE); - creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); - break; - case NPC_ICE_WALL_TARGET: - if (creature->GetPositionX() > 5525.0f) - NPC_IceWallTargetGUID[0] = creature->GetGUID(); - else if (creature->GetPositionX() > 5475.0f) - NPC_IceWallTargetGUID[1] = creature->GetGUID(); - else if (creature->GetPositionX() > 5400.0f) - NPC_IceWallTargetGUID[2] = creature->GetGUID(); - else - NPC_IceWallTargetGUID[3] = creature->GetGUID(); - break; - case NPC_ALTAR_BUNNY: - NPC_AltarBunnyGUID = creature->GetGUID(); - break; - case NPC_QUEL_DELAR: - NPC_QuelDelarGUID = creature->GetGUID(); - creature->SetReactState(REACT_PASSIVE); - break; - case NPC_HIGH_CAPTAIN_JUSTIN_BARLETT: - case NPC_SKY_REAVER_KORM_BLACKSKAR: - NPC_ShipCaptainGUID = creature->GetGUID(); + if ((--_remainingTrashKills) == 0 && _waveNumber % 5 && _nextWaveTimer > 5000) + _nextWaveTimer = 5000; break; } } - void OnGameObjectCreate(GameObject* go) override - { - switch (go->GetEntry()) + if (unit->GetEntry() == NPC_QUEL_DELAR) + if (Creature* uther = GetCreature(NPC_UTHER)) { - case GO_FROSTMOURNE: - GO_FrostmourneGUID = go->GetGUID(); - HandleGameObject(ObjectGuid::Empty, false, go); - if (EncounterMask & (1 << DATA_INTRO)) - go->SetPhaseMask(2, true); - break; - case GO_FROSTMOURNE_ALTAR: - GO_FrostmourneAltarGUID = go->GetGUID(); - break; - case GO_FRONT_DOOR: - GO_FrontDoorGUID = go->GetGUID(); - HandleGameObject(ObjectGuid::Empty, true, go); - break; - case GO_ARTHAS_DOOR: - GO_ArthasDoorGUID = go->GetGUID(); - HandleGameObject(ObjectGuid::Empty, (EncounterMask & (1 << DATA_MARWYN)), go); - break; - case GO_CAVE_IN: - GO_CaveInGUID = go->GetGUID(); - break; - case GO_DOOR_BEFORE_THRONE: - GO_DoorBeforeThroneGUID = go->GetGUID(); - break; - case GO_DOOR_AFTER_THRONE: - GO_DoorAfterThroneGUID = go->GetGUID(); - break; - case GO_ICE_WALL: - GO_IceWallGUID = go->GetGUID(); - break; + uther->SetStandState(UNIT_STAND_STATE_STAND); + uther->SetWalk(false); + uther->GetMotionMaster()->MovePoint(0, 5313.92f, 1989.36f, 707.70f); + uther->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*uther, 9), 7s); } - } + } - void SetData(uint32 type, uint32 data) override + void RandomizeCompositionsAndShow() + { + uint8 r1 = urand(0, 1); + uint8 r2 = urand(2, 3); + for (uint8 i = 0; i < MAX_SPIRITUAL_REFLECTIONS; ++i) { - switch (type) - { - case DATA_INTRO: - EncounterMask |= (1 << DATA_INTRO); - AddWave1(); - break; - case ACTION_SHOW_TRASH: - RandomizeCompositionsAndShow(); - break; - case DATA_FALRIC: - if (WaveNumber) - { - if (data == NOT_STARTED) - DoWipe1(); - else if (data == DONE) - { - NextWaveTimer = 60000; - EncounterMask |= (1 << DATA_FALRIC); - } - } - break; - case DATA_MARWYN: - if (WaveNumber) - { - if (data == NOT_STARTED) - DoWipe1(); - else if (data == DONE) - { - EncounterMask |= (1 << DATA_MARWYN); - HandleGameObject(GO_FrontDoorGUID, true); - HandleGameObject(GO_ArthasDoorGUID, true); - if (Creature* c = instance->GetCreature(NPC_FrostswornGeneralGUID)) - { - c->SetVisible(true); - c->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - c->SetReactState(REACT_AGGRESSIVE); - } - if (Creature* c = instance->GetCreature(NPC_FrostswornGeneralGUID)) - { - c->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE); - } - WaveNumber = 0; - DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVES_ENABLED, 0); - - // give quest - Map::PlayerList const& pl = instance->GetPlayers(); - for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - if (Player* p = itr->GetSource()) - p->CastSpell(p, p->GetTeamId() == TEAM_ALLIANCE ? SPELL_HOR_START_QUEST_ALLY : SPELL_HOR_START_QUEST_HORDE, true); - } - } - break; - case DATA_FROSTSWORN_GENERAL: - EncounterMask |= (1 << DATA_FROSTSWORN_GENERAL); - if (data == DONE) - { - if (Creature* c = instance->GetCreature(NPC_LichKingGUID)) - c->SetVisible(true); - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - c->SetVisible(true); - } - break; - case ACTION_SPIRITUAL_REFLECTIONS_COPY: - { - uint8 i = 0; - Map::PlayerList const& pl = instance->GetPlayers(); - for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - if (Player* p = itr->GetSource()) - if (p->IsAlive() && !p->IsGameMaster()) - if (Creature* c = instance->GetCreature(NPC_SpiritualReflectionGUID[i++])) - { - if (!c->IsAlive()) - c->Respawn(); - c->SetCanFly(true); - c->SetDisableGravity(true); - c->SetVisible(true); - - Item* i; - i = p->GetWeaponForAttack(BASE_ATTACK); - c->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, i ? i->GetEntry() : 0); - i = p->GetWeaponForAttack(OFF_ATTACK); - c->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, i ? i->GetEntry() : 0); - i = p->GetWeaponForAttack(RANGED_ATTACK); - c->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, i ? i->GetEntry() : 0); - p->CastSpell(c, SPELL_HOR_CLONE, true); - p->CastSpell(c, SPELL_HOR_CLONE_NAME, true); - } - } - break; - case ACTION_SPIRITUAL_REFLECTIONS_ACTIVATE: - if (Creature* fg = instance->GetCreature(NPC_FrostswornGeneralGUID)) - for (uint8 i = 0; i < 5; ++i) - if (Creature* c = instance->GetCreature(NPC_SpiritualReflectionGUID[i])) - if (c->IsVisible()) - { - c->SetInCombatWithZone(); - c->SetCanFly(false); - c->SetDisableGravity(false); - c->GetMotionMaster()->MoveJump(fg->GetPositionX(), fg->GetPositionY(), fg->GetPositionZ(), 20.0f, 10.0f); - } - break; - case ACTION_SPIRITUAL_REFLECTIONS_HIDE: - for (uint8 i = 0; i < 5; ++i) - if (Creature* c = instance->GetCreature(NPC_SpiritualReflectionGUID[i])) - c->AI()->EnterEvadeMode(); - break; - case DATA_LK_INTRO: - EncounterMask |= (1 << DATA_LK_INTRO); - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - c->AI()->DoAction(ACTION_START_INTRO); - break; - case ACTION_START_LK_FIGHT: - IsDuringLKFight = true; - DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_RETREATING_TIMED_EVENT); - DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_RETREATING_TIMED_EVENT); - break; - case ACTION_STOP_LK_FIGHT: - if (!IsDuringLKFight) + _chosenComposition[0][i] = allowedCompositions[r1][i]; + _chosenComposition[1][i] = allowedCompositions[r1 == 0 ? 1 : 0][i]; + _chosenComposition[2][i] = allowedCompositions[r2][i]; + _chosenComposition[3][i] = allowedCompositions[r2 == 2 ? 3 : 2][i]; + } + bool left[4] = {true, true, true, true}; + for (uint8 k = 4; k > 0; --k) + { + uint8 r = urand(0, k - 1); + uint8 ur = 0; + for (uint8 j = 0; j < 4; ++j) + if (left[j]) + { + if (ur == r) { + left[j] = false; + for (uint8 i = 0; i < MAX_SPIRITUAL_REFLECTIONS; ++i) + _chosenComposition[8 - k][i] = allowedCompositions[j + 4][i]; break; } - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - { - if (!c->IsAlive()) - { - c->Respawn(); - if (GetTeamIdInInstance() == TEAM_ALLIANCE) - c->UpdateEntry(NPC_JAINA_PART2); - } - c->GetThreatMgr().ClearAllThreat(); - c->CombatStop(true); - c->InterruptNonMeleeSpells(true); - c->GetMotionMaster()->Clear(); - c->GetMotionMaster()->MoveIdle(); - c->UpdatePosition(LeaderEscapePos, true); - c->StopMovingOnCurrentPos(); - c->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); - c->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); - c->SetHealth(c->GetMaxHealth() / 20); - c->AI()->Reset(); - c->setActive(false); - c->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); - c->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - } - if (Creature* c = instance->GetCreature(NPC_LichKingGUID)) - { - c->GetThreatMgr().ClearAllThreat(); - c->CombatStop(true); - c->InterruptNonMeleeSpells(true); - c->GetMotionMaster()->Clear(); - c->GetMotionMaster()->MoveIdle(); - c->UpdatePosition(c->GetHomePosition(), true); - c->StopMovingOnCurrentPos(); - c->RemoveAllAuras(); - c->AddAura(GetTeamIdInInstance() == TEAM_ALLIANCE ? SPELL_JAINA_ICE_PRISON : SPELL_SYLVANAS_DARK_BINDING, c); - c->AI()->Reset(); - c->setActive(false); - c->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); - c->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - c->SetSpeed(MOVE_RUN, c->GetCreatureTemplate()->speed_run); - } - IsDuringLKFight = false; - outroTimer = 0; - outroStep = 0; - [[fallthrough]]; - case ACTION_DELETE_ICE_WALL: - HandleGameObject(GO_IceWallGUID, true); - GO_IceWallGUID.Clear(); - break; - case DATA_LICH_KING: - if (data == DONE) - { - EncounterMask |= (1 << DATA_LICH_KING); - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - c->setActive(false); - if (Creature* c = instance->GetCreature(NPC_LichKingGUID)) - c->setActive(false); - IsDuringLKFight = false; - outroStep = 1; - outroTimer = 0; - } - break; - case DATA_BATTERED_HILT: - { - if (EncounterMask & (1 << DATA_BATTERED_HILT)) - return; - - switch (data) - { - case 1: // talked to leader - EncounterMask |= (1 << DATA_BATTERED_HILT); - SaveToDB(); - break; - case 2: - if (BatteredHiltStatus) - break; - BatteredHiltStatus |= BHSF_STARTED; - if (Creature* c = instance->GetCreature(NPC_AltarBunnyGUID)) - c->CastSpell(c, 70720, true); - if (Creature* c = instance->GetCreature(NPC_UtherGUID)) - c->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*c, 1), 3s); - break; - case 3: - if ((BatteredHiltStatus & BHSF_STARTED) == 0 || (BatteredHiltStatus & BHSF_THROWN)) - break; - BatteredHiltStatus |= BHSF_THROWN; - if (Creature* c = instance->GetCreature(NPC_UtherGUID)) - { - c->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*c, 3), 5500ms); - } - break; - case 4: - if (Creature* c = instance->GetCreature(NPC_QuelDelarGUID)) - { - c->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); - c->SetSpeed(MOVE_RUN, 2.5f); - } - break; - case 5: - if (Creature* c = instance->GetCreature(NPC_UtherGUID)) - c->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*c, 6), 3s); - break; - case 6: - if (Creature* c = instance->GetCreature(NPC_QuelDelarGUID)) - { - c->SetSpeed(MOVE_RUN, c->GetCreatureTemplate()->speed_run); - c->GetMotionMaster()->MoveLand(0, c->GetPositionX(), c->GetPositionY(), 707.70f, 7.0f); - } - break; - case 7: - if (Creature* c = instance->GetCreature(NPC_QuelDelarGUID)) - { - c->SetReactState(REACT_AGGRESSIVE); - c->SetImmuneToAll(false); - c->RemoveAurasDueToSpell(70300); - } - break; - case 8: - if (Creature* c = instance->GetCreature(NPC_QuelDelarGUID)) - c->SetInCombatWithZone(); - break; - case 9: - EncounterMask |= (1 << DATA_BATTERED_HILT); - BatteredHiltStatus |= BHSF_FINISHED; - SaveToDB(); - break; - } - } - return; - } - - if (data == DONE) - SaveToDB(); - } - - uint32 GetData(uint32 type) const override - { - switch (type) - { - case DATA_INTRO: - case DATA_FALRIC: - case DATA_MARWYN: - case DATA_FROSTSWORN_GENERAL: - case DATA_LK_INTRO: - case DATA_LICH_KING: - return EncounterMask & (1 << type); - case DATA_WAVE_NUMBER: - return (uint32)WaveNumber; - case DATA_BATTERED_HILT: - return BatteredHiltStatus; - } - - return 0; - } - - ObjectGuid GetGuidData(uint32 type) const override - { - switch (type) - { - case NPC_DARK_RANGER_LORALEN: - return NPC_GuardGUID; - case NPC_LICH_KING_EVENT: - return NPC_LichKingIntroGUID; - case NPC_UTHER: - return NPC_UtherGUID; - case DATA_FALRIC: - return NPC_FalricGUID; - case DATA_MARWYN: - return NPC_MarwynGUID; - case NPC_LICH_KING_BOSS: - return NPC_LichKingGUID; - case NPC_SYLVANAS_PART2: - return NPC_LeaderGUID; - case NPC_ICE_WALL_TARGET: - case NPC_ICE_WALL_TARGET+1: - case NPC_ICE_WALL_TARGET+2: - case NPC_ICE_WALL_TARGET+3: - return NPC_IceWallTargetGUID[type - NPC_ICE_WALL_TARGET]; - case GO_FROSTMOURNE: - return GO_FrostmourneGUID; - case GO_ARTHAS_DOOR: - return GO_ArthasDoorGUID; - case GO_FRONT_DOOR: - return GO_FrontDoorGUID; - } - return ObjectGuid::Empty; - } - - void ReadSaveDataMore(std::istringstream& data) override - { - data >> EncounterMask; - BatteredHiltStatus = (EncounterMask & (1 << DATA_BATTERED_HILT)) ? BHSF_FINISHED : BHSF_NONE; - } - - void WriteSaveDataMore(std::ostringstream& data) override - { - data << EncounterMask; - } - - void OnUnitDeath(Unit* unit) override - { - if (WaveNumber && reqKillCount) - if (unit->GetEntry() == NPC_WAVE_MERCENARY || unit->GetEntry() == NPC_WAVE_FOOTMAN || unit->GetEntry() == NPC_WAVE_RIFLEMAN || unit->GetEntry() == NPC_WAVE_PRIEST || unit->GetEntry() == NPC_WAVE_MAGE) - if ((--reqKillCount) == 0 && WaveNumber % 5 && NextWaveTimer > 5000) - NextWaveTimer = 5000; - - if (unit->GetEntry() == NPC_QUEL_DELAR) - if (Creature* c = instance->GetCreature(NPC_UtherGUID)) - { - c->SetStandState(UNIT_STAND_STATE_STAND); - c->SetWalk(false); - c->GetMotionMaster()->MovePoint(0, 5313.92f, 1989.36f, 707.70f); - c->m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(*c, 9), 7s); + ++ur; } } - - void RandomizeCompositionsAndShow() + if (_falricPhaseComplete) { - uint8 r1 = urand(0, 1), r2 = urand(2, 3); - for (uint8 i = 0; i < 5; ++i) + for (; _waveNumber < 4; ++_waveNumber) { - chosenComposition[0][i] = allowedCompositions[r1][i]; - chosenComposition[1][i] = allowedCompositions[r1 == 0 ? 1 : 0][i]; - chosenComposition[2][i] = allowedCompositions[r2][i]; - chosenComposition[3][i] = allowedCompositions[r2 == 2 ? 3 : 2][i]; - } - bool left[4] = {true, true, true, true}; - for (uint8 k = 4; k > 0; --k) - { - uint8 r = urand(0, k - 1); - uint8 ur = 0; - for (uint8 j = 0; j < 4; ++j) - if (left[j]) - { - if (ur == r) - { - left[j] = false; - for (uint8 i = 0; i < 5; ++i) - chosenComposition[8 - k][i] = allowedCompositions[j + 4][i]; - break; - } - ++ur; - } - } - if (bFinished5Waves) - { - for (; WaveNumber < 4; ++WaveNumber) + uint8 numToActivate = (_waveNumber <= 1) ? 3 : 4; + + for (uint8 i = 0; i < numToActivate; ++i) { - uint8 num_to_activate; - if (WaveNumber <= 1) - num_to_activate = 3; - else - num_to_activate = 4; - - for (uint8 i = 0; i < num_to_activate; ++i) - { - uint32 entry = chosenComposition[WaveNumber][i]; - bool forward = !!urand(0, 1); - for (int8 j = (forward ? 0 : NUM_OF_TRASH - 1); (forward ? j= 0); (forward ? ++j : --j)) - if (!TrashActive[j]) - if (Creature* c = instance->GetCreature(NPC_TrashGUID[j])) - if (c->GetEntry() == entry) - { - TrashActive[j] = true; - Unit::Kill(c, c); - break; - } - } - } - WaveNumber = 5; - } - - for (uint8 i = 0; i < NUM_OF_TRASH; ++i) - if (!TrashActive[i]) - if (Creature* c = instance->GetCreature(NPC_TrashGUID[i])) - { - c->SetVisible(true); - c->CastSpell(c, SPELL_WELL_OF_SOULS_VISUAL, false); - } - } - - void AddWave1() - { - if (WaveNumber >= 10) - return; - - ++WaveNumber; - if (WaveNumber >= 6) - bFinished5Waves = true; - - DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVES_ENABLED, 1); - DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVE_COUNT, WaveNumber); - HandleGameObject(GO_FrontDoorGUID, false); - - // some of them could go back to spawn due to vanish, etc. - // on activating next wave make those attack again - for (uint8 i = 0; i < NUM_OF_TRASH; ++i) - if (TrashActive[i]) - if (Creature* c = instance->GetCreature(NPC_TrashGUID[i])) - if (c->IsAlive() && !c->IsInCombat()) - c->AI()->DoAction(1); - - if (WaveNumber == 5 || WaveNumber == 10) - { - NextWaveTimer = 0; - if (WaveNumber == 5) - { - if (Creature* falric = instance->GetCreature(NPC_FalricGUID)) - { - if (falric->IsAlive()) - falric->AI()->DoAction(1); - else - NextWaveTimer = 1; - } - } - else - { - if (Creature* marwyn = instance->GetCreature(NPC_MarwynGUID)) - if (marwyn->IsAlive()) // should always be true, but just in case - marwyn->AI()->DoAction(1); - } - } - else - { - NextWaveTimer = 150000; - - uint8 num_to_activate = 5; - if (WaveNumber <= 2) - num_to_activate = 3; - else if (WaveNumber <= 4) - num_to_activate = 4; - reqKillCount += num_to_activate; - for (uint8 i = 0; i < num_to_activate; ++i) - { - uint32 entry = chosenComposition[WaveNumber - (WaveNumber > 5 ? 2 : 1)][i]; + uint32 entry = _chosenComposition[_waveNumber][i]; bool forward = !!urand(0, 1); - for (int8 j = (forward ? 0 : NUM_OF_TRASH - 1); (forward ? j= 0); (forward ? ++j : --j)) - if (!TrashActive[j]) - if (Creature* c = instance->GetCreature(NPC_TrashGUID[j])) + for (int8 j = (forward ? 0 : NUM_OF_TRASH - 1); (forward ? j < NUM_OF_TRASH : j >= 0); (forward ? ++j : --j)) + if (!_trashActive[j]) + if (Creature* c = instance->GetCreature(_trashGUID[j])) if (c->GetEntry() == entry) { - TrashActive[j] = true; - c->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - c->SetImmuneToAll(false); - c->AI()->DoAction(1); + _trashActive[j] = true; + Unit::Kill(c, c); break; } } } + _waveNumber = 5; } - void DoWipe1() - { - if (!WaveNumber) - return; - - DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVES_ENABLED, 0); - DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVE_COUNT, 0); - HandleGameObject(GO_FrontDoorGUID, true); - - TrashCounter = NUM_OF_TRASH; - WaveNumber = 0; - NextWaveTimer = 0; - memset(&chosenComposition, 0, sizeof(chosenComposition)); - - for (uint8 i = 0; i < NUM_OF_TRASH; ++i) - if (TrashActive[i]) - if (Creature* c = instance->GetCreature(NPC_TrashGUID[i])) - { - c->GetThreatMgr().ClearAllThreat(); - c->CombatStop(true); - c->InterruptNonMeleeSpells(true); - c->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - c->SetImmuneToAll(true); - c->Respawn(true); - c->UpdatePosition(c->GetHomePosition(), true); - c->StopMovingOnCurrentPos(); - } - memset(&TrashActive, 0, sizeof(TrashActive)); - if (Creature* falric = instance->GetCreature(NPC_FalricGUID)) - falric->AI()->EnterEvadeMode(); - if (Creature* marwyn = instance->GetCreature(NPC_MarwynGUID)) - marwyn->AI()->EnterEvadeMode(); - - ResumeFirstEventTimer = 5000; - ResumeFirstEventStep = 2; - reqKillCount = 0; - } - - void Update(uint32 diff) override - { - if (!instance->HavePlayers()) - return; - - if (CheckPlayersTimer <= diff) - { - CheckPlayersTimer = 5000; - if ((EncounterMask & (1 << DATA_INTRO)) && !(EncounterMask & (1 << DATA_MARWYN))) // first event + for (uint8 i = 0; i < NUM_OF_TRASH; ++i) + if (!_trashActive[i]) + if (Creature* c = instance->GetCreature(_trashGUID[i])) { - Map::PlayerList const& pl = instance->GetPlayers(); - if (WaveNumber || NextWaveTimer) - { - bool allDead = true; - bool outOfRange = false; - for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - if (Player* p = itr->GetSource()) - { - if (p->IsGameMaster()) - continue; - if (p->IsAlive()) - allDead = false; - if (p->GetExactDist2d(&CenterPos) > MAX_DIST_FROM_CENTER_IN_COMBAT) - { - outOfRange = true; - break; - } - } - if (allDead || outOfRange) - DoWipe1(); - } - else if (!ResumeFirstEventTimer) - { - bool allInRangeAndAlive = (instance->GetPlayersCountExceptGMs() > 0); - for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - if (Player* p = itr->GetSource()) - if (!p->IsGameMaster() && (p->GetExactDist2d(&CenterPos) > MAX_DIST_FROM_CENTER_TO_START || !p->IsAlive())) - { - allInRangeAndAlive = false; - break; - } - if (allInRangeAndAlive) - { - ResumeFirstEventTimer = 1; - ResumeFirstEventStep = 0; - } - } + c->SetVisible(true); + c->CastSpell(c, SPELL_WELL_OF_SOULS_VISUAL, false); + } + } + + void StartNextWave() + { + if (_waveNumber >= 10) + return; + + ++_waveNumber; + if (_waveNumber >= 6) + _falricPhaseComplete = true; + + DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVES_ENABLED, 1); + DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVE_COUNT, _waveNumber); + HandleGameObject(GO_FRONT_DOOR, false); + + for (uint8 i = 0; i < NUM_OF_TRASH; ++i) + if (_trashActive[i]) + if (Creature* c = instance->GetCreature(_trashGUID[i])) + if (c->IsAlive() && !c->IsInCombat()) + c->AI()->DoAction(1); + + if (_waveNumber == 5 || _waveNumber == 10) + { + _nextWaveTimer = 0; + if (_waveNumber == 5) + { + if (Creature* falric = GetCreature(DATA_FALRIC)) + { + if (falric->IsAlive()) + falric->AI()->DoAction(1); + else + _nextWaveTimer = 1; } } else - CheckPlayersTimer -= diff; - - if (NextWaveTimer) { - if (NextWaveTimer <= diff) - { - NextWaveTimer = 0; - AddWave1(); - } - else - NextWaveTimer -= diff; - } - - if (ResumeFirstEventTimer) - { - if (ResumeFirstEventTimer <= diff) - { - switch (ResumeFirstEventStep) // After a wipe - { - case 0: - if (Creature* pFalric = instance->GetCreature(NPC_FalricGUID)) - { - pFalric->UpdatePosition(5274.9f, 2039.2f, 709.319f, 5.4619f, true); - pFalric->StopMovingOnCurrentPos(); - pFalric->SetVisible(true); - if (pFalric->IsAlive()) - { - pFalric->GetMotionMaster()->MovePoint(0, FalricMovePos); - if (Aura* a = pFalric->AddAura(SPELL_SHADOWMOURNE_VISUAL, pFalric)) - a->SetDuration(8000); - } - } - if (Creature* pMarwyn = instance->GetCreature(NPC_MarwynGUID)) - { - pMarwyn->UpdatePosition(5343.77f, 1973.86f, 709.319f, 2.35173f, true); - pMarwyn->StopMovingOnCurrentPos(); - pMarwyn->SetVisible(true); - if (pMarwyn->IsAlive()) - { - pMarwyn->GetMotionMaster()->MovePoint(0, MarwynMovePos); - if (Aura* a = pMarwyn->AddAura(SPELL_SHADOWMOURNE_VISUAL, pMarwyn)) - a->SetDuration(8000); - } - pMarwyn->AI()->Talk(EMOTE_MARWYN_INTRO_SPIRIT); - } - ++ResumeFirstEventStep; - ResumeFirstEventTimer = 7500; - break; - case 1: - if (Creature* pFalric = instance->GetCreature(NPC_FalricGUID)) - { - if (pFalric->IsAlive()) - { - pFalric->AI()->Talk(SAY_FALRIC_INTRO_2); // Between wave 1 and 4 - } - else - { - if (Creature* marwyn = instance->GetCreature(NPC_MarwynGUID)) - { - marwyn->AI()->Talk(SAY_MARWYN_WIPE_AFTER_FALRIC); // Between wave 6 and 9 - } - } - } - SetData(ACTION_SHOW_TRASH, 1); - ResumeFirstEventStep = 0; - ResumeFirstEventTimer = 0; - NextWaveTimer = 7000; - break; - default: - for (uint8 i = 0; i < NUM_OF_TRASH; ++i) - if (Creature* c = instance->GetCreature(NPC_TrashGUID[i])) - c->SetVisible(false); - if (Creature* falric = instance->GetCreature(NPC_FalricGUID)) - falric->SetVisible(false); - if (Creature* marwyn = instance->GetCreature(NPC_MarwynGUID)) - marwyn->SetVisible(false); - ResumeFirstEventStep = 0; - ResumeFirstEventTimer = 0; - break; - } - } - else - ResumeFirstEventTimer -= diff; - } - if (outroStep) - { - if (outroTimer <= diff) - { - switch (outroStep) - { - case 1: - if (Creature* lk = instance->GetCreature(NPC_LichKingGUID)) - { - lk->UpdatePosition(PathWaypoints[PATH_WP_COUNT - 2], true); - lk->StopMovingOnCurrentPos(); - lk->RemoveAllAuras(); - lk->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_CUSTOM_SPELL_02); - if (!lk->IsVisible()) - lk->SetVisible(true); - if (Creature* leader = instance->GetCreature(NPC_LeaderGUID)) - { - leader->UpdatePosition(PathWaypoints[PATH_WP_COUNT - 1], true); - leader->StopMovingOnCurrentPos(); - leader->RemoveAllAuras(); - leader->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_READY1H); - if (!leader->IsVisible()) - leader->SetVisible(true); - lk->CastSpell(leader, SPELL_HARVEST_SOUL, false); - } - } - ++outroStep; - outroTimer = 500; - break; - case 2: - { - uint32 entry = GetTeamIdInInstance() == TEAM_ALLIANCE ? GO_THE_SKYBREAKER : GO_ORGRIMS_HAMMER; - T1 = sTransportMgr->CreateTransport(entry, 0, instance); - - ++outroStep; - outroTimer = GetTeamIdInInstance() == TEAM_ALLIANCE ? 10000 : 10500; - } - break; - case 3: - if (T1) - T1->EnableMovement(false); - if (Creature* c = instance->GetCreature(NPC_ShipCaptainGUID)) - c->AI()->Talk(GetTeamIdInInstance() == TEAM_ALLIANCE ? SAY_FIRE_ALLY : SAY_FIRE_HORDE); - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - { - c->RemoveAllAuras(); - c->CastSpell(c, SPELL_GUNSHIP_CANNON_FIRE_PERIODIC, true); - } - if (Creature* c = instance->GetCreature(NPC_LichKingGUID)) - { - c->InterruptNonMeleeSpells(true); - c->RemoveAllAuras(); - } - ++outroStep; - outroTimer = 5000; - break; - case 4: - HandleGameObject(GO_CaveInGUID, false); - ++outroStep; - outroTimer = 3000; - break; - case 5: - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - c->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - if (Creature* c = instance->GetCreature(NPC_LichKingGUID)) - { - c->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - c->GetThreatMgr().ClearAllThreat(); - c->CombatStop(true); - c->InterruptNonMeleeSpells(true); - c->SetVisible(false); - } - if (instance->IsHeroic()) - instance->ToInstanceMap()->PermBindAllPlayers(); - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - c->CastSpell(c, SPELL_ACHIEVEMENT_CHECK, true); - ++outroStep; - outroTimer = 1000; - break; - case 6: - if (T1) - T1->EnableMovement(true); - ++outroStep; - outroTimer = 3500; - break; - case 7: - if (T1) - T1->EnableMovement(false); - if (Creature* leader = instance->GetCreature(NPC_LeaderGUID)) - { - uint8 index = GetTeamIdInInstance() == TEAM_ALLIANCE ? 0 : 1; - for (uint8 i = 0; i < 3; ++i) - if (StairsPos[index][i].GetPositionX()) - if (GameObject* go = leader->SummonGameObject(GetTeamIdInInstance() == TEAM_ALLIANCE ? GO_STAIRS_ALLIANCE : GO_STAIRS_HORDE, StairsPos[index][i].GetPositionX(), StairsPos[index][i].GetPositionY(), StairsPos[index][i].GetPositionZ(), StairsPos[index][i].GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 86400, false)) - go->SetGameObjectFlag(GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE); - //Position pos = TeamIdInInstance == TEAM_ALLIANCE ? AllyPortalPos : HordePortalPos; - //leader->SummonGameObject(GO_PORTAL_TO_DALARAN, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 86400); - //pos = TeamIdInInstance == TEAM_ALLIANCE ? AllyChestPos : HordeChestPos; - //leader->SummonGameObject(instance->GetSpawnMode() ? GO_CHEST_HEROIC : GO_CHEST_NORMAL, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 86400); - } - ++outroStep; - outroTimer = 1000; - break; - case 8: - if (Creature* c = instance->GetCreature(NPC_ShipCaptainGUID)) - c->AI()->Talk(GetTeamIdInInstance() == TEAM_ALLIANCE ? SAY_ONBOARD_ALLY : SAY_ONBOARD_HORDE); - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - { - c->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); - c->GetMotionMaster()->MovePoint(0, WalkCaveInPos); - c->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); // need gossip ID 10931 - } - ++outroStep; - outroTimer = 6000; - break; - case 9: - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - c->AI()->Talk(GetTeamIdInInstance() == TEAM_ALLIANCE ? SAY_JAINA_FINAL_1 : SAY_SYLVANA_FINAL); - HandleGameObject(GO_CaveInGUID, true); - ++outroStep; - outroTimer = 11000; - break; - case 10: - ++outroStep; - outroTimer = 0; - for (Map::PlayerList::const_iterator itr = instance->GetPlayers().begin(); itr != instance->GetPlayers().end(); ++itr) - if (Player* p = itr->GetSource()) - p->KilledMonsterCredit(NPC_WRATH_OF_THE_LICH_KING_CREDIT); - if (GetTeamIdInInstance() == TEAM_ALLIANCE) - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - { - c->AI()->Talk(SAY_JAINA_FINAL_2); - outroTimer = 10000; - } - break; - case 11: - if (Creature* c = instance->GetCreature(NPC_LeaderGUID)) - c->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); - ++outroStep; - outroTimer = 300 * 1000; - break; - case 12: - outroStep = 0; - outroTimer = 0; - if (T1) - T1->setActive(false); - break; - } - } - else - outroTimer -= diff; + if (Creature* marwyn = GetCreature(DATA_MARWYN)) + if (marwyn->IsAlive()) + marwyn->AI()->DoAction(1); } } - }; + else + { + _nextWaveTimer = 150000; + + uint8 numToActivate = 5; + if (_waveNumber <= 2) + numToActivate = 3; + else if (_waveNumber <= 4) + numToActivate = 4; + _remainingTrashKills += numToActivate; + for (uint8 i = 0; i < numToActivate; ++i) + { + uint32 entry = _chosenComposition[_waveNumber - (_waveNumber > 5 ? 2 : 1)][i]; + bool forward = !!urand(0, 1); + for (int8 j = (forward ? 0 : NUM_OF_TRASH - 1); (forward ? j < NUM_OF_TRASH : j >= 0); (forward ? ++j : --j)) + if (!_trashActive[j]) + if (Creature* c = instance->GetCreature(_trashGUID[j])) + if (c->GetEntry() == entry) + { + _trashActive[j] = true; + c->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + c->SetImmuneToAll(false); + c->AI()->DoAction(1); + break; + } + } + } + } + + void HandleWaveWipe() + { + if (!_waveNumber) + return; + + DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVES_ENABLED, 0); + DoUpdateWorldState(WORLD_STATE_HALLS_OF_REFLECTION_WAVE_COUNT, 0); + HandleGameObject(GO_FRONT_DOOR, true); + + _trashCounter = NUM_OF_TRASH; + _waveNumber = 0; + _nextWaveTimer = 0; + std::memset(&_chosenComposition, 0, sizeof(_chosenComposition)); + + for (uint8 i = 0; i < NUM_OF_TRASH; ++i) + if (_trashActive[i]) + if (Creature* c = instance->GetCreature(_trashGUID[i])) + { + c->GetThreatMgr().ClearAllThreat(); + c->CombatStop(true); + c->InterruptNonMeleeSpells(true); + c->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + c->SetImmuneToAll(true); + c->Respawn(true); + c->UpdatePosition(c->GetHomePosition(), true); + c->StopMovingOnCurrentPos(); + } + std::fill(std::begin(_trashActive), std::end(_trashActive), false); + if (Creature* falric = GetCreature(DATA_FALRIC)) + falric->AI()->EnterEvadeMode(); + if (Creature* marwyn = GetCreature(DATA_MARWYN)) + marwyn->AI()->EnterEvadeMode(); + + _waveResumeTimer = 5000; + _waveResumeStep = 2; + _remainingTrashKills = 0; + } + + void Update(uint32 diff) override + { + if (!instance->HavePlayers()) + return; + + if (_playerCheckTimer <= diff) + { + _playerCheckTimer = 5000; + if ((GetPersistentData(PERSISTENT_DATA_INTRO)) && GetBossState(DATA_MARWYN) != DONE) + { + Map::PlayerList const& pl = instance->GetPlayers(); + if (_waveNumber || _nextWaveTimer) + { + bool allDead = true; + bool outOfRange = false; + for (auto itr = pl.begin(); itr != pl.end(); ++itr) + { + Player* p = itr->GetSource(); + if (!p || p->IsGameMaster()) + continue; + if (p->IsAlive()) + allDead = false; + if (p->GetExactDist2d(&CenterPos) > MAX_DIST_FROM_CENTER_IN_COMBAT) + { + outOfRange = true; + break; + } + } + if (allDead || outOfRange) + HandleWaveWipe(); + } + else if (!_waveResumeTimer) + { + bool allInRange = (instance->GetPlayersCountExceptGMs() > 0); + for (auto itr = pl.begin(); itr != pl.end(); ++itr) + { + Player* p = itr->GetSource(); + if (!p || p->IsGameMaster()) + continue; + if (p->GetExactDist2d(&CenterPos) > MAX_DIST_FROM_CENTER_TO_START || !p->IsAlive()) + { + allInRange = false; + break; + } + } + if (allInRange) + { + _waveResumeTimer = 1; + _waveResumeStep = 0; + } + } + } + } + else + _playerCheckTimer -= diff; + + if (_nextWaveTimer) + { + if (_nextWaveTimer <= diff) + { + _nextWaveTimer = 0; + StartNextWave(); + } + else + _nextWaveTimer -= diff; + } + + if (_waveResumeTimer) + { + if (_waveResumeTimer <= diff) + { + switch (_waveResumeStep) + { + case 0: + if (Creature* falric = GetCreature(DATA_FALRIC)) + { + falric->UpdatePosition(5274.9f, 2039.2f, 709.319f, 5.4619f, true); + falric->StopMovingOnCurrentPos(); + falric->SetVisible(true); + if (falric->IsAlive()) + { + falric->GetMotionMaster()->MovePoint(0, FalricMovePos); + if (Aura* a = falric->AddAura(SPELL_SHADOWMOURNE_VISUAL, falric)) + a->SetDuration(8000); + } + } + if (Creature* marwyn = GetCreature(DATA_MARWYN)) + { + marwyn->UpdatePosition(5343.77f, 1973.86f, 709.319f, 2.35173f, true); + marwyn->StopMovingOnCurrentPos(); + marwyn->SetVisible(true); + if (marwyn->IsAlive()) + { + marwyn->GetMotionMaster()->MovePoint(0, MarwynMovePos); + if (Aura* a = marwyn->AddAura(SPELL_SHADOWMOURNE_VISUAL, marwyn)) + a->SetDuration(8000); + } + marwyn->AI()->Talk(EMOTE_MARWYN_INTRO_SPIRIT); + } + ++_waveResumeStep; + _waveResumeTimer = 7500; + break; + case 1: + if (Creature* falric = GetCreature(DATA_FALRIC)) + { + if (falric->IsAlive()) + falric->AI()->Talk(SAY_FALRIC_INTRO_2); + else if (Creature* marwyn = GetCreature(DATA_MARWYN)) + marwyn->AI()->Talk(SAY_MARWYN_WIPE_AFTER_FALRIC); + } + SetData(ACTION_SHOW_TRASH, 1); + _waveResumeStep = 0; + _waveResumeTimer = 0; + _nextWaveTimer = 7000; + break; + default: + for (uint8 i = 0; i < NUM_OF_TRASH; ++i) + if (Creature* c = instance->GetCreature(_trashGUID[i])) + c->SetVisible(false); + if (Creature* falric = GetCreature(DATA_FALRIC)) + falric->SetVisible(false); + if (Creature* marwyn = GetCreature(DATA_MARWYN)) + marwyn->SetVisible(false); + _waveResumeStep = 0; + _waveResumeTimer = 0; + break; + } + } + else + _waveResumeTimer -= diff; + } + + if (_outroStep) + { + if (_outroTimer <= diff) + UpdateOutro(); + else + _outroTimer -= diff; + } + } + +private: + void UpdateOutro() + { + switch (_outroStep) + { + case 1: + if (Creature* lk = GetCreature(NPC_LICH_KING_BOSS)) + { + lk->UpdatePosition(PathWaypoints[PATH_WP_COUNT - 2], true); + lk->StopMovingOnCurrentPos(); + lk->RemoveAllAuras(); + lk->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_CUSTOM_SPELL_02); + if (!lk->IsVisible()) + lk->SetVisible(true); + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + { + leader->UpdatePosition(PathWaypoints[PATH_WP_COUNT - 1], true); + leader->StopMovingOnCurrentPos(); + leader->RemoveAllAuras(); + leader->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_READY1H); + if (!leader->IsVisible()) + leader->SetVisible(true); + lk->CastSpell(leader, SPELL_HARVEST_SOUL, false); + } + } + ++_outroStep; + _outroTimer = 500; + break; + case 2: + { + uint32 entry = GetTeamIdInInstance() == TEAM_ALLIANCE ? GO_THE_SKYBREAKER : GO_ORGRIMS_HAMMER; + _transport = sTransportMgr->CreateTransport(entry, 0, instance); + ++_outroStep; + _outroTimer = GetTeamIdInInstance() == TEAM_ALLIANCE ? 10000 : 10500; + break; + } + case 3: + if (_transport) + _transport->EnableMovement(false); + if (Creature* captain = GetCreature(DATA_SHIP_CAPTAIN)) + captain->AI()->Talk(GetTeamIdInInstance() == TEAM_ALLIANCE ? SAY_FIRE_ALLY : SAY_FIRE_HORDE); + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + { + leader->RemoveAllAuras(); + leader->CastSpell(leader, SPELL_GUNSHIP_CANNON_FIRE_PERIODIC, true); + } + if (Creature* lichKing = GetCreature(NPC_LICH_KING_BOSS)) + { + lichKing->InterruptNonMeleeSpells(true); + lichKing->RemoveAllAuras(); + } + ++_outroStep; + _outroTimer = 5000; + break; + case 4: + HandleGameObject(GO_CAVE_IN, false); + ++_outroStep; + _outroTimer = 3000; + break; + case 5: + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + leader->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + if (Creature* lichKing = GetCreature(NPC_LICH_KING_BOSS)) + { + lichKing->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + lichKing->GetThreatMgr().ClearAllThreat(); + lichKing->CombatStop(true); + lichKing->InterruptNonMeleeSpells(true); + lichKing->SetVisible(false); + } + if (instance->IsHeroic()) + instance->ToInstanceMap()->PermBindAllPlayers(); + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + leader->CastSpell(leader, SPELL_ACHIEVEMENT_CHECK, true); + ++_outroStep; + _outroTimer = 1000; + break; + case 6: + if (_transport) + _transport->EnableMovement(true); + ++_outroStep; + _outroTimer = 3500; + break; + case 7: + if (_transport) + _transport->EnableMovement(false); + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + { + uint8 index = GetTeamIdInInstance() == TEAM_ALLIANCE ? 0 : 1; + for (uint8 i = 0; i < 3; ++i) + if (StairsPos[index][i].GetPositionX()) + if (GameObject* go = leader->SummonGameObject(GetTeamIdInInstance() == TEAM_ALLIANCE ? GO_STAIRS_ALLIANCE : GO_STAIRS_HORDE, StairsPos[index][i].GetPositionX(), StairsPos[index][i].GetPositionY(), StairsPos[index][i].GetPositionZ(), StairsPos[index][i].GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 86400, false)) + go->SetGameObjectFlag(GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE); + } + ++_outroStep; + _outroTimer = 1000; + break; + case 8: + if (Creature* captain = GetCreature(DATA_SHIP_CAPTAIN)) + captain->AI()->Talk(GetTeamIdInInstance() == TEAM_ALLIANCE ? SAY_ONBOARD_ALLY : SAY_ONBOARD_HORDE); + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + { + leader->AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + leader->GetMotionMaster()->MovePoint(0, WalkCaveInPos); + leader->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + } + ++_outroStep; + _outroTimer = 6000; + break; + case 9: + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + leader->AI()->Talk(GetTeamIdInInstance() == TEAM_ALLIANCE ? SAY_JAINA_FINAL_1 : SAY_SYLVANA_FINAL); + HandleGameObject(GO_CAVE_IN, true); + ++_outroStep; + _outroTimer = 11000; + break; + case 10: + ++_outroStep; + _outroTimer = 0; + instance->DoForAllPlayers([](Player* player) + { + player->KilledMonsterCredit(NPC_WRATH_OF_THE_LICH_KING_CREDIT); + }); + if (GetTeamIdInInstance() == TEAM_ALLIANCE) + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + { + leader->AI()->Talk(SAY_JAINA_FINAL_2); + _outroTimer = 10000; + } + break; + case 11: + if (Creature* leader = GetCreature(NPC_SYLVANAS_PART2)) + leader->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); + ++_outroStep; + _outroTimer = 300 * 1000; + break; + case 12: + _outroStep = 0; + _outroTimer = 0; + if (_transport) + _transport->setActive(false); + break; + } + } + + // GUIDs not handled by ObjectData (arrays with position-based assignment) + ObjectGuid _spiritualReflectionGUID[MAX_SPIRITUAL_REFLECTIONS]; + ObjectGuid _iceWallTargetGUID[MAX_ICE_WALL_TARGETS]; + ObjectGuid _trashGUID[NUM_OF_TRASH]; + + // Runtime state (not persisted) + uint32 _batteredHiltStatus{}; + + // Wave system + bool _trashActive[NUM_OF_TRASH]{}; + uint8 _trashCounter{}; + uint32 _chosenComposition[8][5]{}; + uint8 _waveNumber{}; + uint32 _nextWaveTimer{}; + uint16 _playerCheckTimer{}; + uint16 _waveResumeTimer{}; + uint8 _waveResumeStep{}; + bool _falricPhaseComplete{}; + uint8 _remainingTrashKills{}; + + // Lich King fight + bool _isLichKingFightActive{}; + uint32 _outroTimer{}; + uint8 _outroStep{}; + MotionTransport* _transport{}; }; void AddSC_instance_halls_of_reflection() { - new instance_halls_of_reflection(); + RegisterInstanceScript(instance_halls_of_reflection, MAP_HALLS_OF_REFLECTION); } From d3f1be6d2223fc1a1d08751316885b609b693a20 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:07:29 -0500 Subject: [PATCH 037/132] fix(Core/Scripts): Fix boss scripts resetting during death/defeat RP after threat system port (#25185) Co-authored-by: blinkysc Co-authored-by: Treeston --- src/server/game/AI/ScriptedAI/ScriptedCreature.cpp | 2 +- src/server/game/Entities/Unit/Unit.cpp | 6 +----- .../MagistersTerrace/boss_felblood_kaelthas.cpp | 3 +-- .../SunwellPlateau/boss_kalecgos.cpp | 1 - .../Kalimdor/TempleOfAhnQiraj/boss_skeram.cpp | 4 ++-- .../scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp | 8 ++++++++ .../scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp | 8 ++++++++ .../scripts/Outland/BlackTemple/boss_illidan.cpp | 13 +++++++++++++ 8 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index b19fdddce..ce9ea88e9 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -472,7 +472,7 @@ void ScriptedAI::DoResetThreat(Unit* unit) void ScriptedAI::DoResetThreatList() { - if (!me->CanHaveThreatList() || me->GetThreatMgr().IsThreatListEmpty()) + if (!me->CanHaveThreatList() || me->GetThreatMgr().IsThreatListEmpty(true)) { LOG_ERROR("entities.unit.ai", "DoResetThreatList called for creature that either cannot have threat list or has empty threat list (me entry = {})", me->GetEntry()); return; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 7d8cb92c0..0d94a956a 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -11419,12 +11419,8 @@ Unit* Creature::SelectVictim() Unit* target = nullptr; - // Taunt handling is now done inside ThreatManager via TauntState if (CanHaveThreatList()) - { - if (!m_threatManager.IsThreatListEmpty()) - target = m_threatManager.GetCurrentVictim(); - } + target = m_threatManager.GetCurrentVictim(); else if (!HasReactState(REACT_PASSIVE)) { // we have player pet probably diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp index d25849cd0..9caf6524e 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp @@ -190,11 +190,10 @@ struct boss_felblood_kaelthas : public BossAI damage = me->GetHealth() - 1; if (me->isRegeneratingHealth()) { - me->CombatStop(); me->CastStop(); me->SetRegeneratingHealth(false); me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE); - me->SetImmuneToAll(true); + me->SetImmuneToAll(true, true); me->SetReactState(REACT_PASSIVE); LapseAction(ACTION_REMOVE_FLY); scheduler.CancelAll(); diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kalecgos.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kalecgos.cpp index 8253bf810..f32644ea0 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kalecgos.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kalecgos.cpp @@ -161,7 +161,6 @@ struct boss_kalecgos : public BossAI me->m_Events.AddEventAtOffset([&] { me->SetReactState(REACT_PASSIVE); - me->CombatStop(); me->RemoveAllAuras(); me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); Talk(SAY_EVIL_ENRAGE); diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_skeram.cpp b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_skeram.cpp index fc9fe8949..69adba859 100644 --- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_skeram.cpp +++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_skeram.cpp @@ -95,7 +95,7 @@ struct boss_skeram : public BossAI creature->CastSpell(creature, SPELL_BIRTH, true); creature->SetControlled(true, UNIT_STATE_ROOT); creature->SetReactState(REACT_PASSIVE); - creature->SetImmuneToAll(true); + creature->SetImmuneToAll(true, true); _copiesGUIDs.push_back(creature->GetGUID()); } @@ -211,7 +211,7 @@ struct boss_skeram : public BossAI _copiesGUIDs.clear(); DoCast(me, SPELL_SUMMON_IMAGES, true); me->SetReactState(REACT_PASSIVE); - me->SetImmuneToAll(true); + me->SetImmuneToAll(true, true); me->SetControlled(true, UNIT_STATE_ROOT); Talk(SAY_SPLIT); _hpct -= 25.0f; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp index 391a73146..545a3b422 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp @@ -217,6 +217,14 @@ struct boss_hodir : public BossAI const Position ENTRANCE_DOOR{ 1999.160034f, -297.792999f, 431.960999f, 0 }; const Position EXIT_DOOR{ 1999.709961f, -166.259003f, 432.822998f, 0 }; + void JustExitedCombat() override + { + EngagementOver(); + if (me->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE)) + return; + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + } + void Reset() override { _Reset(); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp index 6b94236bd..4fc2897f6 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp @@ -399,6 +399,14 @@ struct boss_thorim : public BossAI go->SetGoState(GO_STATE_ACTIVE); } + void JustExitedCombat() override + { + EngagementOver(); + if (_encounterFinished) + return; + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + } + void EnterEvadeMode(EvadeReason why) override { DisableThorim(false); diff --git a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp index c41e1ecc2..2e0cfe9f0 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp @@ -1101,16 +1101,29 @@ struct npc_maiev_illidan : public ScriptedAI instance = creature->GetInstanceScript(); } + bool _outroActive{ false }; + void Reset() override { + if (_outroActive) + return; scheduler.CancelAll(); me->m_Events.KillAllEvents(false); } + void JustExitedCombat() override + { + EngagementOver(); + if (_outroActive) + return; + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + } + void DoAction(int32 param) override { if (param == ACTION_MAIEV_ENDING) { + _outroActive = true; scheduler.CancelAll(); me->SetReactState(REACT_PASSIVE); DoStopAttack(); From b5da3363f1f0fc77b8f1643963c6bc881caa2352 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:13:35 -0300 Subject: [PATCH 038/132] fix(Core/Battlefield): fix stale queue/invite entries and wrong-team kicks (#25168) Co-authored-by: Claude Opus 4.6 --- src/server/game/Battlefield/Battlefield.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/server/game/Battlefield/Battlefield.cpp b/src/server/game/Battlefield/Battlefield.cpp index 2edfa3fa4..a7494f1e6 100644 --- a/src/server/game/Battlefield/Battlefield.cpp +++ b/src/server/game/Battlefield/Battlefield.cpp @@ -130,6 +130,7 @@ void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/) cp->HandlePlayerLeave(player); InvitedPlayers[player->GetTeamId()].erase(player->GetGUID()); + PlayersInQueue[player->GetTeamId()].erase(player->GetGUID()); PlayersWillBeKick[player->GetTeamId()].erase(player->GetGUID()); Players[player->GetTeamId()].erase(player->GetGUID()); SendRemoveWorldStates(player); @@ -290,7 +291,8 @@ void Battlefield::KickAfkPlayers() void Battlefield::KickPlayerFromBattlefield(ObjectGuid guid) { if (Player* player = ObjectAccessor::FindPlayer(guid)) - if (player->GetZoneId() == GetZoneId() && !player->IsGameMaster()) + if (player->GetZoneId() == GetZoneId() && !player->IsGameMaster() + && !PlayersInWar[player->GetTeamId()].count(guid)) player->TeleportTo(KickPosition); } @@ -416,7 +418,7 @@ void Battlefield::PlayerAcceptInviteToWar(Player* player) { player->GetSession()->SendBfEntered(BattleId); PlayersInWar[player->GetTeamId()].insert(player->GetGUID()); - InvitedPlayers[player->GetTeamId(true)].erase(player->GetGUID()); + InvitedPlayers[player->GetTeamId()].erase(player->GetGUID()); if (player->isAFK()) player->ToggleAFK(); From f63a93276e43fe3329a5e964c9852eb1f228ada4 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:13:53 -0300 Subject: [PATCH 039/132] refactor(Scripts/FoS): modernize Forge of Souls bosses to use BossAI (#25174) Co-authored-by: Claude Opus 4.6 --- .../ForgeOfSouls/boss_bronjahm.cpp | 351 ++++++------- .../ForgeOfSouls/boss_devourer_of_souls.cpp | 462 ++++++++---------- .../FrozenHalls/ForgeOfSouls/forge_of_souls.h | 7 + .../ForgeOfSouls/instance_forge_of_souls.cpp | 139 ++---- 4 files changed, 431 insertions(+), 528 deletions(-) diff --git a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp index ef62d41ad..9866eb162 100644 --- a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp @@ -32,7 +32,7 @@ enum Yells SAY_CORRUPT_SOUL = 4, }; -enum eSpells +enum Spells { SPELL_SOULSTORM_CHANNEL_OOC = 69008, @@ -51,7 +51,7 @@ enum eSpells SPELL_SOULSTORM = 68872, }; -enum eEvents +enum Events { EVENT_SPELL_SHADOW_BOLT = 1, EVENT_SPELL_FEAR, @@ -60,207 +60,171 @@ enum eEvents EVENT_START_SOULSTORM, }; -class boss_bronjahm : public CreatureScript +struct boss_bronjahm : public BossAI { -public: - boss_bronjahm() : CreatureScript("boss_bronjahm") { } + boss_bronjahm(Creature* creature) : BossAI(creature, DATA_BRONJAHM) { } - struct boss_bronjahmAI : public ScriptedAI + void JustReachedHome() override { - boss_bronjahmAI(Creature* creature) : ScriptedAI(creature), summons(me) - { - pInstance = creature->GetInstanceScript(); - } + BossAI::JustReachedHome(); + DoCastSelf(SPELL_SOULSTORM_CHANNEL_OOC, true); + } - InstanceScript* pInstance; - EventMap events; - SummonList summons; - - void JustReachedHome() override - { - me->CastSpell(me, SPELL_SOULSTORM_CHANNEL_OOC, true); - } - - void Reset() override - { - me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); - me->CastSpell(me, SPELL_SOULSTORM_CHANNEL_OOC, true); - events.Reset(); - summons.DespawnAll(); - if (pInstance) - pInstance->SetData(DATA_BRONJAHM, NOT_STARTED); - } - - void JustEngagedWith(Unit* /*who*/) override - { - Talk(SAY_AGGRO); - me->RemoveAurasDueToSpell(SPELL_SOULSTORM_CHANNEL_OOC); - - DoZoneInCombat(); - events.Reset(); - events.RescheduleEvent(EVENT_SPELL_SHADOW_BOLT, 2s); - events.RescheduleEvent(EVENT_SPELL_MAGICS_BANE, 5s, 10s); - events.RescheduleEvent(EVENT_SPELL_CORRUPT_SOUL, 14s, 20s); - - if (pInstance) - pInstance->SetData(DATA_BRONJAHM, IN_PROGRESS); - } - - void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - if (!me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE) && me->HealthBelowPctDamaged(35, damage)) - { - me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE); - me->GetMotionMaster()->Clear(); - me->GetMotionMaster()->MoveIdle(); - me->CastSpell(me, SPELL_TELEPORT, false); - events.CancelEvent(EVENT_SPELL_CORRUPT_SOUL); - events.DelayEvents(6s); - events.RescheduleEvent(EVENT_SPELL_FEAR, 8s, 14s); - } - } - - void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override - { - if (spell->Id == SPELL_TELEPORT) - { - me->CastSpell(me, SPELL_TELEPORT_VISUAL, true); - events.RescheduleEvent(EVENT_START_SOULSTORM, 1ms); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - if (me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE)) - if (me->isAttackReady()) - me->SetFacingToObject(me->GetVictim()); - - switch (events.ExecuteEvent()) - { - case 0: - break; - case EVENT_SPELL_SHADOW_BOLT: - if (!me->IsWithinMeleeRange(me->GetVictim())) - me->CastSpell(me->GetVictim(), SPELL_SHADOW_BOLT, false); - events.Repeat(2s); - break; - case EVENT_SPELL_FEAR: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 10.0f, true)) - me->CastCustomSpell(SPELL_FEAR, SPELLVALUE_MAX_TARGETS, 1, target, false); - events.Repeat(8s, 12s); - break; - case EVENT_SPELL_MAGICS_BANE: - me->CastSpell(me->GetVictim(), SPELL_MAGICS_BANE, false); - events.Repeat(10s, 15s); - break; - case EVENT_SPELL_CORRUPT_SOUL: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true)) - { - Talk(SAY_CORRUPT_SOUL); - me->CastSpell(target, SPELL_CORRUPT_SOUL, false); - } - events.Repeat(20s, 25s); - break; - case EVENT_START_SOULSTORM: - Talk(SAY_SOUL_STORM); - me->CastSpell(me, SPELL_SOULSTORM, false); - me->CastSpell(me, SPELL_TELEPORT_VISUAL, true); - me->CastSpell(me, SPELL_SOULSTORM_VISUAL, true); - - break; - } - - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_DEATH); - if (pInstance) - pInstance->SetData(DATA_BRONJAHM, DONE); - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer()) - Talk(SAY_SLAY); - } - - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - summon->SetReactState(REACT_PASSIVE); - } - - void EnterEvadeMode(EvadeReason why) override - { - me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); - ScriptedAI::EnterEvadeMode(why); - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetForgeOfSoulsAI(creature); + BossAI::Reset(); + me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); + DoCastSelf(SPELL_SOULSTORM_CHANNEL_OOC, true); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + Talk(SAY_AGGRO); + me->RemoveAurasDueToSpell(SPELL_SOULSTORM_CHANNEL_OOC); + + events.RescheduleEvent(EVENT_SPELL_SHADOW_BOLT, 2s); + events.RescheduleEvent(EVENT_SPELL_MAGICS_BANE, 5s, 10s); + events.RescheduleEvent(EVENT_SPELL_CORRUPT_SOUL, 14s, 20s); + } + + void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override + { + if (!me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE) && me->HealthBelowPctDamaged(35, damage)) + { + me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveIdle(); + DoCastSelf(SPELL_TELEPORT); + events.CancelEvent(EVENT_SPELL_CORRUPT_SOUL); + events.DelayEvents(6s); + events.RescheduleEvent(EVENT_SPELL_FEAR, 8s, 14s); + } + } + + void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override + { + if (spell->Id == SPELL_TELEPORT) + { + DoCastSelf(SPELL_TELEPORT_VISUAL, true); + events.RescheduleEvent(EVENT_START_SOULSTORM, 1ms); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE)) + if (me->isAttackReady()) + me->SetFacingToObject(me->GetVictim()); + + switch (events.ExecuteEvent()) + { + case EVENT_SPELL_SHADOW_BOLT: + if (!me->IsWithinMeleeRange(me->GetVictim())) + DoCastVictim(SPELL_SHADOW_BOLT); + events.Repeat(2s); + break; + case EVENT_SPELL_FEAR: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 10.0f, true)) + me->CastCustomSpell(SPELL_FEAR, SPELLVALUE_MAX_TARGETS, 1, target); + events.Repeat(8s, 12s); + break; + case EVENT_SPELL_MAGICS_BANE: + DoCastVictim(SPELL_MAGICS_BANE); + events.Repeat(10s, 15s); + break; + case EVENT_SPELL_CORRUPT_SOUL: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true)) + { + Talk(SAY_CORRUPT_SOUL); + DoCast(target, SPELL_CORRUPT_SOUL); + } + events.Repeat(20s, 25s); + break; + case EVENT_START_SOULSTORM: + Talk(SAY_SOUL_STORM); + DoCastSelf(SPELL_SOULSTORM); + DoCastSelf(SPELL_TELEPORT_VISUAL, true); + DoCastSelf(SPELL_SOULSTORM_VISUAL, true); + break; + } + + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer()) + Talk(SAY_SLAY); + } + + void JustSummoned(Creature* summon) override + { + BossAI::JustSummoned(summon); + summon->SetReactState(REACT_PASSIVE); + } + + void EnterEvadeMode(EvadeReason why) override + { + me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); + BossAI::EnterEvadeMode(why); } }; -class npc_fos_corrupted_soul_fragment : public CreatureScript +struct npc_fos_corrupted_soul_fragment : public NullCreatureAI { -public: - npc_fos_corrupted_soul_fragment() : CreatureScript("npc_fos_corrupted_soul_fragment") { } - - struct npc_fos_corrupted_soul_fragmentAI : public NullCreatureAI + npc_fos_corrupted_soul_fragment(Creature* creature) : NullCreatureAI(creature) { - npc_fos_corrupted_soul_fragmentAI(Creature* creature) : NullCreatureAI(creature) - { - pInstance = me->GetInstanceScript(); - } + Instance = me->GetInstanceScript(); + } - uint32 timer; - InstanceScript* pInstance; + uint32 Timer = 0; + InstanceScript* Instance = nullptr; - void Reset() override - { - timer = 0; - } - - void UpdateAI(uint32 diff) override - { - if (pInstance) - if (Creature* b = pInstance->instance->GetCreature(pInstance->GetGuidData(DATA_BRONJAHM))) - { - if (me->GetExactDist2d(b) <= 2.0f) - { - me->GetMotionMaster()->MoveIdle(); - me->CastSpell(b, SPELL_CONSUME_SOUL, true); - me->DespawnOrUnsummon(1ms); - return; - } - - if (timer <= diff) - { - if (!me->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED)) - me->GetMotionMaster()->MovePoint(0, *b); - timer = 1000; - } - else - timer -= diff; - } - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetForgeOfSoulsAI(creature); + Timer = 0; + } + + void UpdateAI(uint32 diff) override + { + if (!Instance) + return; + + Creature* bronjahm = Instance->GetCreature(DATA_BRONJAHM); + if (!bronjahm) + return; + + if (me->GetExactDist2d(bronjahm) <= 2.0f) + { + me->GetMotionMaster()->MoveIdle(); + me->CastSpell(bronjahm, SPELL_CONSUME_SOUL, true); + me->DespawnOrUnsummon(1ms); + return; + } + + if (Timer <= diff) + { + if (!me->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED)) + me->GetMotionMaster()->MovePoint(0, *bronjahm); + Timer = 1000; + } + else + Timer -= diff; } }; @@ -275,7 +239,7 @@ class spell_bronjahm_magic_bane : public SpellScript if (Unit* caster = GetCaster()) { - const int32 maxDamage = caster->GetMap()->GetSpawnMode() == 1 ? 15000 : 10000; + int32 const maxDamage = caster->GetMap()->GetSpawnMode() == 1 ? 15000 : 10000; int32 newDamage = GetHitDamage(); newDamage += GetHitUnit()->GetMaxPower(POWER_MANA) / 2; newDamage = std::min(maxDamage, newDamage); @@ -365,9 +329,8 @@ class spell_bronjahm_soulstorm_targeting : public SpellScript void AddSC_boss_bronjahm() { - new boss_bronjahm(); - new npc_fos_corrupted_soul_fragment(); - + RegisterForgeOfSoulsCreatureAI(boss_bronjahm); + RegisterForgeOfSoulsCreatureAI(npc_fos_corrupted_soul_fragment); RegisterSpellScript(spell_bronjahm_magic_bane); RegisterSpellScript(spell_bronjahm_soulstorm_channel_ooc_aura); RegisterSpellScript(spell_bronjahm_soulstorm_visual_aura); diff --git a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_devourer_of_souls.cpp b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_devourer_of_souls.cpp index 102b87ed6..fec69d495 100644 --- a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_devourer_of_souls.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_devourer_of_souls.cpp @@ -24,7 +24,7 @@ #include "SpellScriptLoader.h" #include "forge_of_souls.h" -enum eTexts +enum Texts { SAY_FACE_AGGRO = 0, SAY_FACE_ANGER_SLAY = 1, @@ -38,7 +38,7 @@ enum eTexts SAY_FACE_WAILING_SOUL = 9, }; -enum eSpells +enum Spells { SPELL_PHANTOM_BLAST = 68982, SPELL_PHANTOM_BLAST_H = 70322, @@ -57,7 +57,7 @@ enum eSpells SPELL_WAILING_SOULS_DMG_H = 70324, // 100yd, 104.0 }; -enum eEvents +enum Events { EVENT_SPELL_PHANTOM_BLAST = 1, EVENT_SPELL_MIRRORED_SOUL, @@ -66,245 +66,214 @@ enum eEvents EVENT_SPELL_WAILING_SOULS, }; -enum eDisplayIds +enum ModelIds { - DISPLAY_ANGER = 30148, - DISPLAY_SORROW = 30149, - DISPLAY_DESIRE = 30150, + MODEL_ANGER = 30148, + MODEL_SORROW = 30149, + MODEL_DESIRE = 30150, }; -enum eMisc +enum Misc { NPC_CRUCIBLE_OF_SOULS = 37094, + NPC_UNLEASHED_SOUL = 36595, QUEST_TEMPERING_THE_BLADE_A = 24476, QUEST_TEMPERING_THE_BLADE_H = 24560, }; -class boss_devourer_of_souls : public CreatureScript +struct boss_devourer_of_souls : public BossAI { -public: - boss_devourer_of_souls() : CreatureScript("boss_devourer_of_souls") { } + boss_devourer_of_souls(Creature* creature) : BossAI(creature, DATA_DEVOURER) { } - struct boss_devourer_of_soulsAI : public ScriptedAI + bool AchievementCompleted = true; + + void Reset() override { - boss_devourer_of_soulsAI(Creature* creature) : ScriptedAI(creature), summons(me) - { - pInstance = creature->GetInstanceScript(); - } + BossAI::Reset(); + AchievementCompleted = true; + me->SetControlled(false, UNIT_STATE_ROOT); + me->DisableRotate(false); + me->SetReactState(REACT_AGGRESSIVE); + } - InstanceScript* pInstance; - EventMap events; - SummonList summons; - bool bAchiev; - - void Reset() override - { - bAchiev = true; - me->SetControlled(false, UNIT_STATE_ROOT); - me->DisableRotate(false); - me->SetReactState(REACT_AGGRESSIVE); - events.Reset(); - summons.DespawnAll(); - if (pInstance) - pInstance->SetData(DATA_DEVOURER, NOT_STARTED); - } - - uint32 GetData(uint32 id) const override - { - if (id == 1) - return bAchiev; - - return 0; - } - - void JustEngagedWith(Unit* /*who*/) override - { - Talk(SAY_FACE_AGGRO); - DoZoneInCombat(); - events.Reset(); - events.RescheduleEvent(EVENT_SPELL_PHANTOM_BLAST, 5s); - events.RescheduleEvent(EVENT_SPELL_MIRRORED_SOUL, 9s); - events.RescheduleEvent(EVENT_SPELL_WELL_OF_SOULS, 6s, 8s); - events.RescheduleEvent(EVENT_SPELL_UNLEASHED_SOULS, 18s, 20s); - events.RescheduleEvent(EVENT_SPELL_WAILING_SOULS, 65s); - - if (pInstance) - pInstance->SetData(DATA_DEVOURER, IN_PROGRESS); - - // Suport for Quest Tempering the Blade - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr) - { - Player* player = itr->GetSource(); - if ((player->GetTeamId() == TEAM_ALLIANCE && player->GetQuestStatus(QUEST_TEMPERING_THE_BLADE_A) == QUEST_STATUS_INCOMPLETE) || - (player->GetTeamId() == TEAM_HORDE && player->GetQuestStatus(QUEST_TEMPERING_THE_BLADE_H) == QUEST_STATUS_INCOMPLETE)) - { - if (!me->FindNearestCreature(NPC_CRUCIBLE_OF_SOULS, 100.0f)) - me->SummonCreature(NPC_CRUCIBLE_OF_SOULS, 5672.29f, 2520.69f, 713.44f, 0.96f); - } - } - } - - void SpellHitTarget(Unit* target, SpellInfo const* spell) override - { - if (spell->Id == SPELL_PHANTOM_BLAST_H) - bAchiev = false; - else if (spell->Id == SPELL_WAILING_SOULS_TARGETING) - { - me->SetOrientation(me->GetAngle(target)); - me->SetControlled(true, UNIT_STATE_ROOT); - me->DisableRotate(true); - me->SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty); - me->SetReactState(REACT_PASSIVE); - me->GetMotionMaster()->Clear(false); - me->GetMotionMaster()->MoveIdle(); - me->StopMovingOnCurrentPos(); - - me->SetFacingToObject(target); - me->SendMovementFlagUpdate(); - me->CastSpell(me, SPELL_WAILING_SOULS, false); - } - } - - bool CanAIAttack(Unit const* target) const override { return target->GetPositionZ() > 706.5f; } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (Spell* s = me->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) - if (s->m_spellInfo->Id == SPELL_MIRRORED_SOUL) - { - switch (events.ExecuteEvent()) - { - case 0: - break; - case EVENT_SPELL_PHANTOM_BLAST: - me->CastSpell(me->GetVictim(), SPELL_PHANTOM_BLAST, false); - events.Repeat(5s); - break; - default: - events.Repeat(1s); - break; - } - - if (!me->GetCurrentSpell(CURRENT_GENERIC_SPELL)) - { - me->ClearUnitState(UNIT_STATE_CASTING); - DoMeleeAttackIfReady(); - me->AddUnitState(UNIT_STATE_CASTING); - } - - return; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case 0: - break; - case EVENT_SPELL_PHANTOM_BLAST: - me->CastSpell(me->GetVictim(), SPELL_PHANTOM_BLAST, false); - events.Repeat(5s); - break; - case EVENT_SPELL_MIRRORED_SOUL: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 90.0f, true)) - { - me->CastSpell(target, SPELL_MIRRORED_SOUL, false); - me->setAttackTimer(BASE_ATTACK, 2500); - Talk(EMOTE_MIRRORED_SOUL); - } - events.Repeat(20s, 30s); - break; - case EVENT_SPELL_WELL_OF_SOULS: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true)) - me->CastSpell(target, SPELL_WELL_OF_SOULS, false); - events.Repeat(25s, 30s); - events.DelayEventsToMax(4s, 0); - break; - case EVENT_SPELL_UNLEASHED_SOULS: - me->CastSpell(me, SPELL_UNLEASHED_SOULS, false); - Talk(SAY_FACE_UNLEASH_SOUL); - Talk(EMOTE_UNLEASH_SOUL); - events.Repeat(30s, 40s); - events.DelayEventsToMax(5s, 0); - me->setAttackTimer(BASE_ATTACK, 5500); - break; - case EVENT_SPELL_WAILING_SOULS: - Talk(SAY_FACE_WAILING_SOUL); - Talk(EMOTE_WAILING_SOUL); - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) - me->CastCustomSpell(SPELL_WAILING_SOULS_TARGETING, SPELLVALUE_MAX_TARGETS, 1, target, false); - events.Repeat(80s); - events.DelayEventsToMax(20s, 0); - break; - } - - DoMeleeAttackIfReady(); - } - - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_FACE_DEATH); - summons.DespawnAll(); - if (pInstance) - pInstance->SetData(DATA_DEVOURER, DONE); - } - - void KilledUnit(Unit* victim) override - { - if (!victim->IsPlayer()) - return; - - int32 textId = 0; - switch (me->GetDisplayId()) - { - case DISPLAY_ANGER: - textId = SAY_FACE_ANGER_SLAY; - break; - case DISPLAY_SORROW: - textId = SAY_FACE_SORROW_SLAY; - break; - case DISPLAY_DESIRE: - textId = SAY_FACE_DESIRE_SLAY; - break; - default: - break; - } - - if (textId) - Talk(textId); - } - - void JustSummoned(Creature* summon) override - { - if (summon->GetEntry() != NPC_CRUCIBLE_OF_SOULS) - summons.Summon(summon); - - if (summon->GetEntry() == 36595) - if (Player* plr = summon->SelectNearestPlayer(100.0f)) - { - summon->AddThreat(plr, 100000.0f); - summon->AI()->AttackStart(plr); - } - } - - void EnterEvadeMode(EvadeReason why) override - { - me->SetControlled(false, UNIT_STATE_ROOT); - me->DisableRotate(false); - ScriptedAI::EnterEvadeMode(why); - } - }; - - CreatureAI* GetAI(Creature* creature) const override + uint32 GetData(uint32 id) const override { - return GetForgeOfSoulsAI(creature); + if (id == 1) + return AchievementCompleted; + + return 0; + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + Talk(SAY_FACE_AGGRO); + events.RescheduleEvent(EVENT_SPELL_PHANTOM_BLAST, 5s); + events.RescheduleEvent(EVENT_SPELL_MIRRORED_SOUL, 9s); + events.RescheduleEvent(EVENT_SPELL_WELL_OF_SOULS, 6s, 8s); + events.RescheduleEvent(EVENT_SPELL_UNLEASHED_SOULS, 18s, 20s); + events.RescheduleEvent(EVENT_SPELL_WAILING_SOULS, 65s); + + // Support for Quest Tempering the Blade + me->GetMap()->DoForAllPlayers([this](Player* player) + { + if (me->FindNearestCreature(NPC_CRUCIBLE_OF_SOULS, 100.0f)) + return; + + uint32 questId = player->GetTeamId() == TEAM_ALLIANCE ? QUEST_TEMPERING_THE_BLADE_A : QUEST_TEMPERING_THE_BLADE_H; + if (player->GetQuestStatus(questId) == QUEST_STATUS_INCOMPLETE) + me->SummonCreature(NPC_CRUCIBLE_OF_SOULS, 5672.29f, 2520.69f, 713.44f, 0.96f); + }); + } + + void SpellHitTarget(Unit* target, SpellInfo const* spell) override + { + if (spell->Id == SPELL_PHANTOM_BLAST_H) + AchievementCompleted = false; + else if (spell->Id == SPELL_WAILING_SOULS_TARGETING) + { + me->SetOrientation(me->GetAngle(target)); + me->SetControlled(true, UNIT_STATE_ROOT); + me->DisableRotate(true); + me->SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty); + me->SetReactState(REACT_PASSIVE); + me->GetMotionMaster()->Clear(false); + me->GetMotionMaster()->MoveIdle(); + me->StopMovingOnCurrentPos(); + + me->SetFacingToObject(target); + me->SendMovementFlagUpdate(); + DoCastSelf(SPELL_WAILING_SOULS); + } + } + + bool CanAIAttack(Unit const* target) const override { return target->GetPositionZ() > 706.5f; } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (Spell* s = me->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) + if (s->m_spellInfo->Id == SPELL_MIRRORED_SOUL) + { + switch (events.ExecuteEvent()) + { + case EVENT_SPELL_PHANTOM_BLAST: + DoCastVictim(SPELL_PHANTOM_BLAST); + events.Repeat(5s); + break; + default: + events.Repeat(1s); + break; + } + + if (!me->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + me->ClearUnitState(UNIT_STATE_CASTING); + DoMeleeAttackIfReady(); + me->AddUnitState(UNIT_STATE_CASTING); + } + + return; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_SPELL_PHANTOM_BLAST: + DoCastVictim(SPELL_PHANTOM_BLAST); + events.Repeat(5s); + break; + case EVENT_SPELL_MIRRORED_SOUL: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 90.0f, true)) + { + DoCast(target, SPELL_MIRRORED_SOUL); + me->setAttackTimer(BASE_ATTACK, 2500); + Talk(EMOTE_MIRRORED_SOUL); + } + events.Repeat(20s, 30s); + break; + case EVENT_SPELL_WELL_OF_SOULS: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true)) + DoCast(target, SPELL_WELL_OF_SOULS); + events.Repeat(25s, 30s); + events.DelayEventsToMax(4s, 0); + break; + case EVENT_SPELL_UNLEASHED_SOULS: + DoCastSelf(SPELL_UNLEASHED_SOULS); + Talk(SAY_FACE_UNLEASH_SOUL); + Talk(EMOTE_UNLEASH_SOUL); + events.Repeat(30s, 40s); + events.DelayEventsToMax(5s, 0); + me->setAttackTimer(BASE_ATTACK, 5500); + break; + case EVENT_SPELL_WAILING_SOULS: + Talk(SAY_FACE_WAILING_SOUL); + Talk(EMOTE_WAILING_SOUL); + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) + me->CastCustomSpell(SPELL_WAILING_SOULS_TARGETING, SPELLVALUE_MAX_TARGETS, 1, target); + events.Repeat(80s); + events.DelayEventsToMax(20s, 0); + break; + } + + DoMeleeAttackIfReady(); + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_FACE_DEATH); + } + + void KilledUnit(Unit* victim) override + { + if (!victim->IsPlayer()) + return; + + uint8 textId = 0; + switch (me->GetDisplayId()) + { + case MODEL_ANGER: + textId = SAY_FACE_ANGER_SLAY; + break; + case MODEL_SORROW: + textId = SAY_FACE_SORROW_SLAY; + break; + case MODEL_DESIRE: + textId = SAY_FACE_DESIRE_SLAY; + break; + default: + break; + } + + if (textId) + Talk(textId); + } + + void JustSummoned(Creature* summon) override + { + if (summon->GetEntry() != NPC_CRUCIBLE_OF_SOULS) + BossAI::JustSummoned(summon); + + if (summon->GetEntry() == NPC_UNLEASHED_SOUL) + if (Player* player = summon->SelectNearestPlayer(100.0f)) + { + summon->AddThreat(player, 100000.0f); + summon->AI()->AttackStart(player); + } + } + + void EnterEvadeMode(EvadeReason why) override + { + me->SetControlled(false, UNIT_STATE_ROOT); + me->DisableRotate(false); + BossAI::EnterEvadeMode(why); } }; @@ -317,7 +286,7 @@ class spell_wailing_souls_periodic_aura : public AuraScript return ValidateSpellInfo({ SPELL_WAILING_SOULS_DMG_N }); } - int8 dir; + int8 dir = 0; bool Load() override { @@ -328,31 +297,30 @@ class spell_wailing_souls_periodic_aura : public AuraScript void HandlePeriodicTick(AuraEffect const* aurEff) { PreventDefaultAction(); - if (Unit* t = GetTarget()) + if (Unit* target = GetTarget()) { if (aurEff->GetTickNumber() < 30) { - // spinning, casting, etc. float diff = (2 * M_PI) / (4 * 30); - float new_o = t->GetOrientation() + diff * dir; - if (new_o >= 2 * M_PI) - new_o -= 2 * M_PI; - else if (new_o < 0) - new_o += 2 * M_PI; - t->UpdateOrientation(new_o); - t->SetFacingTo(new_o); - t->CastSpell(t, SPELL_WAILING_SOULS_DMG_N, true); + float newOrientation = target->GetOrientation() + diff * dir; + if (newOrientation >= 2 * M_PI) + newOrientation -= 2 * M_PI; + else if (newOrientation < 0) + newOrientation += 2 * M_PI; + target->UpdateOrientation(newOrientation); + target->SetFacingTo(newOrientation); + target->CastSpell(target, SPELL_WAILING_SOULS_DMG_N, true); } else if (aurEff->GetTickNumber() == 33) { - t->SetControlled(false, UNIT_STATE_ROOT); - t->DisableRotate(false); - if (t->IsCreature()) - t->ToCreature()->SetReactState(REACT_AGGRESSIVE); - if (t->GetVictim()) + target->SetControlled(false, UNIT_STATE_ROOT); + target->DisableRotate(false); + if (target->IsCreature()) + target->ToCreature()->SetReactState(REACT_AGGRESSIVE); + if (target->GetVictim()) { - t->SetGuidValue(UNIT_FIELD_TARGET, t->GetVictim()->GetGUID()); - t->GetMotionMaster()->MoveChase(t->GetVictim()); + target->SetGuidValue(UNIT_FIELD_TARGET, target->GetVictim()->GetGUID()); + target->GetMotionMaster()->MoveChase(target->GetVictim()); } } else if (aurEff->GetTickNumber() >= 34) @@ -368,6 +336,6 @@ class spell_wailing_souls_periodic_aura : public AuraScript void AddSC_boss_devourer_of_souls() { - new boss_devourer_of_souls(); + RegisterForgeOfSoulsCreatureAI(boss_devourer_of_souls); RegisterSpellScript(spell_wailing_souls_periodic_aura); } diff --git a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/forge_of_souls.h b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/forge_of_souls.h index e8ec81c40..117b04719 100644 --- a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/forge_of_souls.h +++ b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/forge_of_souls.h @@ -30,6 +30,11 @@ enum Data DATA_BRONJAHM, DATA_DEVOURER, MAX_ENCOUNTER, + + DATA_LEADER_FIRST, + DATA_LEADER_SECOND, + DATA_GUARD_FIRST, + DATA_GUARD_SECOND, }; enum Creatures @@ -99,4 +104,6 @@ inline AI* GetForgeOfSoulsAI(T* obj) return GetInstanceAI(obj, ForgeOfSoulsScriptName); } +#define RegisterForgeOfSoulsCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetForgeOfSoulsAI) + #endif diff --git a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/instance_forge_of_souls.cpp b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/instance_forge_of_souls.cpp index 4646d90ae..e77fcda11 100644 --- a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/instance_forge_of_souls.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/instance_forge_of_souls.cpp @@ -21,12 +21,29 @@ #include "forge_of_souls.h" #include "Group.h" +enum Misc +{ + NPC_CORRUPTED_SOUL_FRAGMENT = 36535, + + ACHIEV_CRITERIA_SOUL_POWER = 12752, + ACHIEV_CRITERIA_THREE_FACED = 12976, + + SOUL_POWER_FRAGMENT_COUNT = 4, +}; + BossBoundaryData const boundaries = { { DATA_BRONJAHM, new CircleBoundary(Position(5297.3f, 2506.45f), 100.96) }, { DATA_DEVOURER, new ParallelogramBoundary(Position(5663.56f, 2570.53f), Position(5724.39f, 2520.45f), Position(5570.36f, 2461.42f)) } }; +static ObjectData const creatureData[] = +{ + { NPC_BRONJAHM, DATA_BRONJAHM }, + { NPC_DEVOURER, DATA_DEVOURER }, + { 0, 0 } +}; + class instance_forge_of_souls : public InstanceMapScript { public: @@ -42,38 +59,22 @@ public: instance_forge_of_souls_InstanceScript(Map* map) : InstanceScript(map) { SetHeaders(DataHeader); + SetBossNumber(MAX_ENCOUNTER); + LoadObjectData(creatureData, nullptr); LoadBossBoundaries(boundaries); } - uint32 m_auiEncounter[MAX_ENCOUNTER]; - std::string str_data; - ObjectGuid NPC_BronjahmGUID; - ObjectGuid NPC_DevourerGUID; - - ObjectGuid NPC_LeaderFirstGUID; - ObjectGuid NPC_LeaderSecondGUID; - ObjectGuid NPC_GuardFirstGUID; - ObjectGuid NPC_GuardSecondGUID; - - void Initialize() override - { - memset(&m_auiEncounter, 0, sizeof(m_auiEncounter)); - } - - bool IsEncounterInProgress() const override - { - for (uint8 i = 0; i < MAX_ENCOUNTER; ++i) - if (m_auiEncounter[i] == IN_PROGRESS) return true; - - return false; - } + ObjectGuid LeaderFirstGUID; + ObjectGuid LeaderSecondGUID; + ObjectGuid GuardFirstGUID; + ObjectGuid GuardSecondGUID; void OnPlayerEnter(Player* player) override { InstanceScript::OnPlayerEnter(player); // this will happen only after crash and loading the instance from db - if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE && (!NPC_LeaderSecondGUID || !instance->GetCreature(NPC_LeaderSecondGUID))) + if (AllBossesDone() && (!LeaderSecondGUID || !instance->GetCreature(LeaderSecondGUID))) { Position pos = {5658.15f, 2502.564f, 708.83f, 0.885207f}; instance->SummonCreature(NPC_SYLVANAS_PART2, pos); @@ -82,44 +83,40 @@ public: void OnCreatureCreate(Creature* creature) override { + InstanceScript::OnCreatureCreate(creature); + switch (creature->GetEntry()) { - case NPC_BRONJAHM: - NPC_BronjahmGUID = creature->GetGUID(); - break; - case NPC_DEVOURER: - NPC_DevourerGUID = creature->GetGUID(); - break; case NPC_SYLVANAS_PART1: if (GetTeamIdInInstance() == TEAM_ALLIANCE) creature->UpdateEntry(NPC_JAINA_PART1); - NPC_LeaderFirstGUID = creature->GetGUID(); + LeaderFirstGUID = creature->GetGUID(); - if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE) + if (AllBossesDone()) creature->SetVisible(false); break; case NPC_SYLVANAS_PART2: if (GetTeamIdInInstance() == TEAM_ALLIANCE) creature->UpdateEntry(NPC_JAINA_PART2); - NPC_LeaderSecondGUID = creature->GetGUID(); + LeaderSecondGUID = creature->GetGUID(); break; case NPC_LORALEN: if (GetTeamIdInInstance() == TEAM_ALLIANCE) creature->UpdateEntry(NPC_ELANDRA); - if (!NPC_GuardFirstGUID) + if (!GuardFirstGUID) { - NPC_GuardFirstGUID = creature->GetGUID(); - if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE) + GuardFirstGUID = creature->GetGUID(); + if (AllBossesDone()) creature->SetVisible(false); } break; case NPC_KALIRA: if (GetTeamIdInInstance() == TEAM_ALLIANCE) creature->UpdateEntry(NPC_KORELN); - if (!NPC_GuardSecondGUID) + if (!GuardSecondGUID) { - NPC_GuardSecondGUID = creature->GetGUID(); - if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE) + GuardSecondGUID = creature->GetGUID(); + if (AllBossesDone()) creature->SetVisible(false); } break; @@ -128,9 +125,9 @@ public: void HandleOutro() { - if (!NPC_LeaderSecondGUID || !instance->GetCreature(NPC_LeaderSecondGUID)) + if (!LeaderSecondGUID || !instance->GetCreature(LeaderSecondGUID)) if (Creature* leader = instance->SummonCreature(NPC_SYLVANAS_PART2, outroSpawnPoint)) - if (Creature* boss = instance->GetCreature(NPC_DevourerGUID)) + if (Creature* boss = GetCreature(DATA_DEVOURER)) { float angle = boss->GetAngle(leader); leader->GetMotionMaster()->MovePoint(1, boss->GetPositionX() + 10.0f * cos(angle), boss->GetPositionY() + 10.0f * std::sin(angle), boss->GetPositionZ()); @@ -144,69 +141,37 @@ public: } } - void SetData(uint32 type, uint32 data) override + bool SetBossState(uint32 type, EncounterState state) override { - switch (type) - { - case DATA_BRONJAHM: - m_auiEncounter[type] = data; - break; - case DATA_DEVOURER: - m_auiEncounter[type] = data; - if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE) - HandleOutro(); - break; - } + if (!InstanceScript::SetBossState(type, state)) + return false; - if (data == DONE) - SaveToDB(); - } + if (state == DONE && AllBossesDone()) + HandleOutro(); - ObjectGuid GetGuidData(uint32 type) const override - { - switch (type) - { - case DATA_BRONJAHM: - return NPC_BronjahmGUID; - } - - return ObjectGuid::Empty; + return true; } bool CheckAchievementCriteriaMeet(uint32 criteria_id, Player const* /*source*/, Unit const* /*target*/, uint32 /*miscvalue1*/) override { switch (criteria_id) { - case 12752: // Soul Power - if (Creature* c = instance->GetCreature(NPC_BronjahmGUID)) + case ACHIEV_CRITERIA_SOUL_POWER: + if (Creature* bronjahm = GetCreature(DATA_BRONJAHM)) { - std::list L; - uint8 count = 0; - c->GetCreaturesWithEntryInRange(L, 200.0f, 36535); // find all Corrupted Soul Fragment (36535) - for( std::list::const_iterator itr = L.begin(); itr != L.end(); ++itr ) - if ((*itr)->IsAlive()) - ++count; - return (count >= 4); + std::list fragments; + bronjahm->GetCreaturesWithEntryInRange(fragments, 200.0f, NPC_CORRUPTED_SOUL_FRAGMENT); + return std::count_if(fragments.begin(), fragments.end(), + [](Creature const* fragment) { return fragment->IsAlive(); }) >= SOUL_POWER_FRAGMENT_COUNT; } break; - case 12976: - if (Creature* c = instance->GetCreature(NPC_DevourerGUID)) - return (bool)c->AI()->GetData(1); + case ACHIEV_CRITERIA_THREE_FACED: + if (Creature* devourer = GetCreature(DATA_DEVOURER)) + return devourer->AI()->GetData(1) != 0; break; } return false; } - - void ReadSaveDataMore(std::istringstream& data) override - { - data >> m_auiEncounter[0]; - data >> m_auiEncounter[1]; - } - - void WriteSaveDataMore(std::ostringstream& data) override - { - data << m_auiEncounter[0] << ' ' << m_auiEncounter[1]; - } }; }; From 2c4b7c3cd4cca7ddbb367025fa4a5a4cc229de39 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:38:10 -0300 Subject: [PATCH 040/132] refactor(Core/Scripts): convert Blackrock Mountain scripts to register macros (#25178) Co-authored-by: Claude Opus 4.6 (1M context) --- .../BlackrockDepths/blackrock_depths.cpp | 803 +++++---- .../BlackrockDepths/blackrock_depths.h | 3 + .../boss_ambassador_flamelash.cpp | 325 ++-- .../BlackrockDepths/boss_anubshiah.cpp | 117 +- .../boss_emperor_dagran_thaurissan.cpp | 145 +- .../BlackrockDepths/boss_eviscerator.cpp | 101 +- .../boss_general_angerforge.cpp | 156 +- .../boss_gorosh_the_dervish.cpp | 99 +- .../BlackrockDepths/boss_grizzle.cpp | 90 +- .../BlackrockDepths/boss_hedrum.cpp | 91 +- .../boss_high_interrogator_gerstahn.cpp | 108 +- .../BlackrockDepths/boss_magmus.cpp | 78 +- .../boss_moira_bronzebeard.cpp | 52 +- .../BlackrockDepths/boss_okthor.cpp | 122 +- .../BlackrockDepths/boss_tomb_of_seven.cpp | 218 ++- .../instance_blackrock_depths.cpp | 1341 ++++++++-------- .../BlackrockSpire/blackrock_spire.h | 1 + .../BlackrockSpire/boss_drakkisath.cpp | 161 +- .../BlackrockSpire/boss_gyth.cpp | 187 +-- .../BlackrockSpire/boss_halycon.cpp | 95 +- .../BlackrockSpire/boss_highlord_omokk.cpp | 89 +- .../BlackrockSpire/boss_lord_valthalak.cpp | 247 ++- .../BlackrockSpire/boss_mor_grayhoof.cpp | 10 - .../boss_overlord_wyrmthalak.cpp | 136 +- .../boss_pyroguard_emberseer.cpp | 580 ++++--- .../boss_quartermaster_zigris.cpp | 6 - .../BlackrockSpire/boss_rend_blackhand.cpp | 569 ++++--- .../boss_solakar_flamewreath.cpp | 315 ++-- .../BlackrockSpire/boss_the_beast.cpp | 271 ++-- .../BlackrockSpire/boss_urok_doomhowl.cpp | 117 +- .../BlackrockSpire/boss_warmaster_voone.cpp | 131 +- .../instance_blackrock_spire.cpp | 1428 ++++++++--------- .../BlackwingLair/blackwing_lair.h | 1 + .../boss_broodlord_lashlayer.cpp | 315 ++-- .../BlackwingLair/boss_chromaggus.cpp | 314 ++-- .../BlackwingLair/boss_ebonroc.cpp | 109 +- .../BlackwingLair/boss_firemaw.cpp | 89 +- .../BlackwingLair/boss_flamegor.cpp | 91 +- .../BlackwingLair/boss_nefarian.cpp | 589 +++---- .../BlackwingLair/boss_razorgore.cpp | 382 ++--- .../BlackwingLair/boss_vaelastrasz.cpp | 371 ++--- .../BlackwingLair/instance_blackwing_lair.cpp | 651 ++++---- .../MoltenCore/boss_baron_geddon.cpp | 149 +- .../MoltenCore/boss_garr.cpp | 166 +- .../MoltenCore/boss_gehennas.cpp | 89 +- .../MoltenCore/boss_golemagg.cpp | 286 ++-- .../MoltenCore/boss_lucifron.cpp | 67 +- .../MoltenCore/boss_magmadar.cpp | 99 +- .../MoltenCore/boss_majordomo_executus.cpp | 808 +++++----- .../MoltenCore/boss_ragnaros.cpp | 659 ++++---- .../MoltenCore/boss_shazzrah.cpp | 109 +- .../MoltenCore/boss_sulfuron_harbinger.cpp | 238 ++- .../MoltenCore/instance_molten_core.cpp | 693 ++++---- .../MoltenCore/molten_core.cpp | 127 +- .../MoltenCore/molten_core.h | 3 + 55 files changed, 6659 insertions(+), 7938 deletions(-) diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp index 14ab0e15e..3409fe6fa 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp @@ -18,6 +18,7 @@ #include "blackrock_depths.h" #include "AreaTriggerScript.h" #include "CreatureScript.h" +#include "GameObjectAI.h" #include "GameObjectScript.h" #include "GameTime.h" #include "Player.h" @@ -34,93 +35,76 @@ enum IronhandData constexpr Milliseconds IRONHAND_FLAMES_TIMER = 16s; constexpr Milliseconds IRONHAND_FLAMES_TIMER_RAND = 3s; -class go_shadowforge_brazier : public GameObjectScript +struct go_shadowforge_brazier : public GameObjectAI { -public: - go_shadowforge_brazier() : GameObjectScript("go_shadowforge_brazier") {} + go_shadowforge_brazier(GameObject* go) : GameObjectAI(go) {} - bool OnGossipHello(Player* /*player*/, GameObject* go) override + bool GossipHello(Player* /*player*/, bool /*reportUse*/) override { - if (InstanceScript* instance = go->GetInstanceScript()) + if (InstanceScript* instance = me->GetInstanceScript()) { - GameObject* northBrazier = ObjectAccessor::GetGameObject(*go, instance->GetGuidData(DATA_SF_BRAZIER_N)); - GameObject* southBrazier = ObjectAccessor::GetGameObject(*go, instance->GetGuidData(DATA_SF_BRAZIER_S)); + GameObject* northBrazier = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_SF_BRAZIER_N)); + GameObject* southBrazier = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_SF_BRAZIER_S)); if (!northBrazier || !southBrazier) - { return false; - } // should only happen on first brazier if (instance->GetData(TYPE_LYCEUM) == NOT_STARTED) - { instance->SetData(TYPE_LYCEUM, IN_PROGRESS); - } // Check if the opposite brazier is lit - if it is, open the gates. - if ((go->GetGUID() == northBrazier->GetGUID() && southBrazier->GetGoState() == GO_STATE_ACTIVE) || (go->GetGUID() == southBrazier->GetGUID() && northBrazier->GetGoState() == GO_STATE_ACTIVE)) + if ((me->GetGUID() == northBrazier->GetGUID() && southBrazier->GetGoState() == GO_STATE_ACTIVE) || (me->GetGUID() == southBrazier->GetGUID() && northBrazier->GetGoState() == GO_STATE_ACTIVE)) { instance->SetData(TYPE_LYCEUM, DONE); } return false; } return false; - }; + } }; -class ironhand_guardian : public CreatureScript +struct brd_ironhand_guardian : public CreatureAI { -public: - ironhand_guardian() : CreatureScript("brd_ironhand_guardian") {} + brd_ironhand_guardian(Creature* creature) : CreatureAI(creature) {} - CreatureAI* GetAI(Creature* creature) const override + void SetData(uint32 id, uint32 value) override { - return GetBlackrockDepthsAI(creature); + if (id == 0) + if (value == 0 || value == 1) + { + _flamesEnabled = (bool) (value); + events.ScheduleEvent(SPELL_GOUT_OF_FLAMES, urand(1, IRONHAND_N_GROUPS) * IRONHAND_FLAMES_TIMER / IRONHAND_N_GROUPS); + } } - struct ironhand_guardianAI : public CreatureAI + void UpdateAI(uint32 diff) override { - ironhand_guardianAI(Creature* creature) : CreatureAI(creature) {} - bool flames_enabled = false; + events.Update(diff); - void SetData(uint32 id, uint32 value) override + if (_flamesEnabled) { - if (id == 0) + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - if (value == 0 || value == 1) + switch (eventId) { - flames_enabled = (bool) (value); - events.ScheduleEvent(SPELL_GOUT_OF_FLAMES, urand(1, IRONHAND_N_GROUPS) * IRONHAND_FLAMES_TIMER / IRONHAND_N_GROUPS); + case SPELL_GOUT_OF_FLAMES: + DoCast(SPELL_GOUT_OF_FLAMES); + events.RescheduleEvent(SPELL_GOUT_OF_FLAMES, IRONHAND_FLAMES_TIMER - IRONHAND_FLAMES_TIMER_RAND, IRONHAND_FLAMES_TIMER + IRONHAND_FLAMES_TIMER_RAND); + break; + default: + break; } } } + } - void UpdateAI(uint32 diff) override - { - events.Update(diff); - - if (flames_enabled) - { - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_GOUT_OF_FLAMES: - DoCast(SPELL_GOUT_OF_FLAMES); - events.RescheduleEvent(SPELL_GOUT_OF_FLAMES, IRONHAND_FLAMES_TIMER - IRONHAND_FLAMES_TIMER_RAND, IRONHAND_FLAMES_TIMER + IRONHAND_FLAMES_TIMER_RAND); - break; - default: - break; - } - } - } - } - EventMap events; - }; +private: + bool _flamesEnabled = false; + EventMap events; }; struct WaveCreature @@ -159,13 +143,10 @@ public: { time_t now = GameTime::GetGameTime().count(); if (instance->GetData(TYPE_RING_OF_LAW) == IN_PROGRESS || instance->GetData(TYPE_RING_OF_LAW) == DONE) - { return false; - } + if (now - instance->GetData(DATA_TIME_RING_FAIL) < 2 * 60) // in case of wipe, so people can rez. - { return false; - } instance->SetData(TYPE_RING_OF_LAW, IN_PROGRESS); return true; @@ -185,249 +166,232 @@ enum GrimstoneTexts SAY_TEXT6 = 5 }; -class npc_grimstone : public CreatureScript +struct npc_grimstone : public npc_escortAI { -public: - npc_grimstone() : CreatureScript("npc_grimstone") { } - - CreatureAI* GetAI(Creature* creature) const override + npc_grimstone(Creature* creature) : npc_escortAI(creature), summons(me) { - return GetBlackrockDepthsAI(creature); + instance = creature->GetInstanceScript(); + MobSpawnId = instance ? instance->GetData(DATA_ARENA_MOBS) : urand(0, 5); + BossSpawnId = instance ? instance->GetData(DATA_ARENA_BOSS) : urand(0, 5); + eventPhase = 0; + eventTimer = 1000; + resetTimer = 0; + theldrenEvent = false; + summons.DespawnAll(); } - struct npc_grimstoneAI : public npc_escortAI + InstanceScript* instance; + SummonList summons; + + uint8 eventPhase; + uint32 eventTimer; + uint32 resetTimer; + uint8 MobSpawnId; + uint8 BossSpawnId; + bool theldrenEvent; + + void Reset() override { - npc_grimstoneAI(Creature* creature) : npc_escortAI(creature), summons(me) + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + } + + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + if (Unit* target = SelectTargetFromPlayerList(100.0f)) + summon->AI()->AttackStart(target); + } + + void SummonedCreatureDies(Creature* summon, Unit*) override + { + summons.Despawn(summon); + // All Summons killed, next phase + if (summons.empty()) { - instance = creature->GetInstanceScript(); - MobSpawnId = instance ? instance->GetData(DATA_ARENA_MOBS) : urand(0, 5); - BossSpawnId = instance ? instance->GetData(DATA_ARENA_BOSS) : urand(0, 5); - eventPhase = 0; - eventTimer = 1000; resetTimer = 0; - theldrenEvent = false; - summons.DespawnAll(); + eventTimer = 5000; } + } - InstanceScript* instance; - SummonList summons; - - uint8 eventPhase; - uint32 eventTimer; - uint32 resetTimer; - uint8 MobSpawnId; - uint8 BossSpawnId; - bool theldrenEvent; - - void Reset() override + void WaypointReached(uint32 waypointId) override + { + switch (waypointId) { - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - } - - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - if (Unit* target = SelectTargetFromPlayerList(100.0f)) - summon->AI()->AttackStart(target); - } - - void SummonedCreatureDies(Creature* summon, Unit*) override - { - summons.Despawn(summon); - // All Summons killed, next phase - if (summons.empty()) - { - resetTimer = 0; + case 0: + Talk(SAY_TEXT1); + SetEscortPaused(true); eventTimer = 5000; - } + break; + case 1: + Talk(SAY_TEXT2); + SetEscortPaused(true); + eventTimer = 5000; + break; + case 2: + SetEscortPaused(true); + break; + case 3: + Talk(SAY_TEXT3); + break; + case 4: + Talk(SAY_TEXT4); + SetEscortPaused(true); + eventTimer = 5000; + break; + case 5: + if (instance) + { + me->GetMap()->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, NPC_GRIMSTONE, me); + instance->SetData(TYPE_RING_OF_LAW, DONE); + } + break; } + } - void WaypointReached(uint32 waypointId) override + void HandleGameObject(uint32 id, bool open) + { + instance->HandleGameObject(instance->GetGuidData(id), open); + } + + void SummonBoss() + { + if (me->FindNearestGameObject(GO_BANNER_OF_PROVOCATION, 100.0f)) { - switch (waypointId) + theldrenEvent = true; + me->SummonCreature(NPC_THELDREN, 644.300f, -175.989f, -53.739f, 3.418f, TEMPSUMMON_DEAD_DESPAWN, 0); + uint8 rand = urand(0, 4); + for (uint8 i = rand; i < rand + 4; ++i) + me->SummonCreature(theldrenTeam[i], 644.300f, -175.989f, -53.739f, 3.418f, TEMPSUMMON_DEAD_DESPAWN, 0); + } + else + me->SummonCreature(RingBoss[BossSpawnId], 644.300f, -175.989f, -53.739f, 3.418f, TEMPSUMMON_DEAD_DESPAWN, 0); + resetTimer = 30000; + } + + bool updateReset(uint32 diff) + { + // as long as the summoned creatures have someone to attack, we reset the timer. + // once they don't find anyone, the timer will count down until it is smaller than diff and reset. + bool doReset = false; + if (resetTimer > 0) + { + for (auto const& sum : summons) { - case 0: - Talk(SAY_TEXT1); - SetEscortPaused(true); - eventTimer = 5000; - break; - case 1: - Talk(SAY_TEXT2); - SetEscortPaused(true); - eventTimer = 5000; - break; - case 2: - SetEscortPaused(true); - break; - case 3: - Talk(SAY_TEXT3); - break; - case 4: - Talk(SAY_TEXT4); - SetEscortPaused(true); - eventTimer = 5000; - break; - case 5: - if (instance) + if (Creature* creature = ObjectAccessor::GetCreature(*me, sum)) + { + if (creature->IsAlive() && creature->GetVictim()) { - me->GetMap()->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, NPC_GRIMSTONE, me); - instance->SetData(TYPE_RING_OF_LAW, DONE); + resetTimer = 30000; + break; // only need to find one. } - break; + } } + + resetTimer -= diff; + if (resetTimer <= diff) + doReset = true; + } + return doReset; + } + + void SpawnWave(uint32 mobId) + { + for (uint32 i = 0; i < RingMobs[mobId].amount; i++) + me->SummonCreature(RingMobs[mobId].entry, 608.960f + 0.4f * i, -235.322f, -53.907f, 1.857f, TEMPSUMMON_DEAD_DESPAWN, 0); + resetTimer = 30000; + } + + void UpdateEscortAI(uint32 diff) override + { + if (!instance) + return; + + // reset if our mobs don't have a target. + if (updateReset(diff)) + { + summons.DespawnAll(); + HandleGameObject(DATA_ARENA4, true); + HandleGameObject(DATA_ARENA3, false); + HandleGameObject(DATA_ARENA2, false); + HandleGameObject(DATA_ARENA1, false); + instance->SetData(TYPE_RING_OF_LAW, FAIL); } - void HandleGameObject(uint32 id, bool open) + if (eventTimer) { - instance->HandleGameObject(instance->GetGuidData(id), open); - } - - void SummonBoss() - { - if (me->FindNearestGameObject(GO_BANNER_OF_PROVOCATION, 100.0f)) + if (eventTimer <= diff) { - theldrenEvent = true; - me->SummonCreature(NPC_THELDREN, 644.300f, -175.989f, -53.739f, 3.418f, TEMPSUMMON_DEAD_DESPAWN, 0); - uint8 rand = urand(0, 4); - for (uint8 i = rand; i < rand + 4; ++i) - me->SummonCreature(theldrenTeam[i], 644.300f, -175.989f, -53.739f, 3.418f, TEMPSUMMON_DEAD_DESPAWN, 0); + switch (eventPhase) + { + case 0: + Talk(SAY_TEXT5); + HandleGameObject(DATA_ARENA4, false); + me->SetWalk(true); + Start(false); + eventTimer = 0; + break; + case 1: + SetEscortPaused(false); + eventTimer = 0; + break; + case 2: + eventTimer = 2000; + break; + case 3: + HandleGameObject(DATA_ARENA1, true); + eventTimer = 3000; + break; + case 4: + SetEscortPaused(false); + me->SetVisible(false); + SpawnWave(MobSpawnId); // wave 1 + eventTimer = 15000; + break; + case 5: + SpawnWave(MobSpawnId); // wave 2 + eventTimer = 0; // will be set from SummonedCreatureDies + break; + case 6: + me->SetVisible(true); + HandleGameObject(DATA_ARENA1, false); + Talk(SAY_TEXT6); + SetEscortPaused(false); + eventTimer = 0; + break; + case 7: + HandleGameObject(DATA_ARENA2, true); + eventTimer = 5000; + break; + case 8: + me->SetVisible(false); + SummonBoss(); + eventTimer = 0; + break; + case 9: + if (theldrenEvent) + { + // All objects are removed from world once tempsummons despawn, so have a player spawn it instead. + Player* player = me->SelectNearestPlayer(100.0f); + if (GameObject* go = player->SummonGameObject(GO_ARENA_SPOILS, 596.48f, -187.91f, -54.14f, 4.9f, 0.0f, 0.0f, 0.0f, 0.0f, 300)) + go->SetOwnerGUID(ObjectGuid::Empty); + + Map::PlayerList const& pl = me->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) + itr->GetSource()->KilledMonsterCredit(16166); + } + + HandleGameObject(DATA_ARENA2, false); + HandleGameObject(DATA_ARENA3, true); + HandleGameObject(DATA_ARENA4, true); + SetEscortPaused(false); + break; + } + ++eventPhase; } else - me->SummonCreature(RingBoss[BossSpawnId], 644.300f, -175.989f, -53.739f, 3.418f, TEMPSUMMON_DEAD_DESPAWN, 0); - resetTimer = 30000; + eventTimer -= diff; } - - bool updateReset(uint32 diff) - { - // as long as the summoned creatures have someone to attack, we reset the timer. - // once they don't find anyone, the timer will count down until it is smaller than diff and reset. - bool doReset = false; - if (resetTimer > 0) - { - for (auto const& sum : summons) - { - if (Creature* creature = ObjectAccessor::GetCreature(*me, sum)) - { - if (creature->IsAlive() && creature->GetVictim()) - { - resetTimer = 30000; - break; // only need to find one. - } - } - } - - resetTimer -= diff; - if (resetTimer <= diff) - { - doReset = true; - } - } - return doReset; - } - - void SpawnWave(uint32 mobId) - { - for (uint32 i = 0; i < RingMobs[mobId].amount; i++) - { - me->SummonCreature(RingMobs[mobId].entry, 608.960f + 0.4f * i, -235.322f, -53.907f, 1.857f, TEMPSUMMON_DEAD_DESPAWN, 0); - } - resetTimer = 30000; - } - - void UpdateEscortAI(uint32 diff) override - { - if (!instance) - return; - - // reset if our mobs don't have a target. - if (updateReset(diff)) - { - summons.DespawnAll(); - HandleGameObject(DATA_ARENA4, true); - HandleGameObject(DATA_ARENA3, false); - HandleGameObject(DATA_ARENA2, false); - HandleGameObject(DATA_ARENA1, false); - instance->SetData(TYPE_RING_OF_LAW, FAIL); - } - - if (eventTimer) - { - if (eventTimer <= diff) - { - switch (eventPhase) - { - case 0: - Talk(SAY_TEXT5); - HandleGameObject(DATA_ARENA4, false); - me->SetWalk(true); - Start(false); - eventTimer = 0; - break; - case 1: - SetEscortPaused(false); - eventTimer = 0; - break; - case 2: - eventTimer = 2000; - break; - case 3: - HandleGameObject(DATA_ARENA1, true); - eventTimer = 3000; - break; - case 4: - SetEscortPaused(false); - me->SetVisible(false); - SpawnWave(MobSpawnId); // wave 1 - eventTimer = 15000; - break; - case 5: - SpawnWave(MobSpawnId); // wave 2 - eventTimer = 0; // will be set from SummonedCreatureDies - break; - case 6: - me->SetVisible(true); - HandleGameObject(DATA_ARENA1, false); - Talk(SAY_TEXT6); - SetEscortPaused(false); - eventTimer = 0; - break; - case 7: - HandleGameObject(DATA_ARENA2, true); - eventTimer = 5000; - break; - case 8: - me->SetVisible(false); - SummonBoss(); - eventTimer = 0; - break; - case 9: - if (theldrenEvent) - { - // All objects are removed from world once tempsummons despawn, so have a player spawn it instead. - Player* player = me->SelectNearestPlayer(100.0f); - if (GameObject* go = player->SummonGameObject(GO_ARENA_SPOILS, 596.48f, -187.91f, -54.14f, 4.9f, 0.0f, 0.0f, 0.0f, 0.0f, 300)) - { - go->SetOwnerGUID(ObjectGuid::Empty); - } - - Map::PlayerList const& pl = me->GetMap()->GetPlayers(); - for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - itr->GetSource()->KilledMonsterCredit(16166); - } - - HandleGameObject(DATA_ARENA2, false); - HandleGameObject(DATA_ARENA3, true); - HandleGameObject(DATA_ARENA4, true); - SetEscortPaused(false); - break; - } - ++eventPhase; - } - else - eventTimer -= diff; - } - } - }; + } }; // npc_phalanx @@ -438,67 +402,53 @@ enum PhalanxSpells SPELL_MIGHTYBLOW = 14099 }; -class npc_phalanx : public CreatureScript +struct npc_phalanx : public ScriptedAI { -public: - npc_phalanx() : CreatureScript("npc_phalanx") { } + npc_phalanx(Creature* creature) : ScriptedAI(creature) { } - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockDepthsAI(creature); + _thunderClapTimer = 12000; + _fireballVolleyTimer = 0; + _mightyBlowTimer = 15000; } - struct npc_phalanxAI : public ScriptedAI + void UpdateAI(uint32 diff) override { - npc_phalanxAI(Creature* creature) : ScriptedAI(creature) { } + if (!UpdateVictim()) + return; - uint32 ThunderClap_Timer; - uint32 FireballVolley_Timer; - uint32 MightyBlow_Timer; - - void Reset() override + if (_thunderClapTimer <= diff) { - ThunderClap_Timer = 12000; - FireballVolley_Timer = 0; - MightyBlow_Timer = 15000; + DoCastVictim(SPELL_THUNDERCLAP); + _thunderClapTimer = 10000; + } + else _thunderClapTimer -= diff; + + if (HealthBelowPct(51)) + { + if (_fireballVolleyTimer <= diff) + { + DoCastVictim(SPELL_FIREBALLVOLLEY); + _fireballVolleyTimer = 15000; + } + else _fireballVolleyTimer -= diff; } - void UpdateAI(uint32 diff) override + if (_mightyBlowTimer <= diff) { - //Return since we have no target - if (!UpdateVictim()) - return; - - //ThunderClap_Timer - if (ThunderClap_Timer <= diff) - { - DoCastVictim(SPELL_THUNDERCLAP); - ThunderClap_Timer = 10000; - } - else ThunderClap_Timer -= diff; - - //FireballVolley_Timer - if (HealthBelowPct(51)) - { - if (FireballVolley_Timer <= diff) - { - DoCastVictim(SPELL_FIREBALLVOLLEY); - FireballVolley_Timer = 15000; - } - else FireballVolley_Timer -= diff; - } - - //MightyBlow_Timer - if (MightyBlow_Timer <= diff) - { - DoCastVictim(SPELL_MIGHTYBLOW); - MightyBlow_Timer = 10000; - } - else MightyBlow_Timer -= diff; - - DoMeleeAttackIfReady(); + DoCastVictim(SPELL_MIGHTYBLOW); + _mightyBlowTimer = 10000; } - }; + else _mightyBlowTimer -= diff; + + DoMeleeAttackIfReady(); + } + +private: + uint32 _thunderClapTimer; + uint32 _fireballVolleyTimer; + uint32 _mightyBlowTimer; }; // npc_lokhtos_darkbargainer @@ -518,12 +468,11 @@ enum LokhtosSpells SPELL_CREATE_THORIUM_BROTHERHOOD_CONTRACT_DND = 23059 }; -class npc_lokhtos_darkbargainer : public CreatureScript +struct npc_lokhtos_darkbargainer : public ScriptedAI { -public: - npc_lokhtos_darkbargainer() : CreatureScript("npc_lokhtos_darkbargainer") { } + npc_lokhtos_darkbargainer(Creature* creature) : ScriptedAI(creature) { } - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override + void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override { ClearGossipMenuFor(player); if (action == GOSSIP_ACTION_INFO_DEF + 1) @@ -532,32 +481,26 @@ public: player->CastSpell(player, SPELL_CREATE_THORIUM_BROTHERHOOD_CONTRACT_DND, false); } if (action == GOSSIP_ACTION_TRADE) - player->GetSession()->SendListInventory(creature->GetGUID()); - - return true; + player->GetSession()->SendListInventory(me->GetGUID()); } - bool OnGossipHello(Player* player, Creature* creature) override + void sGossipHello(Player* player) override { - if (creature->IsQuestGiver()) - player->PrepareQuestMenu(creature->GetGUID()); + if (me->IsQuestGiver()) + player->PrepareQuestMenu(me->GetGUID()); - if (creature->IsVendor() && player->GetReputationRank(59) >= REP_FRIENDLY) + if (me->IsVendor() && player->GetReputationRank(59) >= REP_FRIENDLY) AddGossipItemFor(player, 4781, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_TRADE); if (player->GetQuestRewardStatus(QUEST_A_BINDING_CONTRACT) != 1 && !player->HasItemCount(ITEM_THRORIUM_BROTHERHOOD_CONTRACT, 1, true) && player->HasItemCount(ITEM_SULFURON_INGOT)) - { AddGossipItemFor(player, 4781, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - } if (player->GetReputationRank(59) < REP_FRIENDLY) - SendGossipMenuFor(player, 3673, creature->GetGUID()); + SendGossipMenuFor(player, 3673, me->GetGUID()); else - SendGossipMenuFor(player, 3677, creature->GetGUID()); - - return true; + SendGossipMenuFor(player, 3677, me->GetGUID()); } }; @@ -577,19 +520,29 @@ enum RocknotQuests QUEST_ALE = 4295 }; -class npc_rocknot : public CreatureScript +struct npc_rocknot : public npc_escortAI { -public: - npc_rocknot() : CreatureScript("npc_rocknot") { } - - bool OnQuestReward(Player* /*player*/, Creature* creature, Quest const* quest, uint32 /*item*/) override + npc_rocknot(Creature* creature) : npc_escortAI(creature) + { + instance = creature->GetInstanceScript(); + } + + void Reset() override + { + if (HasEscortState(STATE_ESCORT_ESCORTING)) + return; + + _breakKegTimer = 0; + _breakDoorTimer = 0; + } + + void sQuestReward(Player* /*player*/, Quest const* quest, uint32 /*opt*/) override { - InstanceScript* instance = creature->GetInstanceScript(); if (!instance) - return true; + return; if (instance->GetData(TYPE_BAR) == DONE || instance->GetData(TYPE_BAR) == SPECIAL) - return true; + return; if (quest->GetQuestId() == QUEST_ALE) { @@ -601,120 +554,92 @@ public: //keep track of amount in instance script, returns SPECIAL if amount ok and event in progress if (instance->GetData(TYPE_BAR) == SPECIAL) { - creature->AI()->Talk(SAY_GOT_BEER); - creature->CastSpell(creature, SPELL_DRUNKEN_RAGE, false); - - if (npc_escortAI* escortAI = CAST_AI(npc_rocknot::npc_rocknotAI, creature->AI())) - { - creature->SetWalk(true); - escortAI->Start(false); - } + Talk(SAY_GOT_BEER); + me->CastSpell(me, SPELL_DRUNKEN_RAGE, false); + me->SetWalk(true); + Start(false); } } - - return true; } - CreatureAI* GetAI(Creature* creature) const override + void DoGo(uint32 id, uint32 state) { - return GetBlackrockDepthsAI(creature); + if (GameObject* go = instance->instance->GetGameObject(instance->GetGuidData(id))) + go->SetGoState((GOState)state); } - struct npc_rocknotAI : public npc_escortAI + void WaypointReached(uint32 waypointId) override { - npc_rocknotAI(Creature* creature) : npc_escortAI(creature) + switch (waypointId) { - instance = creature->GetInstanceScript(); + case 1: + me->HandleEmoteCommand(EMOTE_ONESHOT_KICK); + break; + case 2: + me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK_UNARMED); + break; + case 3: + me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK_UNARMED); + break; + case 4: + me->HandleEmoteCommand(EMOTE_ONESHOT_KICK); + break; + case 5: + me->HandleEmoteCommand(EMOTE_ONESHOT_KICK); + _breakKegTimer = 2000; + break; } + } - InstanceScript* instance; - - uint32 BreakKeg_Timer; - uint32 BreakDoor_Timer; - - void Reset() override + void UpdateAI(uint32 diff) override + { + if (_breakKegTimer) { - if (HasEscortState(STATE_ESCORT_ESCORTING)) - return; - - BreakKeg_Timer = 0; - BreakDoor_Timer = 0; - } - - void DoGo(uint32 id, uint32 state) - { - if (GameObject* go = instance->instance->GetGameObject(instance->GetGuidData(id))) - go->SetGoState((GOState)state); - } - - void WaypointReached(uint32 waypointId) override - { - switch (waypointId) + if (_breakKegTimer <= diff) { - case 1: - me->HandleEmoteCommand(EMOTE_ONESHOT_KICK); - break; - case 2: - me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK_UNARMED); - break; - case 3: - me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK_UNARMED); - break; - case 4: - me->HandleEmoteCommand(EMOTE_ONESHOT_KICK); - break; - case 5: - me->HandleEmoteCommand(EMOTE_ONESHOT_KICK); - BreakKeg_Timer = 2000; - break; + DoGo(DATA_GO_BAR_KEG, 0); + _breakKegTimer = 0; + _breakDoorTimer = 1000; } + else _breakKegTimer -= diff; } - void UpdateAI(uint32 diff) override + if (_breakDoorTimer) { - if (BreakKeg_Timer) + if (_breakDoorTimer <= diff) { - if (BreakKeg_Timer <= diff) - { - DoGo(DATA_GO_BAR_KEG, 0); - BreakKeg_Timer = 0; - BreakDoor_Timer = 1000; - } - else BreakKeg_Timer -= diff; + DoGo(DATA_GO_BAR_DOOR, 2); + DoGo(DATA_GO_BAR_KEG_TRAP, 0); //doesn't work very well, leaving code here for future + //spell by trap has effect61, this indicate the bar go hostile + + if (Unit* tmp = ObjectAccessor::GetUnit(*me, instance->GetGuidData(DATA_PHALANX))) + tmp->SetFaction(FACTION_MONSTER); + + //for later, this event(s) has alot more to it. + //optionally, DONE can trigger bar to go hostile. + instance->SetData(TYPE_BAR, DONE); + + _breakDoorTimer = 0; } - - if (BreakDoor_Timer) - { - if (BreakDoor_Timer <= diff) - { - DoGo(DATA_GO_BAR_DOOR, 2); - DoGo(DATA_GO_BAR_KEG_TRAP, 0); //doesn't work very well, leaving code here for future - //spell by trap has effect61, this indicate the bar go hostile - - if (Unit* tmp = ObjectAccessor::GetUnit(*me, instance->GetGuidData(DATA_PHALANX))) - tmp->SetFaction(FACTION_MONSTER); - - //for later, this event(s) has alot more to it. - //optionally, DONE can trigger bar to go hostile. - instance->SetData(TYPE_BAR, DONE); - - BreakDoor_Timer = 0; - } - else BreakDoor_Timer -= diff; - } - - npc_escortAI::UpdateAI(diff); + else _breakDoorTimer -= diff; } - }; + + npc_escortAI::UpdateAI(diff); + } + +private: + InstanceScript* instance; + uint32 _breakKegTimer; + uint32 _breakDoorTimer; }; void AddSC_blackrock_depths() { - new go_shadowforge_brazier(); + RegisterBlackrockDepthsGameObjectAI(go_shadowforge_brazier); new at_ring_of_law(); - new npc_grimstone(); - new npc_phalanx(); - new npc_lokhtos_darkbargainer(); - new npc_rocknot(); - new ironhand_guardian(); + RegisterBlackrockDepthsCreatureAI(npc_grimstone); + RegisterBlackrockDepthsCreatureAI(npc_phalanx); + RegisterBlackrockDepthsCreatureAI(npc_lokhtos_darkbargainer); + RegisterBlackrockDepthsCreatureAI(npc_rocknot); + RegisterBlackrockDepthsCreatureAI(brd_ironhand_guardian); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h index 82c587049..03d6aac22 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h @@ -151,4 +151,7 @@ inline AI* GetBlackrockDepthsAI(T* obj) return GetInstanceAI(obj, BRDScriptName); } +#define RegisterBlackrockDepthsCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetBlackrockDepthsAI) +#define RegisterBlackrockDepthsGameObjectAI(ai_name) RegisterGameObjectAIWithFactory(ai_name, GetBlackrockDepthsAI) + #endif diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_ambassador_flamelash.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_ambassador_flamelash.cpp index e60657290..e76062bd7 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_ambassador_flamelash.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_ambassador_flamelash.cpp @@ -50,219 +50,184 @@ const Position SummonPositions[7] = std::vector gobjectDwarfRunesEntry { 170578, 170579, 170580, 170581, 170582, 170583, 170584 }; -class boss_ambassador_flamelash : public CreatureScript +struct boss_ambassador_flamelash : public BossAI { -public: - boss_ambassador_flamelash() : CreatureScript("boss_ambassador_flamelash") { } + boss_ambassador_flamelash(Creature* creature) : BossAI(creature, BOSS_AMBASSADOR_FLAMELASH) { } - CreatureAI* GetAI(Creature* creature) const override + void DoAction(int32 param) override { - return GetBlackrockDepthsAI(creature); + switch (param) + { + case EVENT_SUMMON_SPIRITS: + events.ScheduleEvent(EVENT_SUMMON_SPIRITS, 12s, 14s); + break; + } } - struct boss_ambassador_flamelashAI : public BossAI + void Reset() override { - boss_ambassador_flamelashAI(Creature* creature) : BossAI(creature, BOSS_AMBASSADOR_FLAMELASH), summons(me) { } + _Reset(); + TurnRunes(false); + _foundValidPosition = false; + _validPosition.clear(); + } - EventMap _events; + void TurnRunes(bool mode) + { + // Active makes the runes burn, ready turns them off + GOState state = mode ? GO_STATE_ACTIVE : GO_STATE_READY; - // This will help reseting the boss - SummonList summons; + for (int RuneEntry : gobjectDwarfRunesEntry) + if (GameObject* dwarfRune = me->FindNearestGameObject(RuneEntry, 200.0f)) + dwarfRune->SetGoState(state); + } - // This will allow to find a valid position to spawn them - std::vector validPosition; - bool foundValidPosition = false; + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_SPELL_FIREBLAST, 2s); - void JustSummoned(Creature* cr) override { summons.Summon(cr); } + // Spawn 7 Embers initially + for (int i = 0; i < 4; ++i) + events.ScheduleEvent(EVENT_SUMMON_SPIRITS, 4s); - void DoAction(int32 param) override + // Activate the runes (Start burning) + TurnRunes(true); + + Talk(AGGRO_TEXT); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + TurnRunes(false); + } + + int getValidRandomPosition() + { + /* Generate a random position which + * have not been used in 4 summonings. + * Since we are calling the event whenever the Spirit + * dies and not all at the time, we need to save at + * least 4 positions until reseting the vector + */ + + // Searching a new position so reset this bool check + _foundValidPosition = false; + int randomPosition; + + while (!_foundValidPosition) { - switch (param) + /* When we have summoned 4 creatures, reset the vector + * so we can summon new spirits in other positions.*/ + if (_validPosition.size() == 4) + _validPosition.clear(); + + // The random ranges from the position 0 to the position 6 + randomPosition = urand(0, 6); + + // When we have an empty vector we can use any random position generated. + if (_validPosition.empty()) + _foundValidPosition = true; + + /* This check is done to avoid running the vector + * when it is empty. Because if it is empty, then any + * position can be used to summon Spirits. + */ + if (!_foundValidPosition) { - case EVENT_SUMMON_SPIRITS: - _events.ScheduleEvent(EVENT_SUMMON_SPIRITS, 12s, 14s); - break; - } - } - - void Reset() override - { - _events.Reset(); - summons.DespawnAll(); - TurnRunes(false); - foundValidPosition = false; - validPosition.clear(); - } - - void TurnRunes(bool mode) - { - // Active makes the runes burn, ready turns them off - GOState state = mode ? GO_STATE_ACTIVE : GO_STATE_READY; - - for (int RuneEntry : gobjectDwarfRunesEntry) - if (GameObject* dwarfRune = me->FindNearestGameObject(RuneEntry, 200.0f)) - dwarfRune->SetGoState(state); - } - - void JustEngagedWith(Unit* /*who*/) override - { - _events.ScheduleEvent(EVENT_SPELL_FIREBLAST, 2s); - - // Spawn 7 Embers initially - for (int i = 0; i < 4; ++i) - _events.ScheduleEvent(EVENT_SUMMON_SPIRITS, 4s); - - // Activate the runes (Start burning) - TurnRunes(true); - - Talk(AGGRO_TEXT); - } - - void JustDied(Unit* /*killer*/) override - { - TurnRunes(false); - _events.Reset(); - summons.DespawnAll(); - } - - int getValidRandomPosition() - { - /* Generate a random position which - * have not been used in 4 summonings. - * Since we are calling the event whenever the Spirit - * dies and not all at the time, we need to save at - * least 4 positions until reseting the vector - */ - - // Searching a new position so reset this bool check - foundValidPosition = false; - int randomPosition; - - while (!foundValidPosition) - { - /* When we have summoned 4 creatures, reset the vector - * so we can summon new spirits in other positions.*/ - if (validPosition.size() == 4) - validPosition.clear(); - - // The random ranges from the position 0 to the position 6 - randomPosition = urand(0, 6); - - // When we have an empty vector we can use any random position generated. - if (validPosition.empty()) - foundValidPosition = true; - - /* This check is done to avoid running the vector - * when it is empty. Because if it is empty, then any - * position can be used to summon Spirits. - */ - if (!foundValidPosition) + // Check every position inside the vector + for (int pos : _validPosition) { - // Check every position inside the vector - for (int pos : validPosition) + // If the random is different, we found a temporary true, + // until we find one that is equal, which means it has been used. + if (pos != randomPosition) + _foundValidPosition = true; + else { - // If the random is different, we found a temporary true, - // until we find one that is equal, which means it has been used. - if (pos != randomPosition) - foundValidPosition = true; - else - { - foundValidPosition = false; - break; - } + _foundValidPosition = false; + break; } } } - - // We found a valid position. Save it and return it to summon. - validPosition.emplace_back(randomPosition); - return randomPosition; } - void SummonSpirits() + // We found a valid position. Save it and return it to summon. + _validPosition.emplace_back(randomPosition); + return randomPosition; + } + + void SummonSpirits() + { + // Make the Spirits chase Ambassador Flamelash + me->SummonCreature(NPC_FIRE_SPIRIT, SummonPositions[getValidRandomPosition()], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_SUMMON_SPIRITS, 12s, 14s); + } + + void UpdateAI(uint32 diff) override + { + //Return since we have no target + if (!UpdateVictim()) + return; + + events.Update(diff); + + switch (events.ExecuteEvent()) { - // Make the Spirits chase Ambassador Flamelash - me->SummonCreature(NPC_FIRE_SPIRIT, SummonPositions[getValidRandomPosition()], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60 * IN_MILLISECONDS); - _events.ScheduleEvent(EVENT_SUMMON_SPIRITS, 12s, 14s); + case EVENT_SPELL_FIREBLAST: + DoCastVictim(SPELL_FIREBLAST); + events.ScheduleEvent(EVENT_SPELL_FIREBLAST, 7s); + break; + case EVENT_SUMMON_SPIRITS: + SummonSpirits(); + break; } - void UpdateAI(uint32 diff) override - { - //Return since we have no target - if (!UpdateVictim()) - return; + DoMeleeAttackIfReady(); + } - _events.Update(diff); - - switch (_events.ExecuteEvent()) - { - case EVENT_SPELL_FIREBLAST: - DoCastVictim(SPELL_FIREBLAST); - _events.ScheduleEvent(EVENT_SPELL_FIREBLAST, 7s); - break; - case EVENT_SUMMON_SPIRITS: - SummonSpirits(); - break; - } - - DoMeleeAttackIfReady(); - } - }; +private: + // This will allow to find a valid position to spawn them + std::vector _validPosition; + bool _foundValidPosition = false; }; -class npc_burning_spirit : public CreatureScript +struct npc_burning_spirit : public ScriptedAI { -public: - npc_burning_spirit() : CreatureScript("npc_burning_spirit") { } + npc_burning_spirit(Creature* creature) : ScriptedAI(creature) {} - struct npc_burning_spiritAI : public ScriptedAI + void IsSummonedBy(WorldObject* summoner) override { - npc_burning_spiritAI(Creature* creature) : ScriptedAI(creature) {} + if (!summoner->IsCreature()) + return; - void IsSummonedBy(WorldObject* summoner) override - { - if (!summoner->IsCreature()) - { - return; - } - - _flamelasherGUID = summoner->GetGUID(); - me->GetMotionMaster()->MoveFollow(summoner->ToCreature(), 0.f, 0.f); - } - - void EnterEvadeMode(EvadeReason /*why*/) override - { - if (Creature* flamelasher = ObjectAccessor::GetCreature(*me, _flamelasherGUID)) - { - me->GetMotionMaster()->MoveFollow(flamelasher, 5.f, 0.f); - } - } - - void MovementInform(uint32 type, uint32 /*id*/) override - { - if (type != FOLLOW_MOTION_TYPE) - return; - - if (Creature* flamelasher = ObjectAccessor::GetCreature(*me, _flamelasherGUID)) - { - flamelasher->CastSpell(flamelasher, SPELL_BURNING_SPIRIT); - Unit::Kill(flamelasher, me); - } - } - - private: - EventMap _events; - ObjectGuid _flamelasherGUID; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockDepthsAI(creature); + _flamelasherGUID = summoner->GetGUID(); + me->GetMotionMaster()->MoveFollow(summoner->ToCreature(), 0.f, 0.f); } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + if (Creature* flamelasher = ObjectAccessor::GetCreature(*me, _flamelasherGUID)) + me->GetMotionMaster()->MoveFollow(flamelasher, 5.f, 0.f); + } + + void MovementInform(uint32 type, uint32 /*id*/) override + { + if (type != FOLLOW_MOTION_TYPE) + return; + + if (Creature* flamelasher = ObjectAccessor::GetCreature(*me, _flamelasherGUID)) + { + flamelasher->CastSpell(flamelasher, SPELL_BURNING_SPIRIT); + Unit::Kill(flamelasher, me); + } + } + +private: + ObjectGuid _flamelasherGUID; }; void AddSC_boss_ambassador_flamelash() { - new boss_ambassador_flamelash(); - new npc_burning_spirit(); + RegisterBlackrockDepthsCreatureAI(boss_ambassador_flamelash); + RegisterBlackrockDepthsCreatureAI(npc_burning_spirit); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_anubshiah.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_anubshiah.cpp index f706bf5a0..1e7a9296a 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_anubshiah.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_anubshiah.cpp @@ -34,84 +34,67 @@ constexpr Milliseconds TIMER_CURSE_WEAKNESS = 12s; constexpr Milliseconds TIMER_DEMON_ARMOR = 3s; //virtually only cast once constexpr Milliseconds TIMER_ENVELOPING_WEB = 16s; -class boss_anubshiah : public CreatureScript +struct boss_anubshiah : public BossAI { -public: - boss_anubshiah() : CreatureScript("boss_anubshiah") { } + boss_anubshiah(Creature* creature) : BossAI(creature, DATA_ANUBSHIAH) { } - CreatureAI* GetAI(Creature* creature) const override + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockDepthsAI(creature); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_SHADOWBOLT, TIMER_SHADOWBOLT / 5); + events.ScheduleEvent(SPELL_CURSE_TONGUES, TIMER_CURSE_TONGUES / 5); + events.ScheduleEvent(SPELL_CURSE_WEAKNESS, TIMER_CURSE_WEAKNESS / 5); + events.ScheduleEvent(SPELL_DEMON_ARMOR, TIMER_DEMON_ARMOR / 5); + events.ScheduleEvent(SPELL_ENVELOPING_WEB, TIMER_ENVELOPING_WEB / 5); } - struct boss_anubshiahAI : public BossAI + void UpdateAI(uint32 diff) override { - boss_anubshiahAI(Creature* creature) : BossAI(creature, DATA_ANUBSHIAH) { } + //Return since we have no target + if (!UpdateVictim()) + return; - void JustEngagedWith(Unit* /*who*/) override + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_SHADOWBOLT, TIMER_SHADOWBOLT / 5); - events.ScheduleEvent(SPELL_CURSE_TONGUES, TIMER_CURSE_TONGUES / 5); - events.ScheduleEvent(SPELL_CURSE_WEAKNESS, TIMER_CURSE_WEAKNESS / 5); - events.ScheduleEvent(SPELL_DEMON_ARMOR, TIMER_DEMON_ARMOR / 5); - events.ScheduleEvent(SPELL_ENVELOPING_WEB, TIMER_ENVELOPING_WEB / 5); + switch (eventId) + { + case SPELL_SHADOWBOLT: + DoCastVictim(SPELL_SHADOWBOLT); + events.ScheduleEvent(SPELL_SHADOWBOLT, TIMER_SHADOWBOLT - 2s, TIMER_SHADOWBOLT + 2s); + break; + case SPELL_CURSE_TONGUES: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + DoCast(target, SPELL_CURSE_TONGUES); + events.ScheduleEvent(SPELL_CURSE_TONGUES, TIMER_CURSE_TONGUES - 2s, TIMER_CURSE_TONGUES + 2s); + break; + case SPELL_CURSE_WEAKNESS: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + DoCast(target, SPELL_CURSE_WEAKNESS); + events.ScheduleEvent(SPELL_CURSE_WEAKNESS, TIMER_CURSE_WEAKNESS - 2s, TIMER_CURSE_WEAKNESS + 2s); + break; + case SPELL_DEMON_ARMOR: + DoCast(me, SPELL_DEMON_ARMOR); + events.ScheduleEvent(SPELL_DEMON_ARMOR, TIMER_DEMON_ARMOR); + break; + case SPELL_ENVELOPING_WEB: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + DoCast(target, SPELL_ENVELOPING_WEB); + events.ScheduleEvent(SPELL_ENVELOPING_WEB, TIMER_ENVELOPING_WEB - 2s, TIMER_ENVELOPING_WEB + 2s); + break; + default: + break; + } } - - void UpdateAI(uint32 diff) override - { - //Return since we have no target - if (!UpdateVictim()) - { - return; - } - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_SHADOWBOLT: - DoCastVictim(SPELL_SHADOWBOLT); - events.ScheduleEvent(SPELL_SHADOWBOLT, TIMER_SHADOWBOLT - 2s, TIMER_SHADOWBOLT + 2s); - break; - case SPELL_CURSE_TONGUES: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - { - DoCast(target, SPELL_CURSE_TONGUES); - } - events.ScheduleEvent(SPELL_CURSE_TONGUES, TIMER_CURSE_TONGUES - 2s, TIMER_CURSE_TONGUES + 2s); - break; - case SPELL_CURSE_WEAKNESS: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - { - DoCast(target, SPELL_CURSE_WEAKNESS); - } - events.ScheduleEvent(SPELL_CURSE_WEAKNESS, TIMER_CURSE_WEAKNESS - 2s, TIMER_CURSE_WEAKNESS + 2s); - break; - case SPELL_DEMON_ARMOR: - DoCast(me, SPELL_DEMON_ARMOR); - events.ScheduleEvent(SPELL_DEMON_ARMOR, TIMER_DEMON_ARMOR); - break; - case SPELL_ENVELOPING_WEB: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - DoCast(target, SPELL_ENVELOPING_WEB); - events.ScheduleEvent(SPELL_ENVELOPING_WEB, TIMER_ENVELOPING_WEB - 2s, TIMER_ENVELOPING_WEB + 2s); - break; - default: - break; - } - } - DoMeleeAttackIfReady(); - } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_anubshiah() { - new boss_anubshiah(); + RegisterBlackrockDepthsCreatureAI(boss_anubshiah); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_emperor_dagran_thaurissan.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_emperor_dagran_thaurissan.cpp index cb97adeb7..2b725fef1 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_emperor_dagran_thaurissan.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_emperor_dagran_thaurissan.cpp @@ -34,106 +34,87 @@ enum Spells #define DATA_PERCENT_DEAD_SENATORS 0 -class boss_emperor_dagran_thaurissan : public CreatureScript +struct boss_emperor_dagran_thaurissan : public BossAI { -public: - boss_emperor_dagran_thaurissan() : CreatureScript("boss_emperor_dagran_thaurissan") { } + uint32 hasYelled = 0; + uint32 SenatorYells[5] = {3, 4, 5, 6, 7}; // IDs in creature_text database - CreatureAI* GetAI(Creature* creature) const override + boss_emperor_dagran_thaurissan(Creature* creature) : BossAI(creature, DATA_EMPEROR){} + + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockDepthsAI(creature); + if (hasYelled != 5) + Talk(YELL_SENATORS_ALIVE); + else + Talk(YELL_SENATORS_DEAD); + + me->CallForHelp(VISIBLE_RANGE); + events.ScheduleEvent(SPELL_HANDOFTHAURISSAN, 4s, 7s); + events.ScheduleEvent(SPELL_AVATAROFFLAME, 10s, 12s); } - struct boss_draganthaurissanAI : public BossAI + void KilledUnit(Unit* /*victim*/) override { - uint32 hasYelled = 0; - uint32 SenatorYells[5] = {3, 4, 5, 6, 7}; // IDs in creature_text database + Talk(SAY_SLAY); + } - boss_draganthaurissanAI(Creature* creature) : BossAI(creature, DATA_EMPEROR){} - - void JustEngagedWith(Unit* /*who*/) override + void SetData(uint32 type, uint32 data) override + { + if (type == DATA_PERCENT_DEAD_SENATORS) { - if (hasYelled != 5) + if (data >= 20 * (hasYelled + 1)) // map the 5 yells to %. Yell after 20,40,60,80,100% { - Talk(YELL_SENATORS_ALIVE); - } - else - { - Talk(YELL_SENATORS_DEAD); - } - - me->CallForHelp(VISIBLE_RANGE); - events.ScheduleEvent(SPELL_HANDOFTHAURISSAN, 4s, 7s); - events.ScheduleEvent(SPELL_AVATAROFFLAME, 10s, 12s); - } - - void KilledUnit(Unit* /*victim*/) override - { - Talk(SAY_SLAY); - } - - void SetData(uint32 type, uint32 data) override - { - if (type == DATA_PERCENT_DEAD_SENATORS) - { - if (data >= 20 * (hasYelled + 1)) // map the 5 yells to %. Yell after 20,40,60,80,100% - { - if (hasYelled < 5) - { - if (me->IsAlive()) - { - Talk(SenatorYells[hasYelled]); - } - } - hasYelled++; - } + if (hasYelled < 5) + if (me->IsAlive()) + Talk(SenatorYells[hasYelled]); + hasYelled++; } } + } - void JustDied(Unit* /*killer*/) override + void JustDied(Unit* /*killer*/) override + { + if (Creature* Moira = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_MOIRA))) { - if (Creature* Moira = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_MOIRA))) + Moira->AI()->EnterEvadeMode(); + Moira->AI()->Talk(0); + Moira->SetFaction(FACTION_FRIENDLY); + } + } + + void UpdateAI(uint32 diff) override + { + //Return since we have no target + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) { - Moira->AI()->EnterEvadeMode(); - Moira->AI()->Talk(0); - Moira->SetFaction(FACTION_FRIENDLY); + case SPELL_HANDOFTHAURISSAN: + DoCast(SelectTarget(SelectTargetMethod::Random), SPELL_HANDOFTHAURISSAN); + //DoCastVictim(SPELL_HANDOFTHAURISSAN); + events.ScheduleEvent(SPELL_HANDOFTHAURISSAN, 4s, 7s); + break; + case SPELL_AVATAROFFLAME: + DoCastSelf(SPELL_AVATAROFFLAME); + events.ScheduleEvent(SPELL_AVATAROFFLAME, 23s, 27s); + break; + default: + break; } } - - void UpdateAI(uint32 diff) override - { - //Return since we have no target - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_HANDOFTHAURISSAN: - DoCast(SelectTarget(SelectTargetMethod::Random), SPELL_HANDOFTHAURISSAN); - //DoCastVictim(SPELL_HANDOFTHAURISSAN); - events.ScheduleEvent(SPELL_HANDOFTHAURISSAN, 4s, 7s); - break; - case SPELL_AVATAROFFLAME: - DoCastSelf(SPELL_AVATAROFFLAME); - events.ScheduleEvent(SPELL_AVATAROFFLAME, 23s, 27s); - break; - default: - break; - } - } - DoMeleeAttackIfReady(); - } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_draganthaurissan() { - new boss_emperor_dagran_thaurissan(); + RegisterBlackrockDepthsCreatureAI(boss_emperor_dagran_thaurissan); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_eviscerator.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_eviscerator.cpp index 3952b9f9c..b02b0ce31 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_eviscerator.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_eviscerator.cpp @@ -30,78 +30,65 @@ constexpr Milliseconds TIMER_SHADOWBOLT_VOLLEY = 7s; constexpr Milliseconds TIMER_REND = 20s; constexpr Milliseconds TIMER_SHIELD = 12s; -class boss_eviscerator : public CreatureScript +struct boss_eviscerator : public BossAI { -public: - boss_eviscerator() : CreatureScript("boss_eviscerator") {} + boss_eviscerator(Creature* creature) : BossAI(creature, DATA_EVISCERATOR) {} - CreatureAI* GetAI(Creature* creature) const override + bool SpellShieldReady = false; + + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockDepthsAI(creature); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_SHADOWBOLT_VOLLEY, TIMER_SHADOWBOLT_VOLLEY / 5); + events.ScheduleEvent(SPELL_REND, TIMER_REND / 5); + events.ScheduleEvent(SPELL_SHIELD, TIMER_SHIELD / 5); } - struct boss_evisceratorAI : public BossAI + void DamageTaken(Unit* /* doneBy */, uint32& /* damage */, DamageEffectType /* damagetype */, SpellSchoolMask damageSchoolMask) override { - boss_evisceratorAI(Creature* creature) : BossAI(creature, DATA_EVISCERATOR) {} - - bool SpellShieldReady = false; - - void JustEngagedWith(Unit* /*who*/) override + if ((damageSchoolMask & SPELL_SCHOOL_MASK_MAGIC) && SpellShieldReady) { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_SHADOWBOLT_VOLLEY, TIMER_SHADOWBOLT_VOLLEY / 5); - events.ScheduleEvent(SPELL_REND, TIMER_REND / 5); - events.ScheduleEvent(SPELL_SHIELD, TIMER_SHIELD / 5); + DoCast(SPELL_SHIELD); + SpellShieldReady = false; + events.ScheduleEvent(SPELL_SHIELD, TIMER_SHIELD); } + } - void DamageTaken(Unit* /* doneBy */, uint32& /* damage */, DamageEffectType /* damagetype */, SpellSchoolMask damageSchoolMask) override + void UpdateAI(uint32 diff) override + { + //Return since we have no target + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - if ((damageSchoolMask & SPELL_SCHOOL_MASK_MAGIC) && SpellShieldReady) + switch (eventId) { - DoCast(SPELL_SHIELD); - SpellShieldReady = false; - events.ScheduleEvent(SPELL_SHIELD, TIMER_SHIELD); + case SPELL_SHADOWBOLT_VOLLEY: + DoCastVictim(SPELL_SHADOWBOLT_VOLLEY); + events.ScheduleEvent(SPELL_SHADOWBOLT_VOLLEY, TIMER_SHADOWBOLT_VOLLEY - 2s, TIMER_SHADOWBOLT_VOLLEY + 2s); + break; + case SPELL_REND: + DoCastVictim(SPELL_REND); + events.ScheduleEvent(SPELL_REND, TIMER_REND - 2s, TIMER_REND + 2s); + break; + case SPELL_SHIELD: + SpellShieldReady = true; + break; + default: + break; } } - - void UpdateAI(uint32 diff) override - { - //Return since we have no target - if (!UpdateVictim()) - { - return; - } - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_SHADOWBOLT_VOLLEY: - DoCastVictim(SPELL_SHADOWBOLT_VOLLEY); - events.ScheduleEvent(SPELL_SHADOWBOLT_VOLLEY, TIMER_SHADOWBOLT_VOLLEY - 2s, TIMER_SHADOWBOLT_VOLLEY + 2s); - break; - case SPELL_REND: - DoCastVictim(SPELL_REND); - events.ScheduleEvent(SPELL_REND, TIMER_REND - 2s, TIMER_REND + 2s); - break; - case SPELL_SHIELD: - SpellShieldReady = true; - break; - default: - break; - } - } - DoMeleeAttackIfReady(); - } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_eviscerator() { - new boss_eviscerator(); + RegisterBlackrockDepthsCreatureAI(boss_eviscerator); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_general_angerforge.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_general_angerforge.cpp index d04b97eca..21b6597df 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_general_angerforge.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_general_angerforge.cpp @@ -26,108 +26,94 @@ enum Spells SPELL_CLEAVE = 20691 }; -class boss_general_angerforge : public CreatureScript +struct boss_general_angerforge : public ScriptedAI { -public: - boss_general_angerforge() : CreatureScript("boss_general_angerforge") { } + boss_general_angerforge(Creature* creature) : ScriptedAI(creature) { } - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockDepthsAI(creature); + _mightyBlowTimer = 8000; + _hamStringTimer = 12000; + _cleaveTimer = 16000; + _addsTimer = 0; + _medics = false; } - struct boss_general_angerforgeAI : public ScriptedAI + void JustEngagedWith(Unit* /*who*/) override { } + + void SummonAdds(Unit* victim) { - boss_general_angerforgeAI(Creature* creature) : ScriptedAI(creature) { } + if (Creature* SummonedAdd = DoSpawnCreature(8901, float(irand(-14, 14)), float(irand(-14, 14)), 0, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 120000)) + SummonedAdd->AI()->AttackStart(victim); + } - uint32 MightyBlow_Timer; - uint32 HamString_Timer; - uint32 Cleave_Timer; - uint32 Adds_Timer; - bool Medics; + void SummonMedics(Unit* victim) + { + if (Creature* SummonedMedic = DoSpawnCreature(8894, float(irand(-9, 9)), float(irand(-9, 9)), 0, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 120000)) + SummonedMedic->AI()->AttackStart(victim); + } - void Reset() override + void UpdateAI(uint32 diff) override + { + //Return since we have no target + if (!UpdateVictim()) + return; + + if (_mightyBlowTimer <= diff) { - MightyBlow_Timer = 8000; - HamString_Timer = 12000; - Cleave_Timer = 16000; - Adds_Timer = 0; - Medics = false; + DoCastVictim(SPELL_MIGHTYBLOW); + _mightyBlowTimer = 18000; + } + else _mightyBlowTimer -= diff; + + if (_hamStringTimer <= diff) + { + DoCastVictim(SPELL_HAMSTRING); + _hamStringTimer = 15000; + } + else _hamStringTimer -= diff; + + if (_cleaveTimer <= diff) + { + DoCastVictim(SPELL_CLEAVE); + _cleaveTimer = 9000; + } + else _cleaveTimer -= diff; + + if (HealthBelowPct(21)) + { + if (_addsTimer <= diff) + { + // summon 3 Adds every 25s + SummonAdds(me->GetVictim()); + SummonAdds(me->GetVictim()); + SummonAdds(me->GetVictim()); + + _addsTimer = 25000; + } + else _addsTimer -= diff; } - void JustEngagedWith(Unit* /*who*/) override { } - - void SummonAdds(Unit* victim) + //Summon Medics + if (!_medics && HealthBelowPct(21)) { - if (Creature* SummonedAdd = DoSpawnCreature(8901, float(irand(-14, 14)), float(irand(-14, 14)), 0, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 120000)) - SummonedAdd->AI()->AttackStart(victim); + SummonMedics(me->GetVictim()); + SummonMedics(me->GetVictim()); + _medics = true; } - void SummonMedics(Unit* victim) - { - if (Creature* SummonedMedic = DoSpawnCreature(8894, float(irand(-9, 9)), float(irand(-9, 9)), 0, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 120000)) - SummonedMedic->AI()->AttackStart(victim); - } + DoMeleeAttackIfReady(); + } - void UpdateAI(uint32 diff) override - { - //Return since we have no target - if (!UpdateVictim()) - return; - - //MightyBlow_Timer - if (MightyBlow_Timer <= diff) - { - DoCastVictim(SPELL_MIGHTYBLOW); - MightyBlow_Timer = 18000; - } - else MightyBlow_Timer -= diff; - - //HamString_Timer - if (HamString_Timer <= diff) - { - DoCastVictim(SPELL_HAMSTRING); - HamString_Timer = 15000; - } - else HamString_Timer -= diff; - - //Cleave_Timer - if (Cleave_Timer <= diff) - { - DoCastVictim(SPELL_CLEAVE); - Cleave_Timer = 9000; - } - else Cleave_Timer -= diff; - - //Adds_Timer - if (HealthBelowPct(21)) - { - if (Adds_Timer <= diff) - { - // summon 3 Adds every 25s - SummonAdds(me->GetVictim()); - SummonAdds(me->GetVictim()); - SummonAdds(me->GetVictim()); - - Adds_Timer = 25000; - } - else Adds_Timer -= diff; - } - - //Summon Medics - if (!Medics && HealthBelowPct(21)) - { - SummonMedics(me->GetVictim()); - SummonMedics(me->GetVictim()); - Medics = true; - } - - DoMeleeAttackIfReady(); - } - }; +private: + uint32 _mightyBlowTimer; + uint32 _hamStringTimer; + uint32 _cleaveTimer; + uint32 _addsTimer; + bool _medics; }; void AddSC_boss_general_angerforge() { - new boss_general_angerforge(); + RegisterBlackrockDepthsCreatureAI(boss_general_angerforge); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_gorosh_the_dervish.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_gorosh_the_dervish.cpp index 43d93e397..449e7f801 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_gorosh_the_dervish.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_gorosh_the_dervish.cpp @@ -30,78 +30,65 @@ constexpr Milliseconds TIMER_WHIRLWIND = 12s; constexpr Milliseconds TIMER_MORTAL = 22s; constexpr Milliseconds TIMER_BLOODLUST = 30s; -class boss_gorosh_the_dervish : public CreatureScript +struct boss_gorosh_the_dervish : public BossAI { -public: - boss_gorosh_the_dervish() : CreatureScript("boss_gorosh_the_dervish") { } + boss_gorosh_the_dervish(Creature* creature) : BossAI(creature, DATA_GOROSH) { } - CreatureAI* GetAI(Creature* creature) const override + Milliseconds nextWhirlwindTime; + + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockDepthsAI(creature); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_WHIRLWIND, TIMER_WHIRLWIND / 5); + events.ScheduleEvent(SPELL_MORTALSTRIKE, TIMER_MORTAL / 5); + events.ScheduleEvent(SPELL_BLOODLUST, TIMER_BLOODLUST / 5); } - struct boss_gorosh_the_dervishAI : public BossAI + void UpdateAI(uint32 diff) override { - boss_gorosh_the_dervishAI(Creature* creature) : BossAI(creature, DATA_GOROSH) { } + // Return since we have no target + if (!UpdateVictim()) + return; - Milliseconds nextWhirlwindTime; + events.Update(diff); - void JustEngagedWith(Unit* /*who*/) override + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_WHIRLWIND, TIMER_WHIRLWIND / 5); - events.ScheduleEvent(SPELL_MORTALSTRIKE, TIMER_MORTAL / 5); - events.ScheduleEvent(SPELL_BLOODLUST, TIMER_BLOODLUST / 5); - } - - void UpdateAI(uint32 diff) override - { - // Return since we have no target - if (!UpdateVictim()) + switch (eventId) { - return; - } - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) + case SPELL_WHIRLWIND: + if (me->GetDistance2d(me->GetVictim()) < 10.0f) { - case SPELL_WHIRLWIND: - if (me->GetDistance2d(me->GetVictim()) < 10.0f) - { - DoCastVictim(SPELL_WHIRLWIND); - nextWhirlwindTime = randtime(TIMER_WHIRLWIND - 2s, TIMER_WHIRLWIND + 2s); - } - else - { - // reschedule sooner - nextWhirlwindTime = randtime(TIMER_WHIRLWIND - 2s, TIMER_WHIRLWIND + 2s) / 3; - } - events.ScheduleEvent(SPELL_WHIRLWIND, nextWhirlwindTime); - break; - case SPELL_MORTALSTRIKE: - DoCastVictim(SPELL_MORTALSTRIKE); - events.ScheduleEvent(SPELL_MORTALSTRIKE, TIMER_MORTAL - 2s, TIMER_MORTAL + 2s); - break; - case SPELL_BLOODLUST: - DoCastSelf(SPELL_BLOODLUST); - events.ScheduleEvent(SPELL_BLOODLUST, TIMER_BLOODLUST - 2s, TIMER_BLOODLUST + 2s); - break; - default: - break; + DoCastVictim(SPELL_WHIRLWIND); + nextWhirlwindTime = randtime(TIMER_WHIRLWIND - 2s, TIMER_WHIRLWIND + 2s); } + else + { + // reschedule sooner + nextWhirlwindTime = randtime(TIMER_WHIRLWIND - 2s, TIMER_WHIRLWIND + 2s) / 3; + } + events.ScheduleEvent(SPELL_WHIRLWIND, nextWhirlwindTime); + break; + case SPELL_MORTALSTRIKE: + DoCastVictim(SPELL_MORTALSTRIKE); + events.ScheduleEvent(SPELL_MORTALSTRIKE, TIMER_MORTAL - 2s, TIMER_MORTAL + 2s); + break; + case SPELL_BLOODLUST: + DoCastSelf(SPELL_BLOODLUST); + events.ScheduleEvent(SPELL_BLOODLUST, TIMER_BLOODLUST - 2s, TIMER_BLOODLUST + 2s); + break; + default: + break; } - DoMeleeAttackIfReady(); } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_gorosh_the_dervish() { - new boss_gorosh_the_dervish(); + RegisterBlackrockDepthsCreatureAI(boss_gorosh_the_dervish); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_grizzle.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_grizzle.cpp index eff584834..646f65992 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_grizzle.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_grizzle.cpp @@ -29,74 +29,60 @@ enum Grizzle constexpr Milliseconds TIMER_GROUNDTREMOR = 10s; constexpr Milliseconds TIMER_FRENZY = 15s; -class boss_grizzle : public CreatureScript +struct boss_grizzle : public BossAI { -public: - boss_grizzle() : CreatureScript("boss_grizzle") { } + boss_grizzle(Creature* creature) : BossAI(creature, DATA_GRIZZLE) {} - CreatureAI* GetAI(Creature* creature) const override + Milliseconds nextTremorTime; + + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockDepthsAI(creature); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_GROUNDTREMOR, TIMER_GROUNDTREMOR / 5); + events.ScheduleEvent(SPELL_FRENZY, TIMER_FRENZY / 5); } - struct boss_grizzleAI : public BossAI + void UpdateAI(uint32 diff) override { - boss_grizzleAI(Creature* creature) : BossAI(creature, DATA_GRIZZLE) {} + //Return since we have no target + if (!UpdateVictim()) + return; - Milliseconds nextTremorTime; + events.Update(diff); - void JustEngagedWith(Unit* /*who*/) override + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_GROUNDTREMOR, TIMER_GROUNDTREMOR / 5); - events.ScheduleEvent(SPELL_FRENZY, TIMER_FRENZY / 5); - } - - void UpdateAI(uint32 diff) override - { - //Return since we have no target - if (!UpdateVictim()) + switch (eventId) { - return; - } - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) + case SPELL_GROUNDTREMOR: + if (me->GetDistance2d(me->GetVictim()) < 10.0f) { - case SPELL_GROUNDTREMOR: - if (me->GetDistance2d(me->GetVictim()) < 10.0f) - { - DoCastVictim(SPELL_GROUNDTREMOR); - nextTremorTime = randtime(TIMER_GROUNDTREMOR - 2s, TIMER_GROUNDTREMOR + 2s); - } - else - { - nextTremorTime = randtime(TIMER_GROUNDTREMOR - 2s, TIMER_GROUNDTREMOR + 2s) / 3; - } - events.ScheduleEvent(SPELL_GROUNDTREMOR, nextTremorTime); - break; - case SPELL_FRENZY: - DoCastSelf(SPELL_FRENZY); - events.ScheduleEvent(SPELL_FRENZY, TIMER_FRENZY - 2s, TIMER_FRENZY + 2s); - Talk(EMOTE_FRENZY_KILL); - break; - default: - break; + DoCastVictim(SPELL_GROUNDTREMOR); + nextTremorTime = randtime(TIMER_GROUNDTREMOR - 2s, TIMER_GROUNDTREMOR + 2s); } + else + { + nextTremorTime = randtime(TIMER_GROUNDTREMOR - 2s, TIMER_GROUNDTREMOR + 2s) / 3; + } + events.ScheduleEvent(SPELL_GROUNDTREMOR, nextTremorTime); + break; + case SPELL_FRENZY: + DoCastSelf(SPELL_FRENZY); + events.ScheduleEvent(SPELL_FRENZY, TIMER_FRENZY - 2s, TIMER_FRENZY + 2s); + Talk(EMOTE_FRENZY_KILL); + break; + default: + break; } - DoMeleeAttackIfReady(); } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_grizzle() { - new boss_grizzle(); + RegisterBlackrockDepthsCreatureAI(boss_grizzle); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_hedrum.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_hedrum.cpp index 787c72780..c12f84f18 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_hedrum.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_hedrum.cpp @@ -30,70 +30,55 @@ constexpr Milliseconds TIMER_PARALYZING = 20s; constexpr Milliseconds TIMER_BANEFUL = 24s; constexpr Milliseconds TIMER_WEB_EXPLOSION = 20s; -class boss_hedrum : public CreatureScript +struct boss_hedrum : public BossAI { -public: - boss_hedrum() : CreatureScript("boss_hedrum") {} + boss_hedrum(Creature* creature) : BossAI(creature, DATA_HEDRUM) {} - CreatureAI* GetAI(Creature* creature) const override + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockDepthsAI(creature); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_PARALYZING, TIMER_PARALYZING / 5); + events.ScheduleEvent(SPELL_BANEFUL, TIMER_BANEFUL / 5); + events.ScheduleEvent(SPELL_WEB_EXPLOSION, TIMER_WEB_EXPLOSION / 5); } - struct boss_hedrumAI : public BossAI + void UpdateAI(uint32 diff) override { - boss_hedrumAI(Creature* creature) : BossAI(creature, DATA_HEDRUM) {} + // Return since we have no target + if (!UpdateVictim()) + return; - void JustEngagedWith(Unit* /*who*/) override + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_PARALYZING, TIMER_PARALYZING / 5); - events.ScheduleEvent(SPELL_BANEFUL, TIMER_BANEFUL / 5); - events.ScheduleEvent(SPELL_WEB_EXPLOSION, TIMER_WEB_EXPLOSION / 5); + switch (eventId) + { + case SPELL_PARALYZING: + DoCastVictim(SPELL_PARALYZING); + events.ScheduleEvent(SPELL_PARALYZING, TIMER_PARALYZING - 2s, TIMER_PARALYZING + 2s); + break; + case SPELL_BANEFUL: + DoCastVictim(SPELL_BANEFUL); + events.ScheduleEvent(SPELL_BANEFUL, TIMER_BANEFUL - 2s, TIMER_BANEFUL + 2s); + break; + case SPELL_WEB_EXPLOSION: + if (me->GetDistance2d(me->GetVictim()) < 100.0f) + DoCast(SPELL_WEB_EXPLOSION); + events.ScheduleEvent(SPELL_WEB_EXPLOSION, TIMER_WEB_EXPLOSION - 2s, TIMER_WEB_EXPLOSION + 2s); + break; + default: + break; + } } - - void UpdateAI(uint32 diff) override - { - // Return since we have no target - if (!UpdateVictim()) - { - return; - } - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_PARALYZING: - DoCastVictim(SPELL_PARALYZING); - events.ScheduleEvent(SPELL_PARALYZING, TIMER_PARALYZING - 2s, TIMER_PARALYZING + 2s); - break; - case SPELL_BANEFUL: - DoCastVictim(SPELL_BANEFUL); - events.ScheduleEvent(SPELL_BANEFUL, TIMER_BANEFUL - 2s, TIMER_BANEFUL + 2s); - break; - case SPELL_WEB_EXPLOSION: - if (me->GetDistance2d(me->GetVictim()) < 100.0f) - { - DoCast(SPELL_WEB_EXPLOSION); - } - events.ScheduleEvent(SPELL_WEB_EXPLOSION, TIMER_WEB_EXPLOSION - 2s, TIMER_WEB_EXPLOSION + 2s); - break; - default: - break; - } - } - DoMeleeAttackIfReady(); - } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_hedrum() { - new boss_hedrum(); + RegisterBlackrockDepthsCreatureAI(boss_hedrum); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_high_interrogator_gerstahn.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_high_interrogator_gerstahn.cpp index 87202e6f0..dc6ada187 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_high_interrogator_gerstahn.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_high_interrogator_gerstahn.cpp @@ -27,81 +27,67 @@ enum Spells SPELL_SHADOWSHIELD = 22417 }; -class boss_high_interrogator_gerstahn : public CreatureScript +struct boss_high_interrogator_gerstahn : public ScriptedAI { -public: - boss_high_interrogator_gerstahn() : CreatureScript("boss_high_interrogator_gerstahn") { } + boss_high_interrogator_gerstahn(Creature* creature) : ScriptedAI(creature) { } - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockDepthsAI(creature); + _shadowWordPainTimer = 4000; + _manaBurnTimer = 14000; + _psychicScreamTimer = 32000; + _shadowShieldTimer = 8000; } - struct boss_high_interrogator_gerstahnAI : public ScriptedAI + void JustEngagedWith(Unit* /*who*/) override { } + + void UpdateAI(uint32 diff) override { - boss_high_interrogator_gerstahnAI(Creature* creature) : ScriptedAI(creature) { } + //Return since we have no target + if (!UpdateVictim()) + return; - uint32 ShadowWordPain_Timer; - uint32 ManaBurn_Timer; - uint32 PsychicScream_Timer; - uint32 ShadowShield_Timer; - - void Reset() override + if (_shadowWordPainTimer <= diff) { - ShadowWordPain_Timer = 4000; - ManaBurn_Timer = 14000; - PsychicScream_Timer = 32000; - ShadowShield_Timer = 8000; + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + DoCast(target, SPELL_SHADOWWORDPAIN); + _shadowWordPainTimer = 7000; } + else _shadowWordPainTimer -= diff; - void JustEngagedWith(Unit* /*who*/) override { } - - void UpdateAI(uint32 diff) override + if (_manaBurnTimer <= diff) { - //Return since we have no target - if (!UpdateVictim()) - return; - - //ShadowWordPain_Timer - if (ShadowWordPain_Timer <= diff) - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - DoCast(target, SPELL_SHADOWWORDPAIN); - ShadowWordPain_Timer = 7000; - } - else ShadowWordPain_Timer -= diff; - - //ManaBurn_Timer - if (ManaBurn_Timer <= diff) - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - DoCast(target, SPELL_MANABURN); - ManaBurn_Timer = 10000; - } - else ManaBurn_Timer -= diff; - - //PsychicScream_Timer - if (PsychicScream_Timer <= diff) - { - DoCastVictim(SPELL_PSYCHICSCREAM); - PsychicScream_Timer = 30000; - } - else PsychicScream_Timer -= diff; - - //ShadowShield_Timer - if (ShadowShield_Timer <= diff) - { - DoCast(me, SPELL_SHADOWSHIELD); - ShadowShield_Timer = 25000; - } - else ShadowShield_Timer -= diff; - - DoMeleeAttackIfReady(); + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + DoCast(target, SPELL_MANABURN); + _manaBurnTimer = 10000; } - }; + else _manaBurnTimer -= diff; + + if (_psychicScreamTimer <= diff) + { + DoCastVictim(SPELL_PSYCHICSCREAM); + _psychicScreamTimer = 30000; + } + else _psychicScreamTimer -= diff; + + if (_shadowShieldTimer <= diff) + { + DoCast(me, SPELL_SHADOWSHIELD); + _shadowShieldTimer = 25000; + } + else _shadowShieldTimer -= diff; + + DoMeleeAttackIfReady(); + } + +private: + uint32 _shadowWordPainTimer; + uint32 _manaBurnTimer; + uint32 _psychicScreamTimer; + uint32 _shadowShieldTimer; }; void AddSC_boss_high_interrogator_gerstahn() { - new boss_high_interrogator_gerstahn(); + RegisterBlackrockDepthsCreatureAI(boss_high_interrogator_gerstahn); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_magmus.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_magmus.cpp index 8551a20b1..3e326e817 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_magmus.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_magmus.cpp @@ -25,66 +25,54 @@ enum Spells SPELL_WARSTOMP = 24375 }; -class boss_magmus : public CreatureScript +struct boss_magmus : public BossAI { -public: - boss_magmus() : CreatureScript("boss_magmus") { } + boss_magmus(Creature* creature) : BossAI(creature, TYPE_IRON_HALL) {} - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockDepthsAI(creature); + _Reset(); + instance->SetData(TYPE_IRON_HALL, NOT_STARTED); } - struct boss_magmusAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_magmusAI(Creature* creature) : BossAI(creature, TYPE_IRON_HALL) {} + instance->SetData(TYPE_IRON_HALL, IN_PROGRESS); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_WARSTOMP, 8s, 12s); + events.ScheduleEvent(SPELL_FIERYBURST, 4s, 8s); + } - void Reset() override - { - _Reset(); - instance->SetData(TYPE_IRON_HALL, NOT_STARTED); - } + void UpdateAI(uint32 diff) override + { + //Return since we have no target + if (!UpdateVictim()) + return; - void JustEngagedWith(Unit* /*who*/) override - { - instance->SetData(TYPE_IRON_HALL, IN_PROGRESS); - _JustEngagedWith(); - events.ScheduleEvent(SPELL_WARSTOMP, 8s, 12s); - events.ScheduleEvent(SPELL_FIERYBURST, 4s, 8s); - } + events.Update(diff); - void UpdateAI(uint32 diff) override + while (uint32 eventId = events.ExecuteEvent()) { - //Return since we have no target - if (!UpdateVictim()) + switch (eventId) { - return; + case SPELL_WARSTOMP: + DoCastVictim(SPELL_WARSTOMP); + events.ScheduleEvent(SPELL_WARSTOMP, 8s, 12s); + break; + case SPELL_FIERYBURST: + DoCastVictim(SPELL_FIERYBURST); + events.ScheduleEvent(SPELL_FIERYBURST, 4s, 8s); + break; + default: + break; } - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_WARSTOMP: - DoCastVictim(SPELL_WARSTOMP); - events.ScheduleEvent(SPELL_WARSTOMP, 8s, 12s); - break; - case SPELL_FIERYBURST: - DoCastVictim(SPELL_FIERYBURST); - events.ScheduleEvent(SPELL_FIERYBURST, 4s, 8s); - break; - default: - break; - } - } - - DoMeleeAttackIfReady(); } - }; + + DoMeleeAttackIfReady(); + } }; void AddSC_boss_magmus() { - new boss_magmus(); + RegisterBlackrockDepthsCreatureAI(boss_magmus); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_moira_bronzebeard.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_moira_bronzebeard.cpp index ddcbfaa9e..15804801a 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_moira_bronzebeard.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_moira_bronzebeard.cpp @@ -34,10 +34,10 @@ constexpr Milliseconds TIMER_RENEW = 12s; constexpr Milliseconds TIMER_SHADOWBOLT = 16s; constexpr Milliseconds TIMER_WORDPAIN = 12s; -struct boss_moira_bronzebeardAI : public BossAI +struct boss_moira_bronzebeard : public BossAI { // use a default value so we can inherit for priestess - boss_moira_bronzebeardAI(Creature* creature, uint32 data = DATA_MOIRA) : BossAI(creature, data) {} + boss_moira_bronzebeard(Creature* creature, uint32 data = DATA_MOIRA) : BossAI(creature, data) {} void JustEngagedWith(Unit* /*who*/) override { _JustEngagedWith(); @@ -49,15 +49,13 @@ struct boss_moira_bronzebeardAI : public BossAI void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } + events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } + while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) @@ -83,22 +81,18 @@ struct boss_moira_bronzebeardAI : public BossAI { Creature* emperor = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_EMPEROR)); if (emperor && emperor->HealthBelowPct(90)) - { DoCast(emperor, spell); - } else if (HealthBelowPct(90)) - { DoCastSelf(spell); - } events.ScheduleEvent(spell, timer - 2s, timer + 2s); } }; // high priestess should be fairly identical to Moira. // Running away when emperor dies is handled through GUID from emperor, therefore not relevant here. -struct boss_high_priestess_thaurissanAI : public boss_moira_bronzebeardAI +struct boss_high_priestess_thaurissan : public boss_moira_bronzebeard { - boss_high_priestess_thaurissanAI(Creature* creature) : boss_moira_bronzebeardAI(creature, DATA_PRIESTESS) {} + boss_high_priestess_thaurissan(Creature* creature) : boss_moira_bronzebeard(creature, DATA_PRIESTESS) {} void JustEngagedWith(Unit* /*who*/) override { @@ -110,18 +104,16 @@ struct boss_high_priestess_thaurissanAI : public boss_moira_bronzebeardAI events.ScheduleEvent(SPELL_SHADOWBOLT, TIMER_SHADOWBOLT / 2); } - void UpdateAI(uint32 diff) override + void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } + events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } + while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) @@ -148,34 +140,12 @@ struct boss_high_priestess_thaurissanAI : public boss_moira_bronzebeardAI } }; -class boss_moira_bronzebeard : public CreatureScript -{ -public: - boss_moira_bronzebeard() : CreatureScript("boss_moira_bronzebeard") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockDepthsAI(creature); - } -}; - -class boss_high_priestess_thaurissan : public CreatureScript -{ -public: - boss_high_priestess_thaurissan() : CreatureScript("boss_high_priestess_thaurissan") {} - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockDepthsAI(creature); - } -}; - void AddSC_boss_moira_bronzebeard() { - new boss_moira_bronzebeard(); + RegisterBlackrockDepthsCreatureAI(boss_moira_bronzebeard); } void AddSC_boss_high_priestess_thaurissan() { - new boss_high_priestess_thaurissan(); + RegisterBlackrockDepthsCreatureAI(boss_high_priestess_thaurissan); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_okthor.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_okthor.cpp index 0fd86d6be..688858ac0 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_okthor.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_okthor.cpp @@ -32,92 +32,74 @@ constexpr Milliseconds TIMER_ARCANE_EXPLOSION = 24s; constexpr Milliseconds TIMER_POLYMORPH = 12s; constexpr Milliseconds TIMER_SLOW = 15s; -class boss_okthor : public CreatureScript +struct boss_okthor : public BossAI { -public: - boss_okthor() : CreatureScript("boss_okthor") {} + boss_okthor(Creature* creature) : BossAI(creature, DATA_OKTHOR) {} - CreatureAI* GetAI(Creature* creature) const override + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockDepthsAI(creature); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_ARCANE_BOLT, TIMER_ARCANE_BOLT / 5); + events.ScheduleEvent(SPELL_ARCANE_EXPLOSION, TIMER_ARCANE_EXPLOSION / 5); + events.ScheduleEvent(SPELL_POLYMORPH, TIMER_POLYMORPH / 5); + events.ScheduleEvent(SPELL_SLOW, 500ms); } - struct boss_okthorAI : public BossAI + void UpdateAI(uint32 diff) override { - boss_okthorAI(Creature* creature) : BossAI(creature, DATA_OKTHOR) {} + //Return since we have no target + if (!UpdateVictim()) + return; - Milliseconds nextArcaneExplosionTime; + events.Update(diff); - void JustEngagedWith(Unit* /*who*/) override + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_ARCANE_BOLT, TIMER_ARCANE_BOLT / 5); - events.ScheduleEvent(SPELL_ARCANE_EXPLOSION, TIMER_ARCANE_EXPLOSION / 5); - events.ScheduleEvent(SPELL_POLYMORPH, TIMER_POLYMORPH / 5); - events.ScheduleEvent(SPELL_SLOW, 500ms); - } - - void UpdateAI(uint32 diff) override - { - //Return since we have no target - if (!UpdateVictim()) + switch (eventId) { - return; - } - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) + case SPELL_ARCANE_BOLT: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + DoCast(target, SPELL_ARCANE_BOLT); + events.ScheduleEvent(SPELL_ARCANE_BOLT, TIMER_ARCANE_BOLT - 2s, TIMER_ARCANE_BOLT + 2s); + break; + case SPELL_ARCANE_EXPLOSION: + if (me->GetDistance2d(me->GetVictim()) < 50.0f) { - case SPELL_ARCANE_BOLT: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - { - DoCast(target, SPELL_ARCANE_BOLT); - } - events.ScheduleEvent(SPELL_ARCANE_BOLT, TIMER_ARCANE_BOLT - 2s, TIMER_ARCANE_BOLT + 2s); - break; - case SPELL_ARCANE_EXPLOSION: - if (me->GetDistance2d(me->GetVictim()) < 50.0f) - { - DoCast(SPELL_ARCANE_EXPLOSION); - nextArcaneExplosionTime = randtime(TIMER_ARCANE_EXPLOSION - 2s, TIMER_ARCANE_EXPLOSION + 2s); - } - else - { - nextArcaneExplosionTime = randtime(TIMER_ARCANE_EXPLOSION - 2s, TIMER_ARCANE_EXPLOSION + 2s) / 3; - } - events.ScheduleEvent(SPELL_ARCANE_EXPLOSION, nextArcaneExplosionTime); - break; - case SPELL_POLYMORPH: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - { - DoCast(target, SPELL_POLYMORPH); - } - events.ScheduleEvent(SPELL_POLYMORPH, TIMER_POLYMORPH - 2s, TIMER_POLYMORPH + 2s); - break; - case SPELL_SLOW: - if (me->GetDistance2d(me->GetVictim()) < 50.0f) - { - DoCast(SPELL_SLOW); - } - events.ScheduleEvent(SPELL_SLOW, TIMER_SLOW); - break; - - default: - break; + DoCast(SPELL_ARCANE_EXPLOSION); + _nextArcaneExplosionTime = randtime(TIMER_ARCANE_EXPLOSION - 2s, TIMER_ARCANE_EXPLOSION + 2s); } + else + { + _nextArcaneExplosionTime = randtime(TIMER_ARCANE_EXPLOSION - 2s, TIMER_ARCANE_EXPLOSION + 2s) / 3; + } + events.ScheduleEvent(SPELL_ARCANE_EXPLOSION, _nextArcaneExplosionTime); + break; + case SPELL_POLYMORPH: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + DoCast(target, SPELL_POLYMORPH); + events.ScheduleEvent(SPELL_POLYMORPH, TIMER_POLYMORPH - 2s, TIMER_POLYMORPH + 2s); + break; + case SPELL_SLOW: + if (me->GetDistance2d(me->GetVictim()) < 50.0f) + DoCast(SPELL_SLOW); + events.ScheduleEvent(SPELL_SLOW, TIMER_SLOW); + break; + + default: + break; } - DoMeleeAttackIfReady(); } - }; + DoMeleeAttackIfReady(); + } + +private: + Milliseconds _nextArcaneExplosionTime; }; void AddSC_boss_okthor() { - new boss_okthor(); + RegisterBlackrockDepthsCreatureAI(boss_okthor); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp index 3feabc95f..da9242590 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp @@ -53,19 +53,18 @@ enum Gossip SAY_QUEST_COMPLETED_END = 2606, // You have shown me your desire, and have payed with precious stone. I will teach you... }; -class boss_gloomrel : public CreatureScript +struct boss_gloomrel : public ScriptedAI { -public: - boss_gloomrel() : CreatureScript("boss_gloomrel") { } + boss_gloomrel(Creature* creature) : ScriptedAI(creature) { } - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override + void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override { ClearGossipMenuFor(player); switch (action) { case GOSSIP_ACTION_INFO_DEF+1: AddGossipItemFor(player, GOSSIP_TEXT_CONTINUE, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 11); - SendGossipMenuFor(player, SAY_QUEST_COMPLETED_END, creature->GetGUID()); + SendGossipMenuFor(player, SAY_QUEST_COMPLETED_END, me->GetGUID()); break; case GOSSIP_ACTION_INFO_DEF+11: CloseGossipMenuFor(player); @@ -73,33 +72,29 @@ public: break; case GOSSIP_ACTION_INFO_DEF+2: AddGossipItemFor(player, GOSSIP_TEXT_CONTINUE, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 22); - SendGossipMenuFor(player, SAY_QUEST_ACCEPTED, creature->GetGUID()); + SendGossipMenuFor(player, SAY_QUEST_ACCEPTED, me->GetGUID()); break; case GOSSIP_ACTION_INFO_DEF+22: CloseGossipMenuFor(player); - if (InstanceScript* instance = creature->GetInstanceScript()) - { + if (InstanceScript* instance = me->GetInstanceScript()) //are 5 minutes expected? go template may have data to despawn when used at quest instance->DoRespawnGameObject(instance->GetGuidData(DATA_GO_CHALICE), MINUTE * 5); - } break; } - return true; } - bool OnGossipHello(Player* player, Creature* creature) override + void sGossipHello(Player* player) override { if (player->GetQuestRewardStatus(QUEST_SPECTRAL_CHALICE) == 1 && player->GetSkillValue(SKILL_MINING) >= DATA_SKILLPOINT_MIN && !player->HasSpell(SPELL_SMELT_DARK_IRON)) { AddGossipItemFor(player, GOSSIP_GROOMREL, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, SAY_QUEST_COMPLETED, creature->GetGUID()); + SendGossipMenuFor(player, SAY_QUEST_COMPLETED, me->GetGUID()); } if (player->GetQuestRewardStatus(QUEST_SPECTRAL_CHALICE) == 0 && player->GetSkillValue(SKILL_MINING) >= DATA_SKILLPOINT_MIN) AddGossipItemFor(player, GOSSIP_GROOMREL, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID()); - return true; + SendGossipMenuFor(player, player->GetGossipTextId(me), me->GetGUID()); } }; @@ -120,145 +115,124 @@ enum DoomrelEvents EVENT_SPELL_SUMMON_VOIDWALKERS = 5, }; -class boss_doomrel : public CreatureScript +struct boss_doomrel : public ScriptedAI { -public: - boss_doomrel() : CreatureScript("boss_doomrel") { } + boss_doomrel(Creature* creature) : ScriptedAI(creature) + { + instance = creature->GetInstanceScript(); + } - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override + InstanceScript* instance; + EventMap _events; + bool Voidwalkers; + + void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override { ClearGossipMenuFor(player); switch (action) { case GOSSIP_ACTION_INFO_DEF+1: AddGossipItemFor(player, GOSSIP_TEXT_CONTINUE, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - SendGossipMenuFor(player, SAY_QUEST_COMPLETED, creature->GetGUID()); + SendGossipMenuFor(player, SAY_QUEST_COMPLETED, me->GetGUID()); break; case GOSSIP_ACTION_INFO_DEF+2: CloseGossipMenuFor(player); - creature->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); // Start encounter - InstanceScript* instance = creature->GetInstanceScript(); if (instance) - { instance->SetData(TYPE_TOMB_OF_SEVEN, IN_PROGRESS); - } - creature->AI()->Talk(SAY_START_FIGHT); + Talk(SAY_START_FIGHT); break; } - return true; } - bool OnGossipHello(Player* player, Creature* creature) override + void sGossipHello(Player* player) override { AddGossipItemFor(player, GOSSIP_DOOMREL_START_COMBAT, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, SAY_DOOMREL_HELLO, creature->GetGUID()); - - return true; + SendGossipMenuFor(player, SAY_DOOMREL_HELLO, me->GetGUID()); } - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockDepthsAI(creature); + Voidwalkers = false; + me->SetFaction(FACTION_FRIENDLY); + + // was set before event start, so set again + me->SetImmuneToPC(true); + + if (instance->GetData(TYPE_TOMB_OF_SEVEN) == DONE) // what is this trying to do? Probably some kind of crash recovery + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); + else + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_GOSSIP); } - struct boss_doomrelAI : public ScriptedAI + void JustEngagedWith(Unit* /*who*/) override { - boss_doomrelAI(Creature* creature) : ScriptedAI(creature) + _events.ScheduleEvent(EVENT_SPELL_SHADOWBOLTVOLLEY, 10s); + _events.ScheduleEvent(EVENT_SPELL_IMMOLATE, 18s); + _events.ScheduleEvent(EVENT_SPELL_CURSEOFWEAKNESS, 5s); + _events.ScheduleEvent(EVENT_SPELL_DEMONARMOR, 16s); + _events.ScheduleEvent(EVENT_SPELL_SUMMON_VOIDWALKERS, 1s); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + me->RemoveAllAuras(); + me->GetThreatMgr().ClearAllThreat(); + me->CombatStop(true); + me->LoadCreaturesAddon(true); + if (me->IsAlive()) + me->GetMotionMaster()->MoveTargetedHome(); + me->SetLootRecipient(nullptr); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + switch (_events.ExecuteEvent()) { - instance = creature->GetInstanceScript(); + case EVENT_SPELL_SHADOWBOLTVOLLEY: + DoCastVictim(SPELL_SHADOWBOLTVOLLEY); + _events.ScheduleEvent(EVENT_SPELL_SHADOWBOLTVOLLEY, 12s); + break; + case EVENT_SPELL_IMMOLATE: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) + { + DoCast(target, SPELL_IMMOLATE); + _events.ScheduleEvent(EVENT_SPELL_IMMOLATE, 25s); + } + // Didn't get a target, try again in 1s + _events.ScheduleEvent(EVENT_SPELL_IMMOLATE, 1s); + break; + case EVENT_SPELL_CURSEOFWEAKNESS: + DoCastVictim(SPELL_CURSEOFWEAKNESS); + _events.ScheduleEvent(EVENT_SPELL_CURSEOFWEAKNESS, 45s); + break; + case EVENT_SPELL_DEMONARMOR: + DoCast(me, SPELL_DEMONARMOR); + _events.ScheduleEvent(EVENT_SPELL_DEMONARMOR, 300s); + break; + case EVENT_SPELL_SUMMON_VOIDWALKERS: + if (!Voidwalkers && HealthBelowPct(51)) + { + DoCastVictim(SPELL_SUMMON_VOIDWALKERS, true); + Voidwalkers = true; + } + // Not ready yet, try again in 1s + _events.ScheduleEvent(EVENT_SPELL_SUMMON_VOIDWALKERS, 1s); + break; } - InstanceScript* instance; - EventMap _events; - bool Voidwalkers; - - void Reset() override - { - Voidwalkers = false; - me->SetFaction(FACTION_FRIENDLY); - - // was set before event start, so set again - me->SetImmuneToPC(true); - - if (instance->GetData(TYPE_TOMB_OF_SEVEN) == DONE) // what is this trying to do? Probably some kind of crash recovery - { - me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); - } - else - { - me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_GOSSIP); - } - } - - void JustEngagedWith(Unit* /*who*/) override - { - _events.ScheduleEvent(EVENT_SPELL_SHADOWBOLTVOLLEY, 10s); - _events.ScheduleEvent(EVENT_SPELL_IMMOLATE, 18s); - _events.ScheduleEvent(EVENT_SPELL_CURSEOFWEAKNESS, 5s); - _events.ScheduleEvent(EVENT_SPELL_DEMONARMOR, 16s); - _events.ScheduleEvent(EVENT_SPELL_SUMMON_VOIDWALKERS, 1s); - } - - void EnterEvadeMode(EvadeReason /*why*/) override - { - me->RemoveAllAuras(); - me->GetThreatMgr().ClearAllThreat(); - me->CombatStop(true); - me->LoadCreaturesAddon(true); - if (me->IsAlive()) - me->GetMotionMaster()->MoveTargetedHome(); - me->SetLootRecipient(nullptr); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - _events.Update(diff); - - switch (_events.ExecuteEvent()) - { - case EVENT_SPELL_SHADOWBOLTVOLLEY: - DoCastVictim(SPELL_SHADOWBOLTVOLLEY); - _events.ScheduleEvent(EVENT_SPELL_SHADOWBOLTVOLLEY, 12s); - break; - case EVENT_SPELL_IMMOLATE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - { - DoCast(target, SPELL_IMMOLATE); - _events.ScheduleEvent(EVENT_SPELL_IMMOLATE, 25s); - } - // Didn't get a target, try again in 1s - _events.ScheduleEvent(EVENT_SPELL_IMMOLATE, 1s); - break; - case EVENT_SPELL_CURSEOFWEAKNESS: - DoCastVictim(SPELL_CURSEOFWEAKNESS); - _events.ScheduleEvent(EVENT_SPELL_CURSEOFWEAKNESS, 45s); - break; - case EVENT_SPELL_DEMONARMOR: - DoCast(me, SPELL_DEMONARMOR); - _events.ScheduleEvent(EVENT_SPELL_DEMONARMOR, 300s); - break; - case EVENT_SPELL_SUMMON_VOIDWALKERS: - if (!Voidwalkers && HealthBelowPct(51)) - { - DoCastVictim(SPELL_SUMMON_VOIDWALKERS, true); - Voidwalkers = true; - } - // Not ready yet, try again in 1s - _events.ScheduleEvent(EVENT_SPELL_SUMMON_VOIDWALKERS, 1s); - break; - } - - DoMeleeAttackIfReady(); - } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_tomb_of_seven() { - new boss_gloomrel(); - new boss_doomrel(); + RegisterBlackrockDepthsCreatureAI(boss_gloomrel); + RegisterBlackrockDepthsCreatureAI(boss_doomrel); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp index f6c487ba9..678885ce2 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp @@ -95,762 +95,715 @@ private: Creature* _boss; }; -class instance_blackrock_depths : public InstanceMapScript +struct instance_blackrock_depths : public InstanceScript { -public: - instance_blackrock_depths() : InstanceMapScript("instance_blackrock_depths", MAP_BLACKROCK_DEPTHS) { } - - InstanceScript* GetInstanceScript(InstanceMap* map) const override + instance_blackrock_depths(Map* map) : InstanceScript(map) { - return new instance_blackrock_depths_InstanceMapScript(map); + SetHeaders(DataHeader); } - struct instance_blackrock_depths_InstanceMapScript : public InstanceScript + uint32 encounter[MAX_ENCOUNTER]; + std::string str_data; + + ObjectGuid EmperorGUID; + ObjectGuid PhalanxGUID; + ObjectGuid MagmusGUID; + ObjectGuid MoiraGUID; + ObjectGuid PriestessGUID; + ObjectGuid IronhandGUID[6]; + ObjectGuid CorenGUID; + + ObjectGuid GoArena1GUID; + ObjectGuid GoArena2GUID; + ObjectGuid GoArena3GUID; + ObjectGuid GoArena4GUID; + ObjectGuid GoShadowLockGUID; + ObjectGuid GoShadowMechGUID; + ObjectGuid GoShadowGiantGUID; + ObjectGuid GoShadowDummyGUID; + ObjectGuid GoBarKegGUID; + ObjectGuid GoBarKegTrapGUID; + ObjectGuid GoBarDoorGUID; + ObjectGuid GoTombEnterGUID; + ObjectGuid GoTombExitGUID; + ObjectGuid GoLyceumGUID; + ObjectGuid GoSFSGUID; + ObjectGuid GoSFNGUID; + ObjectGuid GoGolemNGUID; + ObjectGuid GoGolemSGUID; + ObjectGuid GoThroneGUID; + ObjectGuid GoChestGUID; + ObjectGuid GoSpectralChaliceGUID; + + uint32 BarAleCount; + uint32 GhostKillCount; + ObjectGuid TombBossGUIDs[7]; + uint32 tombResetTimer; + uint32 TombTimer; + uint32 TombEventCounter; + uint32 OpenedCoofers; + uint32 IronhandCounter; + + GuidList ArgelmachAdds; + ObjectGuid ArgelmachGUID; + + TempSummon* TempSummonGrimstone = nullptr; + Position GrimstonePositon = Position(625.559f, -205.618f, -52.735f, 2.609f); + time_t timeRingFail = 0; + uint8 arenaMobsToSpawn; + uint8 arenaBossToSpawn; + + std::vector ArenaSpectators; + Position CenterOfRingOfLaw = Position(595.289, -186.56); + + ObjectGuid EmperorSenators[5]; + std::vector EmperorSenatorsVector; + Position EmperorSpawnPos = Position(1380.52, -831, 115); + + void OnPlayerEnter(Player* /* player */) override { - instance_blackrock_depths_InstanceMapScript(Map* map) : InstanceScript(map) + ReplaceMoiraIfSaved(); // In case a player joins the party during the run + // SetData(TYPE_RING_OF_LAW, DONE); + } + + void ReplaceMoiraIfSaved() + { + ObjectGuid* GUIDToReplace = &PriestessGUID; // default to having Moira + ObjectGuid* GUIDToSpawn = &MoiraGUID; + uint32 NPCEntry = NPC_MOIRA; + bool MoiraSaved = true; + + // check if all players saved her. + Map::PlayerList const& lPlayers = instance->GetPlayers(); + if (!lPlayers.IsEmpty()) { - SetHeaders(DataHeader); - } - - uint32 encounter[MAX_ENCOUNTER]; - std::string str_data; - - ObjectGuid EmperorGUID; - ObjectGuid PhalanxGUID; - ObjectGuid MagmusGUID; - ObjectGuid MoiraGUID; - ObjectGuid PriestessGUID; - ObjectGuid IronhandGUID[6]; - ObjectGuid CorenGUID; - - ObjectGuid GoArena1GUID; - ObjectGuid GoArena2GUID; - ObjectGuid GoArena3GUID; - ObjectGuid GoArena4GUID; - ObjectGuid GoShadowLockGUID; - ObjectGuid GoShadowMechGUID; - ObjectGuid GoShadowGiantGUID; - ObjectGuid GoShadowDummyGUID; - ObjectGuid GoBarKegGUID; - ObjectGuid GoBarKegTrapGUID; - ObjectGuid GoBarDoorGUID; - ObjectGuid GoTombEnterGUID; - ObjectGuid GoTombExitGUID; - ObjectGuid GoLyceumGUID; - ObjectGuid GoSFSGUID; - ObjectGuid GoSFNGUID; - ObjectGuid GoGolemNGUID; - ObjectGuid GoGolemSGUID; - ObjectGuid GoThroneGUID; - ObjectGuid GoChestGUID; - ObjectGuid GoSpectralChaliceGUID; - - uint32 BarAleCount; - uint32 GhostKillCount; - ObjectGuid TombBossGUIDs[7]; - uint32 tombResetTimer; - uint32 TombTimer; - uint32 TombEventCounter; - uint32 OpenedCoofers; - uint32 IronhandCounter; - - GuidList ArgelmachAdds; - ObjectGuid ArgelmachGUID; - - TempSummon* TempSummonGrimstone = nullptr; - Position GrimstonePositon = Position(625.559f, -205.618f, -52.735f, 2.609f); - time_t timeRingFail = 0; - uint8 arenaMobsToSpawn; - uint8 arenaBossToSpawn; - - std::vector ArenaSpectators; - Position CenterOfRingOfLaw = Position(595.289, -186.56); - - ObjectGuid EmperorSenators[5]; - std::vector EmperorSenatorsVector; - Position EmperorSpawnPos = Position(1380.52, -831, 115); - - void OnPlayerEnter(Player* /* player */) override - { - ReplaceMoiraIfSaved(); // In case a player joins the party during the run - // SetData(TYPE_RING_OF_LAW, DONE); - } - - void ReplaceMoiraIfSaved() - { - ObjectGuid* GUIDToReplace = &PriestessGUID; // default to having Moira - ObjectGuid* GUIDToSpawn = &MoiraGUID; - uint32 NPCEntry = NPC_MOIRA; - bool MoiraSaved = true; - - // check if all players saved her. - Map::PlayerList const& lPlayers = instance->GetPlayers(); - if (!lPlayers.IsEmpty()) + for (Map::PlayerList::const_iterator itr = lPlayers.begin(); itr != lPlayers.end(); ++itr) { - for (Map::PlayerList::const_iterator itr = lPlayers.begin(); itr != lPlayers.end(); ++itr) + if (Player* player = itr->GetSource()) { - if (Player* player = itr->GetSource()) - { - // set to false if this player hasn't saved her. Another player can't put it to true. - MoiraSaved = MoiraSaved && ((player->GetQuestStatus(PRINCESS_QUEST_HORDE) == QUEST_STATUS_REWARDED) - || (player->GetQuestStatus(PRINCESS_QUEST_ALLIANCE) == QUEST_STATUS_REWARDED)); - } + // set to false if this player hasn't saved her. Another player can't put it to true. + MoiraSaved = MoiraSaved && ((player->GetQuestStatus(PRINCESS_QUEST_HORDE) == QUEST_STATUS_REWARDED) + || (player->GetQuestStatus(PRINCESS_QUEST_ALLIANCE) == QUEST_STATUS_REWARDED)); } } - - // assign correct GUIDs and spawn targets - if (MoiraSaved) - { - GUIDToReplace = &MoiraGUID; - GUIDToSpawn = &PriestessGUID; - NPCEntry = NPC_PRIESTESS; - } - - if (Creature* CreatureToReplace = instance->GetCreature(*GUIDToReplace)) - { - Creature* NewSpawn = instance->SummonCreature(NPCEntry, CreatureToReplace->GetPosition()); - CreatureToReplace->RemoveFromWorld(); - *GUIDToSpawn = NewSpawn->GetGUID(); - } } - void Initialize() override + // assign correct GUIDs and spawn targets + if (MoiraSaved) { - memset(&encounter, 0, sizeof(encounter)); - - BarAleCount = 0; - GhostKillCount = 0; - TombTimer = TIMER_TOMB_START; - TombEventCounter = 0; - tombResetTimer = 0; - OpenedCoofers = 0; - IronhandCounter = 0; - ArenaSpectators.clear(); - - // these are linked to the dungeon and not how many times the arena started. - arenaMobsToSpawn = urand(0, 5); - arenaBossToSpawn = urand(0, 5); + GUIDToReplace = &MoiraGUID; + GUIDToSpawn = &PriestessGUID; + NPCEntry = NPC_PRIESTESS; } - void OnCreatureCreate(Creature* creature) override + if (Creature* CreatureToReplace = instance->GetCreature(*GUIDToReplace)) { - switch (creature->GetEntry()) - { - case NPC_EMPEROR: - EmperorGUID = creature->GetGUID(); - break; - case NPC_PHALANX: - PhalanxGUID = creature->GetGUID(); - break; - case NPC_MOIRA: - MoiraGUID = creature->GetGUID(); - break; - case NPC_COREN_DIREBREW: - CorenGUID = creature->GetGUID(); - break; - case NPC_ANGERREL: - TombBossGUIDs[0] = creature->GetGUID(); - break; - case NPC_SEETHREL: - TombBossGUIDs[1] = creature->GetGUID(); - break; - case NPC_DOPEREL: - TombBossGUIDs[2] = creature->GetGUID(); - break; - case NPC_GLOOMREL: - TombBossGUIDs[3] = creature->GetGUID(); - break; - case NPC_VILEREL: - TombBossGUIDs[4] = creature->GetGUID(); - break; - case NPC_HATEREL: - TombBossGUIDs[5] = creature->GetGUID(); - break; - case NPC_DOOMREL: - TombBossGUIDs[6] = creature->GetGUID(); - break; - case NPC_MAGMUS: - MagmusGUID = creature->GetGUID(); - if (!creature->IsAlive()) - HandleGameObject(GoThroneGUID, true); // if Magmus is dead open door to last boss - break; - case NPC_WEAPON_TECHNICIAN: - case NPC_DOOMFORGE_ARCANASMITH: - case NPC_RAGEREAVER_GOLEM: - case NPC_WRATH_HAMMER_CONSTRUCT: - if (creature->IsAlive() && creature->GetPositionZ() < -51.5f && creature->GetPositionZ() > -55.f) - { - ArgelmachAdds.push_back(creature->GetGUID()); - } - break; - case NPC_GOLEM_LORD_ARGELMACH: - ArgelmachGUID = creature->GetGUID(); - break; - case NPC_IRONHAND_GUARDIAN: - IronhandGUID[IronhandCounter] = creature->GetGUID(); - IronhandCounter++; - break; - case NPC_ARENA_SPECTATOR: - ArenaSpectators.push_back(creature->GetGUID()); - if (encounter[TYPE_RING_OF_LAW] == DONE) // added for crashes - { - creature->SetFaction(FACTION_NEUTRAL); - creature->SetReactState(REACT_DEFENSIVE); - } - break; - case NPC_SHADOWFORGE_PEASANT: - case NPC_SHADOWFORCE_CITIZEN: // both do the same - if (creature->GetDistance2d(CenterOfRingOfLaw.GetPositionX(), CenterOfRingOfLaw.GetPositionY()) < (float)RADIUS_RING_OF_LAW) - { - ArenaSpectators.push_back(creature->GetGUID()); - } - if (encounter[TYPE_RING_OF_LAW] == DONE) // added for crashes - { - creature->SetFaction(FACTION_NEUTRAL); - creature->SetReactState(REACT_DEFENSIVE); - } - break; - case NPC_SHADOWFORGE_SENATOR: - // keep track of Senators that are not too far from emperor. Can't really use emperor as creature due to him possibly not being spawned. - // some senators spawn at ring of law - if (creature->GetDistance2d(EmperorSpawnPos.GetPositionX(), EmperorSpawnPos.GetPositionY()) < (float)DISTANCE_EMPEROR_ROOM) - { - EmperorSenatorsVector.push_back(creature->GetGUID()); - } - break; - default: - break; - } + Creature* NewSpawn = instance->SummonCreature(NPCEntry, CreatureToReplace->GetPosition()); + CreatureToReplace->RemoveFromWorld(); + *GUIDToSpawn = NewSpawn->GetGUID(); } + } - void OnGameObjectCreate(GameObject* go) override + void Initialize() override + { + memset(&encounter, 0, sizeof(encounter)); + + BarAleCount = 0; + GhostKillCount = 0; + TombTimer = TIMER_TOMB_START; + TombEventCounter = 0; + tombResetTimer = 0; + OpenedCoofers = 0; + IronhandCounter = 0; + ArenaSpectators.clear(); + + // these are linked to the dungeon and not how many times the arena started. + arenaMobsToSpawn = urand(0, 5); + arenaBossToSpawn = urand(0, 5); + } + + void OnCreatureCreate(Creature* creature) override + { + switch (creature->GetEntry()) { - switch (go->GetEntry()) - { - case GO_ARENA1: - GoArena1GUID = go->GetGUID(); - break; - case GO_ARENA2: - GoArena2GUID = go->GetGUID(); - break; - case GO_ARENA3: - GoArena3GUID = go->GetGUID(); - break; - case GO_ARENA4: - GoArena4GUID = go->GetGUID(); - break; - case GO_SHADOW_LOCK: - GoShadowLockGUID = go->GetGUID(); - break; - case GO_SHADOW_MECHANISM: - GoShadowMechGUID = go->GetGUID(); - break; - case GO_SHADOW_GIANT_DOOR: - GoShadowGiantGUID = go->GetGUID(); - break; - case GO_SHADOW_DUMMY: - GoShadowDummyGUID = go->GetGUID(); - break; - case GO_BAR_KEG_SHOT: - GoBarKegGUID = go->GetGUID(); - break; - case GO_BAR_KEG_TRAP: - GoBarKegTrapGUID = go->GetGUID(); - break; - case GO_BAR_DOOR: - GoBarDoorGUID = go->GetGUID(); - break; - case GO_TOMB_ENTER: - GoTombEnterGUID = go->GetGUID(); - break; - case GO_TOMB_EXIT: - GoTombExitGUID = go->GetGUID(); - if (GhostKillCount >= 7) - HandleGameObject(ObjectGuid::Empty, true, go); - else - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_LYCEUM: - GoLyceumGUID = go->GetGUID(); - break; - case GO_SF_S: - GoSFSGUID = go->GetGUID(); - break; - case GO_SF_N: - GoSFNGUID = go->GetGUID(); - break; - case GO_GOLEM_ROOM_N: - GoGolemNGUID = go->GetGUID(); - break; - case GO_GOLEM_ROOM_S: - GoGolemSGUID = go->GetGUID(); - break; - case GO_THRONE_ROOM: - GoThroneGUID = go->GetGUID(); - break; - case GO_CHEST_SEVEN: - GoChestGUID = go->GetGUID(); - break; - case GO_SPECTRAL_CHALICE: - GoSpectralChaliceGUID = go->GetGUID(); - break; - } - } - - void OnUnitDeath(Unit* unit) override - { - uint32 deadSenators = 0; - switch (unit->GetEntry()) - { - case NPC_WEAPON_TECHNICIAN: - case NPC_DOOMFORGE_ARCANASMITH: - case NPC_RAGEREAVER_GOLEM: - case NPC_WRATH_HAMMER_CONSTRUCT: - ArgelmachAdds.remove(unit->GetGUID()); - break; - case NPC_MAGMUS: - SetData(TYPE_IRON_HALL, DONE); - break; - case NPC_SHADOWFORGE_SENATOR: - deadSenators = 1; //hacky, but we cannot count the unit that just died through its state because OnUnitDeath() is called before the state is set. - for (const auto &senatorGUID: EmperorSenatorsVector) - { - if (Creature* senator = instance->GetCreature(senatorGUID)) - { - if (!senator->IsAlive() || senator->isDying()) - { - deadSenators++; - } - } - } - - if (Creature* emperor = instance->GetCreature(EmperorGUID)) - { - // send % of senators that died - emperor->AI()->SetData(0, (100 * deadSenators) / EmperorSenatorsVector.size()); - } - break; - case NPC_ANGERREL: - case NPC_DOPEREL: - case NPC_HATEREL: - case NPC_VILEREL: - case NPC_SEETHREL: - case NPC_GLOOMREL: - case NPC_DOOMREL: - GhostKillCount++; - if (GhostKillCount >= 7) - { - SetData(TYPE_TOMB_OF_SEVEN, DONE); - } - break; - default: - break; - } - } - - void SetData(uint32 type, uint32 data) override - { - LOG_DEBUG("scripts.ai", "Instance Blackrock Depths: SetData update (Type: {} Data {})", type, data); - - switch (type) - { - case TYPE_RING_OF_LAW: - encounter[0] = data; - switch (data) - { - case IN_PROGRESS: - TempSummonGrimstone = instance->SummonCreature(NPC_GRIMSTONE, GrimstonePositon); - break; - case FAIL: - if (TempSummonGrimstone) - { - TempSummonGrimstone->RemoveFromWorld(); - TempSummonGrimstone = nullptr; - timeRingFail = GameTime::GetGameTime().count(); - } - SetData(TYPE_RING_OF_LAW, NOT_STARTED); - break; - case DONE: - for (auto const& itr : ArenaSpectators) - { - if (Creature* spectator = instance->GetCreature(itr)) - { - spectator->SetFaction(FACTION_NEUTRAL); - spectator->SetReactState(REACT_DEFENSIVE); - } - } - break; - default: - break; - } - break; - case TYPE_VAULT: - encounter[1] = data; - break; - case TYPE_BAR: - if (data == SPECIAL) - ++BarAleCount; - else - encounter[2] = data; - break; - case TYPE_TOMB_OF_SEVEN: - encounter[3] = data; - switch (data) - { - case IN_PROGRESS: - HandleGameObject(GoTombExitGUID, false); - HandleGameObject(GoTombEnterGUID, false); - break; - case DONE: - DoRespawnGameObject(GoChestGUID, DAY); - HandleGameObject(GoTombExitGUID, true); - HandleGameObject(GoTombEnterGUID, true); - break; - } - break; - case TYPE_LYCEUM: - encounter[4] = data; - if (data == DONE) - { - HandleGameObject(GetGuidData(DATA_GOLEM_DOOR_N), true); - HandleGameObject(GetGuidData(DATA_GOLEM_DOOR_S), true); - if (Creature* magmus = instance->GetCreature(MagmusGUID)) - { - magmus->AI()->Talk(0); - } - ReplaceMoiraIfSaved(); // Need to place the correct final boss, but we need her to be spawned first. - } - break; - case TYPE_IRON_HALL: - encounter[5] = data; - switch (data) - { - case NOT_STARTED: - case IN_PROGRESS: - for (int i = 0; i < 6; i++) - { - if (Creature* ironhand = instance->GetCreature(IronhandGUID[i])) - { - ironhand->AI()->SetData(0, data == IN_PROGRESS); - } - } - break; - case DONE: - HandleGameObject(GetGuidData(DATA_THRONE_DOOR), true); - break; - default: - break; - } - break; - case DATA_OPEN_COFFER_DOORS: - OpenedCoofers += 1; - if (OpenedCoofers == 12) - { - Position pos = {812.15f, -348.91f, -50.579f, 0.7f}; - if (TempSummon* summon = instance->SummonCreature(NPC_WATCHMAN_DOOMGRIP, pos)) - summon->SetTempSummonType(TEMPSUMMON_MANUAL_DESPAWN); - } - break; - case DATA_GOLEM_LORD_ARGELMACH_INIT: + case NPC_EMPEROR: + EmperorGUID = creature->GetGUID(); + break; + case NPC_PHALANX: + PhalanxGUID = creature->GetGUID(); + break; + case NPC_MOIRA: + MoiraGUID = creature->GetGUID(); + break; + case NPC_COREN_DIREBREW: + CorenGUID = creature->GetGUID(); + break; + case NPC_ANGERREL: + TombBossGUIDs[0] = creature->GetGUID(); + break; + case NPC_SEETHREL: + TombBossGUIDs[1] = creature->GetGUID(); + break; + case NPC_DOPEREL: + TombBossGUIDs[2] = creature->GetGUID(); + break; + case NPC_GLOOMREL: + TombBossGUIDs[3] = creature->GetGUID(); + break; + case NPC_VILEREL: + TombBossGUIDs[4] = creature->GetGUID(); + break; + case NPC_HATEREL: + TombBossGUIDs[5] = creature->GetGUID(); + break; + case NPC_DOOMREL: + TombBossGUIDs[6] = creature->GetGUID(); + break; + case NPC_MAGMUS: + MagmusGUID = creature->GetGUID(); + if (!creature->IsAlive()) + HandleGameObject(GoThroneGUID, true); // if Magmus is dead open door to last boss + break; + case NPC_WEAPON_TECHNICIAN: + case NPC_DOOMFORGE_ARCANASMITH: + case NPC_RAGEREAVER_GOLEM: + case NPC_WRATH_HAMMER_CONSTRUCT: + if (creature->IsAlive() && creature->GetPositionZ() < -51.5f && creature->GetPositionZ() > -55.f) + ArgelmachAdds.push_back(creature->GetGUID()); + break; + case NPC_GOLEM_LORD_ARGELMACH: + ArgelmachGUID = creature->GetGUID(); + break; + case NPC_IRONHAND_GUARDIAN: + IronhandGUID[IronhandCounter] = creature->GetGUID(); + IronhandCounter++; + break; + case NPC_ARENA_SPECTATOR: + ArenaSpectators.push_back(creature->GetGUID()); + if (encounter[TYPE_RING_OF_LAW] == DONE) // added for crashes { - if (Creature* argelmach = instance->GetCreature(ArgelmachGUID)) + creature->SetFaction(FACTION_NEUTRAL); + creature->SetReactState(REACT_DEFENSIVE); + } + break; + case NPC_SHADOWFORGE_PEASANT: + case NPC_SHADOWFORCE_CITIZEN: // both do the same + if (creature->GetDistance2d(CenterOfRingOfLaw.GetPositionX(), CenterOfRingOfLaw.GetPositionY()) < (float)RADIUS_RING_OF_LAW) + ArenaSpectators.push_back(creature->GetGUID()); + if (encounter[TYPE_RING_OF_LAW] == DONE) // added for crashes + { + creature->SetFaction(FACTION_NEUTRAL); + creature->SetReactState(REACT_DEFENSIVE); + } + break; + case NPC_SHADOWFORGE_SENATOR: + // keep track of Senators that are not too far from emperor. Can't really use emperor as creature due to him possibly not being spawned. + // some senators spawn at ring of law + if (creature->GetDistance2d(EmperorSpawnPos.GetPositionX(), EmperorSpawnPos.GetPositionY()) < (float)DISTANCE_EMPEROR_ROOM) + EmperorSenatorsVector.push_back(creature->GetGUID()); + break; + default: + break; + } + } + + void OnGameObjectCreate(GameObject* go) override + { + switch (go->GetEntry()) + { + case GO_ARENA1: + GoArena1GUID = go->GetGUID(); + break; + case GO_ARENA2: + GoArena2GUID = go->GetGUID(); + break; + case GO_ARENA3: + GoArena3GUID = go->GetGUID(); + break; + case GO_ARENA4: + GoArena4GUID = go->GetGUID(); + break; + case GO_SHADOW_LOCK: + GoShadowLockGUID = go->GetGUID(); + break; + case GO_SHADOW_MECHANISM: + GoShadowMechGUID = go->GetGUID(); + break; + case GO_SHADOW_GIANT_DOOR: + GoShadowGiantGUID = go->GetGUID(); + break; + case GO_SHADOW_DUMMY: + GoShadowDummyGUID = go->GetGUID(); + break; + case GO_BAR_KEG_SHOT: + GoBarKegGUID = go->GetGUID(); + break; + case GO_BAR_KEG_TRAP: + GoBarKegTrapGUID = go->GetGUID(); + break; + case GO_BAR_DOOR: + GoBarDoorGUID = go->GetGUID(); + break; + case GO_TOMB_ENTER: + GoTombEnterGUID = go->GetGUID(); + break; + case GO_TOMB_EXIT: + GoTombExitGUID = go->GetGUID(); + if (GhostKillCount >= 7) + HandleGameObject(ObjectGuid::Empty, true, go); + else + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_LYCEUM: + GoLyceumGUID = go->GetGUID(); + break; + case GO_SF_S: + GoSFSGUID = go->GetGUID(); + break; + case GO_SF_N: + GoSFNGUID = go->GetGUID(); + break; + case GO_GOLEM_ROOM_N: + GoGolemNGUID = go->GetGUID(); + break; + case GO_GOLEM_ROOM_S: + GoGolemSGUID = go->GetGUID(); + break; + case GO_THRONE_ROOM: + GoThroneGUID = go->GetGUID(); + break; + case GO_CHEST_SEVEN: + GoChestGUID = go->GetGUID(); + break; + case GO_SPECTRAL_CHALICE: + GoSpectralChaliceGUID = go->GetGUID(); + break; + } + } + + void OnUnitDeath(Unit* unit) override + { + uint32 deadSenators = 0; + switch (unit->GetEntry()) + { + case NPC_WEAPON_TECHNICIAN: + case NPC_DOOMFORGE_ARCANASMITH: + case NPC_RAGEREAVER_GOLEM: + case NPC_WRATH_HAMMER_CONSTRUCT: + ArgelmachAdds.remove(unit->GetGUID()); + break; + case NPC_MAGMUS: + SetData(TYPE_IRON_HALL, DONE); + break; + case NPC_SHADOWFORGE_SENATOR: + deadSenators = 1; //hacky, but we cannot count the unit that just died through its state because OnUnitDeath() is called before the state is set. + for (const auto &senatorGUID: EmperorSenatorsVector) + if (Creature* senator = instance->GetCreature(senatorGUID)) + if (!senator->IsAlive() || senator->isDying()) + deadSenators++; + + if (Creature* emperor = instance->GetCreature(EmperorGUID)) + // send % of senators that died + emperor->AI()->SetData(0, (100 * deadSenators) / EmperorSenatorsVector.size()); + break; + case NPC_ANGERREL: + case NPC_DOPEREL: + case NPC_HATEREL: + case NPC_VILEREL: + case NPC_SEETHREL: + case NPC_GLOOMREL: + case NPC_DOOMREL: + GhostKillCount++; + if (GhostKillCount >= 7) + SetData(TYPE_TOMB_OF_SEVEN, DONE); + break; + default: + break; + } + } + + void SetData(uint32 type, uint32 data) override + { + LOG_DEBUG("scripts.ai", "Instance Blackrock Depths: SetData update (Type: {} Data {})", type, data); + + switch (type) + { + case TYPE_RING_OF_LAW: + encounter[0] = data; + switch (data) + { + case IN_PROGRESS: + TempSummonGrimstone = instance->SummonCreature(NPC_GRIMSTONE, GrimstonePositon); + break; + case FAIL: + if (TempSummonGrimstone) { - GuidList adds = ArgelmachAdds; - for (GuidList::const_iterator itr = adds.begin(); itr != adds.end();) + TempSummonGrimstone->RemoveFromWorld(); + TempSummonGrimstone = nullptr; + timeRingFail = GameTime::GetGameTime().count(); + } + SetData(TYPE_RING_OF_LAW, NOT_STARTED); + break; + case DONE: + for (auto const& itr : ArenaSpectators) + if (Creature* spectator = instance->GetCreature(itr)) { - if (Creature* argelmachAdd = instance->GetCreature(*itr)) + spectator->SetFaction(FACTION_NEUTRAL); + spectator->SetReactState(REACT_DEFENSIVE); + } + break; + default: + break; + } + break; + case TYPE_VAULT: + encounter[1] = data; + break; + case TYPE_BAR: + if (data == SPECIAL) + ++BarAleCount; + else + encounter[2] = data; + break; + case TYPE_TOMB_OF_SEVEN: + encounter[3] = data; + switch (data) + { + case IN_PROGRESS: + HandleGameObject(GoTombExitGUID, false); + HandleGameObject(GoTombEnterGUID, false); + break; + case DONE: + DoRespawnGameObject(GoChestGUID, DAY); + HandleGameObject(GoTombExitGUID, true); + HandleGameObject(GoTombEnterGUID, true); + break; + } + break; + case TYPE_LYCEUM: + encounter[4] = data; + if (data == DONE) + { + HandleGameObject(GetGuidData(DATA_GOLEM_DOOR_N), true); + HandleGameObject(GetGuidData(DATA_GOLEM_DOOR_S), true); + if (Creature* magmus = instance->GetCreature(MagmusGUID)) + magmus->AI()->Talk(0); + ReplaceMoiraIfSaved(); // Need to place the correct final boss, but we need her to be spawned first. + } + break; + case TYPE_IRON_HALL: + encounter[5] = data; + switch (data) + { + case NOT_STARTED: + case IN_PROGRESS: + for (int i = 0; i < 6; i++) + if (Creature* ironhand = instance->GetCreature(IronhandGUID[i])) + ironhand->AI()->SetData(0, data == IN_PROGRESS); + break; + case DONE: + HandleGameObject(GetGuidData(DATA_THRONE_DOOR), true); + break; + default: + break; + } + break; + case DATA_OPEN_COFFER_DOORS: + OpenedCoofers += 1; + if (OpenedCoofers == 12) + { + Position pos = {812.15f, -348.91f, -50.579f, 0.7f}; + if (TempSummon* summon = instance->SummonCreature(NPC_WATCHMAN_DOOMGRIP, pos)) + summon->SetTempSummonType(TEMPSUMMON_MANUAL_DESPAWN); + } + break; + case DATA_GOLEM_LORD_ARGELMACH_INIT: + { + if (Creature* argelmach = instance->GetCreature(ArgelmachGUID)) + { + GuidList adds = ArgelmachAdds; + for (GuidList::const_iterator itr = adds.begin(); itr != adds.end();) + { + if (Creature* argelmachAdd = instance->GetCreature(*itr)) + { + if (argelmachAdd->GetEntry() == NPC_WRATH_HAMMER_CONSTRUCT) { - if (argelmachAdd->GetEntry() == NPC_WRATH_HAMMER_CONSTRUCT) + argelmachAdd->RemoveAurasDueToSpell(SPELL_STONED); + argelmachAdd->AI()->AttackStart(argelmach->GetVictim()); + itr = adds.erase(itr); + } + else if (argelmachAdd->GetEntry() == NPC_RAGEREAVER_GOLEM) + { + if (argelmachAdd->IsWithinDist2d(argelmach, 10.f)) { argelmachAdd->RemoveAurasDueToSpell(SPELL_STONED); argelmachAdd->AI()->AttackStart(argelmach->GetVictim()); itr = adds.erase(itr); } - else if (argelmachAdd->GetEntry() == NPC_RAGEREAVER_GOLEM) - { - if (argelmachAdd->IsWithinDist2d(argelmach, 10.f)) - { - argelmachAdd->RemoveAurasDueToSpell(SPELL_STONED); - argelmachAdd->AI()->AttackStart(argelmach->GetVictim()); - itr = adds.erase(itr); - } - else - ++itr; - } else - { ++itr; - } } else { ++itr; } } - - if (!adds.empty()) - { - argelmach->SetReactState(REACT_PASSIVE); - argelmach->SetTarget(); - argelmach->AI()->SetData(DATA_GOLEM_LORD_ARGELMACH_INIT, IN_PROGRESS); - } else { - argelmach->AI()->SetData(DATA_GOLEM_LORD_ARGELMACH_INIT, DONE); + ++itr; } } - break; - } - case DATA_GOLEM_LORD_ARGELMACH_ADDS: - { - if (Creature* argelmach = instance->GetCreature(ArgelmachGUID)) + + if (!adds.empty()) { - argelmach->HandleEmoteCommand(EMOTE_ONESHOT_SHOUT); - argelmach->m_Events.AddEventAtOffset(new RestoreAttack(argelmach), 3s); - - for (ObjectGuid const& argelmachAddGUID : ArgelmachAdds) - { - if (Creature* argelmachAdd = instance->GetCreature(argelmachAddGUID)) - { - if (!argelmachAdd->IsInCombat()) - { - argelmachAdd->RemoveAurasDueToSpell(SPELL_STONED); - argelmachAdd->AI()->AttackStart(argelmach->GetVictim()); - } - } - } + argelmach->SetReactState(REACT_PASSIVE); + argelmach->SetTarget(); + argelmach->AI()->SetData(DATA_GOLEM_LORD_ARGELMACH_INIT, IN_PROGRESS); } - break; - } - default: - break; - } - - if (data == DONE || GhostKillCount >= 7) - { - OUT_SAVE_INST_DATA; - - std::ostringstream saveStream; - saveStream << encounter[0] << ' ' << encounter[1] << ' ' << encounter[2] << ' ' - << encounter[3] << ' ' << encounter[4] << ' ' << encounter[5] << ' ' << GhostKillCount; - - str_data = saveStream.str(); - - SaveToDB(); - OUT_SAVE_INST_DATA_COMPLETE; - } - } - - uint32 GetData(uint32 type) const override - { - switch (type) - { - case TYPE_RING_OF_LAW: - return encounter[0]; - case TYPE_VAULT: - return encounter[1]; - case TYPE_BAR: - if (encounter[2] == IN_PROGRESS && BarAleCount == 3) - return SPECIAL; else - return encounter[2]; - case TYPE_TOMB_OF_SEVEN: - return encounter[3]; - case TYPE_LYCEUM: - return encounter[4]; - case TYPE_IRON_HALL: - return encounter[5]; - case DATA_TIME_RING_FAIL: - return timeRingFail; - case DATA_ARENA_MOBS: - return arenaMobsToSpawn; - case DATA_ARENA_BOSS: - return arenaBossToSpawn; - } - return 0; - } - - ObjectGuid GetGuidData(uint32 data) const override - { - switch (data) - { - case DATA_EMPEROR: - return EmperorGUID; - case DATA_PHALANX: - return PhalanxGUID; - case DATA_MOIRA: - return MoiraGUID; - case DATA_COREN: - return CorenGUID; - case DATA_ARENA1: - return GoArena1GUID; - case DATA_ARENA2: - return GoArena2GUID; - case DATA_ARENA3: - return GoArena3GUID; - case DATA_ARENA4: - return GoArena4GUID; - case DATA_GO_BAR_KEG: - return GoBarKegGUID; - case DATA_GO_BAR_KEG_TRAP: - return GoBarKegTrapGUID; - case DATA_GO_BAR_DOOR: - return GoBarDoorGUID; - case DATA_SF_BRAZIER_N: - return GoSFNGUID; - case DATA_SF_BRAZIER_S: - return GoSFSGUID; - case DATA_THRONE_DOOR: - return GoThroneGUID; - case DATA_GOLEM_DOOR_N: - return GoGolemNGUID; - case DATA_GOLEM_DOOR_S: - return GoGolemSGUID; - case DATA_GO_CHALICE: - return GoSpectralChaliceGUID; - case DATA_MAGMUS: - { - return MagmusGUID; - } - } - - return ObjectGuid::Empty; - } - - std::string GetSaveData() override - { - return str_data; - } - - void Load(const char* in) override - { - if (!in) - { - OUT_LOAD_INST_DATA_FAIL; - return; - } - - OUT_LOAD_INST_DATA(in); - - std::istringstream loadStream(in); - loadStream >> encounter[0] >> encounter[1] >> encounter[2] >> encounter[3] - >> encounter[4] >> encounter[5] >> GhostKillCount; - - for (uint8 i = 0; i < MAX_ENCOUNTER; ++i) - if (encounter[i] == IN_PROGRESS) - encounter[i] = NOT_STARTED; - if (GhostKillCount > 0 && GhostKillCount < 7) - GhostKillCount = 0;//reset tomb of seven event - if (GhostKillCount >= 7) - GhostKillCount = 7; - - OUT_LOAD_INST_DATA_COMPLETE; - } - - void TombOfSevenEvent() - { - if (GhostKillCount < 7 && TombBossGUIDs[TombEventCounter]) - { - if (Creature* boss = instance->GetCreature(TombBossGUIDs[TombEventCounter])) - { - ++TombEventCounter; - boss->SetFaction(FACTION_DARK_IRON_DWARVES); - boss->SetImmuneToPC(false); - - // find suitable target here. - Player* target = boss->SelectNearestPlayer(130); - if (target && boss->CanCreatureAttack(target, true)) { - boss->AI()->AttackStart(target); - boss->AI()->DoZoneInCombat(); - tombResetTimer = TIMER_TOMB_RESET; + argelmach->AI()->SetData(DATA_GOLEM_LORD_ARGELMACH_INIT, DONE); } } + break; } + case DATA_GOLEM_LORD_ARGELMACH_ADDS: + { + if (Creature* argelmach = instance->GetCreature(ArgelmachGUID)) + { + argelmach->HandleEmoteCommand(EMOTE_ONESHOT_SHOUT); + argelmach->m_Events.AddEventAtOffset(new RestoreAttack(argelmach), 3s); + + for (ObjectGuid const& argelmachAddGUID : ArgelmachAdds) + if (Creature* argelmachAdd = instance->GetCreature(argelmachAddGUID)) + if (!argelmachAdd->IsInCombat()) + { + argelmachAdd->RemoveAurasDueToSpell(SPELL_STONED); + argelmachAdd->AI()->AttackStart(argelmach->GetVictim()); + } + } + break; + } + default: + break; } - void TombOfSevenReset() + if (data == DONE || GhostKillCount >= 7) { - HandleGameObject(GoTombExitGUID, false);// close exit door - HandleGameObject(GoTombEnterGUID, true);// open entrance door - for (uint8 i = 0; i < 7; ++i) - { - if (Creature* boss = instance->GetCreature(TombBossGUIDs[i])) - { - if (!boss->IsAlive()) - { - //do not call EnterEvadeMode(), it will create infinit loops - boss->Respawn(); - boss->RemoveAllAuras(); - boss->GetThreatMgr().ClearAllThreat(); - boss->CombatStop(true); - boss->LoadCreaturesAddon(true); - boss->GetMotionMaster()->MoveTargetedHome(); - boss->SetLootRecipient(nullptr); - } - boss->SetFaction(FACTION_FRIENDLY); - boss->SetImmuneToPC(true); // think this is useless - if (i == 6) // doomrel needs explicit reset - { - boss->AI()->Reset(); - } - } - } + OUT_SAVE_INST_DATA; - GhostKillCount = 0; - TombEventCounter = 0; - TombTimer = TIMER_TOMB_START; - SetData(TYPE_TOMB_OF_SEVEN, NOT_STARTED); + std::ostringstream saveStream; + saveStream << encounter[0] << ' ' << encounter[1] << ' ' << encounter[2] << ' ' + << encounter[3] << ' ' << encounter[4] << ' ' << encounter[5] << ' ' << GhostKillCount; + + str_data = saveStream.str(); + + SaveToDB(); + OUT_SAVE_INST_DATA_COMPLETE; } + } - bool CheckTombReset(uint32 diff) + uint32 GetData(uint32 type) const override + { + switch (type) { - bool anyBossAlive = false; // status of the bosses up until the current one - for (uint8 i = 0; i < TombEventCounter; i++) - { - Creature* boss = instance->GetCreature(TombBossGUIDs[i]); - if (boss) - { - anyBossAlive |= boss->IsAlive(); - if (boss->IsAlive() && boss->IsInCombat()) - { - tombResetTimer = TIMER_TOMB_RESET; - return false; // any boss in combat means we shouldn't reset. - } - } - } - if (!anyBossAlive) // no boss alive, put reset timer back up - { - tombResetTimer = TIMER_TOMB_RESET; - } - tombResetTimer -= diff; - return tombResetTimer < diff; - } - - void Update(uint32 diff) override - { - if ((GetData(TYPE_TOMB_OF_SEVEN) == IN_PROGRESS) && GhostKillCount < 7) - { - if (TombTimer <= diff) - { - TombTimer = TIMER_TOMBOFTHESEVEN; - TombOfSevenEvent(); - } + case TYPE_RING_OF_LAW: + return encounter[0]; + case TYPE_VAULT: + return encounter[1]; + case TYPE_BAR: + if (encounter[2] == IN_PROGRESS && BarAleCount == 3) + return SPECIAL; else - { - TombTimer -= diff; - } + return encounter[2]; + case TYPE_TOMB_OF_SEVEN: + return encounter[3]; + case TYPE_LYCEUM: + return encounter[4]; + case TYPE_IRON_HALL: + return encounter[5]; + case DATA_TIME_RING_FAIL: + return timeRingFail; + case DATA_ARENA_MOBS: + return arenaMobsToSpawn; + case DATA_ARENA_BOSS: + return arenaBossToSpawn; + } + return 0; + } - if (CheckTombReset(diff)) + ObjectGuid GetGuidData(uint32 data) const override + { + switch (data) + { + case DATA_EMPEROR: + return EmperorGUID; + case DATA_PHALANX: + return PhalanxGUID; + case DATA_MOIRA: + return MoiraGUID; + case DATA_COREN: + return CorenGUID; + case DATA_ARENA1: + return GoArena1GUID; + case DATA_ARENA2: + return GoArena2GUID; + case DATA_ARENA3: + return GoArena3GUID; + case DATA_ARENA4: + return GoArena4GUID; + case DATA_GO_BAR_KEG: + return GoBarKegGUID; + case DATA_GO_BAR_KEG_TRAP: + return GoBarKegTrapGUID; + case DATA_GO_BAR_DOOR: + return GoBarDoorGUID; + case DATA_SF_BRAZIER_N: + return GoSFNGUID; + case DATA_SF_BRAZIER_S: + return GoSFSGUID; + case DATA_THRONE_DOOR: + return GoThroneGUID; + case DATA_GOLEM_DOOR_N: + return GoGolemNGUID; + case DATA_GOLEM_DOOR_S: + return GoGolemSGUID; + case DATA_GO_CHALICE: + return GoSpectralChaliceGUID; + case DATA_MAGMUS: + { + return MagmusGUID; + } + } + + return ObjectGuid::Empty; + } + + std::string GetSaveData() override + { + return str_data; + } + + void Load(const char* in) override + { + if (!in) + { + OUT_LOAD_INST_DATA_FAIL; + return; + } + + OUT_LOAD_INST_DATA(in); + + std::istringstream loadStream(in); + loadStream >> encounter[0] >> encounter[1] >> encounter[2] >> encounter[3] + >> encounter[4] >> encounter[5] >> GhostKillCount; + + for (uint8 i = 0; i < MAX_ENCOUNTER; ++i) + if (encounter[i] == IN_PROGRESS) + encounter[i] = NOT_STARTED; + if (GhostKillCount > 0 && GhostKillCount < 7) + GhostKillCount = 0;//reset tomb of seven event + if (GhostKillCount >= 7) + GhostKillCount = 7; + + OUT_LOAD_INST_DATA_COMPLETE; + } + + void TombOfSevenEvent() + { + if (GhostKillCount < 7 && TombBossGUIDs[TombEventCounter]) + { + if (Creature* boss = instance->GetCreature(TombBossGUIDs[TombEventCounter])) + { + ++TombEventCounter; + boss->SetFaction(FACTION_DARK_IRON_DWARVES); + boss->SetImmuneToPC(false); + + // find suitable target here. + Player* target = boss->SelectNearestPlayer(130); + if (target && boss->CanCreatureAttack(target, true)) { - TombOfSevenReset(); + boss->AI()->AttackStart(target); + boss->AI()->DoZoneInCombat(); + tombResetTimer = TIMER_TOMB_RESET; } } } - }; + } + + void TombOfSevenReset() + { + HandleGameObject(GoTombExitGUID, false);// close exit door + HandleGameObject(GoTombEnterGUID, true);// open entrance door + for (uint8 i = 0; i < 7; ++i) + if (Creature* boss = instance->GetCreature(TombBossGUIDs[i])) + { + if (!boss->IsAlive()) + { + //do not call EnterEvadeMode(), it will create infinit loops + boss->Respawn(); + boss->RemoveAllAuras(); + boss->GetThreatMgr().ClearAllThreat(); + boss->CombatStop(true); + boss->LoadCreaturesAddon(true); + boss->GetMotionMaster()->MoveTargetedHome(); + boss->SetLootRecipient(nullptr); + } + boss->SetFaction(FACTION_FRIENDLY); + boss->SetImmuneToPC(true); // think this is useless + if (i == 6) // doomrel needs explicit reset + boss->AI()->Reset(); + } + + GhostKillCount = 0; + TombEventCounter = 0; + TombTimer = TIMER_TOMB_START; + SetData(TYPE_TOMB_OF_SEVEN, NOT_STARTED); + } + + bool CheckTombReset(uint32 diff) + { + bool anyBossAlive = false; // status of the bosses up until the current one + for (uint8 i = 0; i < TombEventCounter; i++) + { + Creature* boss = instance->GetCreature(TombBossGUIDs[i]); + if (boss) + { + anyBossAlive |= boss->IsAlive(); + if (boss->IsAlive() && boss->IsInCombat()) + { + tombResetTimer = TIMER_TOMB_RESET; + return false; // any boss in combat means we shouldn't reset. + } + } + } + if (!anyBossAlive) // no boss alive, put reset timer back up + tombResetTimer = TIMER_TOMB_RESET; + tombResetTimer -= diff; + return tombResetTimer < diff; + } + + void Update(uint32 diff) override + { + if ((GetData(TYPE_TOMB_OF_SEVEN) == IN_PROGRESS) && GhostKillCount < 7) + { + if (TombTimer <= diff) + { + TombTimer = TIMER_TOMBOFTHESEVEN; + TombOfSevenEvent(); + } + else + { + TombTimer -= diff; + } + + if (CheckTombReset(diff)) + TombOfSevenReset(); + } + } }; void AddSC_instance_blackrock_depths() { - new instance_blackrock_depths(); + RegisterInstanceScript(instance_blackrock_depths, MAP_BLACKROCK_DEPTHS); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/blackrock_spire.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/blackrock_spire.h index 8b33f180b..4e8f43dad 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/blackrock_spire.h +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/blackrock_spire.h @@ -155,5 +155,6 @@ inline AI* GetBlackrockSpireAI(T* obj) } #define RegisterBlackrockSpireCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetBlackrockSpireAI) +#define RegisterBlackrockSpireGameObjectAI(ai_name) RegisterGameObjectAIWithFactory(ai_name, GetBlackrockSpireAI) #endif diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp index d979336cb..a4f436277 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp @@ -40,98 +40,85 @@ enum Events EVENT_CHECK_CONFLAGRATION_TARGET }; -class boss_drakkisath : public CreatureScript +struct boss_drakkisath : public BossAI { -public: - boss_drakkisath() : CreatureScript("boss_drakkisath") { } - - struct boss_drakkisathAI : public BossAI + boss_drakkisath(Creature* creature) : BossAI(creature, DATA_GENERAL_DRAKKISATH) { - boss_drakkisathAI(Creature* creature) : BossAI(creature, DATA_GENERAL_DRAKKISATH) - { - _conflagrateThreat = 0.0f; - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_FLAMESTRIKE, 6s); - events.ScheduleEvent(EVENT_CLEAVE, 8s); - events.ScheduleEvent(EVENT_CONFLAGRATION, 15s); - events.ScheduleEvent(EVENT_THUNDERCLAP, 17s); - events.ScheduleEvent(EVENT_PIERCE_ARMOR, 5s); - events.ScheduleEvent(EVENT_RAGE, 1s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_FLAMESTRIKE: - DoCastAOE(SPELL_FLAMESTRIKE); - events.ScheduleEvent(EVENT_FLAMESTRIKE, 10s); - break; - case EVENT_CLEAVE: - DoCastVictim(SPELL_CLEAVE); - events.ScheduleEvent(EVENT_CLEAVE, 8s); - break; - case EVENT_CONFLAGRATION: - DoCastVictim(SPELL_CONFLAGRATION); - - if (Unit* target = me->GetVictim()) - { - _conflagrateTarget = me->GetVictim()->GetGUID(); - _conflagrateThreat = me->GetThreatMgr().GetThreat(me->GetVictim()); - me->GetThreatMgr().ModifyThreatByPercent(target, -100); - } - events.ScheduleEvent(EVENT_CONFLAGRATION, 18s, 25s); - events.ScheduleEvent(EVENT_CHECK_CONFLAGRATION_TARGET, 10s); - break; - case EVENT_THUNDERCLAP: - DoCastVictim(SPELL_THUNDERCLAP); - events.ScheduleEvent(EVENT_THUNDERCLAP, 20s); - break; - case EVENT_PIERCE_ARMOR: - DoCastVictim(SPELL_PIERCE_ARMOR); - events.ScheduleEvent(EVENT_PIERCE_ARMOR, 40s); - break; - case EVENT_RAGE: - DoCastSelf(SPELL_RAGE); - events.ScheduleEvent(EVENT_RAGE, 35s); - break; - case EVENT_CHECK_CONFLAGRATION_TARGET: - if (Unit* target = ObjectAccessor::GetUnit(*me, _conflagrateTarget)) - { - me->GetThreatMgr().AddThreat(target, _conflagrateThreat); - } - break; - } - } - DoMeleeAttackIfReady(); - } - - private: - float _conflagrateThreat; - ObjectGuid _conflagrateTarget; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + _conflagrateThreat = 0.0f; } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + events.ScheduleEvent(EVENT_FLAMESTRIKE, 6s); + events.ScheduleEvent(EVENT_CLEAVE, 8s); + events.ScheduleEvent(EVENT_CONFLAGRATION, 15s); + events.ScheduleEvent(EVENT_THUNDERCLAP, 17s); + events.ScheduleEvent(EVENT_PIERCE_ARMOR, 5s); + events.ScheduleEvent(EVENT_RAGE, 1s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_FLAMESTRIKE: + DoCastAOE(SPELL_FLAMESTRIKE); + events.ScheduleEvent(EVENT_FLAMESTRIKE, 10s); + break; + case EVENT_CLEAVE: + DoCastVictim(SPELL_CLEAVE); + events.ScheduleEvent(EVENT_CLEAVE, 8s); + break; + case EVENT_CONFLAGRATION: + DoCastVictim(SPELL_CONFLAGRATION); + + if (Unit* target = me->GetVictim()) + { + _conflagrateTarget = me->GetVictim()->GetGUID(); + _conflagrateThreat = me->GetThreatMgr().GetThreat(me->GetVictim()); + me->GetThreatMgr().ModifyThreatByPercent(target, -100); + } + events.ScheduleEvent(EVENT_CONFLAGRATION, 18s, 25s); + events.ScheduleEvent(EVENT_CHECK_CONFLAGRATION_TARGET, 10s); + break; + case EVENT_THUNDERCLAP: + DoCastVictim(SPELL_THUNDERCLAP); + events.ScheduleEvent(EVENT_THUNDERCLAP, 20s); + break; + case EVENT_PIERCE_ARMOR: + DoCastVictim(SPELL_PIERCE_ARMOR); + events.ScheduleEvent(EVENT_PIERCE_ARMOR, 40s); + break; + case EVENT_RAGE: + DoCastSelf(SPELL_RAGE); + events.ScheduleEvent(EVENT_RAGE, 35s); + break; + case EVENT_CHECK_CONFLAGRATION_TARGET: + if (Unit* target = ObjectAccessor::GetUnit(*me, _conflagrateTarget)) + me->GetThreatMgr().AddThreat(target, _conflagrateThreat); + break; + } + } + DoMeleeAttackIfReady(); + } + +private: + float _conflagrateThreat; + ObjectGuid _conflagrateTarget; }; void AddSC_boss_drakkisath() { - new boss_drakkisath(); + RegisterBlackrockSpireCreatureAI(boss_drakkisath); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_gyth.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_gyth.cpp index f04431c5c..072ff592d 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_gyth.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_gyth.cpp @@ -51,125 +51,114 @@ enum Events EVENT_SUMMONED_2 = 6 }; -class boss_gyth : public CreatureScript +struct boss_gyth : public BossAI { -public: - boss_gyth() : CreatureScript("boss_gyth") { } + boss_gyth(Creature* creature) : BossAI(creature, DATA_GYTH) { } - struct boss_gythAI : public BossAI + void Reset() override { - boss_gythAI(Creature* creature) : BossAI(creature, DATA_GYTH) { } - - void Reset() override + if (instance->GetBossState(DATA_GYTH) == IN_PROGRESS) { - if (instance->GetBossState(DATA_GYTH) == IN_PROGRESS) - { - instance->SetBossState(DATA_GYTH, NOT_STARTED); - summons.DespawnAll(); - me->DespawnOrUnsummon(); - } - - SetInvincibility(true); // Don't let boss die before summoning Rend. - - ScheduleHealthCheckEvent(25, [&] { - DoCastAOE(SPELL_SUMMON_REND, true); - me->RemoveAura(SPELL_REND_MOUNTS); - SetInvincibility(false); - }); + instance->SetBossState(DATA_GYTH, NOT_STARTED); + summons.DespawnAll(); + me->DespawnOrUnsummon(); } - void JustEngagedWith(Unit* /*who*/) override + SetInvincibility(true); // Don't let boss die before summoning Rend. + + ScheduleHealthCheckEvent(25, [&] { + DoCastAOE(SPELL_SUMMON_REND, true); + me->RemoveAura(SPELL_REND_MOUNTS); + SetInvincibility(false); + }); + } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + + events.ScheduleEvent(EVENT_CORROSIVE_ACID, 8s, 16s); + events.ScheduleEvent(EVENT_FREEZE, 8s, 16s); + events.ScheduleEvent(EVENT_FLAME_BREATH, 8s, 16s); + events.ScheduleEvent(EVENT_KNOCK_AWAY, 12s, 18s); + } + + void EnterEvadeMode(EvadeReason why) override + { + instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, FAIL); + BossAI::EnterEvadeMode(why); + } + + void IsSummonedBy(WorldObject* /*summoner*/) override + { + events.ScheduleEvent(EVENT_SUMMONED_1, 1s); + } + + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + summon->AI()->AttackStart(me->SelectVictim()); + } + + // Prevent clearing summon list, otherwise Rend despawns if the drake is killed first. + void JustDied(Unit* /*killer*/) override + { + instance->SetBossState(DATA_GYTH, DONE); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) { - _JustEngagedWith(); - - events.ScheduleEvent(EVENT_CORROSIVE_ACID, 8s, 16s); - events.ScheduleEvent(EVENT_FREEZE, 8s, 16s); - events.ScheduleEvent(EVENT_FLAME_BREATH, 8s, 16s); - events.ScheduleEvent(EVENT_KNOCK_AWAY, 12s, 18s); - } - - void EnterEvadeMode(EvadeReason why) override - { - instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, FAIL); - BossAI::EnterEvadeMode(why); - } - - void IsSummonedBy(WorldObject* /*summoner*/) override - { - events.ScheduleEvent(EVENT_SUMMONED_1, 1s); - } - - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - summon->AI()->AttackStart(me->SelectVictim()); - } - - // Prevent clearing summon list, otherwise Rend despawns if the drake is killed first. - void JustDied(Unit* /*killer*/) override - { - instance->SetBossState(DATA_GYTH, DONE); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - { - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SUMMONED_1: - me->AddAura(SPELL_REND_MOUNTS, me); - if (GameObject* portcullis = me->FindNearestGameObject(GO_DR_PORTCULLIS, 40.0f)) - portcullis->UseDoorOrButton(); - events.ScheduleEvent(EVENT_SUMMONED_2, 2s); - break; - case EVENT_SUMMONED_2: - me->GetMotionMaster()->MoveWaypoint(GYTH_PATH_1, false); - break; - default: - break; - } - } - return; - } - events.Update(diff); while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { - case EVENT_CORROSIVE_ACID: - DoCast(me, SPELL_CORROSIVE_ACID); - events.ScheduleEvent(EVENT_CORROSIVE_ACID, 10s, 16s); + case EVENT_SUMMONED_1: + me->AddAura(SPELL_REND_MOUNTS, me); + if (GameObject* portcullis = me->FindNearestGameObject(GO_DR_PORTCULLIS, 40.0f)) + portcullis->UseDoorOrButton(); + events.ScheduleEvent(EVENT_SUMMONED_2, 2s); break; - case EVENT_FREEZE: - DoCast(me, SPELL_FREEZE); - events.ScheduleEvent(EVENT_FREEZE, 10s, 16s); - break; - case EVENT_FLAME_BREATH: - DoCast(me, SPELL_FLAMEBREATH); - events.ScheduleEvent(EVENT_FLAME_BREATH, 10s, 16s); - break; - case EVENT_KNOCK_AWAY: - DoCastVictim(SPELL_KNOCK_AWAY); - events.ScheduleEvent(EVENT_KNOCK_AWAY, 14s, 20s); + case EVENT_SUMMONED_2: + me->GetMotionMaster()->MoveWaypoint(GYTH_PATH_1, false); break; default: break; } } - DoMeleeAttackIfReady(); + return; } - }; - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_CORROSIVE_ACID: + DoCast(me, SPELL_CORROSIVE_ACID); + events.ScheduleEvent(EVENT_CORROSIVE_ACID, 10s, 16s); + break; + case EVENT_FREEZE: + DoCast(me, SPELL_FREEZE); + events.ScheduleEvent(EVENT_FREEZE, 10s, 16s); + break; + case EVENT_FLAME_BREATH: + DoCast(me, SPELL_FLAMEBREATH); + events.ScheduleEvent(EVENT_FLAME_BREATH, 10s, 16s); + break; + case EVENT_KNOCK_AWAY: + DoCastVictim(SPELL_KNOCK_AWAY); + events.ScheduleEvent(EVENT_KNOCK_AWAY, 14s, 20s); + break; + default: + break; + } + } + DoMeleeAttackIfReady(); } }; @@ -231,6 +220,6 @@ class spell_gyth_chromatic_protection : public AuraScript void AddSC_boss_gyth() { - new boss_gyth(); + RegisterBlackrockSpireCreatureAI(boss_gyth); RegisterSpellScript(spell_gyth_chromatic_protection); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_halycon.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_halycon.cpp index bd2ffde13..93f1c2054 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_halycon.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_halycon.cpp @@ -38,70 +38,59 @@ enum Events const Position SummonLocation = { -167.9561f, -411.7844f, 76.23057f, 1.53589f }; -class boss_halycon : public CreatureScript +struct boss_halycon : public BossAI { -public: - boss_halycon() : CreatureScript("boss_halycon") { } + boss_halycon(Creature* creature) : BossAI(creature, DATA_HALYCON) { } - struct boss_halyconAI : public BossAI + void Reset() override { - boss_halyconAI(Creature* creature) : BossAI(creature, DATA_HALYCON) { } + _Reset(); + } - void Reset() override + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + events.ScheduleEvent(EVENT_REND, 17s, 20s); + events.ScheduleEvent(EVENT_THRASH, 10s, 12s); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + me->SummonCreature(NPC_GIZRUL_THE_SLAVENER, SummonLocation, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 300000); + Talk(EMOTE_DEATH); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - _Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_REND, 17s, 20s); - events.ScheduleEvent(EVENT_THRASH, 10s, 12s); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - me->SummonCreature(NPC_GIZRUL_THE_SLAVENER, SummonLocation, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 300000); - Talk(EMOTE_DEATH); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) + switch (eventId) { - switch (eventId) - { - case EVENT_REND: - DoCastVictim(SPELL_REND); - events.ScheduleEvent(EVENT_REND, 8s, 10s); - break; - case EVENT_THRASH: - DoCast(me, SPELL_THRASH); - break; - default: - break; - } + case EVENT_REND: + DoCastVictim(SPELL_REND); + events.ScheduleEvent(EVENT_REND, 8s, 10s); + break; + case EVENT_THRASH: + DoCast(me, SPELL_THRASH); + break; + default: + break; } - DoMeleeAttackIfReady(); } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + DoMeleeAttackIfReady(); } }; void AddSC_boss_halycon() { - new boss_halycon(); + RegisterBlackrockSpireCreatureAI(boss_halycon); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_highlord_omokk.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_highlord_omokk.cpp index b7e03f34f..5de41fcf1 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_highlord_omokk.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_highlord_omokk.cpp @@ -31,69 +31,58 @@ enum Events EVENT_KNOCK_AWAY = 2 }; -class boss_highlord_omokk : public CreatureScript +struct boss_highlord_omokk : public BossAI { -public: - boss_highlord_omokk() : CreatureScript("boss_highlord_omokk") { } + boss_highlord_omokk(Creature* creature) : BossAI(creature, DATA_HIGHLORD_OMOKK) { } - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockSpireAI(creature); + _Reset(); } - struct boss_highlordomokkAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_highlordomokkAI(Creature* creature) : BossAI(creature, DATA_HIGHLORD_OMOKK) { } + _JustEngagedWith(); + events.ScheduleEvent(EVENT_FRENZY, 20s); + events.ScheduleEvent(EVENT_KNOCK_AWAY, 18s); + } - void Reset() override + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - _Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_FRENZY, 20s); - events.ScheduleEvent(EVENT_KNOCK_AWAY, 18s); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) + switch (eventId) { - switch (eventId) - { - case EVENT_FRENZY: - DoCastVictim(SPELL_FRENZY); - events.ScheduleEvent(EVENT_FRENZY, 60s); - break; - case EVENT_KNOCK_AWAY: - DoCastVictim(SPELL_KNOCK_AWAY); - events.ScheduleEvent(EVENT_KNOCK_AWAY, 12s); - break; - default: - break; - } + case EVENT_FRENZY: + DoCastVictim(SPELL_FRENZY); + events.ScheduleEvent(EVENT_FRENZY, 60s); + break; + case EVENT_KNOCK_AWAY: + DoCastVictim(SPELL_KNOCK_AWAY); + events.ScheduleEvent(EVENT_KNOCK_AWAY, 12s); + break; + default: + break; } - DoMeleeAttackIfReady(); } - }; + DoMeleeAttackIfReady(); + } }; void AddSC_boss_highlordomokk() { - new boss_highlord_omokk(); + RegisterBlackrockSpireCreatureAI(boss_highlord_omokk); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_lord_valthalak.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_lord_valthalak.cpp index 0eeab9e2a..6b0f31217 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_lord_valthalak.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_lord_valthalak.cpp @@ -49,145 +49,130 @@ enum EventPhase EVENT_PHASE_FIGHT = 2 }; -class boss_lord_valthalak : public CreatureScript +struct boss_lord_valthalak : public BossAI { -public: - boss_lord_valthalak() : CreatureScript("boss_lord_valthalak") { } + boss_lord_valthalak(Creature* creature) : BossAI(creature, DATA_LORD_VALTHALAK) { } - struct boss_lord_valthalakAI : public BossAI + void Reset() override { - boss_lord_valthalakAI(Creature* creature) : BossAI(creature, DATA_LORD_VALTHALAK) { } + BossAI::Reset(); - void Reset() override - { - BossAI::Reset(); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - - frenzy40 = false; - frenzy15 = false; - } - - void IsSummonedBy(WorldObject* /*summoner*/) override - { - StartTalking(TALK_SUMMON, 8s); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - - Talk(TALK_AGGRO); - - events.SetPhase(EVENT_PHASE_FIGHT); - events.ScheduleEvent(EVENT_SUMMON_SPECTRAL_ASSASSIN, 6s, 8s, 0, EVENT_PHASE_FIGHT); - events.ScheduleEvent(EVENT_SHADOW_WRATH, 9s, 18s, 0, EVENT_PHASE_FIGHT); - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - - instance->SetData(DATA_LORD_VALTHALAK, DONE); - } - - void StartTalking(uint32 talkGroupId, Milliseconds timer) - { - me->SetReactState(REACT_PASSIVE); - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - me->AttackStop(); - - Talk(talkGroupId); - - events.SetPhase(EVENT_PHASE_TALK); - events.ScheduleEvent(EVENT_FIGHT, timer, 0, EVENT_PHASE_TALK); - } - - void StartFighting() - { - events.SetPhase(EVENT_PHASE_FIGHT); - - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - - DoZoneInCombat(); - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*type*/, SpellSchoolMask /*school*/) override - { - if (!frenzy40 && me->HealthBelowPctDamaged(40, damage)) - { - frenzy40 = true; - DoCast(me, SPELL_FRENZY); - - events.CancelEvent(EVENT_SUMMON_SPECTRAL_ASSASSIN); - - StartTalking(TALK_40_HP, 5s); - } - - if (!frenzy15 && me->HealthBelowPctDamaged(15, damage)) - { - frenzy15 = true; - - events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 12s, 19s, 0, EVENT_PHASE_FIGHT); - - StartTalking(TALK_15_HP, 5s); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim() && !events.IsInPhase(EVENT_PHASE_TALK)) - { - return; - } - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_FIGHT: - StartFighting(); - break; - case EVENT_SUMMON_SPECTRAL_ASSASSIN: - DoCast(me, SPELL_SUMMON_SPECTRAL_ASSASSIN); - events.ScheduleEvent(EVENT_SUMMON_SPECTRAL_ASSASSIN, 30s, 35s, 0, EVENT_PHASE_FIGHT); - break; - case EVENT_SHADOW_BOLT_VOLLEY: - DoCastVictim(SPELL_SHADOW_BOLT_VOLLEY); - events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 4s, 6s, 0, EVENT_PHASE_FIGHT); - break; - case EVENT_SHADOW_WRATH: - DoCastVictim(SPELL_SHADOW_WRATH); - events.ScheduleEvent(EVENT_SHADOW_WRATH, 19s, 24s, 0, EVENT_PHASE_FIGHT); - break; - default: - break; - } - } - - DoMeleeAttackIfReady(); - } - private: - bool frenzy40; - bool frenzy15; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + _frenzy40 = false; + _frenzy15 = false; } + + void IsSummonedBy(WorldObject* /*summoner*/) override + { + StartTalking(TALK_SUMMON, 8s); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + + Talk(TALK_AGGRO); + + events.SetPhase(EVENT_PHASE_FIGHT); + events.ScheduleEvent(EVENT_SUMMON_SPECTRAL_ASSASSIN, 6s, 8s, 0, EVENT_PHASE_FIGHT); + events.ScheduleEvent(EVENT_SHADOW_WRATH, 9s, 18s, 0, EVENT_PHASE_FIGHT); + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + + instance->SetData(DATA_LORD_VALTHALAK, DONE); + } + + void StartTalking(uint32 talkGroupId, Milliseconds timer) + { + me->SetReactState(REACT_PASSIVE); + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + me->AttackStop(); + + Talk(talkGroupId); + + events.SetPhase(EVENT_PHASE_TALK); + events.ScheduleEvent(EVENT_FIGHT, timer, 0, EVENT_PHASE_TALK); + } + + void StartFighting() + { + events.SetPhase(EVENT_PHASE_FIGHT); + + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + + DoZoneInCombat(); + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*type*/, SpellSchoolMask /*school*/) override + { + if (!_frenzy40 && me->HealthBelowPctDamaged(40, damage)) + { + _frenzy40 = true; + DoCast(me, SPELL_FRENZY); + + events.CancelEvent(EVENT_SUMMON_SPECTRAL_ASSASSIN); + + StartTalking(TALK_40_HP, 5s); + } + + if (!_frenzy15 && me->HealthBelowPctDamaged(15, damage)) + { + _frenzy15 = true; + + events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 12s, 19s, 0, EVENT_PHASE_FIGHT); + + StartTalking(TALK_15_HP, 5s); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim() && !events.IsInPhase(EVENT_PHASE_TALK)) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_FIGHT: + StartFighting(); + break; + case EVENT_SUMMON_SPECTRAL_ASSASSIN: + DoCast(me, SPELL_SUMMON_SPECTRAL_ASSASSIN); + events.ScheduleEvent(EVENT_SUMMON_SPECTRAL_ASSASSIN, 30s, 35s, 0, EVENT_PHASE_FIGHT); + break; + case EVENT_SHADOW_BOLT_VOLLEY: + DoCastVictim(SPELL_SHADOW_BOLT_VOLLEY); + events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 4s, 6s, 0, EVENT_PHASE_FIGHT); + break; + case EVENT_SHADOW_WRATH: + DoCastVictim(SPELL_SHADOW_WRATH); + events.ScheduleEvent(EVENT_SHADOW_WRATH, 19s, 24s, 0, EVENT_PHASE_FIGHT); + break; + default: + break; + } + } + + DoMeleeAttackIfReady(); + } +private: + bool _frenzy40; + bool _frenzy15; }; void AddSC_boss_lord_valthalak() { - new boss_lord_valthalak(); + RegisterBlackrockSpireCreatureAI(boss_lord_valthalak); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_mor_grayhoof.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_mor_grayhoof.cpp index 002047010..c772ed975 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_mor_grayhoof.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_mor_grayhoof.cpp @@ -96,13 +96,9 @@ struct boss_mor_grayhoof : public BossAI case PHASE_HUMAN: spell = Acore::Containers::SelectRandomContainerElement(humanSpells); if (spell == SPELL_REJUVENATION || spell == SPELL_HEALING_TOUCH) - { DoCastSelf(spell); - } else - { DoCastAOE(spell); - } break; case PHASE_BEAR: spell = Acore::Containers::SelectRandomContainerElement(bearSpells); @@ -155,9 +151,7 @@ struct boss_mor_grayhoof : public BossAI { CastRandomSpell(PHASE_BEAR); if (context.GetRepeatCounter() <= 3) - { context.Repeat(); - } }); } else if (_phase == PHASE_BEAR && me->HealthBelowPct(50.f)) @@ -170,9 +164,7 @@ struct boss_mor_grayhoof : public BossAI { CastRandomSpell(PHASE_CAT); if (context.GetRepeatCounter() <= 3) - { context.Repeat(); - } }); } else if (_phase == PHASE_CAT && me->HealthBelowPct(25.f)) @@ -203,9 +195,7 @@ struct boss_mor_grayhoof : public BossAI void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } _scheduler.Update(diff, [this] { diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_overlord_wyrmthalak.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_overlord_wyrmthalak.cpp index 4f2b2511e..74a8227c7 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_overlord_wyrmthalak.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_overlord_wyrmthalak.cpp @@ -46,93 +46,83 @@ constexpr uint32 CALL_HELP = 0; const Position SummonLocation1 = {-49.43f, -455.82f, 77.82f, 4.61f}; const Position SummonLocation2 = {-58.48f, -456.29f, 77.82f, 4.613f}; -class boss_overlord_wyrmthalak : public CreatureScript +struct boss_overlord_wyrmthalak : public BossAI { -public: - boss_overlord_wyrmthalak() : CreatureScript("boss_overlord_wyrmthalak") { } + boss_overlord_wyrmthalak(Creature* creature) : BossAI(creature, DATA_OVERLORD_WYRMTHALAK) { } - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockSpireAI(creature); + _Reset(); + _summoned = false; } - struct boss_overlordwyrmthalakAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_overlordwyrmthalakAI(Creature* creature) : BossAI(creature, DATA_OVERLORD_WYRMTHALAK) { } + _JustEngagedWith(); + events.ScheduleEvent(EVENT_BLAST_WAVE, 20s); + events.ScheduleEvent(EVENT_SHOUT, 2s); + events.ScheduleEvent(EVENT_CLEAVE, 6s); + events.ScheduleEvent(EVENT_KNOCK_AWAY, 12s); + } - bool Summoned; + void JustDied(Unit* /*killer*/) override + { + instance->SetBossState(DATA_OVERLORD_WYRMTHALAK, DONE); + } - void Reset() override + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + if (!_summoned && HealthBelowPct(51)) { - _Reset(); - Summoned = false; - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_BLAST_WAVE, 20s); - events.ScheduleEvent(EVENT_SHOUT, 2s); - events.ScheduleEvent(EVENT_CLEAVE, 6s); - events.ScheduleEvent(EVENT_KNOCK_AWAY, 12s); - } - - void JustDied(Unit* /*killer*/) override - { - instance->SetBossState(DATA_OVERLORD_WYRMTHALAK, DONE); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - if (!Summoned && HealthBelowPct(51)) + Talk(CALL_HELP); + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) { - Talk(CALL_HELP); - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - { - if (Creature* warlord = me->SummonCreature(NPC_SPIRESTONE_WARLORD, SummonLocation1, TEMPSUMMON_TIMED_DESPAWN, 300 * IN_MILLISECONDS)) - warlord->AI()->AttackStart(target); - if (Creature* berserker = me->SummonCreature(NPC_SMOLDERTHORN_BERSERKER, SummonLocation2, TEMPSUMMON_TIMED_DESPAWN, 300 * IN_MILLISECONDS)) - berserker->AI()->AttackStart(target); - Summoned = true; - } + if (Creature* warlord = me->SummonCreature(NPC_SPIRESTONE_WARLORD, SummonLocation1, TEMPSUMMON_TIMED_DESPAWN, 300 * IN_MILLISECONDS)) + warlord->AI()->AttackStart(target); + if (Creature* berserker = me->SummonCreature(NPC_SMOLDERTHORN_BERSERKER, SummonLocation2, TEMPSUMMON_TIMED_DESPAWN, 300 * IN_MILLISECONDS)) + berserker->AI()->AttackStart(target); + _summoned = true; } - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_BLAST_WAVE: - DoCastVictim(SPELL_BLASTWAVE); - events.ScheduleEvent(EVENT_BLAST_WAVE, 20s); - break; - case EVENT_SHOUT: - DoCastVictim(SPELL_SHOUT); - events.ScheduleEvent(EVENT_SHOUT, 10s); - break; - case EVENT_CLEAVE: - DoCastVictim(SPELL_CLEAVE); - events.ScheduleEvent(EVENT_CLEAVE, 7s); - break; - case EVENT_KNOCK_AWAY: - DoCastVictim(SPELL_KNOCKAWAY); - events.ScheduleEvent(EVENT_KNOCK_AWAY, 14s); - break; - } - } - DoMeleeAttackIfReady(); } - }; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_BLAST_WAVE: + DoCastVictim(SPELL_BLASTWAVE); + events.ScheduleEvent(EVENT_BLAST_WAVE, 20s); + break; + case EVENT_SHOUT: + DoCastVictim(SPELL_SHOUT); + events.ScheduleEvent(EVENT_SHOUT, 10s); + break; + case EVENT_CLEAVE: + DoCastVictim(SPELL_CLEAVE); + events.ScheduleEvent(EVENT_CLEAVE, 7s); + break; + case EVENT_KNOCK_AWAY: + DoCastVictim(SPELL_KNOCKAWAY); + events.ScheduleEvent(EVENT_KNOCK_AWAY, 14s); + break; + } + } + DoMeleeAttackIfReady(); + } + +private: + bool _summoned; }; void AddSC_boss_overlordwyrmthalak() { - new boss_overlord_wyrmthalak(); + RegisterBlackrockSpireCreatureAI(boss_overlord_wyrmthalak); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_pyroguard_emberseer.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_pyroguard_emberseer.cpp index 55c171291..753dfcb99 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_pyroguard_emberseer.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_pyroguard_emberseer.cpp @@ -68,241 +68,226 @@ enum Events EVENT_ENTER_COMBAT = 10 }; -class boss_pyroguard_emberseer : public CreatureScript +struct boss_pyroguard_emberseer : public BossAI { -public: - boss_pyroguard_emberseer() : CreatureScript("boss_pyroguard_emberseer") { } + boss_pyroguard_emberseer(Creature* creature) : BossAI(creature, DATA_PYROGAURD_EMBERSEER) { } - struct boss_pyroguard_emberseerAI : public BossAI + void Reset() override { - boss_pyroguard_emberseerAI(Creature* creature) : BossAI(creature, DATA_PYROGAURD_EMBERSEER) { } + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(true); + events.Reset(); + // Apply auras on spawn and reset + // DoCast(me, SPELL_FIRE_SHIELD_TRIGGER); // Need to find this in old DBC if possible + me->RemoveAura(SPELL_EMBERSEER_FULL_STRENGTH); + me->RemoveAura(SPELL_EMBERSEER_GROWING); + me->RemoveAura(SPELL_EMBERSEER_GROWING_TRIGGER); + events.ScheduleEvent(EVENT_RESPAWN, 5s); + // Hack for missing trigger spell + events.ScheduleEvent(EVENT_FIRE_SHIELD, 3s); - void Reset() override + // Open doors on reset + if (instance->GetBossState(DATA_PYROGAURD_EMBERSEER) == IN_PROGRESS) + OpenDoors(false); // Opens 2 entrance doors + } + + void SetData(uint32 /*type*/, uint32 data) override + { + switch (data) { - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToPC(true); - events.Reset(); - // Apply auras on spawn and reset - // DoCast(me, SPELL_FIRE_SHIELD_TRIGGER); // Need to find this in old DBC if possible - me->RemoveAura(SPELL_EMBERSEER_FULL_STRENGTH); - me->RemoveAura(SPELL_EMBERSEER_GROWING); - me->RemoveAura(SPELL_EMBERSEER_GROWING_TRIGGER); - events.ScheduleEvent(EVENT_RESPAWN, 5s); - // Hack for missing trigger spell - events.ScheduleEvent(EVENT_FIRE_SHIELD, 3s); - - // Open doors on reset - if (instance->GetBossState(DATA_PYROGAURD_EMBERSEER) == IN_PROGRESS) - OpenDoors(false); // Opens 2 entrance doors + case 1: + events.ScheduleEvent(EVENT_PRE_FIGHT_1, 2s); + instance->SetBossState(DATA_PYROGAURD_EMBERSEER, IN_PROGRESS); + break; + case 2: + // Close these two doors on Blackhand Incarcerators aggro + if (GameObject* door1 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_IN))) + if (door1->GetGoState() == GO_STATE_ACTIVE) + door1->SetGoState(GO_STATE_READY); + if (GameObject* door2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_DOORS))) + if (door2->GetGoState() == GO_STATE_ACTIVE) + door2->SetGoState(GO_STATE_READY); + break; + case 3: + Reset(); + break; + default: + break; } + } - void SetData(uint32 /*type*/, uint32 data) override + void JustEngagedWith(Unit* /*who*/) override + { + // ### TODO Check combat timing ### + events.ScheduleEvent(EVENT_FIRENOVA, 6s); + events.ScheduleEvent(EVENT_FLAMEBUFFET, 3s); + events.ScheduleEvent(EVENT_PYROBLAST, 14s); + } + + void JustDied(Unit* /*killer*/) override + { + // Activate all the runes + UpdateRunes(GO_STATE_READY); + // Opens all 3 doors + OpenDoors(true); + // Complete encounter + instance->SetBossState(DATA_PYROGAURD_EMBERSEER, DONE); + } + + void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override + { + if (spell->Id == SPELL_ENCAGE_EMBERSEER) + if (!me->GetAuraCount(SPELL_ENCAGED_EMBERSEER)) + me->CastSpell(me, SPELL_ENCAGED_EMBERSEER); + + if (spell->Id == SPELL_EMBERSEER_GROWING_TRIGGER) { - switch (data) + if (me->GetAuraCount(SPELL_EMBERSEER_GROWING_TRIGGER) == 10) + Talk(EMOTE_TEN_STACK); + + if (me->GetAuraCount(SPELL_EMBERSEER_GROWING_TRIGGER) == 20) { - case 1: - events.ScheduleEvent(EVENT_PRE_FIGHT_1, 2s); - instance->SetBossState(DATA_PYROGAURD_EMBERSEER, IN_PROGRESS); - break; - case 2: - // Close these two doors on Blackhand Incarcerators aggro - if (GameObject* door1 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_IN))) - if (door1->GetGoState() == GO_STATE_ACTIVE) - door1->SetGoState(GO_STATE_READY); - if (GameObject* door2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_DOORS))) - if (door2->GetGoState() == GO_STATE_ACTIVE) - door2->SetGoState(GO_STATE_READY); - break; - case 3: - Reset(); - break; - default: - break; + events.CancelEvent(EVENT_FIRE_SHIELD); // temporarily cancel fire shield to keep it from interrupting combat start + + // Schedule out the pre-combat scene + events.ScheduleEvent(EVENT_PRE_ENTER_COMBAT_1, 0s); + events.ScheduleEvent(EVENT_PRE_ENTER_COMBAT_2, 2s); + events.ScheduleEvent(EVENT_ENTER_COMBAT, 5s); } } + } - void JustEngagedWith(Unit* /*who*/) override + void OpenDoors(bool Boss_Killed) + { + // These two doors reopen on reset or boss kill + if (GameObject* door1 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_IN))) + door1->SetGoState(GO_STATE_ACTIVE); + if (GameObject* door2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_DOORS))) + door2->SetGoState(GO_STATE_ACTIVE); + + // This door opens on boss kill + if (Boss_Killed) + if (GameObject* door3 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_OUT))) + door3->SetGoState(GO_STATE_ACTIVE); + } + + void UpdateRunes(GOState state) + { + // update all runes + if (GameObject* rune1 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_1))) + rune1->SetGoState(state); + if (GameObject* rune2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_2))) + rune2->SetGoState(state); + if (GameObject* rune3 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_3))) + rune3->SetGoState(state); + if (GameObject* rune4 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_4))) + rune4->SetGoState(state); + if (GameObject* rune5 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_5))) + rune5->SetGoState(state); + if (GameObject* rune6 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_6))) + rune6->SetGoState(state); + if (GameObject* rune7 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_7))) + rune7->SetGoState(state); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) { - // ### TODO Check combat timing ### - events.ScheduleEvent(EVENT_FIRENOVA, 6s); - events.ScheduleEvent(EVENT_FLAMEBUFFET, 3s); - events.ScheduleEvent(EVENT_PYROBLAST, 14s); - } - - void JustDied(Unit* /*killer*/) override - { - // Activate all the runes - UpdateRunes(GO_STATE_READY); - // Opens all 3 doors - OpenDoors(true); - // Complete encounter - instance->SetBossState(DATA_PYROGAURD_EMBERSEER, DONE); - } - - void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override - { - if (spell->Id == SPELL_ENCAGE_EMBERSEER) - { - if (!me->GetAuraCount(SPELL_ENCAGED_EMBERSEER)) - me->CastSpell(me, SPELL_ENCAGED_EMBERSEER); - } - - if (spell->Id == SPELL_EMBERSEER_GROWING_TRIGGER) - { - if (me->GetAuraCount(SPELL_EMBERSEER_GROWING_TRIGGER) == 10) - Talk(EMOTE_TEN_STACK); - - if (me->GetAuraCount(SPELL_EMBERSEER_GROWING_TRIGGER) == 20) - { - events.CancelEvent(EVENT_FIRE_SHIELD); // temporarily cancel fire shield to keep it from interrupting combat start - - // Schedule out the pre-combat scene - events.ScheduleEvent(EVENT_PRE_ENTER_COMBAT_1, 0s); - events.ScheduleEvent(EVENT_PRE_ENTER_COMBAT_2, 2s); - events.ScheduleEvent(EVENT_ENTER_COMBAT, 5s); - } - } - } - - void OpenDoors(bool Boss_Killed) - { - // These two doors reopen on reset or boss kill - if (GameObject* door1 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_IN))) - door1->SetGoState(GO_STATE_ACTIVE); - if (GameObject* door2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_DOORS))) - door2->SetGoState(GO_STATE_ACTIVE); - - // This door opens on boss kill - if (Boss_Killed) - if (GameObject* door3 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_OUT))) - door3->SetGoState(GO_STATE_ACTIVE); - } - - void UpdateRunes(GOState state) - { - // update all runes - if (GameObject* rune1 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_1))) - rune1->SetGoState(state); - if (GameObject* rune2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_2))) - rune2->SetGoState(state); - if (GameObject* rune3 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_3))) - rune3->SetGoState(state); - if (GameObject* rune4 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_4))) - rune4->SetGoState(state); - if (GameObject* rune5 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_5))) - rune5->SetGoState(state); - if (GameObject* rune6 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_6))) - rune6->SetGoState(state); - if (GameObject* rune7 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_EMBERSEER_RUNE_7))) - rune7->SetGoState(state); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - { - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_RESPAWN: - { - // Respawn all Blackhand Incarcerators - std::list creatureList; - GetCreatureListWithEntryInGrid(creatureList, me, NPC_BLACKHAND_INCARCERATOR, 35.0f); - for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) - if (Creature* creature = *itr) - { - if (!creature->IsAlive()) - creature->Respawn(); - - creature->AI()->SetData(1, 2); - } - me->AddAura(SPELL_ENCAGED_EMBERSEER, me); - instance->SetBossState(DATA_PYROGAURD_EMBERSEER, NOT_STARTED); - break; - } - case EVENT_PRE_FIGHT_1: - { - // Set data on all Blackhand Incarcerators - std::list creatureList; - GetCreatureListWithEntryInGrid(creatureList, me, NPC_BLACKHAND_INCARCERATOR, 35.0f); - for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) - { - if (Creature* creature = *itr) - creature->AI()->SetData(1, 1); - } - events.ScheduleEvent(EVENT_PRE_FIGHT_2, 2s); - break; - } - case EVENT_PRE_FIGHT_2: - me->CastSpell(me, SPELL_FREEZE_ANIM); - me->CastSpell(me, SPELL_EMBERSEER_GROWING); - Talk(EMOTE_ONE_STACK); - break; - case EVENT_FIRE_SHIELD: - // #### Spell isn't doing any damage ??? #### - DoCast(me, SPELL_FIRE_SHIELD); - events.ScheduleEvent(EVENT_FIRE_SHIELD, 3s); - break; - case EVENT_PRE_ENTER_COMBAT_1: - me->RemoveAura(SPELL_ENCAGED_EMBERSEER); - me->RemoveAura(SPELL_FREEZE_ANIM); - me->CastSpell(me, SPELL_EMBERSEER_FULL_STRENGTH); - Talk(EMOTE_FREE_OF_BONDS); - break; - case EVENT_PRE_ENTER_COMBAT_2: - Talk(YELL_FREE_OF_BONDS); - break; - case EVENT_ENTER_COMBAT: - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToPC(false); - DoZoneInCombat(); - // re-enable fire shield - events.ScheduleEvent(EVENT_FIRE_SHIELD, 0s); - break; - default: - break; - } - } - return; - } - events.Update(diff); while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { + case EVENT_RESPAWN: + { + // Respawn all Blackhand Incarcerators + std::list creatureList; + GetCreatureListWithEntryInGrid(creatureList, me, NPC_BLACKHAND_INCARCERATOR, 35.0f); + for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) + if (Creature* creature = *itr) + { + if (!creature->IsAlive()) + creature->Respawn(); + + creature->AI()->SetData(1, 2); + } + me->AddAura(SPELL_ENCAGED_EMBERSEER, me); + instance->SetBossState(DATA_PYROGAURD_EMBERSEER, NOT_STARTED); + break; + } + case EVENT_PRE_FIGHT_1: + { + // Set data on all Blackhand Incarcerators + std::list creatureList; + GetCreatureListWithEntryInGrid(creatureList, me, NPC_BLACKHAND_INCARCERATOR, 35.0f); + for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) + if (Creature* creature = *itr) + creature->AI()->SetData(1, 1); + events.ScheduleEvent(EVENT_PRE_FIGHT_2, 2s); + break; + } + case EVENT_PRE_FIGHT_2: + me->CastSpell(me, SPELL_FREEZE_ANIM); + me->CastSpell(me, SPELL_EMBERSEER_GROWING); + Talk(EMOTE_ONE_STACK); + break; case EVENT_FIRE_SHIELD: + // #### Spell isn't doing any damage ??? #### DoCast(me, SPELL_FIRE_SHIELD); events.ScheduleEvent(EVENT_FIRE_SHIELD, 3s); break; - case EVENT_FIRENOVA: - DoCast(me, SPELL_FIRENOVA); - events.ScheduleEvent(EVENT_FIRENOVA, 6s); + case EVENT_PRE_ENTER_COMBAT_1: + me->RemoveAura(SPELL_ENCAGED_EMBERSEER); + me->RemoveAura(SPELL_FREEZE_ANIM); + me->CastSpell(me, SPELL_EMBERSEER_FULL_STRENGTH); + Talk(EMOTE_FREE_OF_BONDS); break; - case EVENT_FLAMEBUFFET: - DoCast(me, SPELL_FLAMEBUFFET); - events.ScheduleEvent(EVENT_FLAMEBUFFET, 14s); + case EVENT_PRE_ENTER_COMBAT_2: + Talk(YELL_FREE_OF_BONDS); break; - case EVENT_PYROBLAST: - DoCastRandomTarget(SPELL_PYROBLAST, 0, 100.0f); - events.ScheduleEvent(EVENT_PYROBLAST, 15s); + case EVENT_ENTER_COMBAT: + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(false); + DoZoneInCombat(); + // re-enable fire shield + events.ScheduleEvent(EVENT_FIRE_SHIELD, 0s); break; default: break; } } - DoMeleeAttackIfReady(); + return; } - }; - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_FIRE_SHIELD: + DoCast(me, SPELL_FIRE_SHIELD); + events.ScheduleEvent(EVENT_FIRE_SHIELD, 3s); + break; + case EVENT_FIRENOVA: + DoCast(me, SPELL_FIRENOVA); + events.ScheduleEvent(EVENT_FIRENOVA, 6s); + break; + case EVENT_FLAMEBUFFET: + DoCast(me, SPELL_FLAMEBUFFET); + events.ScheduleEvent(EVENT_FLAMEBUFFET, 14s); + break; + case EVENT_PYROBLAST: + DoCastRandomTarget(SPELL_PYROBLAST, 0, 100.0f); + events.ScheduleEvent(EVENT_PYROBLAST, 15s); + break; + default: + break; + } + } + DoMeleeAttackIfReady(); } }; @@ -321,127 +306,114 @@ enum IncarceratorData EMOTE_FLEE = 0 }; -class npc_blackhand_incarcerator : public CreatureScript +struct npc_blackhand_incarcerator : public ScriptedAI { -public: - npc_blackhand_incarcerator() : CreatureScript("npc_blackhand_incarcerator") { } - - struct npc_blackhand_incarceratorAI : public ScriptedAI + npc_blackhand_incarcerator(Creature* creature) : ScriptedAI(creature) { - npc_blackhand_incarceratorAI(Creature* creature) : ScriptedAI(creature) + _fleedForAssistance = false; + } + + void Reset() override + { + me->SetImmuneToAll(true); + if (Creature* Emberseer = me->FindNearestCreature(NPC_PYROGAURD_EMBERSEER, 30.0f, true)) + Emberseer->AI()->SetData(1, 3); + + _fleedForAssistance = false; + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*type*/, SpellSchoolMask /*school*/) override + { + if (!_fleedForAssistance && me->HealthBelowPctDamaged(30, damage)) { - _fleedForAssistance = false; + _fleedForAssistance = true; + me->DoFleeToGetAssistance(); + Talk(EMOTE_FLEE); + } + } + + void SetData(uint32 data, uint32 value) override + { + if (data == 1 && value == 1) + { + me->SetImmuneToAll(false); + me->InterruptSpell(CURRENT_CHANNELED_SPELL); + DoZoneInCombat(); + _events.CancelEvent(EVENT_ENCAGED_EMBERSEER); } - void Reset() override + if (data == 1 && value == 2) + _events.ScheduleEvent(EVENT_ENCAGED_EMBERSEER, 1s); + } + + void JustEngagedWith(Unit* /*who*/) override + { + // Used to close doors + if (Creature* Emberseer = me->FindNearestCreature(NPC_PYROGAURD_EMBERSEER, 30.0f, true)) + Emberseer->AI()->SetData(1, 2); + + // Had to do this because CallForHelp will ignore any npcs without LOS + std::list creatureList; + GetCreatureListWithEntryInGrid(creatureList, me, NPC_BLACKHAND_INCARCERATOR, 60.0f); + for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) + if (Creature* creature = *itr) + creature->SetInCombatWithZone(); // AI()->AttackStart(me->GetVictim()); + + _events.ScheduleEvent(EVENT_STRIKE, 8s, 16s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) { - me->SetImmuneToAll(true); - if (Creature* Emberseer = me->FindNearestCreature(NPC_PYROGAURD_EMBERSEER, 30.0f, true)) - Emberseer->AI()->SetData(1, 3); - - _fleedForAssistance = false; - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*type*/, SpellSchoolMask /*school*/) override - { - if (!_fleedForAssistance && me->HealthBelowPctDamaged(30, damage)) - { - _fleedForAssistance = true; - me->DoFleeToGetAssistance(); - Talk(EMOTE_FLEE); - } - } - - void SetData(uint32 data, uint32 value) override - { - if (data == 1 && value == 1) - { - me->SetImmuneToAll(false); - me->InterruptSpell(CURRENT_CHANNELED_SPELL); - DoZoneInCombat(); - _events.CancelEvent(EVENT_ENCAGED_EMBERSEER); - } - - if (data == 1 && value == 2) - _events.ScheduleEvent(EVENT_ENCAGED_EMBERSEER, 1s); - } - - void JustEngagedWith(Unit* /*who*/) override - { - // Used to close doors - if (Creature* Emberseer = me->FindNearestCreature(NPC_PYROGAURD_EMBERSEER, 30.0f, true)) - Emberseer->AI()->SetData(1, 2); - - // Had to do this because CallForHelp will ignore any npcs without LOS - std::list creatureList; - GetCreatureListWithEntryInGrid(creatureList, me, NPC_BLACKHAND_INCARCERATOR, 60.0f); - for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) - { - if (Creature* creature = *itr) - creature->SetInCombatWithZone(); // AI()->AttackStart(me->GetVictim()); - } - - _events.ScheduleEvent(EVENT_STRIKE, 8s, 16s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - { - _events.Update(diff); - - while (uint32 eventId = _events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_ENCAGED_EMBERSEER: - { - if (me->GetPositionX() == me->GetHomePosition().GetPositionX()) - if (!me->HasAura(SPELL_ENCAGE_EMBERSEER)) - if (Creature* Emberseer = me->FindNearestCreature(NPC_PYROGAURD_EMBERSEER, 30.0f, true)) - DoCast(Emberseer, SPELL_ENCAGE_EMBERSEER); - break; - } - } - } - return; - } - _events.Update(diff); while (uint32 eventId = _events.ExecuteEvent()) { switch (eventId) { - case EVENT_STRIKE: - DoCastVictim(SPELL_STRIKE, true); - _events.ScheduleEvent(EVENT_STRIKE, 14s, 23s); - break; - case EVENT_ENCAGE: - DoCast(SelectTarget(SelectTargetMethod::Random, 0, 100, true), EVENT_ENCAGE, true); - _events.ScheduleEvent(EVENT_ENCAGE, 6s, 12s); - break; - default: - break; + case EVENT_ENCAGED_EMBERSEER: + { + if (me->GetPositionX() == me->GetHomePosition().GetPositionX()) + if (!me->HasAura(SPELL_ENCAGE_EMBERSEER)) + if (Creature* Emberseer = me->FindNearestCreature(NPC_PYROGAURD_EMBERSEER, 30.0f, true)) + DoCast(Emberseer, SPELL_ENCAGE_EMBERSEER); + break; + } } } - - DoMeleeAttackIfReady(); + return; } - private: - EventMap _events; - bool _fleedForAssistance; - }; + _events.Update(diff); - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_STRIKE: + DoCastVictim(SPELL_STRIKE, true); + _events.ScheduleEvent(EVENT_STRIKE, 14s, 23s); + break; + case EVENT_ENCAGE: + DoCast(SelectTarget(SelectTargetMethod::Random, 0, 100, true), EVENT_ENCAGE, true); + _events.ScheduleEvent(EVENT_ENCAGE, 6s, 12s); + break; + default: + break; + } + } + + DoMeleeAttackIfReady(); } + +private: + EventMap _events; + bool _fleedForAssistance; }; void AddSC_boss_pyroguard_emberseer() { - new boss_pyroguard_emberseer(); - new npc_blackhand_incarcerator(); + RegisterBlackrockSpireCreatureAI(boss_pyroguard_emberseer); + RegisterBlackrockSpireCreatureAI(npc_blackhand_incarcerator); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_quartermaster_zigris.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_quartermaster_zigris.cpp index 21145b294..1a1c8f0c6 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_quartermaster_zigris.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_quartermaster_zigris.cpp @@ -87,16 +87,12 @@ struct boss_quartermaster_zigris : public BossAI void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } while (uint32 eventId = events.ExecuteEvent()) { @@ -135,9 +131,7 @@ struct boss_quartermaster_zigris : public BossAI } if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } } DoMeleeAttackIfReady(); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_rend_blackhand.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_rend_blackhand.cpp index 5e6438422..6a26c7bf2 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_rend_blackhand.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_rend_blackhand.cpp @@ -99,346 +99,327 @@ enum Events EVENT_MORTAL_STRIKE = 32, }; -class boss_rend_blackhand : public CreatureScript +struct boss_rend_blackhand : public BossAI { -public: - boss_rend_blackhand() : CreatureScript("boss_rend_blackhand") { } + boss_rend_blackhand(Creature* creature) : BossAI(creature, DATA_WARCHIEF_REND_BLACKHAND) { } - struct boss_rend_blackhandAI : public BossAI + void Reset() override { - boss_rend_blackhandAI(Creature* creature) : BossAI(creature, DATA_WARCHIEF_REND_BLACKHAND) { } + _Reset(); - void Reset() override + if (instance->GetBossState(DATA_GYTH) == IN_PROGRESS) { - _Reset(); - - if (instance->GetBossState(DATA_GYTH) == IN_PROGRESS) - { - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_PREPARATION); - me->SetImmuneToAll(false); - return; - } - - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_PREPARATION); - me->SetImmuneToAll(true); - gythEvent = false; - victorGUID.Clear(); - waveDoorGUID.Clear(); - _currentWave = 0; - - summons.DespawnAll(); - - if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 5.0f, true)) - victor->Respawn(true); - - if (GameObject* exitDoor = me->GetMap()->GetGameObject(instance->GetGuidData(GO_GYTH_ENTRY_DOOR))) - exitDoor->SetGoState(GO_STATE_ACTIVE); - - instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, NOT_STARTED); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_PREPARATION); + me->SetImmuneToAll(false); + return; } - void SummonedCreatureDies(Creature* /*creature*/, Unit* /*killer*/) override + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_PREPARATION); + me->SetImmuneToAll(true); + _gythEvent = false; + _victorGUID.Clear(); + _waveDoorGUID.Clear(); + _currentWave = 0; + + summons.DespawnAll(); + + if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 5.0f, true)) + victor->Respawn(true); + + if (GameObject* exitDoor = me->GetMap()->GetGameObject(instance->GetGuidData(GO_GYTH_ENTRY_DOOR))) + exitDoor->SetGoState(GO_STATE_ACTIVE); + + instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, NOT_STARTED); + } + + void SummonedCreatureDies(Creature* /*creature*/, Unit* /*killer*/) override + { + if (!summons.IsAnyCreatureAlive()) + events.ScheduleEvent(EVENT_WAVES_TEXT_1 + _currentWave, 10s); + } + + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + + if (summon->GetEntry() == NPC_GYTH) { - if (!summons.IsAnyCreatureAlive()) - { - events.ScheduleEvent(EVENT_WAVES_TEXT_1 + _currentWave, 10s); - } - } - - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - - if (summon->GetEntry() == NPC_GYTH) - { - me->DespawnOrUnsummon(); - return; - } - - summon->AI()->DoZoneInCombat(nullptr, 100.0f); - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_WHIRLWIND, 13s, 15s); - events.ScheduleEvent(EVENT_CLEAVE, 15s, 17s); - events.ScheduleEvent(EVENT_MORTAL_STRIKE, 17s, 19s); - } - - void EnterEvadeMode(EvadeReason why) override - { - instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, FAIL); - instance->SetBossState(DATA_GYTH, FAIL); - BossAI::EnterEvadeMode(why); me->DespawnOrUnsummon(); + return; } - void IsSummonedBy(WorldObject* /*summoner*/) override + summon->AI()->DoZoneInCombat(nullptr, 100.0f); + } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + events.ScheduleEvent(EVENT_WHIRLWIND, 13s, 15s); + events.ScheduleEvent(EVENT_CLEAVE, 15s, 17s); + events.ScheduleEvent(EVENT_MORTAL_STRIKE, 17s, 19s); + } + + void EnterEvadeMode(EvadeReason why) override + { + instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, FAIL); + instance->SetBossState(DATA_GYTH, FAIL); + BossAI::EnterEvadeMode(why); + me->DespawnOrUnsummon(); + } + + void IsSummonedBy(WorldObject* /*summoner*/) override + { + Talk(EMOTE_BLACKHAND_DISMOUNT); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 75.0f, true)) + victor->AI()->SetData(1, 2); + + if (GameObject* exitDoor = me->GetMap()->GetGameObject(instance->GetGuidData(GO_GYTH_ENTRY_DOOR))) + exitDoor->SetGoState(GO_STATE_ACTIVE); + + instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, DONE); + } + + void SummonedCreatureDespawn(Creature* creature) override + { + if (creature->IsAlive() && !summons.IsAnyCreatureInCombat()) + instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, FAIL); + + BossAI::SummonedCreatureDespawn(creature); + } + + void SetData(uint32 type, uint32 data) override + { + if (type == AREATRIGGER && data == AREATRIGGER_BLACKROCK_STADIUM) { - Talk(EMOTE_BLACKHAND_DISMOUNT); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 75.0f, true)) - victor->AI()->SetData(1, 2); - - if (GameObject* exitDoor = me->GetMap()->GetGameObject(instance->GetGuidData(GO_GYTH_ENTRY_DOOR))) - exitDoor->SetGoState(GO_STATE_ACTIVE); - - instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, DONE); - } - - void SummonedCreatureDespawn(Creature* creature) override - { - if (creature->IsAlive() && !summons.IsAnyCreatureInCombat()) + if (!_gythEvent) { - instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, FAIL); - } + _gythEvent = true; - BossAI::SummonedCreatureDespawn(creature); - } - - void SetData(uint32 type, uint32 data) override - { - if (type == AREATRIGGER && data == AREATRIGGER_BLACKROCK_STADIUM) - { - if (!gythEvent) + if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 5.0f)) { - gythEvent = true; + if (!victor->IsAlive()) + victor->Respawn(true); - if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 5.0f)) - { - if (!victor->IsAlive()) - { - victor->Respawn(true); - } - - victorGUID = victor->GetGUID(); - } - - if (GameObject* portcullis = me->FindNearestGameObject(GO_DR_PORTCULLIS, 50.0f)) - waveDoorGUID = portcullis->GetGUID(); - - events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); - events.ScheduleEvent(EVENT_START_1, 1s); + _victorGUID = victor->GetGUID(); } + + if (GameObject* portcullis = me->FindNearestGameObject(GO_DR_PORTCULLIS, 50.0f)) + _waveDoorGUID = portcullis->GetGUID(); + + events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); + events.ScheduleEvent(EVENT_START_1, 1s); } } + } - void MovementInform(uint32 type, uint32 id) override + void MovementInform(uint32 type, uint32 id) override + { + if (type == WAYPOINT_MOTION_TYPE) { - if (type == WAYPOINT_MOTION_TYPE) + switch (id) { - switch (id) - { - case 6: - events.ScheduleEvent(EVENT_TELEPORT_1, 2s); - break; - } + case 6: + events.ScheduleEvent(EVENT_TELEPORT_1, 2s); + break; } } + } - void UpdateAI(uint32 diff) override + void UpdateAI(uint32 diff) override + { + if (_gythEvent) { - if (gythEvent) - { - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_START_1: - instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, IN_PROGRESS); - instance->SetData(DATA_VAELASTRASZ, NOT_STARTED); - - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_0); - - if (GameObject* door2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_GYTH_ENTRY_DOOR))) - door2->SetGoState(GO_STATE_READY); - - events.ScheduleEvent(EVENT_START_2, 4s); - break; - case EVENT_START_2: - events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->HandleEmoteCommand(EMOTE_ONESHOT_POINT); - events.ScheduleEvent(EVENT_START_3, 4s); - break; - case EVENT_START_3: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_1); - events.ScheduleEvent(EVENT_SPAWN_WAVE, 2s); - events.ScheduleEvent(EVENT_TURN_TO_REND, 4s); - break; - case EVENT_TURN_TO_REND: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - { - victor->SetFacingToObject(me); - victor->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - } - break; - case EVENT_TURN_TO_PLAYER: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - if (Unit* player = victor->SelectNearestPlayer(60.0f)) - victor->SetFacingToObject(player); - break; - case EVENT_TURN_TO_FACING_1: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->SetFacingTo(1.518436f); - break; - case EVENT_TURN_TO_FACING_2: - me->SetFacingTo(1.658063f); - break; - case EVENT_TURN_TO_FACING_3: - me->SetFacingTo(1.500983f); - break; - case EVENT_WAVES_EMOTE_1: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->HandleEmoteCommand(EMOTE_ONESHOT_QUESTION); - break; - case EVENT_WAVES_EMOTE_2: - me->HandleEmoteCommand(EMOTE_ONESHOT_ROAR); - break; - case EVENT_WAVES_TEXT_1: - events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_2); - me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); - events.ScheduleEvent(EVENT_WAVES_EMOTE_1, 5s); - events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); - break; - case EVENT_WAVES_TEXT_2: - events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_3); - events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); - events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); - break; - case EVENT_WAVES_TEXT_3: - events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_4); - events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); - events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); - break; - case EVENT_WAVES_TEXT_4: - Talk(SAY_BLACKHAND_1); - events.ScheduleEvent(EVENT_WAVES_EMOTE_2, 4s); - events.ScheduleEvent(EVENT_TURN_TO_FACING_3, 8s); - events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); - break; - case EVENT_WAVES_TEXT_5: - events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_5); - events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); - events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); - break; - case EVENT_WAVES_COMPLETE_TEXT_1: - events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_6); - events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); - events.ScheduleEvent(EVENT_WAVES_COMPLETE_TEXT_2, 13s); - break; - case EVENT_WAVES_COMPLETE_TEXT_2: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_7); - Talk(SAY_BLACKHAND_2); - events.ScheduleEvent(EVENT_PATH_REND, 1s); - events.ScheduleEvent(EVENT_WAVES_COMPLETE_TEXT_3, 4s); - break; - case EVENT_WAVES_COMPLETE_TEXT_3: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->AI()->Talk(SAY_NEFARIUS_8); - events.ScheduleEvent(EVENT_PATH_NEFARIUS, 1s); - events.ScheduleEvent(EVENT_PATH_REND, 1s); - break; - case EVENT_PATH_NEFARIUS: - if (Creature* victor = ObjectAccessor::GetCreature(*me, victorGUID)) - victor->GetMotionMaster()->MoveWaypoint(NEFARIUS_PATH_1, true); - break; - case EVENT_PATH_REND: - me->GetMotionMaster()->MoveWaypoint(REND_PATH_1, false); - break; - case EVENT_TELEPORT_1: - me->NearTeleportTo(194.2993f, -474.0814f, 121.4505f, -0.01225555f); - events.ScheduleEvent(EVENT_TELEPORT_2, 13s); - break; - case EVENT_TELEPORT_2: - me->NearTeleportTo(216.485f, -434.93f, 110.888f, -0.01225555f); - me->SummonCreature(NPC_GYTH, 211.762f, -397.5885f, 111.1817f, 4.747295f); - break; - case EVENT_SPAWN_WAVE: - SummonWave(); - break; - default: - break; - } - } - } - - if (!UpdateVictim()) - return; - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { - case EVENT_WHIRLWIND: - DoCast(SPELL_WHIRLWIND); - events.ScheduleEvent(EVENT_WHIRLWIND, 13s, 18s); + case EVENT_START_1: + instance->SetBossState(DATA_WARCHIEF_REND_BLACKHAND, IN_PROGRESS); + instance->SetData(DATA_VAELASTRASZ, NOT_STARTED); + + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_0); + + if (GameObject* door2 = me->GetMap()->GetGameObject(instance->GetGuidData(GO_GYTH_ENTRY_DOOR))) + door2->SetGoState(GO_STATE_READY); + + events.ScheduleEvent(EVENT_START_2, 4s); break; - case EVENT_CLEAVE: - DoCastVictim(SPELL_CLEAVE); - events.ScheduleEvent(EVENT_CLEAVE, 10s, 14s); + case EVENT_START_2: + events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->HandleEmoteCommand(EMOTE_ONESHOT_POINT); + events.ScheduleEvent(EVENT_START_3, 4s); break; - case EVENT_MORTAL_STRIKE: - DoCastVictim(SPELL_MORTAL_STRIKE); - events.ScheduleEvent(EVENT_MORTAL_STRIKE, 14s, 18s); + case EVENT_START_3: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_1); + events.ScheduleEvent(EVENT_SPAWN_WAVE, 2s); + events.ScheduleEvent(EVENT_TURN_TO_REND, 4s); + break; + case EVENT_TURN_TO_REND: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + { + victor->SetFacingToObject(me); + victor->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + } + break; + case EVENT_TURN_TO_PLAYER: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + if (Unit* player = victor->SelectNearestPlayer(60.0f)) + victor->SetFacingToObject(player); + break; + case EVENT_TURN_TO_FACING_1: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->SetFacingTo(1.518436f); + break; + case EVENT_TURN_TO_FACING_2: + me->SetFacingTo(1.658063f); + break; + case EVENT_TURN_TO_FACING_3: + me->SetFacingTo(1.500983f); + break; + case EVENT_WAVES_EMOTE_1: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->HandleEmoteCommand(EMOTE_ONESHOT_QUESTION); + break; + case EVENT_WAVES_EMOTE_2: + me->HandleEmoteCommand(EMOTE_ONESHOT_ROAR); + break; + case EVENT_WAVES_TEXT_1: + events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_2); + me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); + events.ScheduleEvent(EVENT_WAVES_EMOTE_1, 5s); + events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); + break; + case EVENT_WAVES_TEXT_2: + events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_3); + events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); + events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); + break; + case EVENT_WAVES_TEXT_3: + events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_4); + events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); + events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); + break; + case EVENT_WAVES_TEXT_4: + Talk(SAY_BLACKHAND_1); + events.ScheduleEvent(EVENT_WAVES_EMOTE_2, 4s); + events.ScheduleEvent(EVENT_TURN_TO_FACING_3, 8s); + events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); + break; + case EVENT_WAVES_TEXT_5: + events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_5); + events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); + events.ScheduleEvent(EVENT_SPAWN_WAVE, 3s); + break; + case EVENT_WAVES_COMPLETE_TEXT_1: + events.ScheduleEvent(EVENT_TURN_TO_PLAYER, 0ms); + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_6); + events.ScheduleEvent(EVENT_TURN_TO_FACING_1, 4s); + events.ScheduleEvent(EVENT_WAVES_COMPLETE_TEXT_2, 13s); + break; + case EVENT_WAVES_COMPLETE_TEXT_2: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_7); + Talk(SAY_BLACKHAND_2); + events.ScheduleEvent(EVENT_PATH_REND, 1s); + events.ScheduleEvent(EVENT_WAVES_COMPLETE_TEXT_3, 4s); + break; + case EVENT_WAVES_COMPLETE_TEXT_3: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->AI()->Talk(SAY_NEFARIUS_8); + events.ScheduleEvent(EVENT_PATH_NEFARIUS, 1s); + events.ScheduleEvent(EVENT_PATH_REND, 1s); + break; + case EVENT_PATH_NEFARIUS: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + victor->GetMotionMaster()->MoveWaypoint(NEFARIUS_PATH_1, true); + break; + case EVENT_PATH_REND: + me->GetMotionMaster()->MoveWaypoint(REND_PATH_1, false); + break; + case EVENT_TELEPORT_1: + me->NearTeleportTo(194.2993f, -474.0814f, 121.4505f, -0.01225555f); + events.ScheduleEvent(EVENT_TELEPORT_2, 13s); + break; + case EVENT_TELEPORT_2: + me->NearTeleportTo(216.485f, -434.93f, 110.888f, -0.01225555f); + me->SummonCreature(NPC_GYTH, 211.762f, -397.5885f, 111.1817f, 4.747295f); + break; + case EVENT_SPAWN_WAVE: + SummonWave(); + break; + default: break; } } - DoMeleeAttackIfReady(); } - void SummonWave() + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - me->SummonCreatureGroup(_currentWave); - - if (GameObject* waveDoor = me->GetMap()->GetGameObject(waveDoorGUID)) + switch (eventId) { - waveDoor->UseDoorOrButton(); + case EVENT_WHIRLWIND: + DoCast(SPELL_WHIRLWIND); + events.ScheduleEvent(EVENT_WHIRLWIND, 13s, 18s); + break; + case EVENT_CLEAVE: + DoCastVictim(SPELL_CLEAVE); + events.ScheduleEvent(EVENT_CLEAVE, 10s, 14s); + break; + case EVENT_MORTAL_STRIKE: + DoCastVictim(SPELL_MORTAL_STRIKE); + events.ScheduleEvent(EVENT_MORTAL_STRIKE, 14s, 18s); + break; } - - ++_currentWave; } - - private: - bool gythEvent; - uint8 _currentWave; - ObjectGuid victorGUID; - ObjectGuid waveDoorGUID; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + DoMeleeAttackIfReady(); } + + void SummonWave() + { + me->SummonCreatureGroup(_currentWave); + + if (GameObject* waveDoor = me->GetMap()->GetGameObject(_waveDoorGUID)) + waveDoor->UseDoorOrButton(); + + ++_currentWave; + } + +private: + bool _gythEvent; + uint8 _currentWave; + ObjectGuid _victorGUID; + ObjectGuid _waveDoorGUID; }; void AddSC_boss_rend_blackhand() { - new boss_rend_blackhand(); + RegisterBlackrockSpireCreatureAI(boss_rend_blackhand); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_solakar_flamewreath.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_solakar_flamewreath.cpp index d96758ee7..4964a8ff3 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_solakar_flamewreath.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_solakar_flamewreath.cpp @@ -38,211 +38,178 @@ enum Says SAY_SUMMON = 0, }; -class npc_rookery_hatcher : public CreatureScript +struct npc_rookery_hatcher : public CreatureAI { -public: - npc_rookery_hatcher() : CreatureScript("npc_rookery_hatcher") {} + npc_rookery_hatcher(Creature* creature) : CreatureAI(creature) {} - CreatureAI* GetAI(Creature* creature) const override + EventMap events; + GameObject* targetEgg; + Position targetPosition; + + void InitializeAI() override { - return GetBlackrockSpireAI(creature); + CreatureAI::InitializeAI(); + DoZoneInCombat(nullptr, 100.0f); + targetEgg = nullptr; } - struct npc_rookery_hatcherAI : public CreatureAI + void JustEngagedWith(Unit* /*who*/) override { - npc_rookery_hatcherAI(Creature* creature) : CreatureAI(creature) {} + events.ScheduleEvent(SPELL_HATCH_EGG, 1s); + } - EventMap events; + void UpdateAI(uint32 diff) override + { std::list nearbyEggs; - GameObject* targetEgg; - Position targetPosition; + float tempDist = 20; + float minDist = 25; + std::list nearbyWhelps; - void InitializeAI() override + if (!UpdateVictim()) + return; + + GetCreatureListWithEntryInGrid(nearbyWhelps, me, NPC_ROOKERY_WHELP, RANGE_WHELP_CALL_HELP); + for (auto const& whelp : nearbyWhelps) { - CreatureAI::InitializeAI(); - DoZoneInCombat(nullptr, 100.0f); - nearbyEggs.clear(); - targetEgg = nullptr; - } - - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(SPELL_HATCH_EGG, 1s); - } - - void UpdateAI(uint32 diff) override - { - std::list nearbyEggs; - float tempDist = 20; - float minDist = 25; - std::list nearbyWhelps; - - if (!UpdateVictim()) + if (!whelp->IsInCombat()) { - return; + whelp->SetInCombatWith(me->GetVictim()); + whelp->AI()->AttackStart(me->GetVictim()); } - - GetCreatureListWithEntryInGrid(nearbyWhelps, me, NPC_ROOKERY_WHELP, RANGE_WHELP_CALL_HELP); - for (auto const& whelp : nearbyWhelps) - { - if (!whelp->IsInCombat()) - { - whelp->SetInCombatWith(me->GetVictim()); - whelp->AI()->AttackStart(me->GetVictim()); - } - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_HATCH_EGG: - if (!targetEgg) // no target, try to find one - { - minDist = 50; - tempDist = 50; - me->GetGameObjectListWithEntryInGrid(nearbyEggs, GO_ROOKERY_EGG, 40); - for (auto const& egg : nearbyEggs) - { - if (egg->isSpawned() && egg->getLootState() == GO_READY) - { - tempDist = me->GetDistance2d(egg); - if (tempDist < minDist) - { - minDist = tempDist; - targetEgg = egg; - } - } - } - } - - if (targetEgg) //have a target, go to it and cast it - { - me->GetMotionMaster()->MovePoint(0, targetEgg->GetPosition()); - } - break; - default: - break; - } - } - - // cast takes 1.5second, during which we don't have a target - if (targetEgg && targetEgg->getLootState() == GO_READY && me->GetDistance2d(targetEgg) < RANGE_SPELL_HATCH_EGG) - { - me->StopMovingOnCurrentPos(); - me->SetFacingToObject(targetEgg); - targetPosition = me->GetPosition(); - DoCast(SPELL_HATCH_EGG); - targetEgg = nullptr; - events.ScheduleEvent(SPELL_HATCH_EGG, 6s, 8s); - } - else if (!me->HasUnitState(UNIT_STATE_CASTING) && !targetEgg) - { - if (Unit* victim = me->GetVictim()) - { - AttackStart(victim); - } - - if (me->GetDistance2d(me->GetVictim()) > me->GetMeleeReach()) - { - me->GetMotionMaster()->MovePoint(0, me->GetVictim()->GetPosition()); // a bit hacky, but needed to start moving once we've summoned an egg - } - } - DoMeleeAttackIfReady(); - } - }; -}; - -class boss_solakar_flamewreath : public CreatureScript -{ -public: - boss_solakar_flamewreath() : CreatureScript("boss_solakar_flamewreath") { } - - struct boss_solakar_flamewreathAI : public BossAI - { - boss_solakar_flamewreathAI(Creature* creature) : BossAI(creature, DATA_SOLAKAR_FLAMEWREATH) {} - - uint32 resetTimer; - - void Reset() override - { - _Reset(); - resetTimer = 10000; } - void InitializeAI() override - { - BossAI::InitializeAI(); - Talk(SAY_SUMMON); - DoZoneInCombat(nullptr, 100.0f); - } + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + events.Update(diff); - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_WAR_STOMP, 17s, 20s); - resetTimer = 0; - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - instance->SetData(DATA_SOLAKAR_FLAMEWREATH, DONE); - } - - void ExecuteEvent(uint32 eventId) override + while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { - case SPELL_WAR_STOMP: - DoCastVictim(SPELL_WAR_STOMP); - events.ScheduleEvent(SPELL_WAR_STOMP, 17s, 20s); - break; + case SPELL_HATCH_EGG: + if (!targetEgg) // no target, try to find one + { + minDist = 50; + tempDist = 50; + me->GetGameObjectListWithEntryInGrid(nearbyEggs, GO_ROOKERY_EGG, 40); + for (auto const& egg : nearbyEggs) + { + if (egg->isSpawned() && egg->getLootState() == GO_READY) + { + tempDist = me->GetDistance2d(egg); + if (tempDist < minDist) + { + minDist = tempDist; + targetEgg = egg; + } + } + } + } + if (targetEgg) //have a target, go to it and cast it + me->GetMotionMaster()->MovePoint(0, targetEgg->GetPosition()); + break; default: break; } } - void UpdateAI(uint32 diff) override + // cast takes 1.5second, during which we don't have a target + if (targetEgg && targetEgg->getLootState() == GO_READY && me->GetDistance2d(targetEgg) < RANGE_SPELL_HATCH_EGG) { - if (!UpdateVictim()) - { - resetTimer -= diff; - if (resetTimer < diff) - { - instance->SetData(DATA_SOLAKAR_FLAMEWREATH, FAIL); - } - return; - } - resetTimer = 10000; - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - while (uint32 eventId = events.ExecuteEvent()) - { - ExecuteEvent(eventId); - } - - DoMeleeAttackIfReady(); + me->StopMovingOnCurrentPos(); + me->SetFacingToObject(targetEgg); + targetPosition = me->GetPosition(); + DoCast(SPELL_HATCH_EGG); + targetEgg = nullptr; + events.ScheduleEvent(SPELL_HATCH_EGG, 6s, 8s); } - }; + else if (!me->HasUnitState(UNIT_STATE_CASTING) && !targetEgg) + { + if (Unit* victim = me->GetVictim()) + AttackStart(victim); - CreatureAI* GetAI(Creature* creature) const override + if (me->GetDistance2d(me->GetVictim()) > me->GetMeleeReach()) + me->GetMotionMaster()->MovePoint(0, me->GetVictim()->GetPosition()); // a bit hacky, but needed to start moving once we've summoned an egg + } + DoMeleeAttackIfReady(); + } +}; + +struct boss_solakar_flamewreath : public BossAI +{ + boss_solakar_flamewreath(Creature* creature) : BossAI(creature, DATA_SOLAKAR_FLAMEWREATH) {} + + uint32 resetTimer; + + void Reset() override { - return GetBlackrockSpireAI(creature); + _Reset(); + resetTimer = 10000; + } + + void InitializeAI() override + { + BossAI::InitializeAI(); + Talk(SAY_SUMMON); + DoZoneInCombat(nullptr, 100.0f); + } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + events.ScheduleEvent(SPELL_WAR_STOMP, 17s, 20s); + resetTimer = 0; + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + instance->SetData(DATA_SOLAKAR_FLAMEWREATH, DONE); + } + + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) + { + case SPELL_WAR_STOMP: + DoCastVictim(SPELL_WAR_STOMP); + events.ScheduleEvent(SPELL_WAR_STOMP, 17s, 20s); + break; + + default: + break; + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + { + if (resetTimer <= diff) + { + instance->SetData(DATA_SOLAKAR_FLAMEWREATH, FAIL); + return; + } + resetTimer -= diff; + return; + } + resetTimer = 10000; + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + while (uint32 eventId = events.ExecuteEvent()) + { + ExecuteEvent(eventId); + } + + DoMeleeAttackIfReady(); } }; void AddSC_boss_solakar_flamewreath() { - new boss_solakar_flamewreath(); - new npc_rookery_hatcher(); + RegisterBlackrockSpireCreatureAI(boss_solakar_flamewreath); + RegisterBlackrockSpireCreatureAI(npc_rookery_hatcher); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp index 366fb17b7..37d773328 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp @@ -90,185 +90,152 @@ private: // Used to make Hodir disengage whenever he leaves his room constexpr static float FirewalPositionY = -505.f; -class boss_the_beast : public CreatureScript +struct boss_the_beast : public BossAI { -public: - boss_the_beast() : CreatureScript("boss_the_beast") { } + boss_the_beast(Creature* creature) : BossAI(creature, DATA_THE_BEAST), _beastReached(false), _orcYelled(false) {} - CreatureAI* GetAI(Creature* creature) const override + void Reset() override { - return GetBlackrockSpireAI(creature); + _Reset(); + + if (_beastReached) + me->GetMotionMaster()->MoveWaypoint(BEAST_MOVEMENT_ID, true); } - struct boss_thebeastAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_thebeastAI(Creature* creature) : BossAI(creature, DATA_THE_BEAST), _beastReached(false), _orcYelled(false) {} + _JustEngagedWith(); + events.ScheduleEvent(EVENT_FLAME_BREAK, 12s); + events.ScheduleEvent(EVENT_IMMOLATE, 3s); + events.ScheduleEvent(EVENT_TERRIFYING_ROAR, 23s); + events.ScheduleEvent(EVENT_BERSERKER_CHARGE, 2s); + events.ScheduleEvent(EVENT_FIREBALL, 8s, 21s); + events.ScheduleEvent(EVENT_FIREBLAST, 5s, 8s); + } - void Reset() override + void SetData(uint32 type, uint32 /*data*/) override + { + switch (type) { - _Reset(); - - if (_beastReached) + case DATA_BEAST_ROOM: { - me->GetMotionMaster()->MoveWaypoint(BEAST_MOVEMENT_ID, true); - } - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_FLAME_BREAK, 12s); - events.ScheduleEvent(EVENT_IMMOLATE, 3s); - events.ScheduleEvent(EVENT_TERRIFYING_ROAR, 23s); - events.ScheduleEvent(EVENT_BERSERKER_CHARGE, 2s); - events.ScheduleEvent(EVENT_FIREBALL, 8s, 21s); - events.ScheduleEvent(EVENT_FIREBLAST, 5s, 8s); - } - - void SetData(uint32 type, uint32 /*data*/) override - { - switch (type) - { - case DATA_BEAST_ROOM: + if (!_orcYelled) { - if (!_orcYelled) + if (_nearbyOrcsGUIDs.empty()) + FindNearbyOrcs(); + + //! vector still empty, creatures are missing + if (_nearbyOrcsGUIDs.empty()) + return; + + _orcYelled = true; + + bool yelled = false; + for (ObjectGuid guid : _nearbyOrcsGUIDs) { - if (_nearbyOrcsGUIDs.empty()) + if (Creature* orc = ObjectAccessor::GetCreature(*me, guid)) { - FindNearbyOrcs(); - } - - //! vector still empty, creatures are missing - if (_nearbyOrcsGUIDs.empty()) - { - return; - } - - _orcYelled = true; - - bool yelled = false; - for (ObjectGuid guid : _nearbyOrcsGUIDs) - { - if (Creature* orc = ObjectAccessor::GetCreature(*me, guid)) + if (!yelled) { - if (!yelled) - { - yelled = true; - orc->AI()->Talk(SAY_BLACKHAND_DOOMED); - } - - orc->m_Events.AddEventAtOffset(new OrcMoveEvent(orc), 3s); - orc->m_Events.AddEventAtOffset(new OrcDeathEvent(orc), 9s); + yelled = true; + orc->AI()->Talk(SAY_BLACKHAND_DOOMED); } + + orc->m_Events.AddEventAtOffset(new OrcMoveEvent(orc), 3s); + orc->m_Events.AddEventAtOffset(new OrcDeathEvent(orc), 9s); } } - break; } - case DATA_BEAST_REACHED: + break; + } + case DATA_BEAST_REACHED: + { + if (!_beastReached) { - if (!_beastReached) - { - _beastReached = true; - me->GetMotionMaster()->MoveWaypoint(BEAST_MOVEMENT_ID, true); + _beastReached = true; + me->GetMotionMaster()->MoveWaypoint(BEAST_MOVEMENT_ID, true); - // There is a chance player logged in between areatriggers (realm crash or restart) - // executing part of script which happens when player enters boss room - // otherwise we will see weird behaviour when someone steps on the previous areatrigger (dead mob yelling/moving) - SetData(DATA_BEAST_ROOM, DATA_BEAST_ROOM); - } - break; + // There is a chance player logged in between areatriggers (realm crash or restart) + // executing part of script which happens when player enters boss room + // otherwise we will see weird behaviour when someone steps on the previous areatrigger (dead mob yelling/moving) + SetData(DATA_BEAST_ROOM, DATA_BEAST_ROOM); } + break; } } + } - void UpdateAI(uint32 diff) override + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + if (me->GetPositionY() > FirewalPositionY) { - if (!UpdateVictim()) - { - return; - } + EnterEvadeMode(); + return; + } - if (me->GetPositionY() > FirewalPositionY) - { - EnterEvadeMode(); - return; - } + events.Update(diff); - events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_FLAME_BREAK: + DoCastVictim(SPELL_FLAMEBREAK); + events.ScheduleEvent(EVENT_FLAME_BREAK, 10s); + break; + case EVENT_IMMOLATE: + DoCastRandomTarget(SPELL_IMMOLATE, 0, 100.0f); + events.ScheduleEvent(EVENT_IMMOLATE, 8s); + break; + case EVENT_TERRIFYING_ROAR: + DoCastVictim(SPELL_TERRIFYINGROAR); + events.ScheduleEvent(EVENT_TERRIFYING_ROAR, 20s); + break; + case EVENT_BERSERKER_CHARGE: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 38.f, true)) + DoCast(target, SPELL_BERSERKER_CHARGE); + events.ScheduleEvent(EVENT_BERSERKER_CHARGE, 15s, 23s); + break; + case EVENT_FIREBALL: + DoCastVictim(SPELL_FIREBALL); + events.ScheduleEvent(EVENT_FIREBALL, 8s, 21s); + if (events.GetTimeUntilEvent(EVENT_FIREBLAST) < 3s) + events.RescheduleEvent(EVENT_FIREBLAST, 3s); + break; + case EVENT_FIREBLAST: + DoCastVictim(SPELL_FIREBLAST); + events.ScheduleEvent(EVENT_FIREBLAST, 5s, 8s); + if (events.GetTimeUntilEvent(EVENT_FIREBALL) < 3s) + events.RescheduleEvent(EVENT_FIREBALL, 3s); + break; + } if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_FLAME_BREAK: - DoCastVictim(SPELL_FLAMEBREAK); - events.ScheduleEvent(EVENT_FLAME_BREAK, 10s); - break; - case EVENT_IMMOLATE: - DoCastRandomTarget(SPELL_IMMOLATE, 0, 100.0f); - events.ScheduleEvent(EVENT_IMMOLATE, 8s); - break; - case EVENT_TERRIFYING_ROAR: - DoCastVictim(SPELL_TERRIFYINGROAR); - events.ScheduleEvent(EVENT_TERRIFYING_ROAR, 20s); - break; - case EVENT_BERSERKER_CHARGE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 38.f, true)) - { - DoCast(target, SPELL_BERSERKER_CHARGE); - } - events.ScheduleEvent(EVENT_BERSERKER_CHARGE, 15s, 23s); - break; - case EVENT_FIREBALL: - DoCastVictim(SPELL_FIREBALL); - events.ScheduleEvent(EVENT_FIREBALL, 8s, 21s); - if (events.GetTimeUntilEvent(EVENT_FIREBLAST) < 3s) - { - events.RescheduleEvent(EVENT_FIREBLAST, 3s); - } - break; - case EVENT_FIREBLAST: - DoCastVictim(SPELL_FIREBLAST); - events.ScheduleEvent(EVENT_FIREBLAST, 5s, 8s); - if (events.GetTimeUntilEvent(EVENT_FIREBALL) < 3s) - { - events.RescheduleEvent(EVENT_FIREBALL, 3s); - } - break; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - } - - DoMeleeAttackIfReady(); } - void FindNearbyOrcs() - { - std::list temp; - me->GetCreatureListWithEntryInGrid(temp, NPC_BLACKHAND_ELITE, 50.0f); - for (Creature* creature : temp) - { - if (creature->IsAlive()) - { - _nearbyOrcsGUIDs.push_back(creature->GetGUID()); - } - } - } + DoMeleeAttackIfReady(); + } - private: - bool _beastReached; - bool _orcYelled; - GuidVector _nearbyOrcsGUIDs; - }; + void FindNearbyOrcs() + { + std::list temp; + me->GetCreatureListWithEntryInGrid(temp, NPC_BLACKHAND_ELITE, 50.0f); + for (Creature* creature : temp) + if (creature->IsAlive()) + _nearbyOrcsGUIDs.push_back(creature->GetGUID()); + } + +private: + bool _beastReached; + bool _orcYelled; + GuidVector _nearbyOrcsGUIDs; }; //! The beast room areatrigger, this one triggers boss pathing. (AT Id 2066) @@ -280,16 +247,12 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*at*/) override { if (player->IsGameMaster()) - { return false; - } if (InstanceScript* instance = player->GetInstanceScript()) { if (Creature* beast = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THE_BEAST))) - { beast->AI()->SetData(DATA_BEAST_REACHED, DATA_BEAST_REACHED); - } return true; } @@ -306,16 +269,12 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*at*/) override { if (player->IsGameMaster()) - { return false; - } if (InstanceScript* instance = player->GetInstanceScript()) { if (Creature* beast = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THE_BEAST))) - { beast->AI()->SetData(DATA_BEAST_ROOM, DATA_BEAST_ROOM); - } return true; } @@ -326,7 +285,7 @@ public: void AddSC_boss_thebeast() { - new boss_the_beast(); + RegisterBlackrockSpireCreatureAI(boss_the_beast); new at_trigger_the_beast_movement(); new at_the_beast_room(); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_urok_doomhowl.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_urok_doomhowl.cpp index bdebc9ff9..ff9b0f2a0 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_urok_doomhowl.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_urok_doomhowl.cpp @@ -40,82 +40,67 @@ enum Events EVENT_INTIMIDATING_ROAR = 3 }; -class boss_urok_doomhowl : public CreatureScript +struct boss_urok_doomhowl : public BossAI { -public: - boss_urok_doomhowl() : CreatureScript("boss_urok_doomhowl") { } + boss_urok_doomhowl(Creature* creature) : BossAI(creature, DATA_UROK_DOOMHOWL) {} - struct boss_urok_doomhowlAI : public BossAI + void InitializeAI() override { - boss_urok_doomhowlAI(Creature* creature) : BossAI(creature, DATA_UROK_DOOMHOWL) {} + me->CastSpell(me, SPELL_UROK_SPAWN, true); + BossAI::InitializeAI(); + Talk(SAY_SUMMON); + DoZoneInCombat(nullptr, 100.0f); - void InitializeAI() override - { - me->CastSpell(me, SPELL_UROK_SPAWN, true); - BossAI::InitializeAI(); - Talk(SAY_SUMMON); - DoZoneInCombat(nullptr, 100.0f); + if (GameObject* challenge = instance->instance->GetGameObject(instance->GetGuidData(GO_UROK_CHALLENGE))) + challenge->Delete(); - if (GameObject* challenge = instance->instance->GetGameObject(instance->GetGuidData(GO_UROK_CHALLENGE))) - { - challenge->Delete(); - } + if (GameObject* pile = instance->instance->GetGameObject(instance->GetGuidData(GO_UROK_PILE))) + pile->DespawnOrUnsummon(0ms, Seconds(MONTH)); + } - if (GameObject* pile = instance->instance->GetGameObject(instance->GetGuidData(GO_UROK_PILE))) - { - pile->DespawnOrUnsummon(0ms, Seconds(MONTH)); - } - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(SPELL_REND, 17s, 20s); - events.ScheduleEvent(SPELL_STRIKE, 10s, 12s); - events.ScheduleEvent(SPELL_INTIMIDATING_ROAR, 25s, 30s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case SPELL_REND: - DoCastVictim(SPELL_REND); - events.ScheduleEvent(SPELL_REND, 8s, 10s); - break; - case SPELL_STRIKE: - DoCastVictim(SPELL_STRIKE); - events.ScheduleEvent(SPELL_STRIKE, 8s, 10s); - break; - case SPELL_INTIMIDATING_ROAR: - DoCastVictim(SPELL_INTIMIDATING_ROAR); - events.ScheduleEvent(SPELL_INTIMIDATING_ROAR, 40s, 45s); - break; - default: - break; - } - } - DoMeleeAttackIfReady(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void JustEngagedWith(Unit* /*who*/) override { - return GetBlackrockSpireAI(creature); + _JustEngagedWith(); + events.ScheduleEvent(SPELL_REND, 17s, 20s); + events.ScheduleEvent(SPELL_STRIKE, 10s, 12s); + events.ScheduleEvent(SPELL_INTIMIDATING_ROAR, 25s, 30s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case SPELL_REND: + DoCastVictim(SPELL_REND); + events.ScheduleEvent(SPELL_REND, 8s, 10s); + break; + case SPELL_STRIKE: + DoCastVictim(SPELL_STRIKE); + events.ScheduleEvent(SPELL_STRIKE, 8s, 10s); + break; + case SPELL_INTIMIDATING_ROAR: + DoCastVictim(SPELL_INTIMIDATING_ROAR); + events.ScheduleEvent(SPELL_INTIMIDATING_ROAR, 40s, 45s); + break; + default: + break; + } + } + DoMeleeAttackIfReady(); } }; void AddSC_boss_urok_doomhowl() { - new boss_urok_doomhowl(); + RegisterBlackrockSpireCreatureAI(boss_urok_doomhowl); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_warmaster_voone.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_warmaster_voone.cpp index 3240cadd2..339944fd3 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_warmaster_voone.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_warmaster_voone.cpp @@ -55,86 +55,75 @@ enum EventGroups GROUP_WARMASTER }; -class boss_warmaster_voone : public CreatureScript +struct boss_warmaster_voone : public BossAI { -public: - boss_warmaster_voone() : CreatureScript("boss_warmaster_voone") { } + boss_warmaster_voone(Creature* creature) : BossAI(creature, DATA_WARMASTER_VOONE) { } - struct boss_warmastervooneAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_warmastervooneAI(Creature* creature) : BossAI(creature, DATA_WARMASTER_VOONE) { } + _JustEngagedWith(); - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); + events.SetPhase(PHASE_BRAWLER); + events.ScheduleEvent(EVENT_THRASH, 3s, GROUP_BRAWLER, PHASE_BRAWLER); + events.ScheduleEvent(EVENT_THROW_AXE, 1s, GROUP_BRAWLER, PHASE_BRAWLER); + } - events.SetPhase(PHASE_BRAWLER); - events.ScheduleEvent(EVENT_THRASH, 3s, GROUP_BRAWLER, PHASE_BRAWLER); - events.ScheduleEvent(EVENT_THROW_AXE, 1s, GROUP_BRAWLER, PHASE_BRAWLER); - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*type*/, SpellSchoolMask /*school*/) override - { - if (me->HealthBelowPctDamaged(65, damage) && events.IsInPhase(PHASE_BRAWLER)) - { - events.SetPhase(PHASE_THRASHER); - events.CancelEventGroup(GROUP_BRAWLER); - events.ScheduleEvent(EVENT_CLEAVE, 14s, GROUP_THRASHER, PHASE_THRASHER); - events.ScheduleEvent(EVENT_MORTAL_STRIKE, 12s, GROUP_THRASHER, PHASE_THRASHER); - } - else if (me->HealthBelowPctDamaged(40, damage) && events.IsInPhase(PHASE_THRASHER)) - { - events.SetPhase(PHASE_WARMASTER); - events.CancelEventGroup(GROUP_THRASHER); - events.ScheduleEvent(EVENT_SNAP_KICK, 8s, GROUP_WARMASTER, PHASE_WARMASTER); - events.ScheduleEvent(EVENT_UPPERCUT, 20s, GROUP_WARMASTER, PHASE_WARMASTER); - events.ScheduleEvent(EVENT_PUMMEL, 32s, GROUP_WARMASTER, PHASE_WARMASTER); - } - } - - void ExecuteEvent(uint32 eventId) override - { - switch (eventId) - { - case EVENT_SNAP_KICK: - DoCastVictim(SPELL_SNAPKICK); - events.Repeat(6s); - break; - case EVENT_CLEAVE: - DoCastVictim(SPELL_CLEAVE); - events.Repeat(12s); - break; - case EVENT_UPPERCUT: - DoCastVictim(SPELL_UPPERCUT); - events.Repeat(14s); - break; - case EVENT_MORTAL_STRIKE: - DoCastVictim(SPELL_MORTALSTRIKE); - events.Repeat(10s); - break; - case EVENT_PUMMEL: - DoCastVictim(SPELL_PUMMEL); - events.Repeat(16s); - break; - case EVENT_THROW_AXE: - DoCastRandomTarget(SPELL_THROWAXE); - events.Repeat(8s); - break; - case EVENT_THRASH: - DoCastSelf(SPELL_THRASH); - events.Repeat(10s); - break; - } - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*type*/, SpellSchoolMask /*school*/) override { - return GetBlackrockSpireAI(creature); + if (me->HealthBelowPctDamaged(65, damage) && events.IsInPhase(PHASE_BRAWLER)) + { + events.SetPhase(PHASE_THRASHER); + events.CancelEventGroup(GROUP_BRAWLER); + events.ScheduleEvent(EVENT_CLEAVE, 14s, GROUP_THRASHER, PHASE_THRASHER); + events.ScheduleEvent(EVENT_MORTAL_STRIKE, 12s, GROUP_THRASHER, PHASE_THRASHER); + } + else if (me->HealthBelowPctDamaged(40, damage) && events.IsInPhase(PHASE_THRASHER)) + { + events.SetPhase(PHASE_WARMASTER); + events.CancelEventGroup(GROUP_THRASHER); + events.ScheduleEvent(EVENT_SNAP_KICK, 8s, GROUP_WARMASTER, PHASE_WARMASTER); + events.ScheduleEvent(EVENT_UPPERCUT, 20s, GROUP_WARMASTER, PHASE_WARMASTER); + events.ScheduleEvent(EVENT_PUMMEL, 32s, GROUP_WARMASTER, PHASE_WARMASTER); + } + } + + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_SNAP_KICK: + DoCastVictim(SPELL_SNAPKICK); + events.Repeat(6s); + break; + case EVENT_CLEAVE: + DoCastVictim(SPELL_CLEAVE); + events.Repeat(12s); + break; + case EVENT_UPPERCUT: + DoCastVictim(SPELL_UPPERCUT); + events.Repeat(14s); + break; + case EVENT_MORTAL_STRIKE: + DoCastVictim(SPELL_MORTALSTRIKE); + events.Repeat(10s); + break; + case EVENT_PUMMEL: + DoCastVictim(SPELL_PUMMEL); + events.Repeat(16s); + break; + case EVENT_THROW_AXE: + DoCastRandomTarget(SPELL_THROWAXE); + events.Repeat(8s); + break; + case EVENT_THRASH: + DoCastSelf(SPELL_THRASH); + events.Repeat(10s); + break; + } } }; void AddSC_boss_warmastervoone() { - new boss_warmaster_voone(); + RegisterBlackrockSpireCreatureAI(boss_warmaster_voone); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/instance_blackrock_spire.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/instance_blackrock_spire.cpp index dc7cf3d41..62beba867 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/instance_blackrock_spire.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/instance_blackrock_spire.cpp @@ -19,6 +19,7 @@ #include "Cell.h" #include "CellImpl.h" #include "CreatureScript.h" +#include "GameObjectAI.h" #include "GameObjectScript.h" #include "GridNotifiers.h" #include "InstanceMapScript.h" @@ -71,506 +72,534 @@ DoorData const doorData[] = { 0, 0, DOOR_TYPE_ROOM, } // END }; -class instance_blackrock_spire : public InstanceMapScript +struct instance_blackrock_spire : public InstanceScript { -public: - instance_blackrock_spire() : InstanceMapScript(BRSScriptName, MAP_BLACKROCK_SPIRE) { } + uint32 CurrentSolakarWave = 0; + uint32 SolakarState = NOT_STARTED; // there should be a global instance encounter state, where is it? + GuidVector SolakarSummons; + uint32 VaelastraszState = NOT_STARTED; - struct instance_blackrock_spireMapScript : public InstanceScript + instance_blackrock_spire(InstanceMap* map) : InstanceScript(map) { - uint32 CurrentSolakarWave = 0; - uint32 SolakarState = NOT_STARTED; // there should be a global instance encounter state, where is it? - GuidVector SolakarSummons; - uint32 VaelastraszState = NOT_STARTED; + SetHeaders(DataHeader); + SetBossNumber(EncounterCount); + LoadMinionData(minionData); + LoadDoorData(doorData); + CurrentSolakarWave = 0; + SolakarState = NOT_STARTED; + SolakarSummons.clear(); + VaelastraszState = NOT_STARTED; + } - instance_blackrock_spireMapScript(InstanceMap* map) : InstanceScript(map) + void CreatureLooted(Creature* creature, LootType loot) override + { + switch (creature->GetEntry()) { - SetHeaders(DataHeader); - SetBossNumber(EncounterCount); - LoadMinionData(minionData); - LoadDoorData(doorData); - CurrentSolakarWave = 0; - SolakarState = NOT_STARTED; - SolakarSummons.clear(); - VaelastraszState = NOT_STARTED; + case NPC_THE_BEAST: + if (loot == LOOT_SKINNING) + creature->CastSpell(creature, SPELL_FINKLE_IS_EINHORN, true); + break; + } + } + + void OnCreatureCreate(Creature* creature) override + { + switch (creature->GetEntry()) + { + case NPC_UROK_MAGUS: + [[fallthrough]]; + case NPC_UROK_ENFORCER: + UrokMobs.push_back(creature->GetGUID()); + break; + case NPC_HIGHLORD_OMOKK: + HighlordOmokk = creature->GetGUID(); + break; + case NPC_SHADOW_HUNTER_VOSHGAJIN: + ShadowHunterVoshgajin = creature->GetGUID(); + break; + case NPC_WARMASTER_VOONE: + WarMasterVoone = creature->GetGUID(); + break; + case NPC_MOTHER_SMOLDERWEB: + MotherSmolderweb = creature->GetGUID(); + break; + case NPC_UROK_DOOMHOWL: + UrokDoomhowl = creature->GetGUID(); + break; + case NPC_QUARTERMASTER_ZIGRIS: + QuartermasterZigris = creature->GetGUID(); + break; + case NPC_GIZRUL_THE_SLAVENER: + GizrultheSlavener = creature->GetGUID(); + break; + case NPC_HALYCON: + Halycon = creature->GetGUID(); + break; + case NPC_OVERLORD_WYRMTHALAK: + OverlordWyrmthalak = creature->GetGUID(); + break; + case NPC_PYROGAURD_EMBERSEER: + PyroguardEmberseer = creature->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + creature->DisappearAndDie(); + break; + case NPC_WARCHIEF_REND_BLACKHAND: + if (GetBossState(DATA_GYTH) != IN_PROGRESS) + WarchiefRendBlackhand = creature->GetGUID(); + + if (GetBossState(DATA_GYTH) == DONE) + creature->DisappearAndDie(); + break; + case NPC_GYTH: + Gyth = creature->GetGUID(); + break; + case NPC_THE_BEAST: + TheBeast = creature->GetGUID(); + break; + case NPC_GENERAL_DRAKKISATH: + GeneralDrakkisath = creature->GetGUID(); + break; + case NPC_LORD_VICTOR_NEFARIUS: + LordVictorNefarius = creature->GetGUID(); + if (GetBossState(DATA_GYTH) == DONE) + creature->DisappearAndDie(); + break; + case NPC_FINKLE_EINHORN: + creature->AI()->Talk(SAY_FINKLE_GANG); + break; + case NPC_CHROMATIC_ELITE_GUARD: + AddMinion(creature); + break; + } + } + + void OnGameObjectCreate(GameObject* go) override + { + switch (go->GetEntry()) + { + case GO_EMBERSEER_IN: + go_emberseerin = go->GetGUID(); + HandleGameObject(ObjectGuid::Empty, GetBossState(DATA_DRAGONSPIRE_ROOM) == DONE, go); + break; + case GO_DOORS: + go_doors = go->GetGUID(); + if (GetBossState(DATA_DRAGONSPIRE_ROOM) == DONE) + HandleGameObject(ObjectGuid::Empty, true, go); + break; + case GO_EMBERSEER_OUT: + go_emberseerout = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, true, go); + break; + case GO_HALL_RUNE_1: + go_roomrunes[0] = go->GetGUID(); + if (GetBossState(DATA_HALL_RUNE_1) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_HALL_RUNE_2: + go_roomrunes[1] = go->GetGUID(); + if (GetBossState(DATA_HALL_RUNE_2) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_HALL_RUNE_3: + go_roomrunes[2] = go->GetGUID(); + if (GetBossState(DATA_HALL_RUNE_3) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_HALL_RUNE_4: + go_roomrunes[3] = go->GetGUID(); + if (GetBossState(DATA_HALL_RUNE_4) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_HALL_RUNE_5: + go_roomrunes[4] = go->GetGUID(); + if (GetBossState(DATA_HALL_RUNE_5) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_HALL_RUNE_6: + go_roomrunes[5] = go->GetGUID(); + if (GetBossState(DATA_HALL_RUNE_6) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_HALL_RUNE_7: + go_roomrunes[6] = go->GetGUID(); + if (GetBossState(DATA_HALL_RUNE_7) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_EMBERSEER_RUNE_1: + go_emberseerrunes[0] = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_EMBERSEER_RUNE_2: + go_emberseerrunes[1] = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_EMBERSEER_RUNE_3: + go_emberseerrunes[2] = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_EMBERSEER_RUNE_4: + go_emberseerrunes[3] = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_EMBERSEER_RUNE_5: + go_emberseerrunes[4] = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_EMBERSEER_RUNE_6: + go_emberseerrunes[5] = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_EMBERSEER_RUNE_7: + go_emberseerrunes[6] = go->GetGUID(); + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) + HandleGameObject(ObjectGuid::Empty, false, go); + break; + case GO_PORTCULLIS_ACTIVE: + go_portcullis_active = go->GetGUID(); + if (GetBossState(DATA_GYTH) == DONE) + HandleGameObject(ObjectGuid::Empty, true, go); + break; + case GO_UROK_PILE: + go_urokPile = go->GetGUID(); + break; + case GO_UROK_CIRCLE: + go_urokOgreCirles.push_back(go->GetGUID()); + break; + case GO_UROK_CHALLENGE: + go_urokChallenge = go->GetGUID(); + break; + default: + break; } - void CreatureLooted(Creature* creature, LootType loot) override + InstanceScript::OnGameObjectCreate(go); + } + + bool SetBossState(uint32 type, EncounterState state) override + { + if (!InstanceScript::SetBossState(type, state)) + return false; + + switch (type) { - switch (creature->GetEntry()) - { - case NPC_THE_BEAST: - if (loot == LOOT_SKINNING) + case DATA_WARCHIEF_REND_BLACKHAND: + if (state == FAIL) + { + if (Creature* rend = instance->GetCreature(WarchiefRendBlackhand)) + rend->Respawn(true); + + if (Creature* nefarius = instance->GetCreature(LordVictorNefarius)) + nefarius->AI()->Talk(SAY_NEFARIUS_REND_WIPE); + } + break; + default: + break; + } + + return true; + } + + void ProcessEvent(WorldObject* /*obj*/, uint32 eventId) override + { + switch (eventId) + { + case EVENT_PYROGUARD_EMBERSEER: + if (GetBossState(DATA_PYROGAURD_EMBERSEER) == NOT_STARTED) + if (Creature* Emberseer = instance->GetCreature(PyroguardEmberseer)) + Emberseer->AI()->SetData(1, 1); + break; + case EVENT_UROK_DOOMHOWL: + if (GetBossState(DATA_UROK_DOOMHOWL) == NOT_STARTED) + { + SetBossState(DATA_UROK_DOOMHOWL, IN_PROGRESS); + if (GameObject* pile = instance->GetGameObject(go_urokPile)) + pile->SetLootState(GO_JUST_DEACTIVATED); + } + break; + default: + break; + } + } + + void SetData(uint32 type, uint32 data) override + { + switch (type) + { + case AREATRIGGER: + if (data == AREATRIGGER_DRAGONSPIRE_HALL) + { + if (GetBossState(DATA_DRAGONSPIRE_ROOM) != DONE) + Events.ScheduleEvent(EVENT_DRAGONSPIRE_ROOM_STORE, 1s); + } + break; + case DATA_SOLAKAR_FLAMEWREATH: + switch (data) + { + case IN_PROGRESS: + if (SolakarState == NOT_STARTED) + Events.ScheduleEvent(EVENT_SOLAKAR_WAVE, 500ms); + break; + case FAIL: + for (ObjectGuid const& guid : SolakarSummons) + if (Creature* creature = instance->GetCreature(guid)) + creature->DespawnOrUnsummon(); + SolakarSummons.clear(); + CurrentSolakarWave = 0; + SetData(DATA_SOLAKAR_FLAMEWREATH, NOT_STARTED); + break; + case DONE: + break; + } + SolakarState = data; + break; + case DATA_VAELASTRASZ: + VaelastraszState = data; + break; + case DATA_UROK_DOOMHOWL: + if (data == FAIL) + { + if (GetBossState(DATA_UROK_DOOMHOWL) != NOT_STARTED) { - creature->CastSpell(creature, SPELL_FINKLE_IS_EINHORN, true); - } - break; - } - } - - void OnCreatureCreate(Creature* creature) override - { - switch (creature->GetEntry()) - { - case NPC_UROK_MAGUS: - [[fallthrough]]; - case NPC_UROK_ENFORCER: - UrokMobs.push_back(creature->GetGUID()); - break; - case NPC_HIGHLORD_OMOKK: - HighlordOmokk = creature->GetGUID(); - break; - case NPC_SHADOW_HUNTER_VOSHGAJIN: - ShadowHunterVoshgajin = creature->GetGUID(); - break; - case NPC_WARMASTER_VOONE: - WarMasterVoone = creature->GetGUID(); - break; - case NPC_MOTHER_SMOLDERWEB: - MotherSmolderweb = creature->GetGUID(); - break; - case NPC_UROK_DOOMHOWL: - UrokDoomhowl = creature->GetGUID(); - break; - case NPC_QUARTERMASTER_ZIGRIS: - QuartermasterZigris = creature->GetGUID(); - break; - case NPC_GIZRUL_THE_SLAVENER: - GizrultheSlavener = creature->GetGUID(); - break; - case NPC_HALYCON: - Halycon = creature->GetGUID(); - break; - case NPC_OVERLORD_WYRMTHALAK: - OverlordWyrmthalak = creature->GetGUID(); - break; - case NPC_PYROGAURD_EMBERSEER: - PyroguardEmberseer = creature->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - creature->DisappearAndDie(); - break; - case NPC_WARCHIEF_REND_BLACKHAND: - if (GetBossState(DATA_GYTH) != IN_PROGRESS) - { - WarchiefRendBlackhand = creature->GetGUID(); - } - - if (GetBossState(DATA_GYTH) == DONE) - creature->DisappearAndDie(); - break; - case NPC_GYTH: - Gyth = creature->GetGUID(); - break; - case NPC_THE_BEAST: - TheBeast = creature->GetGUID(); - break; - case NPC_GENERAL_DRAKKISATH: - GeneralDrakkisath = creature->GetGUID(); - break; - case NPC_LORD_VICTOR_NEFARIUS: - LordVictorNefarius = creature->GetGUID(); - if (GetBossState(DATA_GYTH) == DONE) - creature->DisappearAndDie(); - break; - case NPC_FINKLE_EINHORN: - creature->AI()->Talk(SAY_FINKLE_GANG); - break; - case NPC_CHROMATIC_ELITE_GUARD: - AddMinion(creature); - break; - } - } - - void OnGameObjectCreate(GameObject* go) override - { - switch (go->GetEntry()) - { - case GO_EMBERSEER_IN: - go_emberseerin = go->GetGUID(); - HandleGameObject(ObjectGuid::Empty, GetBossState(DATA_DRAGONSPIRE_ROOM) == DONE, go); - break; - case GO_DOORS: - go_doors = go->GetGUID(); - if (GetBossState(DATA_DRAGONSPIRE_ROOM) == DONE) - HandleGameObject(ObjectGuid::Empty, true, go); - break; - case GO_EMBERSEER_OUT: - go_emberseerout = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, true, go); - break; - case GO_HALL_RUNE_1: - go_roomrunes[0] = go->GetGUID(); - if (GetBossState(DATA_HALL_RUNE_1) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_HALL_RUNE_2: - go_roomrunes[1] = go->GetGUID(); - if (GetBossState(DATA_HALL_RUNE_2) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_HALL_RUNE_3: - go_roomrunes[2] = go->GetGUID(); - if (GetBossState(DATA_HALL_RUNE_3) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_HALL_RUNE_4: - go_roomrunes[3] = go->GetGUID(); - if (GetBossState(DATA_HALL_RUNE_4) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_HALL_RUNE_5: - go_roomrunes[4] = go->GetGUID(); - if (GetBossState(DATA_HALL_RUNE_5) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_HALL_RUNE_6: - go_roomrunes[5] = go->GetGUID(); - if (GetBossState(DATA_HALL_RUNE_6) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_HALL_RUNE_7: - go_roomrunes[6] = go->GetGUID(); - if (GetBossState(DATA_HALL_RUNE_7) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_EMBERSEER_RUNE_1: - go_emberseerrunes[0] = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_EMBERSEER_RUNE_2: - go_emberseerrunes[1] = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_EMBERSEER_RUNE_3: - go_emberseerrunes[2] = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_EMBERSEER_RUNE_4: - go_emberseerrunes[3] = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_EMBERSEER_RUNE_5: - go_emberseerrunes[4] = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_EMBERSEER_RUNE_6: - go_emberseerrunes[5] = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_EMBERSEER_RUNE_7: - go_emberseerrunes[6] = go->GetGUID(); - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == DONE) - HandleGameObject(ObjectGuid::Empty, false, go); - break; - case GO_PORTCULLIS_ACTIVE: - go_portcullis_active = go->GetGUID(); - if (GetBossState(DATA_GYTH) == DONE) - HandleGameObject(ObjectGuid::Empty, true, go); - break; - case GO_UROK_PILE: - go_urokPile = go->GetGUID(); - break; - case GO_UROK_CIRCLE: - go_urokOgreCirles.push_back(go->GetGUID()); - break; - case GO_UROK_CHALLENGE: - go_urokChallenge = go->GetGUID(); - break; - default: - break; - } - - InstanceScript::OnGameObjectCreate(go); - } - - bool SetBossState(uint32 type, EncounterState state) override - { - if (!InstanceScript::SetBossState(type, state)) - return false; - - switch (type) - { - case DATA_WARCHIEF_REND_BLACKHAND: - if (state == FAIL) - { - if (Creature* rend = instance->GetCreature(WarchiefRendBlackhand)) + SetBossState(DATA_UROK_DOOMHOWL, NOT_STARTED); + if (GameObject* challenge = instance->GetGameObject(go_urokChallenge)) + challenge->Delete(); + if (GameObject* pile = instance->GetGameObject(go_urokPile)) { - rend->Respawn(true); - } - - if (Creature* nefarius = instance->GetCreature(LordVictorNefarius)) - { - nefarius->AI()->Talk(SAY_NEFARIUS_REND_WIPE); + pile->SetLootState(GO_READY); + pile->Respawn(); } + for (auto const& circleGUID : go_urokOgreCirles) + if (GameObject* circle = instance->GetGameObject(circleGUID)) + circle->Delete(); + for (auto const& mobGUID : UrokMobs) + if (Creature* mob = instance->GetCreature(mobGUID)) + mob->DespawnOrUnsummon(); } - break; - default: - break; - } + } + break; + default: + break; + } + } - return true; + uint32 GetData(uint32 type) const override + { + if (type == DATA_SOLAKAR_FLAMEWREATH) + return SolakarState; + else if (type == DATA_VAELASTRASZ) + return VaelastraszState; + else + return InstanceScript::GetData(type); + } + + void SummonSolakarWave(uint8 number) + { + if (number < MAX_WAVE_COUNT) + { + if (Creature* summon = instance->SummonCreature(NPC_ROOKERY_GUARDIAN, SolakarPosLeft)) + SolakarSummons.push_back(summon->GetGUID()); + + if (Creature* summon = instance->SummonCreature(NPC_ROOKERY_HATCHER, SolakarPosRight)) + SolakarSummons.push_back(summon->GetGUID()); + + if (number == 0) + if (Creature* FirstHatcher = instance->GetCreature(SolakarSummons.back())) // works because we spawned a hatcher second + FirstHatcher->AI()->Talk(SAY_SOLAKAR_FIRST_HATCHER); + } + else if (number == MAX_WAVE_COUNT) + { + if (Creature* summon = instance->SummonCreature(NPC_SOLAKAR, SolakarPosBoss)) + SolakarSummons.push_back(summon->GetGUID()); + } + } + + ObjectGuid GetGuidData(uint32 type) const override + { + switch (type) + { + case DATA_HIGHLORD_OMOKK: + return HighlordOmokk; + case DATA_SHADOW_HUNTER_VOSHGAJIN: + return ShadowHunterVoshgajin; + case DATA_WARMASTER_VOONE: + return WarMasterVoone; + case DATA_MOTHER_SMOLDERWEB: + return MotherSmolderweb; + case DATA_UROK_DOOMHOWL: + return UrokDoomhowl; + case DATA_QUARTERMASTER_ZIGRIS: + return QuartermasterZigris; + case DATA_GIZRUL_THE_SLAVENER: + return GizrultheSlavener; + case DATA_HALYCON: + return Halycon; + case DATA_OVERLORD_WYRMTHALAK: + return OverlordWyrmthalak; + case DATA_PYROGAURD_EMBERSEER: + return PyroguardEmberseer; + case DATA_WARCHIEF_REND_BLACKHAND: + return WarchiefRendBlackhand; + case DATA_GYTH: + return Gyth; + case DATA_THE_BEAST: + return TheBeast; + case DATA_GENERAL_DRAKKISATH: + return GeneralDrakkisath; + case GO_EMBERSEER_IN: + return go_emberseerin; + case GO_DOORS: + return go_doors; + case GO_EMBERSEER_OUT: + return go_emberseerout; + case GO_HALL_RUNE_1: + return go_roomrunes[0]; + case GO_HALL_RUNE_2: + return go_roomrunes[1]; + case GO_HALL_RUNE_3: + return go_roomrunes[2]; + case GO_HALL_RUNE_4: + return go_roomrunes[3]; + case GO_HALL_RUNE_5: + return go_roomrunes[4]; + case GO_HALL_RUNE_6: + return go_roomrunes[5]; + case GO_HALL_RUNE_7: + return go_roomrunes[6]; + case GO_EMBERSEER_RUNE_1: + return go_emberseerrunes[0]; + case GO_EMBERSEER_RUNE_2: + return go_emberseerrunes[1]; + case GO_EMBERSEER_RUNE_3: + return go_emberseerrunes[2]; + case GO_EMBERSEER_RUNE_4: + return go_emberseerrunes[3]; + case GO_EMBERSEER_RUNE_5: + return go_emberseerrunes[4]; + case GO_EMBERSEER_RUNE_6: + return go_emberseerrunes[5]; + case GO_EMBERSEER_RUNE_7: + return go_emberseerrunes[6]; + case GO_PORTCULLIS_ACTIVE: + return go_portcullis_active; + case GO_UROK_PILE: + return go_urokPile; + case GO_UROK_CHALLENGE: + return go_urokChallenge; + default: + break; } - void ProcessEvent(WorldObject* /*obj*/, uint32 eventId) override + return ObjectGuid::Empty; + } + + void Update(uint32 diff) override + { + Events.Update(diff); + + while (uint32 eventId = Events.ExecuteEvent()) { switch (eventId) { - case EVENT_PYROGUARD_EMBERSEER: - if (GetBossState(DATA_PYROGAURD_EMBERSEER) == NOT_STARTED) - { - if (Creature* Emberseer = instance->GetCreature(PyroguardEmberseer)) - Emberseer->AI()->SetData(1, 1); - } + case EVENT_DRAGONSPIRE_ROOM_STORE: + Dragonspireroomstore(); + Events.ScheduleEvent(EVENT_DRAGONSPIRE_ROOM_CHECK, 3s); break; - case EVENT_UROK_DOOMHOWL: - if (GetBossState(DATA_UROK_DOOMHOWL) == NOT_STARTED) - { - SetBossState(DATA_UROK_DOOMHOWL, IN_PROGRESS); - if (GameObject* pile = instance->GetGameObject(go_urokPile)) - { - pile->SetLootState(GO_JUST_DEACTIVATED); - } - } - break; - default: - break; - } - } - - void SetData(uint32 type, uint32 data) override - { - switch (type) - { - case AREATRIGGER: - if (data == AREATRIGGER_DRAGONSPIRE_HALL) - { - if (GetBossState(DATA_DRAGONSPIRE_ROOM) != DONE) - Events.ScheduleEvent(EVENT_DRAGONSPIRE_ROOM_STORE, 1s); - } - break; - case DATA_SOLAKAR_FLAMEWREATH: - switch (data) - { - case IN_PROGRESS: - if (SolakarState == NOT_STARTED) - { - Events.ScheduleEvent(EVENT_SOLAKAR_WAVE, 500ms); - } - break; - case FAIL: - for (ObjectGuid const& guid : SolakarSummons) - { - if (Creature* creature = instance->GetCreature(guid)) - { - creature->DespawnOrUnsummon(); - } - } - SolakarSummons.clear(); - CurrentSolakarWave = 0; - SetData(DATA_SOLAKAR_FLAMEWREATH, NOT_STARTED); - break; - case DONE: - break; - } - SolakarState = data; - break; - case DATA_VAELASTRASZ: - VaelastraszState = data; - break; - case DATA_UROK_DOOMHOWL: - if (data == FAIL) - { - if (GetBossState(DATA_UROK_DOOMHOWL) != NOT_STARTED) - { - SetBossState(DATA_UROK_DOOMHOWL, NOT_STARTED); - if (GameObject* challenge = instance->GetGameObject(go_urokChallenge)) - { - challenge->Delete(); - } - if (GameObject* pile = instance->GetGameObject(go_urokPile)) - { - pile->SetLootState(GO_READY); - pile->Respawn(); - } - for (auto const& circleGUID : go_urokOgreCirles) - { - if (GameObject* circle = instance->GetGameObject(circleGUID)) - { - circle->Delete(); - } - } - for (auto const& mobGUID : UrokMobs) - { - if (Creature* mob = instance->GetCreature(mobGUID)) - { - mob->DespawnOrUnsummon(); - } - } - } - } - break; - default: - break; - } - } - - uint32 GetData(uint32 type) const override - { - if (type == DATA_SOLAKAR_FLAMEWREATH) - { - return SolakarState; - } - else if (type == DATA_VAELASTRASZ) - { - return VaelastraszState; - } - else - { - return InstanceScript::GetData(type); - } - } - - void SummonSolakarWave(uint8 number) - { - if (number < MAX_WAVE_COUNT) - { - if (Creature* summon = instance->SummonCreature(NPC_ROOKERY_GUARDIAN, SolakarPosLeft)) - { - SolakarSummons.push_back(summon->GetGUID()); - } - - if (Creature* summon = instance->SummonCreature(NPC_ROOKERY_HATCHER, SolakarPosRight)) - { - SolakarSummons.push_back(summon->GetGUID()); - } - - if (number == 0) - { - if (Creature* FirstHatcher = instance->GetCreature(SolakarSummons.back())) // works because we spawned a hatcher second - { - FirstHatcher->AI()->Talk(SAY_SOLAKAR_FIRST_HATCHER); - } - } - } - else if (number == MAX_WAVE_COUNT) - { - if (Creature* summon = instance->SummonCreature(NPC_SOLAKAR, SolakarPosBoss)) - { - SolakarSummons.push_back(summon->GetGUID()); - } - } - } - - ObjectGuid GetGuidData(uint32 type) const override - { - switch (type) - { - case DATA_HIGHLORD_OMOKK: - return HighlordOmokk; - case DATA_SHADOW_HUNTER_VOSHGAJIN: - return ShadowHunterVoshgajin; - case DATA_WARMASTER_VOONE: - return WarMasterVoone; - case DATA_MOTHER_SMOLDERWEB: - return MotherSmolderweb; - case DATA_UROK_DOOMHOWL: - return UrokDoomhowl; - case DATA_QUARTERMASTER_ZIGRIS: - return QuartermasterZigris; - case DATA_GIZRUL_THE_SLAVENER: - return GizrultheSlavener; - case DATA_HALYCON: - return Halycon; - case DATA_OVERLORD_WYRMTHALAK: - return OverlordWyrmthalak; - case DATA_PYROGAURD_EMBERSEER: - return PyroguardEmberseer; - case DATA_WARCHIEF_REND_BLACKHAND: - return WarchiefRendBlackhand; - case DATA_GYTH: - return Gyth; - case DATA_THE_BEAST: - return TheBeast; - case DATA_GENERAL_DRAKKISATH: - return GeneralDrakkisath; - case GO_EMBERSEER_IN: - return go_emberseerin; - case GO_DOORS: - return go_doors; - case GO_EMBERSEER_OUT: - return go_emberseerout; - case GO_HALL_RUNE_1: - return go_roomrunes[0]; - case GO_HALL_RUNE_2: - return go_roomrunes[1]; - case GO_HALL_RUNE_3: - return go_roomrunes[2]; - case GO_HALL_RUNE_4: - return go_roomrunes[3]; - case GO_HALL_RUNE_5: - return go_roomrunes[4]; - case GO_HALL_RUNE_6: - return go_roomrunes[5]; - case GO_HALL_RUNE_7: - return go_roomrunes[6]; - case GO_EMBERSEER_RUNE_1: - return go_emberseerrunes[0]; - case GO_EMBERSEER_RUNE_2: - return go_emberseerrunes[1]; - case GO_EMBERSEER_RUNE_3: - return go_emberseerrunes[2]; - case GO_EMBERSEER_RUNE_4: - return go_emberseerrunes[3]; - case GO_EMBERSEER_RUNE_5: - return go_emberseerrunes[4]; - case GO_EMBERSEER_RUNE_6: - return go_emberseerrunes[5]; - case GO_EMBERSEER_RUNE_7: - return go_emberseerrunes[6]; - case GO_PORTCULLIS_ACTIVE: - return go_portcullis_active; - case GO_UROK_PILE: - return go_urokPile; - case GO_UROK_CHALLENGE: - return go_urokChallenge; - default: - break; - } - - return ObjectGuid::Empty; - } - - void Update(uint32 diff) override - { - Events.Update(diff); - - while (uint32 eventId = Events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_DRAGONSPIRE_ROOM_STORE: - Dragonspireroomstore(); + case EVENT_DRAGONSPIRE_ROOM_CHECK: + Dragonspireroomcheck(); + if ((GetBossState(DATA_DRAGONSPIRE_ROOM) != DONE)) Events.ScheduleEvent(EVENT_DRAGONSPIRE_ROOM_CHECK, 3s); + break; + case EVENT_SOLAKAR_WAVE: + SummonSolakarWave(CurrentSolakarWave); + if (CurrentSolakarWave < MAX_WAVE_COUNT) + { + Events.ScheduleEvent(EVENT_SOLAKAR_WAVE, TIMER_SOLAKAR_WAVE); + CurrentSolakarWave++; + } + break; + default: + break; + } + } + } + + void Dragonspireroomstore() + { + + for (uint8 i = 0; i < 7; ++i) + { + // Refresh the creature list + runecreaturelist[i].clear(); + + if (GameObject* rune = instance->GetGameObject(go_roomrunes[i])) + { + for (uint8 j = 0; j < 3; ++j) + { + std::list creatureList; + GetCreatureListWithEntryInGrid(creatureList, rune, DragonspireMobs[j], 15.0f); + for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) + if (Creature* creature = *itr) + runecreaturelist[i].push_back(creature->GetGUID()); + } + } + } + } + + void Dragonspireroomcheck() + { + Creature* mob = nullptr; + GameObject* rune = nullptr; + + for (uint8 i = 0; i < 7; ++i) + { + bool _mobAlive = false; + rune = instance->GetGameObject(go_roomrunes[i]); + if (!rune) + continue; + + if (rune->GetGoState() == GO_STATE_ACTIVE) + { + for (ObjectGuid const& guid : runecreaturelist[i]) + { + mob = instance->GetCreature(guid); + if (mob && mob->IsAlive()) + _mobAlive = true; + } + } + + if (!_mobAlive && rune->GetGoState() == GO_STATE_ACTIVE) + { + HandleGameObject(ObjectGuid::Empty, false, rune); + + switch (rune->GetEntry()) + { + case GO_HALL_RUNE_1: + SetBossState(DATA_HALL_RUNE_1, DONE); break; - case EVENT_DRAGONSPIRE_ROOM_CHECK: - Dragonspireroomcheck(); - if ((GetBossState(DATA_DRAGONSPIRE_ROOM) != DONE)) - Events.ScheduleEvent(EVENT_DRAGONSPIRE_ROOM_CHECK, 3s); + case GO_HALL_RUNE_2: + SetBossState(DATA_HALL_RUNE_2, DONE); break; - case EVENT_SOLAKAR_WAVE: - SummonSolakarWave(CurrentSolakarWave); - if (CurrentSolakarWave < MAX_WAVE_COUNT) - { - Events.ScheduleEvent(EVENT_SOLAKAR_WAVE, TIMER_SOLAKAR_WAVE); - CurrentSolakarWave++; - } + case GO_HALL_RUNE_3: + SetBossState(DATA_HALL_RUNE_3, DONE); + break; + case GO_HALL_RUNE_4: + SetBossState(DATA_HALL_RUNE_4, DONE); + break; + case GO_HALL_RUNE_5: + SetBossState(DATA_HALL_RUNE_5, DONE); + break; + case GO_HALL_RUNE_6: + SetBossState(DATA_HALL_RUNE_6, DONE); + break; + case GO_HALL_RUNE_7: + SetBossState(DATA_HALL_RUNE_7, DONE); break; default: break; @@ -578,136 +607,49 @@ public: } } - void Dragonspireroomstore() + if (GetBossState(DATA_HALL_RUNE_1) == DONE && GetBossState(DATA_HALL_RUNE_2) == DONE && GetBossState(DATA_HALL_RUNE_3) == DONE && + GetBossState(DATA_HALL_RUNE_4) == DONE && GetBossState(DATA_HALL_RUNE_5) == DONE && GetBossState(DATA_HALL_RUNE_6) == DONE && + GetBossState(DATA_HALL_RUNE_7) == DONE) { - - for (uint8 i = 0; i < 7; ++i) - { - // Refresh the creature list - runecreaturelist[i].clear(); - - if (GameObject* rune = instance->GetGameObject(go_roomrunes[i])) - { - for (uint8 j = 0; j < 3; ++j) - { - std::list creatureList; - GetCreatureListWithEntryInGrid(creatureList, rune, DragonspireMobs[j], 15.0f); - for (std::list::iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr) - { - if (Creature* creature = *itr) - { - runecreaturelist[i].push_back(creature->GetGUID()); - } - } - } - } - } + SetBossState(DATA_DRAGONSPIRE_ROOM, DONE); + if (GameObject* door1 = instance->GetGameObject(go_emberseerin)) + HandleGameObject(ObjectGuid::Empty, true, door1); + if (GameObject* door2 = instance->GetGameObject(go_doors)) + HandleGameObject(ObjectGuid::Empty, true, door2); + if (GameObject* door3 = instance->GetGameObject(go_emberseerin)) + HandleGameObject(ObjectGuid::Empty, true, door3); } - - void Dragonspireroomcheck() - { - Creature* mob = nullptr; - GameObject* rune = nullptr; - - for (uint8 i = 0; i < 7; ++i) - { - bool _mobAlive = false; - rune = instance->GetGameObject(go_roomrunes[i]); - if (!rune) - continue; - - if (rune->GetGoState() == GO_STATE_ACTIVE) - { - for (ObjectGuid const& guid : runecreaturelist[i]) - { - mob = instance->GetCreature(guid); - if (mob && mob->IsAlive()) - _mobAlive = true; - } - } - - if (!_mobAlive && rune->GetGoState() == GO_STATE_ACTIVE) - { - HandleGameObject(ObjectGuid::Empty, false, rune); - - switch (rune->GetEntry()) - { - case GO_HALL_RUNE_1: - SetBossState(DATA_HALL_RUNE_1, DONE); - break; - case GO_HALL_RUNE_2: - SetBossState(DATA_HALL_RUNE_2, DONE); - break; - case GO_HALL_RUNE_3: - SetBossState(DATA_HALL_RUNE_3, DONE); - break; - case GO_HALL_RUNE_4: - SetBossState(DATA_HALL_RUNE_4, DONE); - break; - case GO_HALL_RUNE_5: - SetBossState(DATA_HALL_RUNE_5, DONE); - break; - case GO_HALL_RUNE_6: - SetBossState(DATA_HALL_RUNE_6, DONE); - break; - case GO_HALL_RUNE_7: - SetBossState(DATA_HALL_RUNE_7, DONE); - break; - default: - break; - } - } - } - - if (GetBossState(DATA_HALL_RUNE_1) == DONE && GetBossState(DATA_HALL_RUNE_2) == DONE && GetBossState(DATA_HALL_RUNE_3) == DONE && - GetBossState(DATA_HALL_RUNE_4) == DONE && GetBossState(DATA_HALL_RUNE_5) == DONE && GetBossState(DATA_HALL_RUNE_6) == DONE && - GetBossState(DATA_HALL_RUNE_7) == DONE) - { - SetBossState(DATA_DRAGONSPIRE_ROOM, DONE); - if (GameObject* door1 = instance->GetGameObject(go_emberseerin)) - HandleGameObject(ObjectGuid::Empty, true, door1); - if (GameObject* door2 = instance->GetGameObject(go_doors)) - HandleGameObject(ObjectGuid::Empty, true, door2); - if (GameObject* door3 = instance->GetGameObject(go_emberseerin)) - HandleGameObject(ObjectGuid::Empty, true, door3); - } - } - - protected: - EventMap Events; - ObjectGuid HighlordOmokk; - ObjectGuid ShadowHunterVoshgajin; - ObjectGuid WarMasterVoone; - ObjectGuid MotherSmolderweb; - ObjectGuid UrokDoomhowl; - ObjectGuid QuartermasterZigris; - ObjectGuid GizrultheSlavener; - ObjectGuid Halycon; - ObjectGuid OverlordWyrmthalak; - ObjectGuid PyroguardEmberseer; - ObjectGuid WarchiefRendBlackhand; - ObjectGuid Gyth; - ObjectGuid LordVictorNefarius; - ObjectGuid TheBeast; - ObjectGuid GeneralDrakkisath; - ObjectGuid go_emberseerin; - ObjectGuid go_doors; - ObjectGuid go_emberseerout; - ObjectGuid go_blackrockaltar; - ObjectGuid go_roomrunes[7]; - ObjectGuid go_emberseerrunes[7]; - GuidVector runecreaturelist[7]; - ObjectGuid go_portcullis_active; - ObjectGuid go_urokPile; - ObjectGuid go_urokChallenge; - std::vector go_urokOgreCirles; - std::vector UrokMobs; - }; - - InstanceScript* GetInstanceScript(InstanceMap* map) const override - { - return new instance_blackrock_spireMapScript(map); } + +protected: + EventMap Events; + ObjectGuid HighlordOmokk; + ObjectGuid ShadowHunterVoshgajin; + ObjectGuid WarMasterVoone; + ObjectGuid MotherSmolderweb; + ObjectGuid UrokDoomhowl; + ObjectGuid QuartermasterZigris; + ObjectGuid GizrultheSlavener; + ObjectGuid Halycon; + ObjectGuid OverlordWyrmthalak; + ObjectGuid PyroguardEmberseer; + ObjectGuid WarchiefRendBlackhand; + ObjectGuid Gyth; + ObjectGuid LordVictorNefarius; + ObjectGuid TheBeast; + ObjectGuid GeneralDrakkisath; + ObjectGuid go_emberseerin; + ObjectGuid go_doors; + ObjectGuid go_emberseerout; + ObjectGuid go_blackrockaltar; + ObjectGuid go_roomrunes[7]; + ObjectGuid go_emberseerrunes[7]; + GuidVector runecreaturelist[7]; + ObjectGuid go_portcullis_active; + ObjectGuid go_urokPile; + ObjectGuid go_urokChallenge; + std::vector go_urokOgreCirles; + std::vector UrokMobs; }; /*##### @@ -769,25 +711,20 @@ public: } }; -class go_father_flame : public GameObjectScript +struct go_father_flame : public GameObjectAI { -public: - go_father_flame() : GameObjectScript("go_father_flame") {} + go_father_flame(GameObject* go) : GameObjectAI(go) {} - void OnLootStateChanged(GameObject* go, uint32 state, Unit* /*unit*/) override + void OnStateChanged(uint32 state, Unit* /*unit*/) override { - if (InstanceScript* instance = go->GetInstanceScript()) - { + if (InstanceScript* instance = me->GetInstanceScript()) if (state == GO_ACTIVATED) { if (instance->GetData(DATA_SOLAKAR_FLAMEWREATH) == IN_PROGRESS || instance->GetData(DATA_SOLAKAR_FLAMEWREATH) == DONE) - { return; - } instance->SetData(DATA_SOLAKAR_FLAMEWREATH, IN_PROGRESS); } - } } }; @@ -861,7 +798,6 @@ class spell_blackrock_spire_call_of_vaelastrasz : public SpellScript void OnEffect(SpellEffIndex /*effIndex*/) { if (Unit* caster = GetCaster()) - { if (InstanceScript* instance = caster->GetInstanceScript()) { instance->SetData(DATA_VAELASTRASZ, IN_PROGRESS); @@ -871,7 +807,6 @@ class spell_blackrock_spire_call_of_vaelastrasz : public SpellScript // despawn is called by the CreatureAI caster->SummonCreature(NPC_VAELASTRASZ_THE_RED, spawnPosition, TEMPSUMMON_TIMED_DESPAWN, 60 * IN_MILLISECONDS); } - } } void Register() override @@ -929,170 +864,153 @@ enum Events EVENT_FLAME_BREATH, }; -class npc_vaelastrasz_the_red : public CreatureScript +struct npc_vaelastrasz_the_red : public CreatureAI { -public: - npc_vaelastrasz_the_red() : CreatureScript("npc_vaelastrasz_the_red") { } + npc_vaelastrasz_the_red(Creature* creature) : CreatureAI(creature) { } - struct npc_vaelastrasz_the_redAI : public CreatureAI + void IsSummonedBy(WorldObject* summoner) override { - npc_vaelastrasz_the_redAI(Creature* creature) : CreatureAI(creature) { } + if (!summoner) + return; + _combatEnabled = false; + me->CastSpell(me, SPELL_VAELAN_SPAWNS, false); + me->SetFacingToObject(summoner); + Talk(SAY_RED_SUMMONED); + if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 100.0f)) + _victorGUID = victor->GetGUID(); + events.ScheduleEvent(EVENT_RED_1_TALK_BEFORE_TRANSFORM, 3s); + } - void IsSummonedBy(WorldObject* summoner) override + void UpdateAI(uint32 diff) override + { + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) { - if (!summoner) + switch (eventId) { - return; - } - _combatEnabled = false; - me->CastSpell(me, SPELL_VAELAN_SPAWNS, false); - me->SetFacingToObject(summoner); - Talk(SAY_RED_SUMMONED); - if (Creature* victor = me->FindNearestCreature(NPC_LORD_VICTOR_NEFARIUS, 100.0f)) - { - _victorGUID = victor->GetGUID(); - } - events.ScheduleEvent(EVENT_RED_1_TALK_BEFORE_TRANSFORM, 3s); - } - - void UpdateAI(uint32 diff) override - { - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_RED_1_TALK_BEFORE_TRANSFORM: - Talk(SAY_RED_BEFORE_TRANSFORM); - events.ScheduleEvent(EVENT_RED_2_TRANSFORM, 2s); - break; - case EVENT_RED_2_TRANSFORM: - me->CastSpell(me, SPELL_TOUCH_OF_VAELASTRASZ, false); - me->SetEntry(NPC_VAELASTRASZ_UBRS); - me->SetDisplayId(MODEL_VAELASTRASZ_UBRS); - events.ScheduleEvent(EVENT_VAEL_TALK_SUMMON, 1s); - events.ScheduleEvent(EVENT_VAEL_1_START_COMBAT, 5s); - break; - case EVENT_VAEL_TALK_SUMMON: - Talk(SAY_VAEL_SUMMONED); - break; - case EVENT_VAEL_1_START_COMBAT: - _combatEnabled = true; - me->SetImmuneToNPC(false); - if (Creature* gyth = me->FindNearestCreature(NPC_GYTH, 100.0f, true)) + case EVENT_RED_1_TALK_BEFORE_TRANSFORM: + Talk(SAY_RED_BEFORE_TRANSFORM); + events.ScheduleEvent(EVENT_RED_2_TRANSFORM, 2s); + break; + case EVENT_RED_2_TRANSFORM: + me->CastSpell(me, SPELL_TOUCH_OF_VAELASTRASZ, false); + me->SetEntry(NPC_VAELASTRASZ_UBRS); + me->SetDisplayId(MODEL_VAELASTRASZ_UBRS); + events.ScheduleEvent(EVENT_VAEL_TALK_SUMMON, 1s); + events.ScheduleEvent(EVENT_VAEL_1_START_COMBAT, 5s); + break; + case EVENT_VAEL_TALK_SUMMON: + Talk(SAY_VAEL_SUMMONED); + break; + case EVENT_VAEL_1_START_COMBAT: + _combatEnabled = true; + me->SetImmuneToNPC(false); + if (Creature* gyth = me->FindNearestCreature(NPC_GYTH, 100.0f, true)) + { + me->AddThreat(gyth, 1000000.f); + me->AI()->AttackStart(gyth); + } + if (Creature* rend = me->FindNearestCreature(NPC_WARCHIEF_REND_BLACKHAND, 100.0f, true)) + { + if (!rend->IsImmuneToNPC() && rend->isTargetableForAttack()) { - me->AddThreat(gyth, 1000000.f); - me->AI()->AttackStart(gyth); - } - if (Creature* rend = me->FindNearestCreature(NPC_WARCHIEF_REND_BLACKHAND, 100.0f, true)) - { - if (!rend->IsImmuneToNPC() && rend->isTargetableForAttack()) + me->AddThreat(rend, 100000.f); + if (!me->FindNearestCreature(NPC_GYTH, 100.0f, true)) { - me->AddThreat(rend, 100000.f); - if (!me->FindNearestCreature(NPC_GYTH, 100.0f, true)) - { - me->AI()->AttackStart(rend); - } + me->AI()->AttackStart(rend); } } - _events2.ScheduleEvent(EVENT_FLAME_BREATH, 5s); - events.ScheduleEvent(EVENT_NEFARIUS_TALK_1, 500ms); - break; - case EVENT_NEFARIUS_TALK_1: - if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) - { - victor->GetMotionMaster()->Clear(); // stop pacing - victor->GetMotionMaster()->MoveIdle(); - victor->StopMovingOnCurrentPos(); - victor->SetFacingToObject(me); - victor->AI()->Talk(SAY_NEFARIUS_15); - } - events.ScheduleEvent(EVENT_NEFARIUS_TALK_2, 6s); - break; - case EVENT_NEFARIUS_TALK_2: - if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) - { - victor->SetFacingToObject(me); - victor->AI()->Talk(SAY_NEFARIUS_16); - } - events.ScheduleEvent(EVENT_NEFARIUS_TALK_3, 5s); - break; - case EVENT_NEFARIUS_TALK_3: - if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) - { - victor->SetFacingToObject(me); - victor->AI()->Talk(SAY_NEFARIUS_17); - } - events.ScheduleEvent(EVENT_NEFARIUS_CORRUPTION, 5s); - break; - case EVENT_NEFARIUS_CORRUPTION: - _combatEnabled = false; - me->AttackStop(); - me->RemoveAllAuras(); - me->StopMovingOnCurrentPos(); - me->SetFaction(FACTION_FRIENDLY); - if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) - { - victor->SetFacingToObject(me); - victor->CastSpell(me, SPELL_NEFARIUS_CORRUPTION, TRIGGERED_CAST_DIRECTLY); - } - events.ScheduleEvent(EVENT_VAEL_2_TRANSFORM, 1s); - break; - case EVENT_VAEL_2_TRANSFORM: - Talk(SAY_VAEL_STOP_COMBAT); - me->SetDisplayId(MODEL_VAELASTRASZ_THE_RED); - events.ScheduleEvent(EVENT_VAEL_3_DESPAWN, 500ms); - break; - case EVENT_VAEL_3_DESPAWN: - DoCast(me, SPELL_VAELASTRASZ_SPAWN); - me->DespawnOrUnsummon(1500ms); - break; - default: - break; - } - } - - if (!_combatEnabled || !UpdateVictim() || me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - - _events2.Update(diff); - - switch (_events2.ExecuteEvent()) - { - case EVENT_FLAME_BREATH: - me->CastSpell(me, SPELL_FLAMEBREATH, false); + } + _events2.ScheduleEvent(EVENT_FLAME_BREATH, 5s); + events.ScheduleEvent(EVENT_NEFARIUS_TALK_1, 500ms); + break; + case EVENT_NEFARIUS_TALK_1: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + { + victor->GetMotionMaster()->Clear(); // stop pacing + victor->GetMotionMaster()->MoveIdle(); + victor->StopMovingOnCurrentPos(); + victor->SetFacingToObject(me); + victor->AI()->Talk(SAY_NEFARIUS_15); + } + events.ScheduleEvent(EVENT_NEFARIUS_TALK_2, 6s); + break; + case EVENT_NEFARIUS_TALK_2: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + { + victor->SetFacingToObject(me); + victor->AI()->Talk(SAY_NEFARIUS_16); + } + events.ScheduleEvent(EVENT_NEFARIUS_TALK_3, 5s); + break; + case EVENT_NEFARIUS_TALK_3: + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + { + victor->SetFacingToObject(me); + victor->AI()->Talk(SAY_NEFARIUS_17); + } + events.ScheduleEvent(EVENT_NEFARIUS_CORRUPTION, 5s); + break; + case EVENT_NEFARIUS_CORRUPTION: + _combatEnabled = false; + me->AttackStop(); + me->RemoveAllAuras(); + me->StopMovingOnCurrentPos(); + me->SetFaction(FACTION_FRIENDLY); + if (Creature* victor = ObjectAccessor::GetCreature(*me, _victorGUID)) + { + victor->SetFacingToObject(me); + victor->CastSpell(me, SPELL_NEFARIUS_CORRUPTION, TRIGGERED_CAST_DIRECTLY); + } + events.ScheduleEvent(EVENT_VAEL_2_TRANSFORM, 1s); + break; + case EVENT_VAEL_2_TRANSFORM: + Talk(SAY_VAEL_STOP_COMBAT); + me->SetDisplayId(MODEL_VAELASTRASZ_THE_RED); + events.ScheduleEvent(EVENT_VAEL_3_DESPAWN, 500ms); + break; + case EVENT_VAEL_3_DESPAWN: + DoCast(me, SPELL_VAELASTRASZ_SPAWN); + me->DespawnOrUnsummon(1500ms); break; default: break; } - - DoMeleeAttackIfReady(); - return; } - private: - ObjectGuid _victorGUID; - bool _combatEnabled; - EventMap _events2; - }; + if (!_combatEnabled || !UpdateVictim() || me->HasUnitState(UNIT_STATE_CASTING)) + return; - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackrockSpireAI(creature); + _events2.Update(diff); + + switch (_events2.ExecuteEvent()) + { + case EVENT_FLAME_BREATH: + me->CastSpell(me, SPELL_FLAMEBREATH, false); + break; + default: + break; + } + + DoMeleeAttackIfReady(); + return; } + +private: + ObjectGuid _victorGUID; + bool _combatEnabled; + EventMap _events2; }; void AddSC_instance_blackrock_spire() { - new instance_blackrock_spire(); + RegisterInstanceScript(instance_blackrock_spire, MAP_BLACKROCK_SPIRE); new at_dragonspire_hall(); new at_blackrock_stadium(); - new go_father_flame(); + RegisterBlackrockSpireGameObjectAI(go_father_flame); new near_scarshield_infiltrator(); new at_scarshield_infiltrator(); RegisterSpellScript(spell_blackrock_spire_call_of_vaelastrasz); - new npc_vaelastrasz_the_red(); + RegisterBlackrockSpireCreatureAI(npc_vaelastrasz_the_red); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h index f2a1aed32..b90747508 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h @@ -127,5 +127,6 @@ inline AI* GetBlackwingLairAI(T* obj) } #define RegisterBlackwingLairCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetBlackwingLairAI) +#define RegisterBlackwingLairGameObjectAI(ai_name) RegisterGameObjectAIWithFactory(ai_name, GetBlackwingLairAI) #endif diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_broodlord_lashlayer.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_broodlord_lashlayer.cpp index 55c5c7a94..467b2f96b 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_broodlord_lashlayer.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_broodlord_lashlayer.cpp @@ -58,196 +58,157 @@ enum Actions ACTION_DISARMED = 1 }; -class boss_broodlord : public CreatureScript +struct boss_broodlord : public BossAI { -public: - boss_broodlord() : CreatureScript("boss_broodlord") { } + boss_broodlord(Creature* creature) : BossAI(creature, DATA_BROODLORD_LASHLAYER) { } - struct boss_broodlordAI : public BossAI + void JustEngagedWith(Unit* who) override { - boss_broodlordAI(Creature* creature) : BossAI(creature, DATA_BROODLORD_LASHLAYER) { } + BossAI::JustEngagedWith(who); + Talk(SAY_AGGRO); - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - Talk(SAY_AGGRO); - - events.ScheduleEvent(EVENT_CLEAVE, 8s); - events.ScheduleEvent(EVENT_BLASTWAVE, 12s); - events.ScheduleEvent(EVENT_MORTALSTRIKE, 20s); - events.ScheduleEvent(EVENT_KNOCKBACK, 30s); - events.ScheduleEvent(EVENT_CHECK, 1s); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - - std::list _goList; - GetGameObjectListWithEntryInGrid(_goList, me, GO_SUPPRESSION_DEVICE, 200.0f); - for (std::list::const_iterator itr = _goList.begin(); itr != _goList.end(); itr++) - { - ((*itr)->AI()->DoAction(ACTION_DEACTIVATE)); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_CLEAVE: - DoCastVictim(SPELL_CLEAVE); - events.ScheduleEvent(EVENT_CLEAVE, 7s); - break; - case EVENT_BLASTWAVE: - DoCastVictim(SPELL_BLASTWAVE); - events.ScheduleEvent(EVENT_BLASTWAVE, 20s, 35s); - break; - case EVENT_MORTALSTRIKE: - DoCastVictim(SPELL_MORTALSTRIKE); - events.ScheduleEvent(EVENT_MORTALSTRIKE, 25s, 35s); - break; - case EVENT_KNOCKBACK: - DoCastVictim(SPELL_KNOCKBACK); - if (DoGetThreat(me->GetVictim())) - DoModifyThreatByPercent(me->GetVictim(), -50); - events.ScheduleEvent(EVENT_KNOCKBACK, 15s, 30s); - break; - case EVENT_CHECK: - if (me->GetDistance(me->GetHomePosition()) > 150.0f) - { - Talk(SAY_LEASH); - EnterEvadeMode(); - } - events.ScheduleEvent(EVENT_CHECK, 1s); - break; - } - } - - DoMeleeAttackIfReady(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); + events.ScheduleEvent(EVENT_CLEAVE, 8s); + events.ScheduleEvent(EVENT_BLASTWAVE, 12s); + events.ScheduleEvent(EVENT_MORTALSTRIKE, 20s); + events.ScheduleEvent(EVENT_KNOCKBACK, 30s); + events.ScheduleEvent(EVENT_CHECK, 1s); } -}; -class go_suppression_device : public GameObjectScript -{ - public: - go_suppression_device() : GameObjectScript("go_suppression_device") { } + void JustDied(Unit* /*killer*/) override + { + _JustDied(); - void OnLootStateChanged(GameObject* go, uint32 state, Unit* /*unit*/) override + std::list _goList; + GetGameObjectListWithEntryInGrid(_goList, me, GO_SUPPRESSION_DEVICE, 200.0f); + for (std::list::const_iterator itr = _goList.begin(); itr != _goList.end(); itr++) + ((*itr)->AI()->DoAction(ACTION_DEACTIVATE)); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) { - switch (state) + switch (eventId) { - case GO_JUST_DEACTIVATED: // This case prevents the Gameobject despawn by Disarm Trap - go->SetLootState(GO_READY); - [[fallthrough]]; - case GO_ACTIVATED: - go->AI()->DoAction(ACTION_DISARMED); + case EVENT_CLEAVE: + DoCastVictim(SPELL_CLEAVE); + events.ScheduleEvent(EVENT_CLEAVE, 7s); + break; + case EVENT_BLASTWAVE: + DoCastVictim(SPELL_BLASTWAVE); + events.ScheduleEvent(EVENT_BLASTWAVE, 20s, 35s); + break; + case EVENT_MORTALSTRIKE: + DoCastVictim(SPELL_MORTALSTRIKE); + events.ScheduleEvent(EVENT_MORTALSTRIKE, 25s, 35s); + break; + case EVENT_KNOCKBACK: + DoCastVictim(SPELL_KNOCKBACK); + if (DoGetThreat(me->GetVictim())) + DoModifyThreatByPercent(me->GetVictim(), -50); + events.ScheduleEvent(EVENT_KNOCKBACK, 15s, 30s); + break; + case EVENT_CHECK: + if (me->GetDistance(me->GetHomePosition()) > 150.0f) + { + Talk(SAY_LEASH); + EnterEvadeMode(); + } + events.ScheduleEvent(EVENT_CHECK, 1s); break; } } - struct go_suppression_deviceAI : public GameObjectAI + DoMeleeAttackIfReady(); + } +}; + +struct go_suppression_device : public GameObjectAI +{ + go_suppression_device(GameObject* go) : GameObjectAI(go), _instance(go->GetInstanceScript()), _active(true) { } + + void InitializeAI() override + { + if (_instance->GetBossState(DATA_BROODLORD_LASHLAYER) == DONE) { - go_suppression_deviceAI(GameObject* go) : GameObjectAI(go), _instance(go->GetInstanceScript()), _active(true) { } - - void InitializeAI() override - { - if (_instance->GetBossState(DATA_BROODLORD_LASHLAYER) == DONE) - { - Deactivate(); - return; - } - - _events.ScheduleEvent(EVENT_SUPPRESSION_CAST, 5s); - } - - void UpdateAI(uint32 diff) override - { - _events.Update(diff); - - while (uint32 eventId = _events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SUPPRESSION_CAST: - if (me->GetGoState() == GO_STATE_READY) - { - me->CastSpell(nullptr, SPELL_SUPPRESSION_AURA); - me->SendCustomAnim(0); - } - _events.ScheduleEvent(EVENT_SUPPRESSION_CAST, 5s); - break; - case EVENT_SUPPRESSION_RESET: - Activate(); - break; - } - } - } - - void DoAction(int32 action) override - { - if (action == ACTION_DEACTIVATE) - { - _events.CancelEvent(EVENT_SUPPRESSION_RESET); - } - else if (action == ACTION_DISARMED) - { - Deactivate(); - _events.CancelEvent(EVENT_SUPPRESSION_CAST); - - if (_instance->GetBossState(DATA_BROODLORD_LASHLAYER) != DONE) - { - _events.ScheduleEvent(EVENT_SUPPRESSION_RESET, 30s, 120s); - } - } - } - - void Activate() - { - if (_active) - return; - _active = true; - if (me->GetGoState() == GO_STATE_ACTIVE) - me->SetGoState(GO_STATE_READY); - me->SetLootState(GO_READY); - me->RemoveGameObjectFlag(GO_FLAG_NOT_SELECTABLE); - _events.ScheduleEvent(EVENT_SUPPRESSION_CAST, 5s); - me->Respawn(); - } - - void Deactivate() - { - if (!_active) - return; - _active = false; - me->SetGoState(GO_STATE_ACTIVE); - me->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE); - _events.CancelEvent(EVENT_SUPPRESSION_CAST); - } - - private: - InstanceScript* _instance; - EventMap _events; - bool _active; - }; - - GameObjectAI* GetAI(GameObject* go) const override - { - return new go_suppression_deviceAI(go); + Deactivate(); + return; } + + _events.ScheduleEvent(EVENT_SUPPRESSION_CAST, 5s); + } + + void UpdateAI(uint32 diff) override + { + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_SUPPRESSION_CAST: + if (me->GetGoState() == GO_STATE_READY) + { + me->CastSpell(nullptr, SPELL_SUPPRESSION_AURA); + me->SendCustomAnim(0); + } + _events.ScheduleEvent(EVENT_SUPPRESSION_CAST, 5s); + break; + case EVENT_SUPPRESSION_RESET: + Activate(); + break; + } + } + } + + void DoAction(int32 action) override + { + if (action == ACTION_DEACTIVATE) + { + _events.CancelEvent(EVENT_SUPPRESSION_RESET); + } + else if (action == ACTION_DISARMED) + { + Deactivate(); + _events.CancelEvent(EVENT_SUPPRESSION_CAST); + + if (_instance->GetBossState(DATA_BROODLORD_LASHLAYER) != DONE) + _events.ScheduleEvent(EVENT_SUPPRESSION_RESET, 30s, 120s); + } + } + + void Activate() + { + if (_active) + return; + _active = true; + if (me->GetGoState() == GO_STATE_ACTIVE) + me->SetGoState(GO_STATE_READY); + me->SetLootState(GO_READY); + me->RemoveGameObjectFlag(GO_FLAG_NOT_SELECTABLE); + _events.ScheduleEvent(EVENT_SUPPRESSION_CAST, 5s); + me->Respawn(); + } + + void Deactivate() + { + if (!_active) + return; + _active = false; + me->SetGoState(GO_STATE_ACTIVE); + me->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE); + _events.CancelEvent(EVENT_SUPPRESSION_CAST); + } + +private: + InstanceScript* _instance; + EventMap _events; + bool _active; }; class spell_suppression_aura : public SpellScript @@ -271,7 +232,7 @@ class spell_suppression_aura : public SpellScript void AddSC_boss_broodlord() { - new boss_broodlord(); - new go_suppression_device(); + RegisterBlackwingLairCreatureAI(boss_broodlord); + RegisterBlackwingLairGameObjectAI(go_suppression_device); RegisterSpellScript(spell_suppression_aura); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_chromaggus.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_chromaggus.cpp index 64d23aa43..e645a2c22 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_chromaggus.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_chromaggus.cpp @@ -70,203 +70,171 @@ enum Misc Position const homePos = { -7491.1587f, -1069.718f, 476.59094, 476.59094f }; -class boss_chromaggus : public CreatureScript +struct boss_chromaggus : public BossAI { -public: - boss_chromaggus() : CreatureScript("boss_chromaggus") { } - - struct boss_chromaggusAI : public BossAI + boss_chromaggus(Creature* creature) : BossAI(creature, DATA_CHROMAGGUS) { - boss_chromaggusAI(Creature* creature) : BossAI(creature, DATA_CHROMAGGUS) + Initialize(); + + // Select the 2 breaths that we are going to use until despawned so we don't end up casting 2 of the same breath. + _breathSpells = { SPELL_INCINERATE, SPELL_TIMELAPSE, SPELL_CORROSIVEACID, SPELL_IGNITEFLESH, SPELL_FROSTBURN }; + + Acore::Containers::RandomResize(_breathSpells, 2); + + // Hack fix: This is here to prevent him from being pulled from the floor underneath, remove it once maps are fixed. + creature->SetImmuneToAll(true); + } + + void Initialize() + { + Enraged = false; + } + + void Reset() override + { + _Reset(); + + Initialize(); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + + events.ScheduleEvent(EVENT_SHIMMER, 1s); + events.ScheduleEvent(EVENT_BREATH, 30s); + events.ScheduleEvent(EVENT_BREATH, 60s); + events.ScheduleEvent(EVENT_AFFLICTION, 10s); + events.ScheduleEvent(EVENT_FRENZY, 15s); + } + + bool CanAIAttack(Unit const* victim) const override + { + return !victim->HasAura(SPELL_TIMELAPSE); + } + + void SetGUID(ObjectGuid const& guid, int32 id) override + { + if (id == GUID_LEVER_USER) { - Initialize(); - - // Select the 2 breaths that we are going to use until despawned so we don't end up casting 2 of the same breath. - _breathSpells = { SPELL_INCINERATE, SPELL_TIMELAPSE, SPELL_CORROSIVEACID, SPELL_IGNITEFLESH, SPELL_FROSTBURN }; - - Acore::Containers::RandomResize(_breathSpells, 2); - + _playerGUID = guid; // Hack fix: This is here to prevent him from being pulled from the floor underneath, remove it once maps are fixed. - creature->SetImmuneToAll(true); + me->SetImmuneToAll(false); } + } - void Initialize() + void PathEndReached(uint32 /*pathId*/) override + { + if (Unit* player = ObjectAccessor::GetUnit(*me, _playerGUID)) + me->SetInCombatWith(player); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - Enraged = false; - } - - void Reset() override - { - _Reset(); - - Initialize(); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - - events.ScheduleEvent(EVENT_SHIMMER, 1s); - events.ScheduleEvent(EVENT_BREATH, 30s); - events.ScheduleEvent(EVENT_BREATH, 60s); - events.ScheduleEvent(EVENT_AFFLICTION, 10s); - events.ScheduleEvent(EVENT_FRENZY, 15s); - } - - bool CanAIAttack(Unit const* victim) const override - { - return !victim->HasAura(SPELL_TIMELAPSE); - } - - void SetGUID(ObjectGuid const& guid, int32 id) override - { - if (id == GUID_LEVER_USER) + switch (eventId) { - _playerGUID = guid; - // Hack fix: This is here to prevent him from being pulled from the floor underneath, remove it once maps are fixed. - me->SetImmuneToAll(false); + case EVENT_SHIMMER: + { + // Cast new random vulnerabilty on self + DoCast(me, SPELL_ELEMENTAL_SHIELD); + Talk(EMOTE_SHIMMER); + events.ScheduleEvent(EVENT_SHIMMER, 17s, 25s); + break; + } + case EVENT_BREATH: + DoCastVictim(_breathSpells.front()); + _breathSpells.reverse(); + events.ScheduleEvent(EVENT_BREATH, 60s); + break; + case EVENT_AFFLICTION: + { + uint32 afflictionSpellID = RAND(SPELL_BROODAF_BLUE, SPELL_BROODAF_BLACK, SPELL_BROODAF_RED, SPELL_BROODAF_BRONZE, SPELL_BROODAF_GREEN); + std::vector playerTargets; + Map::PlayerList const& players = me->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr) + if (Player* player = itr->GetSource()->ToPlayer()) + if (!player->IsGameMaster() && !player->IsSpectator() && player->IsAlive()) + playerTargets.push_back(player); + + if (playerTargets.size() > 12) + Acore::Containers::RandomResize(playerTargets, 12); + + for (Player* player : playerTargets) + { + DoCast(player, afflictionSpellID, true); + + if (player->HasAllAuras(SPELL_BROODAF_BLUE, SPELL_BROODAF_BLACK, SPELL_BROODAF_RED, SPELL_BROODAF_BRONZE, SPELL_BROODAF_GREEN)) + DoCast(player, SPELL_CHROMATIC_MUT_1); + } + } + events.ScheduleEvent(EVENT_AFFLICTION, 10s); + break; + case EVENT_FRENZY: + DoCast(me, SPELL_FRENZY); + events.ScheduleEvent(EVENT_FRENZY, 10s, 15s); + break; } - } - - void PathEndReached(uint32 /*pathId*/) override - { - if (Unit* player = ObjectAccessor::GetUnit(*me, _playerGUID)) - { - me->SetInCombatWith(player); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SHIMMER: - { - // Cast new random vulnerabilty on self - DoCast(me, SPELL_ELEMENTAL_SHIELD); - Talk(EMOTE_SHIMMER); - events.ScheduleEvent(EVENT_SHIMMER, 17s, 25s); - break; - } - case EVENT_BREATH: - DoCastVictim(_breathSpells.front()); - _breathSpells.reverse(); - events.ScheduleEvent(EVENT_BREATH, 60s); - break; - case EVENT_AFFLICTION: - { - uint32 afflictionSpellID = RAND(SPELL_BROODAF_BLUE, SPELL_BROODAF_BLACK, SPELL_BROODAF_RED, SPELL_BROODAF_BRONZE, SPELL_BROODAF_GREEN); - std::vector playerTargets; - Map::PlayerList const& players = me->GetMap()->GetPlayers(); - for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr) - { - if (Player* player = itr->GetSource()->ToPlayer()) - { - if (!player->IsGameMaster() && !player->IsSpectator() && player->IsAlive()) - { - playerTargets.push_back(player); - } - } - } - - if (playerTargets.size() > 12) - { - Acore::Containers::RandomResize(playerTargets, 12); - } - - for (Player* player : playerTargets) - { - DoCast(player, afflictionSpellID, true); - - if (player->HasAllAuras(SPELL_BROODAF_BLUE, SPELL_BROODAF_BLACK, SPELL_BROODAF_RED, SPELL_BROODAF_BRONZE, SPELL_BROODAF_GREEN)) - DoCast(player, SPELL_CHROMATIC_MUT_1); - } - } - events.ScheduleEvent(EVENT_AFFLICTION, 10s); - break; - case EVENT_FRENZY: - DoCast(me, SPELL_FRENZY); - events.ScheduleEvent(EVENT_FRENZY, 10s, 15s); - break; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - } - - // Enrage if not already enraged and below 20% - if (!Enraged && HealthBelowPct(20)) - { - DoCast(me, SPELL_ENRAGE); - Enraged = true; - } - - DoMeleeAttackIfReady(); } - private: - std::list _breathSpells; - bool Enraged; - ObjectGuid _playerGUID; - }; + // Enrage if not already enraged and below 20% + if (!Enraged && HealthBelowPct(20)) + { + DoCast(me, SPELL_ENRAGE); + Enraged = true; + } - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); + DoMeleeAttackIfReady(); } + +private: + std::list _breathSpells; + bool Enraged; + ObjectGuid _playerGUID; }; -class go_chromaggus_lever : public GameObjectScript +struct go_chromaggus_lever : public GameObjectAI { - public: - go_chromaggus_lever() : GameObjectScript("go_chromaggus_lever") { } + go_chromaggus_lever(GameObject* go) : GameObjectAI(go), _instance(go->GetInstanceScript()) { } - struct go_chromaggus_leverAI : public GameObjectAI + bool GossipHello(Player* player, bool reportUse) override + { + if (reportUse) { - go_chromaggus_leverAI(GameObject* go) : GameObjectAI(go), _instance(go->GetInstanceScript()) { } - - bool GossipHello(Player* player, bool reportUse) override + if (_instance->GetBossState(DATA_CHROMAGGUS) != DONE && _instance->GetBossState(DATA_CHROMAGGUS) != IN_PROGRESS) { - if (reportUse) + if (Creature* creature = _instance->GetCreature(DATA_CHROMAGGUS)) { - if (_instance->GetBossState(DATA_CHROMAGGUS) != DONE && _instance->GetBossState(DATA_CHROMAGGUS) != IN_PROGRESS) - { - if (Creature* creature = _instance->GetCreature(DATA_CHROMAGGUS)) - { - creature->SetHomePosition(homePos); - creature->GetMotionMaster()->MoveWaypoint(creature->GetEntry() * 10, false); - creature->AI()->SetGUID(player->GetGUID(), GUID_LEVER_USER); - } - - if (GameObject* go = _instance->GetGameObject(DATA_GO_CHROMAGGUS_DOOR)) - _instance->HandleGameObject(ObjectGuid::Empty, true, go); - } - - me->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE | GO_FLAG_IN_USE); - me->SetGoState(GO_STATE_ACTIVE); + creature->SetHomePosition(homePos); + creature->GetMotionMaster()->MoveWaypoint(creature->GetEntry() * 10, false); + creature->AI()->SetGUID(player->GetGUID(), GUID_LEVER_USER); } - return true; + if (GameObject* go = _instance->GetGameObject(DATA_GO_CHROMAGGUS_DOOR)) + _instance->HandleGameObject(ObjectGuid::Empty, true, go); } - private: - InstanceScript* _instance; - }; - - GameObjectAI* GetAI(GameObject* go) const override - { - return GetBlackwingLairAI(go); + me->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE | GO_FLAG_IN_USE); + me->SetGoState(GO_STATE_ACTIVE); } + + return true; + } + +private: + InstanceScript* _instance; }; enum ElementalShieldSpells @@ -306,9 +274,7 @@ class spell_gen_elemental_shield : public SpellScript if (Unit* caster = GetCaster()) { for (uint32 spell = SPELL_FIRE_ELEMENTAL_SHIELD; spell <= SPELL_ARCANE_ELEMENTAL_SHIELD; ++spell) - { caster->RemoveAurasDueToSpell(spell); - } caster->CastSpell(caster, SPELL_FIRE_ELEMENTAL_SHIELD + urand(0, 4), true); } @@ -341,9 +307,7 @@ class spell_gen_brood_power : public SpellScript if (Unit* caster = GetCaster()) { for (uint32 spell = SPELL_RED_BROOD_POWER; spell <= SPELL_GREEN_BROOD_POWER; ++spell) - { caster->RemoveAurasDueToSpell(spell); - } caster->CastSpell(caster, RAND(SPELL_RED_BROOD_POWER, SPELL_BLUE_BROOD_POWER, SPELL_BRONZE_BROOD_POWER, SPELL_BLACK_BROOD_POWER, SPELL_GREEN_BROOD_POWER), true); } @@ -357,8 +321,8 @@ class spell_gen_brood_power : public SpellScript void AddSC_boss_chromaggus() { - new boss_chromaggus(); - new go_chromaggus_lever(); + RegisterBlackwingLairCreatureAI(boss_chromaggus); + RegisterBlackwingLairGameObjectAI(go_chromaggus_lever); RegisterSpellScript(spell_gen_elemental_shield); RegisterSpellScript(spell_gen_brood_power); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_ebonroc.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_ebonroc.cpp index 832642ef1..0edd68587 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_ebonroc.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_ebonroc.cpp @@ -33,82 +33,69 @@ enum Events EVENT_SHADOWOFEBONROC = 3 }; -class boss_ebonroc : public CreatureScript +struct boss_ebonroc : public BossAI { -public: - boss_ebonroc() : CreatureScript("boss_ebonroc") { } + boss_ebonroc(Creature* creature) : BossAI(creature, DATA_EBONROC) { } - struct boss_ebonrocAI : public BossAI + void MovementInform(uint32 type, uint32 id) override { - boss_ebonrocAI(Creature* creature) : BossAI(creature, DATA_EBONROC) { } + if (type != WAYPOINT_MOTION_TYPE || id != 13) + return; - void MovementInform(uint32 type, uint32 id) override + me->GetMotionMaster()->MoveRandom(10.f); + + me->m_Events.AddEventAtOffset([this]() { - if (type != WAYPOINT_MOTION_TYPE || id != 13) + me->GetMotionMaster()->Initialize(); + }, 15s); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + + events.ScheduleEvent(EVENT_SHADOWFLAME, 18s); + events.ScheduleEvent(EVENT_WINGBUFFET, 30s); + events.ScheduleEvent(EVENT_SHADOWOFEBONROC, 8s, 10s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) { - return; + case EVENT_SHADOWFLAME: + DoCastVictim(SPELL_SHADOWFLAME); + events.ScheduleEvent(EVENT_SHADOWFLAME, 15s, 25s); + break; + case EVENT_WINGBUFFET: + DoCastVictim(SPELL_WINGBUFFET); + events.ScheduleEvent(EVENT_WINGBUFFET, 30s); + break; + case EVENT_SHADOWOFEBONROC: + DoCastVictim(SPELL_SHADOWOFEBONROC); + events.ScheduleEvent(EVENT_SHADOWOFEBONROC, 8s, 10s); + break; } - me->GetMotionMaster()->MoveRandom(10.f); - - me->m_Events.AddEventAtOffset([this]() - { - me->GetMotionMaster()->Initialize(); - }, 15s); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - - events.ScheduleEvent(EVENT_SHADOWFLAME, 18s); - events.ScheduleEvent(EVENT_WINGBUFFET, 30s); - events.ScheduleEvent(EVENT_SHADOWOFEBONROC, 8s, 10s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SHADOWFLAME: - DoCastVictim(SPELL_SHADOWFLAME); - events.ScheduleEvent(EVENT_SHADOWFLAME, 15s, 25s); - break; - case EVENT_WINGBUFFET: - DoCastVictim(SPELL_WINGBUFFET); - events.ScheduleEvent(EVENT_WINGBUFFET, 30s); - break; - case EVENT_SHADOWOFEBONROC: - DoCastVictim(SPELL_SHADOWOFEBONROC); - events.ScheduleEvent(EVENT_SHADOWOFEBONROC, 8s, 10s); - break; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - } - - DoMeleeAttackIfReady(); } - }; - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); + DoMeleeAttackIfReady(); } }; void AddSC_boss_ebonroc() { - new boss_ebonroc(); + RegisterBlackwingLairCreatureAI(boss_ebonroc); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_firemaw.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_firemaw.cpp index 197b1d843..7d5f217fd 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_firemaw.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_firemaw.cpp @@ -33,69 +33,58 @@ enum Events EVENT_FLAMEBUFFET = 3 }; -class boss_firemaw : public CreatureScript +struct boss_firemaw : public BossAI { -public: - boss_firemaw() : CreatureScript("boss_firemaw") { } + boss_firemaw(Creature* creature) : BossAI(creature, DATA_FIREMAW) { } - struct boss_firemawAI : public BossAI + void JustEngagedWith(Unit* who) override { - boss_firemawAI(Creature* creature) : BossAI(creature, DATA_FIREMAW) { } + BossAI::JustEngagedWith(who); - void JustEngagedWith(Unit* who) override + events.ScheduleEvent(EVENT_SHADOWFLAME, 18s); + events.ScheduleEvent(EVENT_WINGBUFFET, 30s); + events.ScheduleEvent(EVENT_FLAMEBUFFET, 5s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - BossAI::JustEngagedWith(who); - - events.ScheduleEvent(EVENT_SHADOWFLAME, 18s); - events.ScheduleEvent(EVENT_WINGBUFFET, 30s); - events.ScheduleEvent(EVENT_FLAMEBUFFET, 5s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); + switch (eventId) + { + case EVENT_SHADOWFLAME: + DoCastVictim(SPELL_SHADOWFLAME); + events.ScheduleEvent(EVENT_SHADOWFLAME, 15s, 25s); + break; + case EVENT_WINGBUFFET: + DoCastVictim(SPELL_WINGBUFFET); + if (DoGetThreat(me->GetVictim())) + DoModifyThreatByPercent(me->GetVictim(), -75); + events.ScheduleEvent(EVENT_WINGBUFFET, 30s); + break; + case EVENT_FLAMEBUFFET: + DoCastVictim(SPELL_FLAMEBUFFET); + events.ScheduleEvent(EVENT_FLAMEBUFFET, 5s); + break; + } if (me->HasUnitState(UNIT_STATE_CASTING)) return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SHADOWFLAME: - DoCastVictim(SPELL_SHADOWFLAME); - events.ScheduleEvent(EVENT_SHADOWFLAME, 15s, 25s); - break; - case EVENT_WINGBUFFET: - DoCastVictim(SPELL_WINGBUFFET); - if (DoGetThreat(me->GetVictim())) - DoModifyThreatByPercent(me->GetVictim(), -75); - events.ScheduleEvent(EVENT_WINGBUFFET, 30s); - break; - case EVENT_FLAMEBUFFET: - DoCastVictim(SPELL_FLAMEBUFFET); - events.ScheduleEvent(EVENT_FLAMEBUFFET, 5s); - break; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - } - - DoMeleeAttackIfReady(); } - }; - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); + DoMeleeAttackIfReady(); } }; void AddSC_boss_firemaw() { - new boss_firemaw(); + RegisterBlackwingLairCreatureAI(boss_firemaw); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_flamegor.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_flamegor.cpp index ce1028fe7..4933a0b63 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_flamegor.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_flamegor.cpp @@ -38,70 +38,59 @@ enum Events EVENT_FRENZY = 3 }; -class boss_flamegor : public CreatureScript +struct boss_flamegor : public BossAI { -public: - boss_flamegor() : CreatureScript("boss_flamegor") { } + boss_flamegor(Creature* creature) : BossAI(creature, DATA_FLAMEGOR) { } - struct boss_flamegorAI : public BossAI + void JustEngagedWith(Unit* who) override { - boss_flamegorAI(Creature* creature) : BossAI(creature, DATA_FLAMEGOR) { } + BossAI::JustEngagedWith(who); - void JustEngagedWith(Unit* who) override + events.ScheduleEvent(EVENT_SHADOWFLAME, 18s); + events.ScheduleEvent(EVENT_WINGBUFFET, 30s); + events.ScheduleEvent(EVENT_FRENZY, 10s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - BossAI::JustEngagedWith(who); - - events.ScheduleEvent(EVENT_SHADOWFLAME, 18s); - events.ScheduleEvent(EVENT_WINGBUFFET, 30s); - events.ScheduleEvent(EVENT_FRENZY, 10s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); + switch (eventId) + { + case EVENT_SHADOWFLAME: + DoCastVictim(SPELL_SHADOWFLAME); + events.ScheduleEvent(EVENT_SHADOWFLAME, 15s, 25s); + break; + case EVENT_WINGBUFFET: + DoCastVictim(SPELL_WINGBUFFET); + if (DoGetThreat(me->GetVictim())) + DoModifyThreatByPercent(me->GetVictim(), -75); + events.ScheduleEvent(EVENT_WINGBUFFET, 30s); + break; + case EVENT_FRENZY: + Talk(EMOTE_FRENZY); + DoCast(me, SPELL_FRENZY); + events.ScheduleEvent(EVENT_FRENZY, 8s, 10s); + break; + } if (me->HasUnitState(UNIT_STATE_CASTING)) return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SHADOWFLAME: - DoCastVictim(SPELL_SHADOWFLAME); - events.ScheduleEvent(EVENT_SHADOWFLAME, 15s, 25s); - break; - case EVENT_WINGBUFFET: - DoCastVictim(SPELL_WINGBUFFET); - if (DoGetThreat(me->GetVictim())) - DoModifyThreatByPercent(me->GetVictim(), -75); - events.ScheduleEvent(EVENT_WINGBUFFET, 30s); - break; - case EVENT_FRENZY: - Talk(EMOTE_FRENZY); - DoCast(me, SPELL_FRENZY); - events.ScheduleEvent(EVENT_FRENZY, 8s, 10s); - break; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - } - - DoMeleeAttackIfReady(); } - }; - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); + DoMeleeAttackIfReady(); } }; void AddSC_boss_flamegor() { - new boss_flamegor(); + RegisterBlackwingLairCreatureAI(boss_flamegor); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_nefarian.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_nefarian.cpp index 55d3d818e..b3eec7834 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_nefarian.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_nefarian.cpp @@ -214,14 +214,10 @@ struct ClassCallSelector : public Acore::unary_function bool operator()(Unit const* target) const { if (!_me || !target || !target->IsPlayer()) - { return false; - } if (target->getClass() != _targetClass) - { return false; - } return true; } @@ -231,304 +227,291 @@ private: uint8 _targetClass; }; -class boss_victor_nefarius : public CreatureScript +struct boss_victor_nefarius : public BossAI { -public: - boss_victor_nefarius() : CreatureScript("boss_victor_nefarius") { } - - struct boss_victor_nefariusAI : public BossAI + boss_victor_nefarius(Creature* creature) : BossAI(creature, DATA_NEFARIAN) { - boss_victor_nefariusAI(Creature* creature) : BossAI(creature, DATA_NEFARIAN) + Initialize(); + + _nefarianLeftTunnel = instance->GetData(DATA_NEFARIAN_LEFT_TUNNEL); + _nefarianRightTunnel = instance->GetData(DATA_NEFARIAN_RIGHT_TUNNEL); + + if (!_nefarianLeftTunnel || !_nefarianRightTunnel) { - Initialize(); + // Victor Nefarius weekly mechanic drakonid spawn + // Pick 2 drakonids and keep them for the whole save duration (the drakonids can't be repeated). + std::vector nefarianDrakonidSpawners = { NPC_BLACK_SPAWNER, NPC_BLUE_SPAWNER, NPC_BRONZE_SPAWNER, NPC_GREEN_SPAWNER, NPC_RED_SPAWNER }; + Acore::Containers::RandomResize(nefarianDrakonidSpawners, 2); - _nefarianLeftTunnel = instance->GetData(DATA_NEFARIAN_LEFT_TUNNEL); - _nefarianRightTunnel = instance->GetData(DATA_NEFARIAN_RIGHT_TUNNEL); + _nefarianRightTunnel = nefarianDrakonidSpawners[0]; + _nefarianLeftTunnel = nefarianDrakonidSpawners[1]; - if (!_nefarianLeftTunnel || !_nefarianRightTunnel) - { - // Victor Nefarius weekly mechanic drakonid spawn - // Pick 2 drakonids and keep them for the whole save duration (the drakonids can't be repeated). - std::vector nefarianDrakonidSpawners = { NPC_BLACK_SPAWNER, NPC_BLUE_SPAWNER, NPC_BRONZE_SPAWNER, NPC_GREEN_SPAWNER, NPC_RED_SPAWNER }; - Acore::Containers::RandomResize(nefarianDrakonidSpawners, 2); - - _nefarianRightTunnel = nefarianDrakonidSpawners[0]; - _nefarianLeftTunnel = nefarianDrakonidSpawners[1]; - - // save it to instance - instance->SetData(DATA_NEFARIAN_LEFT_TUNNEL, _nefarianLeftTunnel); - instance->SetData(DATA_NEFARIAN_RIGHT_TUNNEL, _nefarianRightTunnel); - } + // save it to instance + instance->SetData(DATA_NEFARIAN_LEFT_TUNNEL, _nefarianLeftTunnel); + instance->SetData(DATA_NEFARIAN_RIGHT_TUNNEL, _nefarianRightTunnel); } + } - void Initialize() + void Initialize() + { + _killedAdds = 0; + } + + void Reset() override + { + Initialize(); + + if (me->GetMapId() == MAP_BLACKWING_LAIR) { - KilledAdds = 0; - } - - void Reset() override - { - Initialize(); - - if (me->GetMapId() == MAP_BLACKWING_LAIR) + if (Creature* nefarian = me->FindNearestCreature(NPC_NEFARIAN, 1000.0f, true)) { - if (Creature* nefarian = me->FindNearestCreature(NPC_NEFARIAN, 1000.0f, true)) + // Nefarian is spawned and he didn't finish his intro path yet, despawn it manually. + if (nefarian->GetMotionMaster()->GetCurrentMovementGeneratorType() == MovementGeneratorType::WAYPOINT_MOTION_TYPE) { - // Nefarian is spawned and he didn't finish his intro path yet, despawn it manually. - if (nefarian->GetMotionMaster()->GetCurrentMovementGeneratorType() == MovementGeneratorType::WAYPOINT_MOTION_TYPE) - { - nefarian->DespawnOrUnsummon(); - } - std::list drakonidBones; - me->GetGameObjectListWithEntryInGrid(drakonidBones, GO_DRAKONID_BONES, DEFAULT_VISIBILITY_INSTANCE); - for (auto const& bones : drakonidBones) - { - bones->DespawnOrUnsummon(); - } + nefarian->DespawnOrUnsummon(); } - else + std::list drakonidBones; + me->GetGameObjectListWithEntryInGrid(drakonidBones, GO_DRAKONID_BONES, DEFAULT_VISIBILITY_INSTANCE); + for (auto const& bones : drakonidBones) { - _Reset(); - } - - me->SetVisible(true); - me->SetPhaseMask(1, true); - me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); - me->SetFaction(FACTION_FRIENDLY); - me->SetStandState(UNIT_STAND_STATE_SIT_HIGH_CHAIR); - me->RemoveAura(SPELL_NEFARIANS_BARRIER); - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - } - } - - void JustReachedHome() override - { - Reset(); - } - - void JustSummoned(Creature* summon) override - { - if (summon->GetEntry() != NPC_NEFARIAN) - { - BossAI::JustSummoned(summon); - } - } - - void SummonedCreatureDies(Creature* summon, Unit* /*unit*/) override - { - if (summon->GetEntry() == NPC_NEFARIAN) - { - summons.DespawnEntry(_nefarianLeftTunnel); - summons.DespawnEntry(_nefarianRightTunnel); - me->KillSelf(); - } - } - - void DoAction(int32 action) override - { - if (action == ACTION_RESET) - { - me->RemoveAura(SPELL_ROOT_SELF); - summons.DespawnAll(); - } - - if (action == ACTION_ADD_KILLED) - { - KilledAdds++; - - if (KilledAdds == MAX_DRAKONID_KILLED) - { - if (Creature* nefarian = me->SummonCreature(NPC_NEFARIAN, NefarianSpawn)) - { - nefarian->setActive(true); - nefarian->SetCanFly(true); - nefarian->SetDisableGravity(true); - nefarian->GetMotionMaster()->MoveWaypoint(NEFARIAN_PATH, false); - } - - events.Reset(); - DoCastSelf(SPELL_ROOT_SELF, true); - me->SetVisible(false); - // Stop spawning adds - EntryCheckPredicate pred(_nefarianRightTunnel); - summons.DoAction(ACTION_SPAWNER_STOP, pred); - EntryCheckPredicate pred2(_nefarianLeftTunnel); - summons.DoAction(ACTION_SPAWNER_STOP, pred2); + bones->DespawnOrUnsummon(); } } - } - - void JustDied(Unit* /*killer*/) override - { - instance->SetBossState(DATA_NEFARIAN, DONE); - instance->SaveToDB(); - } - - void BeginEvent() - { - _JustEngagedWith(); - - Talk(SAY_GAMESBEGIN_2); - - DoCast(me, SPELL_NEFARIANS_BARRIER); - me->SetCombatMovement(false); - me->SetImmuneToPC(false); - AttackStart(SelectTarget(SelectTargetMethod::Random, 0, 200.f, true)); - events.ScheduleEvent(EVENT_SHADOWBLINK, 500ms); - events.ScheduleEvent(EVENT_SHADOW_BOLT, 3s); - events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 13s, 15s); - events.ScheduleEvent(EVENT_FEAR, 10s, 20s); - events.ScheduleEvent(EVENT_SILENCE, 20s, 25s); - events.ScheduleEvent(EVENT_MIND_CONTROL, 30s, 35s); - events.ScheduleEvent(EVENT_SPAWN_ADDS, 10s); - } - - void SetData(uint32 type, uint32 data) override - { - if (type == 1 && data == 1) + else { - me->StopMoving(); - events.ScheduleEvent(EVENT_PATH_2, 9s); + _Reset(); } - if (type == 1 && data == 2) - events.ScheduleEvent(EVENT_SUCCESS_1, 5s); + me->SetVisible(true); + me->SetPhaseMask(1, true); + me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + me->SetFaction(FACTION_FRIENDLY); + me->SetStandState(UNIT_STAND_STATE_SIT_HIGH_CHAIR); + me->RemoveAura(SPELL_NEFARIANS_BARRIER); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + } + } + + void JustReachedHome() override + { + Reset(); + } + + void JustSummoned(Creature* summon) override + { + if (summon->GetEntry() != NPC_NEFARIAN) + BossAI::JustSummoned(summon); + } + + void SummonedCreatureDies(Creature* summon, Unit* /*unit*/) override + { + if (summon->GetEntry() == NPC_NEFARIAN) + { + summons.DespawnEntry(_nefarianLeftTunnel); + summons.DespawnEntry(_nefarianRightTunnel); + me->KillSelf(); + } + } + + void DoAction(int32 action) override + { + if (action == ACTION_RESET) + { + me->RemoveAura(SPELL_ROOT_SELF); + summons.DespawnAll(); } - void UpdateAI(uint32 diff) override + if (action == ACTION_ADD_KILLED) { - if (!UpdateVictim()) - { - events.Update(diff); + _killedAdds++; - while (uint32 eventId = events.ExecuteEvent()) + if (_killedAdds == MAX_DRAKONID_KILLED) + { + if (Creature* nefarian = me->SummonCreature(NPC_NEFARIAN, NefarianSpawn)) { - switch (eventId) - { - case EVENT_PATH_2: - me->GetMotionMaster()->MoveWaypoint(NEFARIUS_PATH_2, false); - events.ScheduleEvent(EVENT_CHAOS_1, 7s); - break; - case EVENT_CHAOS_1: - if (Creature* gyth = me->FindNearestCreature(NPC_GYTH, 75.0f, true)) - { - me->SetFacingToObject(gyth); - Talk(SAY_CHAOS_SPELL); - } - events.ScheduleEvent(EVENT_CHAOS_2, 2s); - break; - case EVENT_CHAOS_2: - DoCast(SPELL_CHROMATIC_CHAOS); - me->SetFacingTo(1.570796f); - break; - case EVENT_SUCCESS_1: - if (Unit* player = me->SelectNearestPlayer(60.0f)) - { - me->SetFacingToObject(player); - Talk(SAY_SUCCESS); - if (GameObject* portcullis1 = me->FindNearestGameObject(GO_PORTCULLIS_ACTIVE, 65.0f)) - portcullis1->SetGoState(GO_STATE_ACTIVE); - if (GameObject* portcullis2 = me->FindNearestGameObject(GO_PORTCULLIS_TOBOSSROOMS, 80.0f)) - portcullis2->SetGoState(GO_STATE_ACTIVE); - } - events.ScheduleEvent(EVENT_SUCCESS_2, 4s); - break; - case EVENT_SUCCESS_2: - DoCast(me, SPELL_VAELASTRASZZ_SPAWN); - me->DespawnOrUnsummon(1s); - break; - case EVENT_PATH_3: - me->GetMotionMaster()->MoveWaypoint(NEFARIUS_PATH_3, false); - break; - case EVENT_START_EVENT: - BeginEvent(); - break; - default: - break; - } + nefarian->setActive(true); + nefarian->SetCanFly(true); + nefarian->SetDisableGravity(true); + nefarian->GetMotionMaster()->MoveWaypoint(NEFARIAN_PATH, false); } + + events.Reset(); + DoCastSelf(SPELL_ROOT_SELF, true); + me->SetVisible(false); + // Stop spawning adds + EntryCheckPredicate pred(_nefarianRightTunnel); + summons.DoAction(ACTION_SPAWNER_STOP, pred); + EntryCheckPredicate pred2(_nefarianLeftTunnel); + summons.DoAction(ACTION_SPAWNER_STOP, pred2); + } + } + } + + void JustDied(Unit* /*killer*/) override + { + instance->SetBossState(DATA_NEFARIAN, DONE); + instance->SaveToDB(); + } + + void BeginEvent() + { + _JustEngagedWith(); + + Talk(SAY_GAMESBEGIN_2); + + DoCast(me, SPELL_NEFARIANS_BARRIER); + me->SetCombatMovement(false); + me->SetImmuneToPC(false); + AttackStart(SelectTarget(SelectTargetMethod::Random, 0, 200.f, true)); + events.ScheduleEvent(EVENT_SHADOWBLINK, 500ms); + events.ScheduleEvent(EVENT_SHADOW_BOLT, 3s); + events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 13s, 15s); + events.ScheduleEvent(EVENT_FEAR, 10s, 20s); + events.ScheduleEvent(EVENT_SILENCE, 20s, 25s); + events.ScheduleEvent(EVENT_MIND_CONTROL, 30s, 35s); + events.ScheduleEvent(EVENT_SPAWN_ADDS, 10s); + } + + void SetData(uint32 type, uint32 data) override + { + if (type == 1 && data == 1) + { + me->StopMoving(); + events.ScheduleEvent(EVENT_PATH_2, 9s); + } + + if (type == 1 && data == 2) + events.ScheduleEvent(EVENT_SUCCESS_1, 5s); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + { + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_PATH_2: + me->GetMotionMaster()->MoveWaypoint(NEFARIUS_PATH_2, false); + events.ScheduleEvent(EVENT_CHAOS_1, 7s); + break; + case EVENT_CHAOS_1: + if (Creature* gyth = me->FindNearestCreature(NPC_GYTH, 75.0f, true)) + { + me->SetFacingToObject(gyth); + Talk(SAY_CHAOS_SPELL); + } + events.ScheduleEvent(EVENT_CHAOS_2, 2s); + break; + case EVENT_CHAOS_2: + DoCast(SPELL_CHROMATIC_CHAOS); + me->SetFacingTo(1.570796f); + break; + case EVENT_SUCCESS_1: + if (Unit* player = me->SelectNearestPlayer(60.0f)) + { + me->SetFacingToObject(player); + Talk(SAY_SUCCESS); + if (GameObject* portcullis1 = me->FindNearestGameObject(GO_PORTCULLIS_ACTIVE, 65.0f)) + portcullis1->SetGoState(GO_STATE_ACTIVE); + if (GameObject* portcullis2 = me->FindNearestGameObject(GO_PORTCULLIS_TOBOSSROOMS, 80.0f)) + portcullis2->SetGoState(GO_STATE_ACTIVE); + } + events.ScheduleEvent(EVENT_SUCCESS_2, 4s); + break; + case EVENT_SUCCESS_2: + DoCast(me, SPELL_VAELASTRASZZ_SPAWN); + me->DespawnOrUnsummon(1s); + break; + case EVENT_PATH_3: + me->GetMotionMaster()->MoveWaypoint(NEFARIUS_PATH_3, false); + break; + case EVENT_START_EVENT: + BeginEvent(); + break; + default: + break; + } + } + return; + } + + // Only do this if we haven't spawned nefarian yet + if (UpdateVictim() && _killedAdds <= MAX_DRAKONID_KILLED) + { + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) return; - } - // Only do this if we haven't spawned nefarian yet - if (UpdateVictim() && KilledAdds <= MAX_DRAKONID_KILLED) + while (uint32 eventId = events.ExecuteEvent()) { - events.Update(diff); + switch (eventId) + { + case EVENT_SHADOW_BOLT: + DoCastRandomTarget(SPELL_SHADOWBOLT, 0, 150.f); + events.ScheduleEvent(EVENT_SHADOW_BOLT, 2s, 4s); + break; + case EVENT_SHADOW_BOLT_VOLLEY: + DoCastAOE(SPELL_SHADOWBOLT_VOLLEY); + events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 19s, 25s); + break; + case EVENT_FEAR: + DoCastRandomTarget(SPELL_FEAR, 0, 40.0f); + events.ScheduleEvent(EVENT_FEAR, 10s, 20s); + break; + case EVENT_SILENCE: + DoCastRandomTarget(SPELL_SILENCE, 0, 150.f); + events.ScheduleEvent(EVENT_SILENCE, 14s,23s); + break; + case EVENT_MIND_CONTROL: + DoCastRandomTarget(SPELL_SHADOW_COMMAND, 0, 40.0f); + events.ScheduleEvent(EVENT_MIND_CONTROL, 24s, 30s); + break; + case EVENT_SHADOWBLINK: + DoCastSelf(SPELL_SHADOWBLINK); + events.ScheduleEvent(EVENT_SHADOWBLINK, 30s, 40s); + break; + case EVENT_SPAWN_ADDS: + // Spawn the spawners. + me->SummonCreature(_nefarianLeftTunnel, spawnerPositions[0]); + me->SummonCreature(_nefarianRightTunnel, spawnerPositions[1]); + break; + } if (me->HasUnitState(UNIT_STATE_CASTING)) return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SHADOW_BOLT: - DoCastRandomTarget(SPELL_SHADOWBOLT, 0, 150.f); - events.ScheduleEvent(EVENT_SHADOW_BOLT, 2s, 4s); - break; - case EVENT_SHADOW_BOLT_VOLLEY: - DoCastAOE(SPELL_SHADOWBOLT_VOLLEY); - events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 19s, 25s); - break; - case EVENT_FEAR: - DoCastRandomTarget(SPELL_FEAR, 0, 40.0f); - events.ScheduleEvent(EVENT_FEAR, 10s, 20s); - break; - case EVENT_SILENCE: - DoCastRandomTarget(SPELL_SILENCE, 0, 150.f); - events.ScheduleEvent(EVENT_SILENCE, 14s,23s); - break; - case EVENT_MIND_CONTROL: - DoCastRandomTarget(SPELL_SHADOW_COMMAND, 0, 40.0f); - events.ScheduleEvent(EVENT_MIND_CONTROL, 24s, 30s); - break; - case EVENT_SHADOWBLINK: - DoCastSelf(SPELL_SHADOWBLINK); - events.ScheduleEvent(EVENT_SHADOWBLINK, 30s, 40s); - break; - case EVENT_SPAWN_ADDS: - // Spawn the spawners. - me->SummonCreature(_nefarianLeftTunnel, spawnerPositions[0]); - me->SummonCreature(_nefarianRightTunnel, spawnerPositions[1]); - break; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - } } } - - void sGossipSelect(Player* player, uint32 sender, uint32 action) override - { - if (sender == GOSSIP_ID && action == GOSSIP_OPTION_ID) - { - // pussywizard: - InstanceScript* instance = player->GetInstanceScript(); - if (!instance || instance->GetBossState(DATA_NEFARIAN) == DONE) - return; - - CloseGossipMenuFor(player); - Talk(SAY_GAMESBEGIN_1); - events.ScheduleEvent(EVENT_START_EVENT, 4s); - me->SetFaction(FACTION_DRAGONFLIGHT_BLACK); - me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); - me->SetStandState(UNIT_STAND_STATE_STAND); - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToPC(true); - } - } - - private: - uint32 KilledAdds; - uint32 _nefarianRightTunnel; - uint32 _nefarianLeftTunnel; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); } + + void sGossipSelect(Player* player, uint32 sender, uint32 action) override + { + if (sender == GOSSIP_ID && action == GOSSIP_OPTION_ID) + { + // pussywizard: + InstanceScript* instance = player->GetInstanceScript(); + if (!instance || instance->GetBossState(DATA_NEFARIAN) == DONE) + return; + + CloseGossipMenuFor(player); + Talk(SAY_GAMESBEGIN_1); + events.ScheduleEvent(EVENT_START_EVENT, 4s); + me->SetFaction(FACTION_DRAGONFLIGHT_BLACK); + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + me->SetStandState(UNIT_STAND_STATE_STAND); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(true); + } + } + +private: + uint32 _killedAdds; + uint32 _nefarianRightTunnel; + uint32 _nefarianLeftTunnel; }; struct boss_nefarian : public BossAI @@ -545,12 +528,8 @@ struct boss_nefarian : public BossAI { _Reset(); if (Creature* victor = me->FindNearestCreature(NPC_VICTOR_NEFARIUS, 200.f, true)) - { if (victor->AI()) - { victor->AI()->DoAction(ACTION_RESET); - } - } me->DespawnOrUnsummon(); } @@ -578,9 +557,7 @@ struct boss_nefarian : public BossAI void KilledUnit(Unit* victim) override { if (rand32() % 5) - { return; - } Talk(SAY_SLAY, victim); } @@ -588,14 +565,10 @@ struct boss_nefarian : public BossAI void MovementInform(uint32 type, uint32 id) override { if (type != WAYPOINT_MOTION_TYPE) - { return; - } if (id == 4) - { Talk(SAY_INTRO); - } if (id == 6) { @@ -617,9 +590,7 @@ struct boss_nefarian : public BossAI me->SetReactState(REACT_AGGRESSIVE); DoZoneInCombat(); if (me->GetVictim()) - { AttackStart(me->GetVictim()); - } events.ScheduleEvent(EVENT_SHADOWFLAME, 12s); events.ScheduleEvent(EVENT_FEAR, 25s, 35s); @@ -633,16 +604,12 @@ struct boss_nefarian : public BossAI void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } while (uint32 eventId = events.ExecuteEvent()) { @@ -671,16 +638,10 @@ struct boss_nefarian : public BossAI break; case EVENT_CLASSCALL: if (classesPresent.empty()) - { for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList()) - { if (Unit* victim = ref->GetVictim()) - { if (victim->IsPlayer()) classesPresent.insert(victim->getClass()); - } - } - } uint8 targetClass = Acore::Containers::SelectRandomContainerElement(classesPresent); @@ -731,9 +692,7 @@ struct boss_nefarian : public BossAI me->GetMap()->DoForAllPlayers([&](Player* p) { if (!p->IsGameMaster()) - { me->CastSpell(p, SPELL_DEATH_KNIGHT, true); - } }); break; default: @@ -745,9 +704,7 @@ struct boss_nefarian : public BossAI } if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } } DoMeleeAttackIfReady(); @@ -783,9 +740,7 @@ struct npc_corrupted_totem : public ScriptedAI { me->AddUnitState(UNIT_STATE_ROOT); if (!me->HasAura(SPELL_ROOT_SELF)) - { me->AddAura(SPELL_ROOT_SELF, me); - } me->AddAura(AURA_AVOIDANCE, me); scheduler.CancelAll(); @@ -809,9 +764,7 @@ struct npc_corrupted_totem : public ScriptedAI } if (!spellId) - { return; - } std::vector mobsEntries = { @@ -835,25 +788,19 @@ struct npc_corrupted_totem : public ScriptedAI tmpMobList.pop_front(); if (!curr->IsAlive()) - { continue; - } if (apply && me->IsAlive()) { if (me->IsWithinDistInMap(curr, 40.f)) { if (!curr->HasAura(spellId)) - { curr->AddAura(spellId, curr); - } } else { if (curr->HasAura(spellId)) - { curr->RemoveAurasDueToSpell(spellId); - } } } else @@ -899,9 +846,7 @@ struct npc_corrupted_totem : public ScriptedAI void JustDied(Unit* /*killer*/) override { if (me->GetEntry() != NPC_TOTEM_C_FIRE_NOVA) - { SetAura(false); - } scheduler.CancelAll(); } @@ -909,9 +854,7 @@ struct npc_corrupted_totem : public ScriptedAI void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } scheduler.Update(diff); } @@ -952,9 +895,7 @@ struct npc_drakonid_spawner : public ScriptedAI void SummonedCreatureDies(Creature* summon, Unit* /*unit*/) override { if (Creature* victor = ObjectAccessor::GetCreature(*me, _owner)) - { victor->AI()->DoAction(ACTION_NEFARIUS_ADD_KILLED); - } ObjectGuid summonGuid = summon->GetGUID(); @@ -1002,15 +943,11 @@ class spell_class_call_handler : public SpellScript { Player const* player = target->ToPlayer(); if (!player || player->IsClass(CLASS_DEATH_KNIGHT)) // ignore all death knights from whatever spell, for some reason the condition below is not working x.x - { return true; - } auto it = classCallSpells.find(spellInfo->Id); if (it != classCallSpells.end()) // should never happen but only to be sure. - { return target->ToPlayer()->getClass() != it->second; - } return false; }); @@ -1022,9 +959,7 @@ class spell_class_call_handler : public SpellScript Unit* caster = GetCaster(); Unit* target = GetHitUnit(); if (!caster || !target) - { return; - } Position tp = caster->GetFirstCollisionPosition(5.f, 0.f); target->NearTeleportTo(tp.GetPositionX(), tp.GetPositionY(), tp.GetPositionZ(), tp.GetOrientation()); @@ -1033,9 +968,7 @@ class spell_class_call_handler : public SpellScript void HandleOnHitWarlock() { if (Unit* target = GetHitUnit()) - { target->CastSpell(target, SPELL_SUMMON_INFERNALS, true); - } } void Register() override @@ -1065,9 +998,7 @@ class aura_class_call_wild_magic : public AuraScript void HandlePeriodic(AuraEffect const* /*aurEff*/) { if (!GetTarget()) - { return; - } GetTarget()->CastSpell(GetTarget(), SPELL_POLYMORPH, true); } @@ -1092,12 +1023,8 @@ class aura_class_call_siphon_blessing : public AuraScript PreventDefaultAction(); if (Unit* target = GetTarget()) - { if (Unit* nefarian = target->FindNearestCreature(NPC_NEFARIAN, 100.f)) - { target->CastSpell(nefarian, SPELL_BLESSING_PROTECTION, true); - } - } } void Register() override @@ -1154,9 +1081,7 @@ class spell_corrupted_totems : public SpellScript { PreventHitDefaultEffect(effIndex); if (!GetCaster()) - { return; - } std::list spellList = { SPELL_CORRUPTED_FIRE_NOVA_TOTEM, SPELL_CORRUPTED_HEALING_TOTEM, SPELL_CORRUPTED_STONESKIN_TOTEM, SPELL_CORRUPTED_WINDFURY_TOTEM }; uint32 spellId = Acore::Containers::SelectRandomContainerElement(spellList); @@ -1207,15 +1132,11 @@ class spell_shadowblink : public SpellScript { Unit* caster = GetCaster(); if (!caster || !caster->ToCreature() || !caster->ToCreature()->AI()) - { return; - } Unit* target = caster->ToCreature()->AI()->SelectTarget(SelectTargetMethod::Random, 0, 200.f, true); if (!target) - { return; - } for (auto& itr : spellPos) { @@ -1252,9 +1173,7 @@ class spell_spawn_drakonid : public SpellScript { Unit* caster = GetCaster(); if (!caster) - { return; - } caster->CastSpell(caster, spawnerSpells[caster->GetEntry()], true); } @@ -1267,7 +1186,7 @@ class spell_spawn_drakonid : public SpellScript void AddSC_boss_nefarian() { - new boss_victor_nefarius(); + RegisterBlackwingLairCreatureAI(boss_victor_nefarius); RegisterCreatureAI(boss_nefarian); RegisterCreatureAI(npc_corrupted_totem); RegisterCreatureAI(npc_drakonid_spawner); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp index c17fd24f2..40d801ae0 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp @@ -16,6 +16,8 @@ */ #include "CreatureScript.h" +#include "GameObject.h" +#include "GameObjectAI.h" #include "GameObjectScript.h" #include "Player.h" #include "ScriptedCreature.h" @@ -70,214 +72,190 @@ enum EVENTS EVENT_CONFLAGRATION = 4 }; -class boss_razorgore : public CreatureScript +struct boss_razorgore : public BossAI { -public: - boss_razorgore() : CreatureScript("boss_razorgore") { } + boss_razorgore(Creature* creature) : BossAI(creature, DATA_RAZORGORE_THE_UNTAMED) { } - struct boss_razorgoreAI : public BossAI + void Reset() override { - boss_razorgoreAI(Creature* creature) : BossAI(creature, DATA_RAZORGORE_THE_UNTAMED) { } - - void Reset() override - { - _Reset(); - _charmerGUID.Clear(); - secondPhase = false; - summons.DespawnAll(); - instance->SetData(DATA_EGG_EVENT, NOT_STARTED); - } - - void JustDied(Unit* /*killer*/) override - { - if (secondPhase) - { - _JustDied(); - } - else - { - // Respawn shorty in case of failure during phase 1. - me->SetCorpseRemoveTime(25); - me->SetRespawnTime(30); - me->SaveRespawnTime(); - - // Might not be required, safe measure. - me->SetLootRecipient(nullptr); - - instance->SetData(DATA_EGG_EVENT, FAIL); - } - } - - bool CanAIAttack(Unit const* target) const override - { - return !(target->IsCreature() && !secondPhase); - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - - events.ScheduleEvent(EVENT_CLEAVE, 15s); - events.ScheduleEvent(EVENT_STOMP, 35s); - events.ScheduleEvent(EVENT_FIREBALL, 7s); - events.ScheduleEvent(EVENT_CONFLAGRATION, 12s); - - instance->SetData(DATA_EGG_EVENT, IN_PROGRESS); - } - - void DoChangePhase() - { - secondPhase = true; - _charmerGUID.Clear(); - me->RemoveAllAuras(); - - DoCastSelf(SPELL_WARMING_FLAMES, true); - - if (Creature* troops = instance->GetCreature(DATA_NEFARIAN_TROOPS)) - { - troops->AI()->Talk(EMOTE_TROOPS_RETREAT); - } - - for (ObjectGuid const& guid : _summonGUIDS) - { - if (Creature* creature = ObjectAccessor::GetCreature(*me, guid)) - { - if (creature->IsAlive()) - { - creature->CombatStop(true); - creature->SetReactState(REACT_PASSIVE); - creature->GetMotionMaster()->MovePoint(0, Position(-7560.568848f, -1028.553345f, 408.491211f, 0.523858f)); - } - } - } - } - - void SetGUID(ObjectGuid const& guid, int32 /*id*/) override - { - _charmerGUID = guid; - } - - void OnCharmed(bool apply) override - { - if (apply) - { - if (Unit* charmer = ObjectAccessor::GetUnit(*me, _charmerGUID)) - { - charmer->CastSpell(charmer, SPELL_MIND_EXHAUSTION, true); - charmer->CastSpell(me, SPELL_MINDCONTROL_VISUAL, false); - } - } - else - { - if (Unit* charmer = ObjectAccessor::GetUnit(*me, _charmerGUID)) - { - charmer->RemoveAurasDueToSpell(SPELL_MINDCONTROL_VISUAL); - me->EngageWithTarget(charmer); - me->AddThreat(charmer, 100.0f); - me->AI()->AttackStart(charmer); - } - } - } - - void DoAction(int32 action) override - { - if (action == ACTION_PHASE_TWO) - { - DoChangePhase(); - } - - if (action == TALK_EGG_BROKEN_RAND) - { - Talk(urand(SAY_EGGS_BROKEN1, SAY_EGGS_BROKEN3)); - } - } - - void JustSummoned(Creature* summon) override - { - _summonGUIDS.push_back(summon->GetGUID()); - summon->SetOwnerGUID(me->GetGUID()); - summons.Summon(summon); - } - - void SummonMovementInform(Creature* summon, uint32 movementType, uint32 /*pathId*/) override - { - if (movementType == POINT_MOTION_TYPE) - { - summon->DespawnOrUnsummon(); - } - } - - void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - if (!secondPhase && damage >= me->GetHealth()) - { - Talk(SAY_DEATH); - DoCastAOE(SPELL_EXPLODE_ORB); - DoCastAOE(SPELL_EXPLOSION); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - if (!me->IsCharmed()) - { - events.Update(diff); - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_CLEAVE: - DoCastVictim(SPELL_CLEAVE); - events.ScheduleEvent(EVENT_CLEAVE, 7s, 10s); - break; - case EVENT_STOMP: - DoCastVictim(SPELL_WARSTOMP); - events.ScheduleEvent(EVENT_STOMP, 15s, 25s); - break; - case EVENT_FIREBALL: - DoCastVictim(SPELL_FIREBALLVOLLEY); - events.ScheduleEvent(EVENT_FIREBALL, 12s, 15s); - break; - case EVENT_CONFLAGRATION: - DoCastVictim(SPELL_CONFLAGRATION); - events.ScheduleEvent(EVENT_CONFLAGRATION, 30s); - break; - } - } - - DoMeleeAttackIfReady(); - } - - private: - bool secondPhase; - ObjectGuid _charmerGUID; - GuidVector _summonGUIDS; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); + _Reset(); + _charmerGUID.Clear(); + secondPhase = false; + summons.DespawnAll(); + instance->SetData(DATA_EGG_EVENT, NOT_STARTED); } + + void JustDied(Unit* /*killer*/) override + { + if (secondPhase) + { + _JustDied(); + } + else + { + // Respawn shorty in case of failure during phase 1. + me->SetCorpseRemoveTime(25); + me->SetRespawnTime(30); + me->SaveRespawnTime(); + + // Might not be required, safe measure. + me->SetLootRecipient(nullptr); + + instance->SetData(DATA_EGG_EVENT, FAIL); + } + } + + bool CanAIAttack(Unit const* target) const override + { + return !(target->IsCreature() && !secondPhase); + } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + + events.ScheduleEvent(EVENT_CLEAVE, 15s); + events.ScheduleEvent(EVENT_STOMP, 35s); + events.ScheduleEvent(EVENT_FIREBALL, 7s); + events.ScheduleEvent(EVENT_CONFLAGRATION, 12s); + + instance->SetData(DATA_EGG_EVENT, IN_PROGRESS); + } + + void DoChangePhase() + { + secondPhase = true; + _charmerGUID.Clear(); + me->RemoveAllAuras(); + + DoCastSelf(SPELL_WARMING_FLAMES, true); + + if (Creature* troops = instance->GetCreature(DATA_NEFARIAN_TROOPS)) + troops->AI()->Talk(EMOTE_TROOPS_RETREAT); + + for (ObjectGuid const& guid : _summonGUIDS) + { + if (Creature* creature = ObjectAccessor::GetCreature(*me, guid)) + if (creature->IsAlive()) + { + creature->CombatStop(true); + creature->SetReactState(REACT_PASSIVE); + creature->GetMotionMaster()->MovePoint(0, Position(-7560.568848f, -1028.553345f, 408.491211f, 0.523858f)); + } + } + } + + void SetGUID(ObjectGuid const& guid, int32 /*id*/) override + { + _charmerGUID = guid; + } + + void OnCharmed(bool apply) override + { + if (apply) + { + if (Unit* charmer = ObjectAccessor::GetUnit(*me, _charmerGUID)) + { + charmer->CastSpell(charmer, SPELL_MIND_EXHAUSTION, true); + charmer->CastSpell(me, SPELL_MINDCONTROL_VISUAL, false); + } + } + else + { + if (Unit* charmer = ObjectAccessor::GetUnit(*me, _charmerGUID)) + { + charmer->RemoveAurasDueToSpell(SPELL_MINDCONTROL_VISUAL); + me->EngageWithTarget(charmer); + me->AddThreat(charmer, 100.0f); + me->AI()->AttackStart(charmer); + } + } + } + + void DoAction(int32 action) override + { + if (action == ACTION_PHASE_TWO) + DoChangePhase(); + + if (action == TALK_EGG_BROKEN_RAND) + Talk(urand(SAY_EGGS_BROKEN1, SAY_EGGS_BROKEN3)); + } + + void JustSummoned(Creature* summon) override + { + _summonGUIDS.push_back(summon->GetGUID()); + summon->SetOwnerGUID(me->GetGUID()); + summons.Summon(summon); + } + + void SummonMovementInform(Creature* summon, uint32 movementType, uint32 /*pathId*/) override + { + if (movementType == POINT_MOTION_TYPE) + summon->DespawnOrUnsummon(); + } + + void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override + { + if (!secondPhase && damage >= me->GetHealth()) + { + Talk(SAY_DEATH); + DoCastAOE(SPELL_EXPLODE_ORB); + DoCastAOE(SPELL_EXPLOSION); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + if (!me->IsCharmed()) + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_CLEAVE: + DoCastVictim(SPELL_CLEAVE); + events.ScheduleEvent(EVENT_CLEAVE, 7s, 10s); + break; + case EVENT_STOMP: + DoCastVictim(SPELL_WARSTOMP); + events.ScheduleEvent(EVENT_STOMP, 15s, 25s); + break; + case EVENT_FIREBALL: + DoCastVictim(SPELL_FIREBALLVOLLEY); + events.ScheduleEvent(EVENT_FIREBALL, 12s, 15s); + break; + case EVENT_CONFLAGRATION: + DoCastVictim(SPELL_CONFLAGRATION); + events.ScheduleEvent(EVENT_CONFLAGRATION, 30s); + break; + } + } + + DoMeleeAttackIfReady(); + } + +private: + bool secondPhase; + ObjectGuid _charmerGUID; + GuidVector _summonGUIDS; }; -class go_orb_of_domination : public GameObjectScript +struct go_orb_of_domination : public GameObjectAI { -public: - go_orb_of_domination() : GameObjectScript("go_orb_of_domination") { } + go_orb_of_domination(GameObject* go) : GameObjectAI(go) { } - bool OnGossipHello(Player* player, GameObject* go) override + bool GossipHello(Player* player, bool /*reportUse*/) override { - if (InstanceScript* instance = go->GetInstanceScript()) + if (InstanceScript* instance = me->GetInstanceScript()) if (instance->GetData(DATA_EGG_EVENT) != DONE && !player->HasAura(SPELL_MIND_EXHAUSTION) && !player->GetPet()) - if (Creature* razor = ObjectAccessor::GetCreature(*go, instance->GetGuidData(DATA_RAZORGORE_THE_UNTAMED))) + if (Creature* razor = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAZORGORE_THE_UNTAMED))) { razor->AI()->SetGUID(player->GetGUID()); razor->Attack(player, true); @@ -294,12 +272,9 @@ class spell_egg_event : public SpellScript void HandleOnHit() { if (InstanceScript* instance = GetCaster()->GetInstanceScript()) - { instance->SetData(DATA_EGG_EVENT, SPECIAL); - } if (Creature* razorgore = GetCaster()->ToCreature()) - { if (GameObject* egg = GetHitGObj()) { razorgore->AI()->DoAction(TALK_EGG_BROKEN_RAND); @@ -307,7 +282,6 @@ class spell_egg_event : public SpellScript egg->UseDoorOrButton(10000); egg->SetRespawnTime(WEEK); } - } } void Register() override @@ -318,7 +292,7 @@ class spell_egg_event : public SpellScript void AddSC_boss_razorgore() { - new boss_razorgore(); - new go_orb_of_domination(); + RegisterBlackwingLairCreatureAI(boss_razorgore); + RegisterBlackwingLairGameObjectAI(go_orb_of_domination); RegisterSpellScript(spell_egg_event); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_vaelastrasz.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_vaelastrasz.cpp index 3cfbf13e4..1980d0ba9 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_vaelastrasz.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_vaelastrasz.cpp @@ -72,227 +72,214 @@ enum Events EVENT_BURNING_ADRENALINE = 12, }; -class boss_vaelastrasz : public CreatureScript +struct boss_vaelastrasz : public BossAI { -public: - boss_vaelastrasz() : CreatureScript("boss_vaelastrasz") { } - - struct boss_vaelAI : public BossAI + boss_vaelastrasz(Creature* creature) : BossAI(creature, DATA_VAELASTRAZ_THE_CORRUPT) { - boss_vaelAI(Creature* creature) : BossAI(creature, DATA_VAELASTRAZ_THE_CORRUPT) + Initialize(); + } + + void Initialize() + { + PlayerGUID.Clear(); + HasYelled = false; + _introDone = false; + _burningAdrenalineCast = 0; + me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); + me->SetFaction(FACTION_FRIENDLY); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + } + + void Reset() override + { + _Reset(); + me->SetHealth(me->CountPctFromMaxHealth(30)); + + if (!_introDone) { + me->SetStandState(UNIT_STAND_STATE_DEAD); + me->SetReactState(REACT_PASSIVE); Initialize(); + _eventsIntro.Reset(); } - - void Initialize() + else { - PlayerGUID.Clear(); HasYelled = false; - _introDone = false; _burningAdrenalineCast = 0; - me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); - me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); - me->SetFaction(FACTION_FRIENDLY); - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); } + } - void Reset() override + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + + DoCastAOE(SPELL_ESSENCE_OF_THE_RED); + // now drop damage requirement to be able to take loot + me->ResetPlayerDamageReq(); + + events.ScheduleEvent(EVENT_CLEAVE, 10s); + events.ScheduleEvent(EVENT_FLAME_BREATH, 15s); + events.ScheduleEvent(EVENT_FIRE_NOVA, 5s); + events.ScheduleEvent(EVENT_TAIL_SWEEP, 11s); + events.ScheduleEvent(EVENT_BURNING_ADRENALINE, 15s); + } + + void BeginSpeech(Unit* target) + { + PlayerGUID = target->GetGUID(); + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + _eventsIntro.ScheduleEvent(EVENT_SPEECH_1, 1s); + } + + void KilledUnit(Unit* victim) override + { + if (rand32() % 5) + return; + + Talk(SAY_KILLTARGET, victim); + } + + void UpdateAI(uint32 diff) override + { + events.Update(diff); + _eventsIntro.Update(diff); + + // Speech + if (!_introDone) { - _Reset(); - me->SetHealth(me->CountPctFromMaxHealth(30)); - - if (!_introDone) - { - me->SetStandState(UNIT_STAND_STATE_DEAD); - me->SetReactState(REACT_PASSIVE); - Initialize(); - _eventsIntro.Reset(); - } - else - { - HasYelled = false; - _burningAdrenalineCast = 0; - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - - DoCastAOE(SPELL_ESSENCE_OF_THE_RED); - // now drop damage requirement to be able to take loot - me->ResetPlayerDamageReq(); - - events.ScheduleEvent(EVENT_CLEAVE, 10s); - events.ScheduleEvent(EVENT_FLAME_BREATH, 15s); - events.ScheduleEvent(EVENT_FIRE_NOVA, 5s); - events.ScheduleEvent(EVENT_TAIL_SWEEP, 11s); - events.ScheduleEvent(EVENT_BURNING_ADRENALINE, 15s); - } - - void BeginSpeech(Unit* target) - { - PlayerGUID = target->GetGUID(); - me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); - _eventsIntro.ScheduleEvent(EVENT_SPEECH_1, 1s); - } - - void KilledUnit(Unit* victim) override - { - if (rand32() % 5) - return; - - Talk(SAY_KILLTARGET, victim); - } - - void UpdateAI(uint32 diff) override - { - events.Update(diff); - _eventsIntro.Update(diff); - - // Speech - if (!_introDone) - { - while (uint32 eventId = _eventsIntro.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SPEECH_1: - me->SetStandState(UNIT_STAND_STATE_STAND); - me->SummonCreature(NPC_VICTOR_NEFARIUS, aNefariusSpawnLoc[0], aNefariusSpawnLoc[1], aNefariusSpawnLoc[2], aNefariusSpawnLoc[3], TEMPSUMMON_TIMED_DESPAWN, 26000); - _eventsIntro.ScheduleEvent(EVENT_SPEECH_2, 1s); - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - break; - case EVENT_SPEECH_2: - if (Creature* nefarius = me->GetMap()->GetCreature(m_nefariusGuid)) - { - nefarius->CastSpell(me, SPELL_NEFARIUS_CORRUPTION, TRIGGERED_CAST_DIRECTLY); - nefarius->Yell(SAY_NEFARIAN_VAEL_INTRO); - nefarius->SetStandState(UNIT_STAND_STATE_STAND); - } - _eventsIntro.ScheduleEvent(EVENT_SPEECH_3, 18s); - break; - case EVENT_SPEECH_3: - if (Creature* nefarius = me->GetMap()->GetCreature(m_nefariusGuid)) - nefarius->CastSpell(me, SPELL_RED_LIGHTNING, TRIGGERED_NONE); - _eventsIntro.ScheduleEvent(EVENT_SPEECH_4, 2s); - break; - case EVENT_SPEECH_4: - Talk(SAY_LINE1); - me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - _eventsIntro.ScheduleEvent(EVENT_SPEECH_5, 12s); - break; - case EVENT_SPEECH_5: - Talk(SAY_LINE2); - me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - _eventsIntro.ScheduleEvent(EVENT_SPEECH_6, 12s); - break; - case EVENT_SPEECH_6: - Talk(SAY_LINE3); - me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - _eventsIntro.ScheduleEvent(EVENT_SPEECH_7, 17s); - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - break; - case EVENT_SPEECH_7: - me->SetFaction(FACTION_DRAGONFLIGHT_BLACK); - if (PlayerGUID && ObjectAccessor::GetUnit(*me, PlayerGUID)) - AttackStart(ObjectAccessor::GetUnit(*me, PlayerGUID)); - me->SetReactState(REACT_AGGRESSIVE); - _introDone = true; - break; - } - } - } - - if (!UpdateVictim() || me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) + while (uint32 eventId = _eventsIntro.ExecuteEvent()) { switch (eventId) { - case EVENT_CLEAVE: - events.ScheduleEvent(EVENT_CLEAVE, 15s); - DoCastVictim(SPELL_CLEAVE); + case EVENT_SPEECH_1: + me->SetStandState(UNIT_STAND_STATE_STAND); + me->SummonCreature(NPC_VICTOR_NEFARIUS, aNefariusSpawnLoc[0], aNefariusSpawnLoc[1], aNefariusSpawnLoc[2], aNefariusSpawnLoc[3], TEMPSUMMON_TIMED_DESPAWN, 26000); + _eventsIntro.ScheduleEvent(EVENT_SPEECH_2, 1s); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); break; - case EVENT_FLAME_BREATH: - DoCastVictim(SPELL_FLAME_BREATH); - events.ScheduleEvent(EVENT_FLAME_BREATH, 8s, 14s); - break; - case EVENT_FIRE_NOVA: - DoCastVictim(SPELL_FIRE_NOVA); - events.ScheduleEvent(EVENT_FIRE_NOVA, 3s, 5s); - break; - case EVENT_TAIL_SWEEP: - DoCastAOE(SPELL_TAIL_SWEEP); - events.ScheduleEvent(EVENT_TAIL_SWEEP, 15s); - break; - case EVENT_BURNING_ADRENALINE: - { - if (_burningAdrenalineCast < 2) // It's better to use TaskScheduler for this, but zzz + case EVENT_SPEECH_2: + if (Creature* nefarius = me->GetMap()->GetCreature(m_nefariusGuid)) { - //selects a random target that isn't the current victim and is a mana user (selects mana users) but not pets - //it also ignores targets who have the aura. We don't want to place the debuff on the same target twice. - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, [&](Unit* u) { return u && !u->IsPet() && u->getPowerType() == POWER_MANA && !u->HasAura(SPELL_BURNING_ADRENALINE) && u != me->GetVictim(); })) - { - me->CastSpell(target, SPELL_BURNING_ADRENALINE, true); - } - - _burningAdrenalineCast++; + nefarius->CastSpell(me, SPELL_NEFARIUS_CORRUPTION, TRIGGERED_CAST_DIRECTLY); + nefarius->Yell(SAY_NEFARIAN_VAEL_INTRO); + nefarius->SetStandState(UNIT_STAND_STATE_STAND); } - else - { - me->CastSpell(me->GetVictim(), SPELL_BURNING_ADRENALINE, true); - _burningAdrenalineCast = 0; - } - events.ScheduleEvent(EVENT_BURNING_ADRENALINE, 15s); + _eventsIntro.ScheduleEvent(EVENT_SPEECH_3, 18s); + break; + case EVENT_SPEECH_3: + if (Creature* nefarius = me->GetMap()->GetCreature(m_nefariusGuid)) + nefarius->CastSpell(me, SPELL_RED_LIGHTNING, TRIGGERED_NONE); + _eventsIntro.ScheduleEvent(EVENT_SPEECH_4, 2s); + break; + case EVENT_SPEECH_4: + Talk(SAY_LINE1); + me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + _eventsIntro.ScheduleEvent(EVENT_SPEECH_5, 12s); + break; + case EVENT_SPEECH_5: + Talk(SAY_LINE2); + me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + _eventsIntro.ScheduleEvent(EVENT_SPEECH_6, 12s); + break; + case EVENT_SPEECH_6: + Talk(SAY_LINE3); + me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + _eventsIntro.ScheduleEvent(EVENT_SPEECH_7, 17s); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + break; + case EVENT_SPEECH_7: + me->SetFaction(FACTION_DRAGONFLIGHT_BLACK); + if (PlayerGUID && ObjectAccessor::GetUnit(*me, PlayerGUID)) + AttackStart(ObjectAccessor::GetUnit(*me, PlayerGUID)); + me->SetReactState(REACT_AGGRESSIVE); + _introDone = true; break; - } } } - - // Yell if hp lower than 15% - if (HealthBelowPct(15) && !HasYelled) - { - Talk(SAY_HALFLIFE); - HasYelled = true; - } - - DoMeleeAttackIfReady(); } - void JustSummoned(Creature* summoned) override + if (!UpdateVictim() || me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) { - if (summoned->GetEntry() == NPC_VICTOR_NEFARIUS) + switch (eventId) { - // Set not selectable, so players won't interact with it - summoned->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - m_nefariusGuid = summoned->GetGUID(); + case EVENT_CLEAVE: + events.ScheduleEvent(EVENT_CLEAVE, 15s); + DoCastVictim(SPELL_CLEAVE); + break; + case EVENT_FLAME_BREATH: + DoCastVictim(SPELL_FLAME_BREATH); + events.ScheduleEvent(EVENT_FLAME_BREATH, 8s, 14s); + break; + case EVENT_FIRE_NOVA: + DoCastVictim(SPELL_FIRE_NOVA); + events.ScheduleEvent(EVENT_FIRE_NOVA, 3s, 5s); + break; + case EVENT_TAIL_SWEEP: + DoCastAOE(SPELL_TAIL_SWEEP); + events.ScheduleEvent(EVENT_TAIL_SWEEP, 15s); + break; + case EVENT_BURNING_ADRENALINE: + { + if (_burningAdrenalineCast < 2) // It's better to use TaskScheduler for this, but zzz + { + //selects a random target that isn't the current victim and is a mana user (selects mana users) but not pets + //it also ignores targets who have the aura. We don't want to place the debuff on the same target twice. + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, [&](Unit* u) { return u && !u->IsPet() && u->getPowerType() == POWER_MANA && !u->HasAura(SPELL_BURNING_ADRENALINE) && u != me->GetVictim(); })) + me->CastSpell(target, SPELL_BURNING_ADRENALINE, true); + + _burningAdrenalineCast++; + } + else + { + me->CastSpell(me->GetVictim(), SPELL_BURNING_ADRENALINE, true); + _burningAdrenalineCast = 0; + } + events.ScheduleEvent(EVENT_BURNING_ADRENALINE, 15s); + break; + } } } - void sGossipSelect(Player* player, uint32 sender, uint32 action) override + // Yell if hp lower than 15% + if (HealthBelowPct(15) && !HasYelled) { - if (sender == GOSSIP_ID && action == 0) - { - CloseGossipMenuFor(player); - BeginSpeech(player); - } + Talk(SAY_HALFLIFE); + HasYelled = true; } - private: - ObjectGuid PlayerGUID; - ObjectGuid m_nefariusGuid; - bool HasYelled; - bool _introDone; - EventMap _eventsIntro; - uint8 _burningAdrenalineCast; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetBlackwingLairAI(creature); + DoMeleeAttackIfReady(); } + + void JustSummoned(Creature* summoned) override + { + if (summoned->GetEntry() == NPC_VICTOR_NEFARIUS) + { + // Set not selectable, so players won't interact with it + summoned->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + m_nefariusGuid = summoned->GetGUID(); + } + } + + void sGossipSelect(Player* player, uint32 sender, uint32 action) override + { + if (sender == GOSSIP_ID && action == 0) + { + CloseGossipMenuFor(player); + BeginSpeech(player); + } + } + +private: + ObjectGuid PlayerGUID; + ObjectGuid m_nefariusGuid; + bool HasYelled; + bool _introDone; + EventMap _eventsIntro; + uint8 _burningAdrenalineCast; }; // 18173 - Burning Adrenaline @@ -308,9 +295,7 @@ class spell_vael_burning_adrenaline : public AuraScript void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (!GetTarget()) - { return; - } // Do the explosion, then kill the target. GetTarget()->CastSpell(GetTarget(), SPELL_BURNING_ADRENALINE_EXPLOSION, true); @@ -325,6 +310,6 @@ class spell_vael_burning_adrenaline : public AuraScript void AddSC_boss_vaelastrasz() { - new boss_vaelastrasz(); + RegisterBlackwingLairCreatureAI(boss_vaelastrasz); RegisterSpellScript(spell_vael_burning_adrenaline); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp index 2dfb99fc4..a77af22d0 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp @@ -72,404 +72,355 @@ Position const SummonPosition[8] = uint32 const Entry[3] = { 12422, 12416, 12420 }; -class instance_blackwing_lair : public InstanceMapScript +struct instance_blackwing_lair : public InstanceScript { -public: - instance_blackwing_lair() : InstanceMapScript(BWLScriptName, MAP_BLACKWING_LAIR) { } - - struct instance_blackwing_lair_InstanceMapScript : public InstanceScript + instance_blackwing_lair(Map* map) : InstanceScript(map) { - instance_blackwing_lair_InstanceMapScript(Map* map) : InstanceScript(map) + SetHeaders(DataHeader); + SetBossNumber(EncounterCount); + LoadDoorData(doorData); + LoadObjectData(creatureData, objectData); + } + + void Initialize() override + { + // Razorgore + EggCount = 0; + EggEvent = 0; + NefarianLeftTunnel = 0; + NefarianRightTunnel = 0; + addsCount.fill(0); + } + + void OnCreatureCreate(Creature* creature) override + { + // This is required because the tempspawn at Vael overwrites his GUID. + if (creature->GetEntry() == NPC_VICTOR_NEFARIUS && creature->ToTempSummon()) + return; + + InstanceScript::OnCreatureCreate(creature); + + switch (creature->GetEntry()) { - SetHeaders(DataHeader); - SetBossNumber(EncounterCount); - LoadDoorData(doorData); - LoadObjectData(creatureData, objectData); + case NPC_RAZORGORE: + razorgoreGUID = creature->GetGUID(); + break; + case NPC_BLACKWING_DRAGON: + ++addsCount[0]; + if (Creature* razor = instance->GetCreature(razorgoreGUID)) + if (CreatureAI* razorAI = razor->AI()) + razorAI->JustSummoned(creature); + break; + case NPC_BLACKWING_LEGIONAIRE: + case NPC_BLACKWING_MAGE: + ++addsCount[1]; + if (Creature* razor = instance->GetCreature(razorgoreGUID)) + if (CreatureAI* razorAI = razor->AI()) + razorAI->JustSummoned(creature); + break; + case NPC_BLACKWING_GUARDSMAN: + guardList.push_back(creature->GetGUID()); + break; + case NPC_NEFARIAN: + nefarianGUID = creature->GetGUID(); + break; + case NPC_BLACK_DRAKONID: + case NPC_BLUE_DRAKONID: + case NPC_BRONZE_DRAKONID: + case NPC_CHROMATIC_DRAKONID: + case NPC_GREEN_DRAKONID: + case NPC_RED_DRAKONID: + if (Creature* nefarius = GetCreature(DATA_LORD_VICTOR_NEFARIUS)) + if (CreatureAI* nefariusAI = nefarius->AI()) + nefariusAI->JustSummoned(creature); + break; + default: + break; + } + } + + void OnGameObjectCreate(GameObject* go) override + { + InstanceScript::OnGameObjectCreate(go); + + switch (go->GetEntry()) + { + case GO_BLACK_DRAGON_EGG: + if (GetBossState(DATA_FIREMAW) == DONE) + go->SetPhaseMask(2, true); + else + EggList.push_back(go->GetGUID()); + break; + default: + break; + } + } + + void OnGameObjectRemove(GameObject* go) override + { + InstanceScript::OnGameObjectRemove(go); + + if (go->GetEntry() == GO_BLACK_DRAGON_EGG) + EggList.remove(go->GetGUID()); + } + + uint32 GetData(uint32 data) const override + { + switch (data) + { + case DATA_NEFARIAN_LEFT_TUNNEL: + return NefarianLeftTunnel; + case DATA_NEFARIAN_RIGHT_TUNNEL: + return NefarianRightTunnel; + case DATA_EGG_EVENT: + return EggEvent; + default: + break; } - void Initialize() override + return 0; + } + + bool CheckRequiredBosses(uint32 bossId, Player const* /* player */) const override + { + switch (bossId) { - // Razorgore - EggCount = 0; - EggEvent = 0; - NefarianLeftTunnel = 0; - NefarianRightTunnel = 0; - addsCount.fill(0); + case DATA_BROODLORD_LASHLAYER: + if (GetBossState(DATA_VAELASTRAZ_THE_CORRUPT) != DONE) + return false; + break; + default: + break; } - void OnCreatureCreate(Creature* creature) override + return true; + } + + bool SetBossState(uint32 type, EncounterState state) override + { + if (!InstanceScript::SetBossState(type, state)) + return false; + + switch (type) { - // This is required because the tempspawn at Vael overwrites his GUID. - if (creature->GetEntry() == NPC_VICTOR_NEFARIUS && creature->ToTempSummon()) - { - return; - } - - InstanceScript::OnCreatureCreate(creature); - - switch (creature->GetEntry()) - { - case NPC_RAZORGORE: - razorgoreGUID = creature->GetGUID(); - break; - case NPC_BLACKWING_DRAGON: - ++addsCount[0]; - if (Creature* razor = instance->GetCreature(razorgoreGUID)) + case DATA_RAZORGORE_THE_UNTAMED: + if (state == DONE) + { + for (ObjectGuid const& guid : EggList) { - if (CreatureAI* razorAI = razor->AI()) - { - razorAI->JustSummoned(creature); - } + // Eggs should be destroyed instead + /// @todo: after dynamic spawns + if (GameObject* egg = instance->GetGameObject(guid)) + egg->SetPhaseMask(2, true); } - break; - case NPC_BLACKWING_LEGIONAIRE: - case NPC_BLACKWING_MAGE: - ++addsCount[1]; - if (Creature* razor = instance->GetCreature(razorgoreGUID)) - { - if (CreatureAI* razorAI = razor->AI()) - { - razorAI->JustSummoned(creature); - } - } - break; - case NPC_BLACKWING_GUARDSMAN: - guardList.push_back(creature->GetGUID()); - break; - case NPC_NEFARIAN: - nefarianGUID = creature->GetGUID(); - break; - case NPC_BLACK_DRAKONID: - case NPC_BLUE_DRAKONID: - case NPC_BRONZE_DRAKONID: - case NPC_CHROMATIC_DRAKONID: - case NPC_GREEN_DRAKONID: - case NPC_RED_DRAKONID: - if (Creature* nefarius = GetCreature(DATA_LORD_VICTOR_NEFARIUS)) - { - if (CreatureAI* nefariusAI = nefarius->AI()) - { - nefariusAI->JustSummoned(creature); - } - } - break; - default: - break; - } + } + break; + case DATA_NEFARIAN: + switch (state) + { + case FAIL: + _events.ScheduleEvent(EVENT_RESPAWN_NEFARIUS, 15min); + [[fallthrough]]; + case NOT_STARTED: + if (Creature* nefarian = instance->GetCreature(nefarianGUID)) + nefarian->DespawnOrUnsummon(); + break; + default: + break; + } + break; } + return true; + } - void OnGameObjectCreate(GameObject* go) override - { - InstanceScript::OnGameObjectCreate(go); - - switch (go->GetEntry()) - { - case GO_BLACK_DRAGON_EGG: - if (GetBossState(DATA_FIREMAW) == DONE) - { - go->SetPhaseMask(2, true); - } - else - { - EggList.push_back(go->GetGUID()); - } - break; - default: - break; - } - } - - void OnGameObjectRemove(GameObject* go) override - { - InstanceScript::OnGameObjectRemove(go); - - if (go->GetEntry() == GO_BLACK_DRAGON_EGG) - EggList.remove(go->GetGUID()); - } - - uint32 GetData(uint32 data) const override + void SetData(uint32 type, uint32 data) override + { + if (type == DATA_EGG_EVENT) { switch (data) { - case DATA_NEFARIAN_LEFT_TUNNEL: - return NefarianLeftTunnel; - case DATA_NEFARIAN_RIGHT_TUNNEL: - return NefarianRightTunnel; - case DATA_EGG_EVENT: - return EggEvent; - default: + case DONE: + EggEvent = data; break; - } - - return 0; - } - - bool CheckRequiredBosses(uint32 bossId, Player const* /* player */) const override - { - switch (bossId) - { - case DATA_BROODLORD_LASHLAYER: - if (GetBossState(DATA_VAELASTRAZ_THE_CORRUPT) != DONE) - return false; + case FAIL: + _events.CancelEvent(EVENT_RAZOR_SPAWN); break; - default: + case IN_PROGRESS: + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 45s); + EggEvent = data; + EggCount = 0; + addsCount.fill(0); break; - } + case NOT_STARTED: + _events.CancelEvent(EVENT_RAZOR_SPAWN); + EggEvent = data; + EggCount = 0; + addsCount.fill(0); - return true; - } + for (ObjectGuid const& guid : EggList) + DoRespawnGameObject(guid, 0); - bool SetBossState(uint32 type, EncounterState state) override - { - if (!InstanceScript::SetBossState(type, state)) - return false; + DoRespawnCreature(DATA_GRETHOK); - switch (type) - { - case DATA_RAZORGORE_THE_UNTAMED: - if (state == DONE) + for (ObjectGuid const& guid : guardList) + DoRespawnCreature(guid); + + break; + case SPECIAL: + if (EggEvent == NOT_STARTED) + SetData(DATA_EGG_EVENT, IN_PROGRESS); + if (++EggCount >= EggList.size()) { - for (ObjectGuid const& guid : EggList) + if (Creature* razor = instance->GetCreature(razorgoreGUID)) { - // Eggs should be destroyed instead - /// @todo: after dynamic spawns - if (GameObject* egg = instance->GetGameObject(guid)) - { - egg->SetPhaseMask(2, true); - } + SetData(DATA_EGG_EVENT, DONE); + razor->RemoveAurasDueToSpell(19832); // MindControl + DoRemoveAurasDueToSpellOnPlayers(19832); } - } - break; - case DATA_NEFARIAN: - switch (state) - { - case FAIL: - _events.ScheduleEvent(EVENT_RESPAWN_NEFARIUS, 15min); - [[fallthrough]]; - case NOT_STARTED: - if (Creature* nefarian = instance->GetCreature(nefarianGUID)) - { - nefarian->DespawnOrUnsummon(); - } - break; - default: - break; - } - break; - } - return true; - } - - void SetData(uint32 type, uint32 data) override - { - if (type == DATA_EGG_EVENT) - { - switch (data) - { - case DONE: - EggEvent = data; - break; - case FAIL: + _events.ScheduleEvent(EVENT_RAZOR_PHASE_TWO, 1s); _events.CancelEvent(EVENT_RAZOR_SPAWN); - break; - case IN_PROGRESS: - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 45s); - EggEvent = data; - EggCount = 0; - addsCount.fill(0); - break; - case NOT_STARTED: - _events.CancelEvent(EVENT_RAZOR_SPAWN); - EggEvent = data; - EggCount = 0; - addsCount.fill(0); - - for (ObjectGuid const& guid : EggList) - { - DoRespawnGameObject(guid, 0); - } - - DoRespawnCreature(DATA_GRETHOK); - - for (ObjectGuid const& guid : guardList) - { - DoRespawnCreature(guid); - } - - break; - case SPECIAL: - if (EggEvent == NOT_STARTED) - SetData(DATA_EGG_EVENT, IN_PROGRESS); - if (++EggCount >= EggList.size()) - { - if (Creature* razor = instance->GetCreature(razorgoreGUID)) - { - SetData(DATA_EGG_EVENT, DONE); - razor->RemoveAurasDueToSpell(19832); // MindControl - DoRemoveAurasDueToSpellOnPlayers(19832); - } - _events.ScheduleEvent(EVENT_RAZOR_PHASE_TWO, 1s); - _events.CancelEvent(EVENT_RAZOR_SPAWN); - } - break; - } - } - - if (type == DATA_NEFARIAN_LEFT_TUNNEL) - { - NefarianLeftTunnel = data; - } - - if (type == DATA_NEFARIAN_RIGHT_TUNNEL) - { - NefarianRightTunnel = data; - } - } - - ObjectGuid GetGuidData(uint32 type) const override - { - switch (type) - { - case DATA_RAZORGORE_THE_UNTAMED: - return razorgoreGUID; - default: - break; - } - - return ObjectGuid::Empty; - } - - void OnUnitDeath(Unit* unit) override - { - switch (unit->GetEntry()) - { - case NPC_BLACKWING_DRAGON: - --addsCount[0]; - if (EggEvent != DONE && !_events.HasTimeUntilEvent(EVENT_RAZOR_SPAWN)) - { - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 1s); } break; - case NPC_BLACKWING_LEGIONAIRE: - case NPC_BLACKWING_MAGE: - --addsCount[1]; - if (EggEvent != DONE && !_events.HasTimeUntilEvent(EVENT_RAZOR_SPAWN)) - { - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 1s); - } - break; - default: - break; } } - void Update(uint32 diff) override + if (type == DATA_NEFARIAN_LEFT_TUNNEL) + NefarianLeftTunnel = data; + + if (type == DATA_NEFARIAN_RIGHT_TUNNEL) + NefarianRightTunnel = data; + } + + ObjectGuid GetGuidData(uint32 type) const override + { + switch (type) { - if (_events.Empty()) - return; + case DATA_RAZORGORE_THE_UNTAMED: + return razorgoreGUID; + default: + break; + } - _events.Update(diff); + return ObjectGuid::Empty; + } - while (uint32 eventId = _events.ExecuteEvent()) + void OnUnitDeath(Unit* unit) override + { + switch (unit->GetEntry()) + { + case NPC_BLACKWING_DRAGON: + --addsCount[0]; + if (EggEvent != DONE && !_events.HasTimeUntilEvent(EVENT_RAZOR_SPAWN)) + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 1s); + break; + case NPC_BLACKWING_LEGIONAIRE: + case NPC_BLACKWING_MAGE: + --addsCount[1]; + if (EggEvent != DONE && !_events.HasTimeUntilEvent(EVENT_RAZOR_SPAWN)) + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 1s); + break; + default: + break; + } + } + + void Update(uint32 diff) override + { + if (_events.Empty()) + return; + + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) { - switch (eventId) - { - case EVENT_RAZOR_SPAWN: - if (EggEvent == IN_PROGRESS) + case EVENT_RAZOR_SPAWN: + if (EggEvent == IN_PROGRESS) + { + bool spawnMoreAdds = true; + for (uint8 i = urand(2, 5); i > 0; --i) { - bool spawnMoreAdds = true; - for (uint8 i = urand(2, 5); i > 0; --i) - { - uint32 mobEntry = Entry[urand(0, 2)]; - uint32 dragonkinsCount = addsCount[0]; - uint32 orcsCount = addsCount[1]; + uint32 mobEntry = Entry[urand(0, 2)]; + uint32 dragonkinsCount = addsCount[0]; + uint32 orcsCount = addsCount[1]; - // If more than 12 dragonkins... - if (dragonkinsCount >= 12) + // If more than 12 dragonkins... + if (dragonkinsCount >= 12) + { + //... and more than 40 orcs - stop spawning more adds. + if (orcsCount >= 40) { - //... and more than 40 orcs - stop spawning more adds. - if (orcsCount >= 40) - { - spawnMoreAdds = false; - break; - } - //... - stop spawning them. - else if (mobEntry == NPC_BLACKWING_DRAGON) - { - continue; - } + spawnMoreAdds = false; + break; } - // If more than 40 orcs - stop spawning them. - else if (orcsCount >= 40 && mobEntry != NPC_BLACKWING_DRAGON) + //... - stop spawning them. + else if (mobEntry == NPC_BLACKWING_DRAGON) { continue; } - - if (Creature* summon = instance->SummonCreature(mobEntry, SummonPosition[urand(0, 7)])) - { - summon->AI()->DoZoneInCombat(); - } } - - if (spawnMoreAdds) + // If more than 40 orcs - stop spawning them. + else if (orcsCount >= 40 && mobEntry != NPC_BLACKWING_DRAGON) { - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 15s); + continue; } + + if (Creature* summon = instance->SummonCreature(mobEntry, SummonPosition[urand(0, 7)])) + summon->AI()->DoZoneInCombat(); } - break; - case EVENT_RAZOR_PHASE_TWO: - _events.CancelEvent(EVENT_RAZOR_SPAWN); - if (Creature* razor = instance->GetCreature(razorgoreGUID)) - razor->AI()->DoAction(ACTION_PHASE_TWO); - break; - case EVENT_RESPAWN_NEFARIUS: - if (Creature* nefarius = GetCreature(DATA_LORD_VICTOR_NEFARIUS)) - { - nefarius->SetPhaseMask(1, true); - nefarius->setActive(true); - nefarius->Respawn(); - nefarius->GetMotionMaster()->MoveTargetedHome(); - } - break; - } + + if (spawnMoreAdds) + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 15s); + } + break; + case EVENT_RAZOR_PHASE_TWO: + _events.CancelEvent(EVENT_RAZOR_SPAWN); + if (Creature* razor = instance->GetCreature(razorgoreGUID)) + razor->AI()->DoAction(ACTION_PHASE_TWO); + break; + case EVENT_RESPAWN_NEFARIUS: + if (Creature* nefarius = GetCreature(DATA_LORD_VICTOR_NEFARIUS)) + { + nefarius->SetPhaseMask(1, true); + nefarius->setActive(true); + nefarius->Respawn(); + nefarius->GetMotionMaster()->MoveTargetedHome(); + } + break; } } - - void ReadSaveDataMore(std::istringstream& data) override - { - data >> NefarianLeftTunnel; - data >> NefarianRightTunnel; - } - - void WriteSaveDataMore(std::ostringstream& data) override - { - data << NefarianLeftTunnel << ' ' << NefarianRightTunnel; - } - - protected: - ObjectGuid razorgoreGUID; - ObjectGuid nefarianGUID; - ObjectGuid nefarianDoorGUID; - - // Razorgore - uint8 EggCount; - uint32 EggEvent; - GuidList EggList; - GuidList guardList; - std::array addsCount; - - // Nefarian - uint32 NefarianLeftTunnel; - uint32 NefarianRightTunnel; - - // Misc - EventMap _events; - }; - - InstanceScript* GetInstanceScript(InstanceMap* map) const override - { - return new instance_blackwing_lair_InstanceMapScript(map); } + + void ReadSaveDataMore(std::istringstream& data) override + { + data >> NefarianLeftTunnel; + data >> NefarianRightTunnel; + } + + void WriteSaveDataMore(std::ostringstream& data) override + { + data << NefarianLeftTunnel << ' ' << NefarianRightTunnel; + } + +protected: + ObjectGuid razorgoreGUID; + ObjectGuid nefarianGUID; + ObjectGuid nefarianDoorGUID; + + // Razorgore + uint8 EggCount; + uint32 EggEvent; + GuidList EggList; + GuidList guardList; + std::array addsCount; + + // Nefarian + uint32 NefarianLeftTunnel; + uint32 NefarianRightTunnel; + + // Misc + EventMap _events; }; enum ShadowFlame @@ -527,7 +478,7 @@ public: void AddSC_instance_blackwing_lair() { - new instance_blackwing_lair(); + RegisterInstanceScript(instance_blackwing_lair, MAP_BLACKWING_LAIR); RegisterSpellScript(spell_bwl_shadowflame); new at_orb_of_command(); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp index 448ec46ff..96cfbed45 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp @@ -43,88 +43,73 @@ enum Events EVENT_LIVING_BOMB, }; -class boss_baron_geddon : public CreatureScript +struct boss_baron_geddon : public BossAI { -public: - boss_baron_geddon() : CreatureScript("boss_baron_geddon") { } - - struct boss_baron_geddonAI : public BossAI + boss_baron_geddon(Creature* creature) : BossAI(creature, DATA_GEDDON), + armageddonCasted(false) { - boss_baron_geddonAI(Creature* creature) : BossAI(creature, DATA_GEDDON), - armageddonCasted(false) - { - } - - void Reset() override - { - _Reset(); - armageddonCasted = false; - } - - void JustEngagedWith(Unit* /*attacker*/) override - { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_INFERNO, 13s, 15s); - events.ScheduleEvent(EVENT_IGNITE_MANA, 7s, 19s); - events.ScheduleEvent(EVENT_LIVING_BOMB, 11s, 16s); - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override - { - // If boss is below 2% hp - cast Armageddon - if (!armageddonCasted && damage < me->GetHealth() && me->HealthBelowPctDamaged(2, damage)) - { - me->RemoveAurasDueToSpell(SPELL_INFERNO); - me->StopMoving(); - if (me->CastSpell(me, SPELL_ARMAGEDDON, TRIGGERED_FULL_MASK) == SPELL_CAST_OK) - { - Talk(EMOTE_SERVICE); - armageddonCasted = true; - } - } - } - - void ExecuteEvent(uint32 eventId) override - { - switch (eventId) - { - case EVENT_INFERNO: - { - DoCastAOE(SPELL_INFERNO); - events.Repeat(21s, 26s); - break; - } - case EVENT_IGNITE_MANA: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_IGNITE_MANA)) - { - DoCast(target, SPELL_IGNITE_MANA); - } - - events.Repeat(27s, 32s); - break; - } - case EVENT_LIVING_BOMB: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) - { - DoCast(target, SPELL_LIVING_BOMB); - } - - events.Repeat(11s, 16s); - break; - } - } - } - - private: - bool armageddonCasted; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } + + void Reset() override + { + _Reset(); + armageddonCasted = false; + } + + void JustEngagedWith(Unit* /*attacker*/) override + { + _JustEngagedWith(); + events.ScheduleEvent(EVENT_INFERNO, 13s, 15s); + events.ScheduleEvent(EVENT_IGNITE_MANA, 7s, 19s); + events.ScheduleEvent(EVENT_LIVING_BOMB, 11s, 16s); + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override + { + // If boss is below 2% hp - cast Armageddon + if (!armageddonCasted && damage < me->GetHealth() && me->HealthBelowPctDamaged(2, damage)) + { + me->RemoveAurasDueToSpell(SPELL_INFERNO); + me->StopMoving(); + if (me->CastSpell(me, SPELL_ARMAGEDDON, TRIGGERED_FULL_MASK) == SPELL_CAST_OK) + { + Talk(EMOTE_SERVICE); + armageddonCasted = true; + } + } + } + + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_INFERNO: + { + DoCastAOE(SPELL_INFERNO); + events.Repeat(21s, 26s); + break; + } + case EVENT_IGNITE_MANA: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_IGNITE_MANA)) + DoCast(target, SPELL_IGNITE_MANA); + + events.Repeat(27s, 32s); + break; + } + case EVENT_LIVING_BOMB: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) + DoCast(target, SPELL_LIVING_BOMB); + + events.Repeat(11s, 16s); + break; + } + } + } + +private: + bool armageddonCasted; }; // 19695 Inferno @@ -149,9 +134,7 @@ class spell_geddon_inferno_aura : public AuraScript void HandleAfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (Creature* pCreatureTarget = GetTarget()->ToCreature()) - { pCreatureTarget->SetReactState(REACT_AGGRESSIVE); - } } void PeriodicTick(AuraEffect const* aurEff) @@ -209,9 +192,7 @@ class spell_geddon_armageddon_aura : public AuraScript void HandleAfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (Creature* pCreatureTarget = GetTarget()->ToCreature()) - { pCreatureTarget->SetReactState(REACT_AGGRESSIVE); - } } void Register() override @@ -223,7 +204,7 @@ class spell_geddon_armageddon_aura : public AuraScript void AddSC_boss_baron_geddon() { - new boss_baron_geddon(); + RegisterMoltenCoreCreatureAI(boss_baron_geddon); // Spells RegisterSpellScript(spell_geddon_inferno_aura); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_garr.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_garr.cpp index 15f3995b0..1e18daf55 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_garr.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_garr.cpp @@ -51,123 +51,93 @@ enum Events EVENT_MAGMA_SHACKLES, }; -class boss_garr : public CreatureScript +struct boss_garr : public BossAI { -public: - boss_garr() : CreatureScript("boss_garr") {} - - struct boss_garrAI : public BossAI + boss_garr(Creature* creature) : BossAI(creature, DATA_GARR), + massEruptionTimer(600000) // 10 mins { - boss_garrAI(Creature* creature) : BossAI(creature, DATA_GARR), - massEruptionTimer(600000) // 10 mins + } + + void Reset() override + { + _Reset(); + massEruptionTimer = 600000; + } + + void JustEngagedWith(Unit* /*attacker*/) override + { + _JustEngagedWith(); + DoCastSelf(SPELL_SEPARATION_ANXIETY, true); + events.ScheduleEvent(EVENT_ANTIMAGIC_PULSE, 15s); + events.ScheduleEvent(EVENT_MAGMA_SHACKLES, 10s); + massEruptionTimer = 600000; // 10 mins + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + // This should always process + if (massEruptionTimer <= diff) { + Talk(EMOTE_MASS_ERRUPTION, me); + DoCastAOE(SPELL_ERUPTION_TRIGGER, true); + massEruptionTimer = 20000; + } + else + { + massEruptionTimer -= diff; } - void Reset() override + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 const eventId = events.ExecuteEvent()) { - _Reset(); - massEruptionTimer = 600000; - } - - void JustEngagedWith(Unit* /*attacker*/) override - { - _JustEngagedWith(); - DoCastSelf(SPELL_SEPARATION_ANXIETY, true); - events.ScheduleEvent(EVENT_ANTIMAGIC_PULSE, 15s); - events.ScheduleEvent(EVENT_MAGMA_SHACKLES, 10s); - massEruptionTimer = 600000; // 10 mins - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) + switch (eventId) { - return; + case EVENT_ANTIMAGIC_PULSE: + { + DoCastSelf(SPELL_ANTIMAGIC_PULSE); + events.Repeat(20s); + break; + } + case EVENT_MAGMA_SHACKLES: + { + DoCastSelf(SPELL_MAGMA_SHACKLES); + events.Repeat(15s); + break; + } } - // This should always process - if (massEruptionTimer <= diff) - { - Talk(EMOTE_MASS_ERRUPTION, me); - DoCastAOE(SPELL_ERUPTION_TRIGGER, true); - massEruptionTimer = 20000; - } - else - { - massEruptionTimer -= diff; - } - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } - - while (uint32 const eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_ANTIMAGIC_PULSE: - { - DoCastSelf(SPELL_ANTIMAGIC_PULSE); - events.Repeat(20s); - break; - } - case EVENT_MAGMA_SHACKLES: - { - DoCastSelf(SPELL_MAGMA_SHACKLES); - events.Repeat(15s); - break; - } - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - } - - DoMeleeAttackIfReady(); } - private: - uint32 massEruptionTimer; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); + DoMeleeAttackIfReady(); } + +private: + uint32 massEruptionTimer; }; -class npc_garr_firesworn : public CreatureScript +struct npc_garr_firesworn : public ScriptedAI { -public: - npc_garr_firesworn() : CreatureScript("npc_garr_firesworn") {} + npc_garr_firesworn(Creature* creature) : ScriptedAI(creature) {} - struct npc_garr_fireswornAI : public ScriptedAI + void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/ ) override { - npc_garr_fireswornAI(Creature* creature) : ScriptedAI(creature) {} - - void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/ ) override + if (damage >= me->GetHealth()) { - if (damage >= me->GetHealth()) - { - // Prevent double damage because Firesworn can kill himself with Massive Erruption - if (me != attacker) - { - DoCastSelf(SPELL_ERUPTION, true); - } + // Prevent double damage because Firesworn can kill himself with Massive Eruption + if (me != attacker) + DoCastSelf(SPELL_ERUPTION, true); - DoCastAOE(SPELL_ENRAGE_TRIGGER); - } + DoCastAOE(SPELL_ENRAGE_TRIGGER); } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } }; @@ -210,9 +180,7 @@ class spell_garr_frenzy : public SpellScript void HandleHit(SpellEffIndex /*effIndex*/) { if (Unit* target = GetHitUnit()) - { target->CastSpell(target, SPELL_FRENZY); - } } void Register() override @@ -223,8 +191,8 @@ class spell_garr_frenzy : public SpellScript void AddSC_boss_garr() { - new boss_garr(); - new npc_garr_firesworn(); + RegisterMoltenCoreCreatureAI(boss_garr); + RegisterMoltenCoreCreatureAI(npc_garr_firesworn); // Spells RegisterSpellScript(spell_garr_separation_anxiety_aura); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_gehennas.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_gehennas.cpp index 53662ddca..26d4af08c 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_gehennas.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_gehennas.cpp @@ -34,74 +34,57 @@ enum Events EVENT_SHADOW_BOLT, }; -class boss_gehennas : public CreatureScript +struct boss_gehennas : public BossAI { -public: - boss_gehennas() : CreatureScript("boss_gehennas") { } + boss_gehennas(Creature* creature) : BossAI(creature, DATA_GEHENNAS) {} - struct boss_gehennasAI : public BossAI + void JustEngagedWith(Unit* /*attacker*/) override { - boss_gehennasAI(Creature* creature) : BossAI(creature, DATA_GEHENNAS) {} + _JustEngagedWith(); + events.ScheduleEvent(EVENT_GEHENNAS_CURSE, 6s, 9s); + events.ScheduleEvent(EVENT_RAIN_OF_FIRE, 10s); + events.ScheduleEvent(EVENT_SHADOW_BOLT, 3s, 5s); + } - void JustEngagedWith(Unit* /*attacker*/) override + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_GEHENNAS_CURSE, 6s, 9s); - events.ScheduleEvent(EVENT_RAIN_OF_FIRE, 10s); - events.ScheduleEvent(EVENT_SHADOW_BOLT, 3s, 5s); - } - - void ExecuteEvent(uint32 eventId) override - { - switch (eventId) + case EVENT_GEHENNAS_CURSE: { - case EVENT_GEHENNAS_CURSE: + DoCastVictim(SPELL_GEHENNAS_CURSE); + events.Repeat(25s, 30s); + break; + } + case EVENT_RAIN_OF_FIRE: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) + DoCast(target, SPELL_RAIN_OF_FIRE, true); + events.Repeat(6s); + break; + } + case EVENT_SHADOW_BOLT: + { + if (urand(0, 1)) { - DoCastVictim(SPELL_GEHENNAS_CURSE); - events.Repeat(25s, 30s); - break; - } - case EVENT_RAIN_OF_FIRE: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) - { - DoCast(target, SPELL_RAIN_OF_FIRE, true); - } - events.Repeat(6s); - break; - } - case EVENT_SHADOW_BOLT: - { - if (urand(0, 1)) - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, false)) - { - DoCast(target, SPELL_SHADOW_BOLT_RANDOM); - } - else - { - DoCastVictim(SPELL_SHADOW_BOLT_VICTIM); - } - } + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, false)) + DoCast(target, SPELL_SHADOW_BOLT_RANDOM); else - { DoCastVictim(SPELL_SHADOW_BOLT_VICTIM); - } - - events.Repeat(5s); - break; } + else + { + DoCastVictim(SPELL_SHADOW_BOLT_VICTIM); + } + + events.Repeat(5s); + break; } } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } }; void AddSC_boss_gehennas() { - new boss_gehennas(); + RegisterMoltenCoreCreatureAI(boss_gehennas); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_golemagg.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_golemagg.cpp index d88c1e2b3..84d95bb1d 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_golemagg.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_golemagg.cpp @@ -40,191 +40,157 @@ enum Spells SPELL_FULL_HEAL = 17683, }; -class boss_golemagg : public CreatureScript +struct boss_golemagg : public BossAI { -public: - boss_golemagg() : CreatureScript("boss_golemagg") { } + boss_golemagg(Creature* creature) : BossAI(creature, DATA_GOLEMAGG), + earthquakeTimer(0), + pyroblastTimer(0), + enraged(false) + {} - struct boss_golemaggAI : public BossAI + void Reset() override { - boss_golemaggAI(Creature* creature) : BossAI(creature, DATA_GOLEMAGG), - earthquakeTimer(0), - pyroblastTimer(0), - enraged(false) - {} + _Reset(); + earthquakeTimer = 0; + pyroblastTimer = urand(3000, 7000); + enraged = false; + DoCastSelf(SPELL_MAGMASPLASH); + DoCastSelf(SPELL_GOLEMAGG_TRUST_AURA); + DoCastSelf(SPELL_DOUBLE_ATTACK); + } - void Reset() override + void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override + { + if (!enraged && me->HealthBelowPctDamaged(10, damage)) { - _Reset(); - earthquakeTimer = 0; - pyroblastTimer = urand(3000, 7000); - enraged = false; - DoCastSelf(SPELL_MAGMASPLASH); - DoCastSelf(SPELL_GOLEMAGG_TRUST_AURA); - DoCastSelf(SPELL_DOUBLE_ATTACK); + DoCastSelf(SPELL_ATTRACK_RAGER, true); + DoCastAOE(SPELL_EARTHQUAKE, true); + earthquakeTimer = 5000; + enraged = true; } + } - void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + // Should not get impact by cast state (cast should always happen) + if (earthquakeTimer) { - if (!enraged && me->HealthBelowPctDamaged(10, damage)) + if (earthquakeTimer <= diff) { - DoCastSelf(SPELL_ATTRACK_RAGER, true); - DoCastAOE(SPELL_EARTHQUAKE, true); + DoCastSelf(SPELL_EARTHQUAKE, true); earthquakeTimer = 5000; - enraged = true; - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - { - return; - } - - // Should not get impact by cast state (cast should always happen) - if (earthquakeTimer) - { - if (earthquakeTimer <= diff) - { - DoCastSelf(SPELL_EARTHQUAKE, true); - earthquakeTimer = 5000; - } - else - { - earthquakeTimer -= diff; - } - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - - if (pyroblastTimer <= diff) - { - DoCastRandomTarget(SPELL_PYROBLAST); - - pyroblastTimer = 7000; } else { - pyroblastTimer -= diff; + earthquakeTimer -= diff; } - - DoMeleeAttackIfReady(); } - private: - uint32 earthquakeTimer; - uint32 pyroblastTimer; - bool enraged; - }; + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); + if (pyroblastTimer <= diff) + { + DoCastRandomTarget(SPELL_PYROBLAST); + + pyroblastTimer = 7000; + } + else + { + pyroblastTimer -= diff; + } + + DoMeleeAttackIfReady(); } + +private: + uint32 earthquakeTimer; + uint32 pyroblastTimer; + bool enraged; }; -class npc_core_rager : public CreatureScript +struct npc_core_rager : public ScriptedAI { -public: - npc_core_rager() : CreatureScript("npc_core_rager") { } - - struct npc_core_ragerAI : public ScriptedAI + npc_core_rager(Creature* creature) : ScriptedAI(creature), + instance(creature->GetInstanceScript()), + mangleTimer(7000), + rangeCheckTimer(1000) { - npc_core_ragerAI(Creature* creature) : ScriptedAI(creature), - instance(creature->GetInstanceScript()), - mangleTimer(7000), - rangeCheckTimer(1000) - { - } - - void Reset() override - { - mangleTimer = 7000; // These times are probably wrong - rangeCheckTimer = 1000; - - if (instance->GetBossState(DATA_GOLEMAGG) == DONE) - { - DoCastSelf(SPELL_CORE_RAGER_QUIET_SUICIDE, true); - } - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override - { - // Just in case if something will go bad, let players to kill this creature - if (instance->GetBossState(DATA_GOLEMAGG) == DONE) - { - return; - } - - if (me->HealthBelowPctDamaged(50, damage)) - { - damage = 0; - DoCastSelf(SPELL_FULL_HEAL, true); - Talk(EMOTE_LOWHP); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - { - return; - } - - // Should have no impact from unit state - if (rangeCheckTimer <= diff) - { - Creature const* golemagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_GOLEMAGG)); - if (golemagg && me->GetDistance(golemagg) > 100.0f) - { - instance->DoAction(ACTION_RESET_GOLEMAGG_ENCOUNTER); - return; - } - - rangeCheckTimer = 1000; - } - else - { - rangeCheckTimer -= diff; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - - // Mangle - if (mangleTimer <= diff) - { - DoCastVictim(SPELL_MANGLE); - mangleTimer = 10000; - } - else - { - mangleTimer -= diff; - } - - DoMeleeAttackIfReady(); - } - - private: - InstanceScript* instance; - uint32 mangleTimer; - uint32 rangeCheckTimer; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } + + void Reset() override + { + mangleTimer = 7000; // These times are probably wrong + rangeCheckTimer = 1000; + + if (instance->GetBossState(DATA_GOLEMAGG) == DONE) + DoCastSelf(SPELL_CORE_RAGER_QUIET_SUICIDE, true); + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override + { + // Just in case if something will go bad, let players to kill this creature + if (instance->GetBossState(DATA_GOLEMAGG) == DONE) + return; + + if (me->HealthBelowPctDamaged(50, damage)) + { + damage = 0; + DoCastSelf(SPELL_FULL_HEAL, true); + Talk(EMOTE_LOWHP); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + // Should have no impact from unit state + if (rangeCheckTimer <= diff) + { + Creature const* golemagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_GOLEMAGG)); + if (golemagg && me->GetDistance(golemagg) > 100.0f) + { + instance->DoAction(ACTION_RESET_GOLEMAGG_ENCOUNTER); + return; + } + + rangeCheckTimer = 1000; + } + else + { + rangeCheckTimer -= diff; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + // Mangle + if (mangleTimer <= diff) + { + DoCastVictim(SPELL_MANGLE); + mangleTimer = 10000; + } + else + { + mangleTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + +private: + InstanceScript* instance; + uint32 mangleTimer; + uint32 rangeCheckTimer; }; void AddSC_boss_golemagg() { - new boss_golemagg(); - new npc_core_rager(); + RegisterMoltenCoreCreatureAI(boss_golemagg); + RegisterMoltenCoreCreatureAI(npc_core_rager); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_lucifron.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_lucifron.cpp index dd38b6ca8..1f8f89b3f 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_lucifron.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_lucifron.cpp @@ -33,56 +33,45 @@ enum Events EVENT_SHADOW_SHOCK = 3, }; -class boss_lucifron : public CreatureScript +struct boss_lucifron : public BossAI { -public: - boss_lucifron() : CreatureScript("boss_lucifron") { } + boss_lucifron(Creature* creature) : BossAI(creature, DATA_LUCIFRON) {} - struct boss_lucifronAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_lucifronAI(Creature* creature) : BossAI(creature, DATA_LUCIFRON) {} + _JustEngagedWith(); + events.ScheduleEvent(EVENT_IMPENDING_DOOM, 6s, 11s); + events.ScheduleEvent(EVENT_LUCIFRON_CURSE, 11s, 14s); + events.ScheduleEvent(EVENT_SHADOW_SHOCK, 5s); + } - void JustEngagedWith(Unit* /*who*/) override + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_IMPENDING_DOOM, 6s, 11s); - events.ScheduleEvent(EVENT_LUCIFRON_CURSE, 11s, 14s); - events.ScheduleEvent(EVENT_SHADOW_SHOCK, 5s); - } - - void ExecuteEvent(uint32 eventId) override - { - switch (eventId) + case EVENT_IMPENDING_DOOM: { - case EVENT_IMPENDING_DOOM: - { - DoCastVictim(SPELL_IMPENDING_DOOM); - events.Repeat(20s); - break; - } - case EVENT_LUCIFRON_CURSE: - { - DoCastVictim(SPELL_LUCIFRON_CURSE); - events.Repeat(20s); - break; - } - case EVENT_SHADOW_SHOCK: - { - DoCastVictim(SPELL_SHADOW_SHOCK); - events.Repeat(5s); - break; - } + DoCastVictim(SPELL_IMPENDING_DOOM); + events.Repeat(20s); + break; + } + case EVENT_LUCIFRON_CURSE: + { + DoCastVictim(SPELL_LUCIFRON_CURSE); + events.Repeat(20s); + break; + } + case EVENT_SHADOW_SHOCK: + { + DoCastVictim(SPELL_SHADOW_SHOCK); + events.Repeat(5s); + break; } } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } }; void AddSC_boss_lucifron() { - new boss_lucifron(); + RegisterMoltenCoreCreatureAI(boss_lucifron); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_magmadar.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_magmadar.cpp index f0d7563a8..9d1eb17ae 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_magmadar.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_magmadar.cpp @@ -47,73 +47,58 @@ enum Events constexpr float MELEE_TARGET_LOOKUP_DIST = 10.0f; -class boss_magmadar : public CreatureScript +struct boss_magmadar : public BossAI { -public: - boss_magmadar() : CreatureScript("boss_magmadar") {} + boss_magmadar(Creature* creature) : BossAI(creature, DATA_MAGMADAR) {} - struct boss_magmadarAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_magmadarAI(Creature* creature) : BossAI(creature, DATA_MAGMADAR) {} + _JustEngagedWith(); + events.ScheduleEvent(EVENT_FRENZY, 8500ms); + events.ScheduleEvent(EVENT_PANIC, 9500ms); + events.ScheduleEvent(EVENT_LAVA_BOMB, 12s); + events.ScheduleEvent(EVENT_LAVA_BOMB_RANGED, 15s); + } - void JustEngagedWith(Unit* /*who*/) override + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_FRENZY, 8500ms); - events.ScheduleEvent(EVENT_PANIC, 9500ms); - events.ScheduleEvent(EVENT_LAVA_BOMB, 12s); - events.ScheduleEvent(EVENT_LAVA_BOMB_RANGED, 15s); - } - - void ExecuteEvent(uint32 eventId) override - { - switch (eventId) + case EVENT_FRENZY: { - case EVENT_FRENZY: - { - Talk(EMOTE_FRENZY); - DoCastSelf(SPELL_FRENZY); - events.Repeat(15s, 20s); - break; - } - case EVENT_PANIC: - { - DoCastVictim(SPELL_PANIC); - events.Repeat(31s, 38s); - break; - } - case EVENT_LAVA_BOMB: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, MELEE_TARGET_LOOKUP_DIST, true)) - { - DoCast(target, SPELL_LAVA_BOMB); - } + Talk(EMOTE_FRENZY); + DoCastSelf(SPELL_FRENZY); + events.Repeat(15s, 20s); + break; + } + case EVENT_PANIC: + { + DoCastVictim(SPELL_PANIC); + events.Repeat(31s, 38s); + break; + } + case EVENT_LAVA_BOMB: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, MELEE_TARGET_LOOKUP_DIST, true)) + DoCast(target, SPELL_LAVA_BOMB); - events.Repeat(12s, 15s); - break; - } - case EVENT_LAVA_BOMB_RANGED: + events.Repeat(12s, 15s); + break; + } + case EVENT_LAVA_BOMB_RANGED: + { + std::list targets; + SelectTargetList(targets, 1, SelectTargetMethod::Random, 1, [this](Unit* target) { - std::list targets; - SelectTargetList(targets, 1, SelectTargetMethod::Random, 1, [this](Unit* target) - { - return target && target->IsPlayer() && target->GetDistance(me) > MELEE_TARGET_LOOKUP_DIST && target->GetDistance(me) < 100.0f; - }); + return target && target->IsPlayer() && target->GetDistance(me) > MELEE_TARGET_LOOKUP_DIST && target->GetDistance(me) < 100.0f; + }); - if (!targets.empty()) - { - DoCast(targets.front() , SPELL_LAVA_BOMB_RANGED); - } - events.Repeat(12s, 15s); - break; - } + if (!targets.empty()) + DoCast(targets.front() , SPELL_LAVA_BOMB_RANGED); + events.Repeat(12s, 15s); + break; } } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } }; @@ -162,7 +147,7 @@ class spell_magmadar_lava_bomb : public SpellScript void AddSC_boss_magmadar() { - new boss_magmadar(); + RegisterMoltenCoreCreatureAI(boss_magmadar); // Spells RegisterSpellScript(spell_magmadar_lava_bomb); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_majordomo_executus.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_majordomo_executus.cpp index ad4da94c6..7498b88fc 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_majordomo_executus.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_majordomo_executus.cpp @@ -131,412 +131,389 @@ struct MajordomoAddData MajordomoAddData(ObjectGuid _guid, uint32 _creatureEntry, Position _spawnPos) : guid(_guid), creatureEntry(_creatureEntry), spawnPos(_spawnPos) { } }; -class boss_majordomo : public CreatureScript +struct boss_majordomo : public BossAI { -public: - boss_majordomo() : CreatureScript("boss_majordomo") {} + boss_majordomo(Creature* creature) : BossAI(creature, DATA_MAJORDOMO_EXECUTUS) {} - struct boss_majordomoAI : public BossAI + void JustDied(Unit* /*killer*/) override { - boss_majordomoAI(Creature* creature) : BossAI(creature, DATA_MAJORDOMO_EXECUTUS) {} - - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_DEATH); - me->DespawnOrUnsummon(10s, 0s); - } - - void JustSummoned(Creature* summon) override - { - if (summon->GetEntry() == NPC_RAGNAROS) - { - summon->CastSpell(summon, SPELL_RAGNAROS_FADE); - summon->CastSpell(summon, SPELL_RAGNAROS_SUBMERGE_EFFECT, true); - summon->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - summon->SetImmuneToAll(true); - summon->SetReactState(REACT_PASSIVE); - } - } - - void InitializeAI() override - { - BossAI::InitializeAI(); - if (instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE) - { - events.SetPhase(PHASE_COMBAT); - - std::list p_summons; - me->SummonCreatureGroup(SUMMON_GROUP_ADDS, &p_summons); - if (!p_summons.empty()) - { - for (TempSummon const* summon : p_summons) - { - if (summon) - { - static_minionsGUIDS.insert(summon->GetGUID()); - majordomoSummonsData[summon->GetGUID().GetCounter()] = MajordomoAddData(summon->GetGUID(), summon->GetEntry(), summon->GetPosition()); - } - } - } - } - else - { - events.SetPhase(PHASE_NONE); - me->SetImmuneToAll(true); - me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); - me->SetFaction(FACTION_MAJORDOMO_FRIENDLY); - } - } - - void Reset() override - { - me->ResetLootMode(); - events.Reset(); - aliveMinionsGUIDS.clear(); - - if (instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE) - { - events.SetPhase(PHASE_COMBAT); - instance->SetBossState(DATA_MAJORDOMO_EXECUTUS, NOT_STARTED); - - for (auto const& summon : majordomoSummonsData) - { - if (ObjectAccessor::GetCreature(*me, summon.second.guid)) - { - continue; - } - - if (Creature* spawn = me->SummonCreature(summon.second.creatureEntry, summon.second.spawnPos)) - { - static_minionsGUIDS.erase(summon.second.guid); // Erase the guid from the previous, no longer existing, spawn. - static_minionsGUIDS.insert(spawn->GetGUID()); - majordomoSummonsData.erase(summon.second.guid.GetCounter()); - majordomoSummonsData[spawn->GetGUID().GetCounter()] = MajordomoAddData(spawn->GetGUID(), spawn->GetEntry(), spawn->GetPosition()); - } - } - - me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); - } - else - { - static_minionsGUIDS.clear(); - majordomoSummonsData.clear(); - summons.DespawnAll(); - } - } - - bool CanAIAttack(Unit const* /*target*/) const override - { - return instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE; - } - - void KilledUnit(Unit* victim) override - { - if (roll_chance_i(25) && victim->IsPlayer()) - { - Talk(SAY_SLAY); - } - } - - void JustEngagedWith(Unit* /*attacker*/) override - { - if (!events.IsInPhase(PHASE_COMBAT)) - { - return; - } - - _JustEngagedWith(); - DoCastAOE(SPELL_SEPARATION_ANXIETY); - Talk(SAY_AGGRO); - DoCastSelf(SPELL_AEGIS_OF_RAGNAROS, true); - - events.ScheduleEvent(EVENT_SHIELD_REFLECTION, 30s, PHASE_COMBAT, PHASE_COMBAT); - events.ScheduleEvent(EVENT_TELEPORT_RANDOM, 25s, PHASE_COMBAT, PHASE_COMBAT); - events.ScheduleEvent(EVENT_TELEPORT_TARGET, 15s, PHASE_COMBAT, PHASE_COMBAT); - - aliveMinionsGUIDS.clear(); - aliveMinionsGUIDS = static_minionsGUIDS; - } - - void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override - { - aliveMinionsGUIDS.erase(summon->GetGUID()); - if (summon->GetEntry() == NPC_FLAMEWAKER_HEALER || summon->GetEntry() == NPC_FLAMEWAKER_ELITE) - { - uint32 const remainingAdds = std::count_if(aliveMinionsGUIDS.begin(), aliveMinionsGUIDS.end(), [](ObjectGuid const& summonGuid) - { - return summonGuid.GetEntry() == NPC_FLAMEWAKER_HEALER || summonGuid.GetEntry() == NPC_FLAMEWAKER_ELITE; - }); - - // Last remaining add - if (remainingAdds == 1) - { - Talk(SAY_LAST_ADD); - DoCastAOE(SPELL_CHAMPION); - } - // 50% of adds - else if (remainingAdds == 4) - { - DoCastAOE(SPELL_IMMUNE_POLY); - } - else if (!remainingAdds) - { - static_minionsGUIDS.clear(); - - instance->SetBossState(DATA_MAJORDOMO_EXECUTUS, DONE); - events.CancelEventGroup(PHASE_COMBAT); - me->GetMap()->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, me->GetEntry(), me); - me->SetImmuneToAll(true); - me->SetFaction(FACTION_MAJORDOMO_FRIENDLY); - EnterEvadeMode(); - Talk(SAY_DEFEAT); - return; - } - DoCastAOE(SPELL_ENCOURAGEMENT); - } - } - - void JustReachedHome() override - { - _JustReachedHome(); - if (instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) == DONE) - { - events.Reset(); - events.SetPhase(PHASE_DEFEAT_OUTRO); - events.ScheduleEvent(EVENT_DEFEAT_OUTRO_1, 7500ms, PHASE_DEFEAT_OUTRO, PHASE_DEFEAT_OUTRO); - } - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override - { - if (events.IsInPhase(PHASE_COMBAT) && me->GetHealth() <= damage) - { - damage = 0; - } - } - - void UpdateAI(uint32 diff) override - { - - switch (events.GetPhaseMask()) - { - case (1 << (PHASE_COMBAT - 1)): - { - if (!UpdateVictim()) - { - return; - } - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - - while (uint32 const eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_SHIELD_REFLECTION: - { - if (rand_chance() <= 50.f) - { - DoCastSelf(SPELL_MAGIC_REFLECTION); - } - else - { - DoCastSelf(SPELL_DAMAGE_REFLECTION); - } - events.Repeat(30s); - break; - } - case EVENT_TELEPORT_RANDOM: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, false)) - { - DoCastSelf(SPELL_HATE_TO_ZERO, true); - DoCast(target, SPELL_TELEPORT_RANDOM); - } - - events.Repeat(30s); - break; - } - case EVENT_TELEPORT_TARGET: - { - DoCastSelf(SPELL_HATE_TO_ZERO, true); - DoCastAOE(SPELL_TELEPORT_TARGET); - events.Repeat(30s); - break; - } - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - } - - DoMeleeAttackIfReady(); - break; - } - case (1 << (PHASE_DEFEAT_OUTRO - 1)): - { - events.Update(diff); - while (uint32 const eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_DEFEAT_OUTRO_1: - { - Talk(SAY_DEFEAT_2); - events.ScheduleEvent(EVENT_DEFEAT_OUTRO_2, 8s, PHASE_DEFEAT_OUTRO, PHASE_DEFEAT_OUTRO); - break; - } - case EVENT_DEFEAT_OUTRO_2: - { - Talk(SAY_DEFEAT_3); - events.ScheduleEvent(EVENT_DEFEAT_OUTRO_3, 21500ms, PHASE_DEFEAT_OUTRO, PHASE_DEFEAT_OUTRO); - break; - } - case EVENT_DEFEAT_OUTRO_3: - { - DoCastSelf(SPELL_TELEPORT_SELF); - break; - } - } - } - break; - } - case (1 << (PHASE_RAGNAROS_SUMMONING - 1)): - { - events.Update(diff); - while (uint32 const eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_RAGNAROS_SUMMON_1: - { - if (GameObject* lavaSplash = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_LAVA_SPLASH))) - { - lavaSplash->SetRespawnTime(900); - lavaSplash->Refresh(); - } - if (GameObject* lavaSteam = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_LAVA_STEAM))) - { - lavaSteam->SetRespawnTime(900); - lavaSteam->Refresh(); - } - Talk(SAY_RAG_SUM_2); - // Next event will get triggered in MovementInform - me->SetWalk(true); - me->GetMotionMaster()->MovePoint(POINT_RAGNAROS_SUMMON, MajordomoMoveRagPos, FORCED_MOVEMENT_NONE, 0.f, true, false); - break; - } - case EVENT_RAGNAROS_SUMMON_2: - { - if (GameObject* lavaSteam = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_LAVA_STEAM))) - { - me->SetFacingToObject(lavaSteam); - } - - Talk(SAY_SUMMON_MAJ); - events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_3, 16700ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); - events.ScheduleEvent(EVENT_RAGNAROS_EMERGE, 15s, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); - break; - } - case EVENT_RAGNAROS_SUMMON_3: - { - if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) - { - ragnaros->AI()->Talk(SAY_ARRIVAL1_RAG); - } - events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_4, 11700ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); - break; - } - case EVENT_RAGNAROS_SUMMON_4: - { - Talk(SAY_ARRIVAL2_MAJ); - events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_5, 8700ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); - break; - } - case EVENT_RAGNAROS_SUMMON_5: - { - if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) - { - ragnaros->AI()->Talk(SAY_ARRIVAL3_RAG); - } - - events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_6, 16500ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); - break; - } - case EVENT_RAGNAROS_SUMMON_6: - { - if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) - { - ragnaros->CastSpell(me, SPELL_ELEMENTAL_FIRE, true); - ragnaros->AI()->DoAction(ACTION_FINISH_RAGNAROS_INTRO); - } - break; - } - // Additional events - case EVENT_RAGNAROS_EMERGE: - { - if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) - { - ragnaros->RemoveAurasDueToSpell(SPELL_RAGNAROS_FADE); - ragnaros->CastSpell(ragnaros, SPELL_RAGNA_EMERGE); - } - }break; - } - } - break; - } - } - } - - void MovementInform(uint32 type, uint32 pointId) override - { - if (type == POINT_MOTION_TYPE && pointId == POINT_RAGNAROS_SUMMON) - { - DoCastAOE(SPELL_SUMMON_RAGNAROS); - events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_2, 11500ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); - } - } - - void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override - { - if (events.IsInPhase(PHASE_DEFEAT_OUTRO) && spellInfo->Id == SPELL_TELEPORT_SELF) - { - me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); - me->SetHomePosition(MajordomoRagnaros); - me->NearTeleportTo(MajordomoRagnaros.GetPositionX(), MajordomoRagnaros.GetPositionY(), MajordomoRagnaros.GetPositionZ(), MajordomoRagnaros.GetOrientation()); - events.SetPhase(PHASE_NONE); - } - } - - void DoAction(int32 action) override - { - if (action == ACTION_START_RAGNAROS_INTRO && !events.IsInPhase(PHASE_RAGNAROS_SUMMONING)) - { - events.SetPhase(PHASE_RAGNAROS_SUMMONING); - events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_1, 5s, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); - } - } - private: - GuidSet static_minionsGUIDS; // contained data should be changed on encounter completion - GuidSet aliveMinionsGUIDS; // used for calculations - std::unordered_map majordomoSummonsData; - }; - - bool OnGossipHello(Player* player, Creature* creature) override - { - AddGossipItemFor(player, GOSSIP_ITEM_SUMMON_1, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - SendGossipMenuFor(player, TEXT_ID_SUMMON_1, creature->GetGUID()); - return true; + Talk(SAY_DEATH); + me->DespawnOrUnsummon(10s, 0s); } - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override + void JustSummoned(Creature* summon) override + { + if (summon->GetEntry() == NPC_RAGNAROS) + { + summon->CastSpell(summon, SPELL_RAGNAROS_FADE); + summon->CastSpell(summon, SPELL_RAGNAROS_SUBMERGE_EFFECT, true); + summon->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + summon->SetImmuneToAll(true); + summon->SetReactState(REACT_PASSIVE); + } + } + + void InitializeAI() override + { + BossAI::InitializeAI(); + if (instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE) + { + events.SetPhase(PHASE_COMBAT); + + std::list p_summons; + me->SummonCreatureGroup(SUMMON_GROUP_ADDS, &p_summons); + if (!p_summons.empty()) + { + for (TempSummon const* summon : p_summons) + { + if (summon) + { + static_minionsGUIDS.insert(summon->GetGUID()); + majordomoSummonsData[summon->GetGUID().GetCounter()] = MajordomoAddData(summon->GetGUID(), summon->GetEntry(), summon->GetPosition()); + } + } + } + } + else + { + events.SetPhase(PHASE_NONE); + me->SetImmuneToAll(true); + me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + me->SetFaction(FACTION_MAJORDOMO_FRIENDLY); + } + } + + void Reset() override + { + me->ResetLootMode(); + events.Reset(); + aliveMinionsGUIDS.clear(); + + if (instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE) + { + events.SetPhase(PHASE_COMBAT); + instance->SetBossState(DATA_MAJORDOMO_EXECUTUS, NOT_STARTED); + + for (auto const& summon : majordomoSummonsData) + { + if (ObjectAccessor::GetCreature(*me, summon.second.guid)) + continue; + + if (Creature* spawn = me->SummonCreature(summon.second.creatureEntry, summon.second.spawnPos)) + { + static_minionsGUIDS.erase(summon.second.guid); // Erase the guid from the previous, no longer existing, spawn. + static_minionsGUIDS.insert(spawn->GetGUID()); + majordomoSummonsData.erase(summon.second.guid.GetCounter()); + majordomoSummonsData[spawn->GetGUID().GetCounter()] = MajordomoAddData(spawn->GetGUID(), spawn->GetEntry(), spawn->GetPosition()); + } + } + + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + } + else + { + static_minionsGUIDS.clear(); + majordomoSummonsData.clear(); + summons.DespawnAll(); + } + } + + bool CanAIAttack(Unit const* /*target*/) const override + { + return instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE; + } + + void KilledUnit(Unit* victim) override + { + if (roll_chance_i(25) && victim->IsPlayer()) + Talk(SAY_SLAY); + } + + void JustEngagedWith(Unit* /*attacker*/) override + { + if (!events.IsInPhase(PHASE_COMBAT)) + return; + + _JustEngagedWith(); + DoCastAOE(SPELL_SEPARATION_ANXIETY); + Talk(SAY_AGGRO); + DoCastSelf(SPELL_AEGIS_OF_RAGNAROS, true); + + events.ScheduleEvent(EVENT_SHIELD_REFLECTION, 30s, PHASE_COMBAT, PHASE_COMBAT); + events.ScheduleEvent(EVENT_TELEPORT_RANDOM, 25s, PHASE_COMBAT, PHASE_COMBAT); + events.ScheduleEvent(EVENT_TELEPORT_TARGET, 15s, PHASE_COMBAT, PHASE_COMBAT); + + aliveMinionsGUIDS.clear(); + aliveMinionsGUIDS = static_minionsGUIDS; + } + + void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override + { + aliveMinionsGUIDS.erase(summon->GetGUID()); + if (summon->GetEntry() == NPC_FLAMEWAKER_HEALER || summon->GetEntry() == NPC_FLAMEWAKER_ELITE) + { + uint32 const remainingAdds = std::count_if(aliveMinionsGUIDS.begin(), aliveMinionsGUIDS.end(), [](ObjectGuid const& summonGuid) + { + return summonGuid.GetEntry() == NPC_FLAMEWAKER_HEALER || summonGuid.GetEntry() == NPC_FLAMEWAKER_ELITE; + }); + + // Last remaining add + if (remainingAdds == 1) + { + Talk(SAY_LAST_ADD); + DoCastAOE(SPELL_CHAMPION); + } + // 50% of adds + else if (remainingAdds == 4) + { + DoCastAOE(SPELL_IMMUNE_POLY); + } + else if (!remainingAdds) + { + static_minionsGUIDS.clear(); + + instance->SetBossState(DATA_MAJORDOMO_EXECUTUS, DONE); + events.CancelEventGroup(PHASE_COMBAT); + me->GetMap()->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, me->GetEntry(), me); + me->SetImmuneToAll(true); + me->SetFaction(FACTION_MAJORDOMO_FRIENDLY); + EnterEvadeMode(); + Talk(SAY_DEFEAT); + return; + } + DoCastAOE(SPELL_ENCOURAGEMENT); + } + } + + void JustReachedHome() override + { + _JustReachedHome(); + if (instance->GetBossState(DATA_MAJORDOMO_EXECUTUS) == DONE) + { + events.Reset(); + events.SetPhase(PHASE_DEFEAT_OUTRO); + events.ScheduleEvent(EVENT_DEFEAT_OUTRO_1, 7500ms, PHASE_DEFEAT_OUTRO, PHASE_DEFEAT_OUTRO); + } + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override + { + if (events.IsInPhase(PHASE_COMBAT) && me->GetHealth() <= damage) + { + damage = 0; + } + } + + void UpdateAI(uint32 diff) override + { + + switch (events.GetPhaseMask()) + { + case (1 << (PHASE_COMBAT - 1)): + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 const eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_SHIELD_REFLECTION: + { + if (rand_chance() <= 50.f) + { + DoCastSelf(SPELL_MAGIC_REFLECTION); + } + else + { + DoCastSelf(SPELL_DAMAGE_REFLECTION); + } + events.Repeat(30s); + break; + } + case EVENT_TELEPORT_RANDOM: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, false)) + { + DoCastSelf(SPELL_HATE_TO_ZERO, true); + DoCast(target, SPELL_TELEPORT_RANDOM); + } + + events.Repeat(30s); + break; + } + case EVENT_TELEPORT_TARGET: + { + DoCastSelf(SPELL_HATE_TO_ZERO, true); + DoCastAOE(SPELL_TELEPORT_TARGET); + events.Repeat(30s); + break; + } + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + DoMeleeAttackIfReady(); + break; + } + case (1 << (PHASE_DEFEAT_OUTRO - 1)): + { + events.Update(diff); + while (uint32 const eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_DEFEAT_OUTRO_1: + { + Talk(SAY_DEFEAT_2); + events.ScheduleEvent(EVENT_DEFEAT_OUTRO_2, 8s, PHASE_DEFEAT_OUTRO, PHASE_DEFEAT_OUTRO); + break; + } + case EVENT_DEFEAT_OUTRO_2: + { + Talk(SAY_DEFEAT_3); + events.ScheduleEvent(EVENT_DEFEAT_OUTRO_3, 21500ms, PHASE_DEFEAT_OUTRO, PHASE_DEFEAT_OUTRO); + break; + } + case EVENT_DEFEAT_OUTRO_3: + { + DoCastSelf(SPELL_TELEPORT_SELF); + break; + } + } + } + break; + } + case (1 << (PHASE_RAGNAROS_SUMMONING - 1)): + { + events.Update(diff); + while (uint32 const eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_RAGNAROS_SUMMON_1: + { + if (GameObject* lavaSplash = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_LAVA_SPLASH))) + { + lavaSplash->SetRespawnTime(900); + lavaSplash->Refresh(); + } + if (GameObject* lavaSteam = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_LAVA_STEAM))) + { + lavaSteam->SetRespawnTime(900); + lavaSteam->Refresh(); + } + Talk(SAY_RAG_SUM_2); + // Next event will get triggered in MovementInform + me->SetWalk(true); + me->GetMotionMaster()->MovePoint(POINT_RAGNAROS_SUMMON, MajordomoMoveRagPos, FORCED_MOVEMENT_NONE, 0.f, true, false); + break; + } + case EVENT_RAGNAROS_SUMMON_2: + { + if (GameObject* lavaSteam = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_LAVA_STEAM))) + { + me->SetFacingToObject(lavaSteam); + } + + Talk(SAY_SUMMON_MAJ); + events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_3, 16700ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); + events.ScheduleEvent(EVENT_RAGNAROS_EMERGE, 15s, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); + break; + } + case EVENT_RAGNAROS_SUMMON_3: + { + if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) + { + ragnaros->AI()->Talk(SAY_ARRIVAL1_RAG); + } + events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_4, 11700ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); + break; + } + case EVENT_RAGNAROS_SUMMON_4: + { + Talk(SAY_ARRIVAL2_MAJ); + events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_5, 8700ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); + break; + } + case EVENT_RAGNAROS_SUMMON_5: + { + if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) + { + ragnaros->AI()->Talk(SAY_ARRIVAL3_RAG); + } + + events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_6, 16500ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); + break; + } + case EVENT_RAGNAROS_SUMMON_6: + { + if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) + { + ragnaros->CastSpell(me, SPELL_ELEMENTAL_FIRE, true); + ragnaros->AI()->DoAction(ACTION_FINISH_RAGNAROS_INTRO); + } + break; + } + // Additional events + case EVENT_RAGNAROS_EMERGE: + { + if (Creature* ragnaros = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAGNAROS))) + { + ragnaros->RemoveAurasDueToSpell(SPELL_RAGNAROS_FADE); + ragnaros->CastSpell(ragnaros, SPELL_RAGNA_EMERGE); + } + }break; + } + } + break; + } + } + } + + void MovementInform(uint32 type, uint32 pointId) override + { + if (type == POINT_MOTION_TYPE && pointId == POINT_RAGNAROS_SUMMON) + { + DoCastAOE(SPELL_SUMMON_RAGNAROS); + events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_2, 11500ms, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); + } + } + + void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override + { + if (events.IsInPhase(PHASE_DEFEAT_OUTRO) && spellInfo->Id == SPELL_TELEPORT_SELF) + { + me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + me->SetHomePosition(MajordomoRagnaros); + me->NearTeleportTo(MajordomoRagnaros.GetPositionX(), MajordomoRagnaros.GetPositionY(), MajordomoRagnaros.GetPositionZ(), MajordomoRagnaros.GetOrientation()); + events.SetPhase(PHASE_NONE); + } + } + + void DoAction(int32 action) override + { + if (action == ACTION_START_RAGNAROS_INTRO && !events.IsInPhase(PHASE_RAGNAROS_SUMMONING)) + { + events.SetPhase(PHASE_RAGNAROS_SUMMONING); + events.ScheduleEvent(EVENT_RAGNAROS_SUMMON_1, 5s, PHASE_RAGNAROS_SUMMONING, PHASE_RAGNAROS_SUMMONING); + } + } + + void sGossipHello(Player* player) override + { + AddGossipItemFor(player, GOSSIP_ITEM_SUMMON_1, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); + SendGossipMenuFor(player, TEXT_ID_SUMMON_1, me->GetGUID()); + } + + void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override { ClearGossipMenuFor(player); switch (action) @@ -544,40 +521,39 @@ public: case GOSSIP_ACTION_INFO_DEF: { AddGossipItemFor(player, GOSSIP_ITEM_SUMMON_2, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, TEXT_ID_SUMMON_2, creature->GetGUID()); + SendGossipMenuFor(player, TEXT_ID_SUMMON_2, me->GetGUID()); break; } case GOSSIP_ACTION_INFO_DEF+1: { AddGossipItemFor(player, GOSSIP_ITEM_SUMMON_2, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - SendGossipMenuFor(player, TEXT_ID_SUMMON_2, creature->GetGUID()); + SendGossipMenuFor(player, TEXT_ID_SUMMON_2, me->GetGUID()); break; } case GOSSIP_ACTION_INFO_DEF+2: { AddGossipItemFor(player, GOSSIP_ITEM_SUMMON_3, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); - SendGossipMenuFor(player, TEXT_ID_SUMMON_3, creature->GetGUID()); + SendGossipMenuFor(player, TEXT_ID_SUMMON_3, me->GetGUID()); break; } case GOSSIP_ACTION_INFO_DEF+3: { CloseGossipMenuFor(player); - creature->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); - creature->AI()->Talk(SAY_RAG_SUM_1, player); - creature->AI()->DoAction(ACTION_START_RAGNAROS_INTRO); + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + Talk(SAY_RAG_SUM_1, player); + DoAction(ACTION_START_RAGNAROS_INTRO); break; } default: CloseGossipMenuFor(player); break; } - return true; } - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); - } +private: + GuidSet static_minionsGUIDS; // contained data should be changed on encounter completion + GuidSet aliveMinionsGUIDS; // used for calculations + std::unordered_map majordomoSummonsData; }; // 20538 Hate to Zero (SERVERSIDE) @@ -593,12 +569,8 @@ class spell_hate_to_zero : public SpellScript void HandleHit(SpellEffIndex /*effIndex*/) { if (Unit* caster = GetCaster()) - { if (Creature* creatureCaster = caster->ToCreature()) - { creatureCaster->GetThreatMgr().ResetAllThreat(); - } - } } void Register() override @@ -641,9 +613,7 @@ class spell_summon_ragnaros : public SpellScript void HandleHit() { if (Unit* caster = GetCaster()) - { caster->SummonCreature(NPC_RAGNAROS, RagnarosSummonPos, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 2 * HOUR * IN_MILLISECONDS); - } } void Register() override @@ -654,7 +624,7 @@ class spell_summon_ragnaros : public SpellScript void AddSC_boss_majordomo() { - new boss_majordomo(); + RegisterMoltenCoreCreatureAI(boss_majordomo); // Spells RegisterSpellScript(spell_hate_to_zero); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp index 01145b41e..45ec1dccc 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp @@ -112,390 +112,353 @@ enum Misc constexpr float DEATH_ORIENTATION = 4.0f; -class boss_ragnaros : public CreatureScript +struct boss_ragnaros : public BossAI { -public: - boss_ragnaros() : CreatureScript("boss_ragnaros") {} - - struct boss_ragnarosAI : public BossAI + boss_ragnaros(Creature* creature) : BossAI(creature, DATA_RAGNAROS), + _isIntroDone(false), + _hasYelledMagmaBurst(false), + _processingMagmaBurst(false), + _hasSubmergedOnce(false), + _isKnockbackEmoteAllowed(true) { - boss_ragnarosAI(Creature* creature) : BossAI(creature, DATA_RAGNAROS), - _isIntroDone(false), - _hasYelledMagmaBurst(false), - _processingMagmaBurst(false), - _hasSubmergedOnce(false), - _isKnockbackEmoteAllowed(true) + } + + void Reset() override + { + _Reset(); + + // Never reset intro events! + if (_isIntroDone && !(extraEvents.GetPhaseMask() & (1 << (PHASE_INTRO - 1)))) { - } - - void Reset() override - { - _Reset(); - - // Never reset intro events! - if (_isIntroDone && !(extraEvents.GetPhaseMask() & (1 << (PHASE_INTRO - 1)))) - { - extraEvents.Reset(); - extraEvents.SetPhase(PHASE_EMERGED); - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); - me->SetImmuneToAll(false); - me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); - me->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE); - } - - _hasYelledMagmaBurst = false; - _processingMagmaBurst = false; - _hasSubmergedOnce = false; - _isKnockbackEmoteAllowed = true; - me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); - me->SetControlled(true, UNIT_STATE_ROOT); - _lavaBurstGUIDS.clear(); - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - if (events.IsInPhase(PHASE_SUBMERGED) && damage >= me->GetHealth()) - { - damage = 0; - } - } - - void DoAction(int32 action) override - { - if (action == ACTION_FINISH_RAGNAROS_INTRO) - { - extraEvents.SetPhase(PHASE_INTRO); - extraEvents.ScheduleEvent(EVENT_INTRO_SAY, 5s, 0, PHASE_INTRO); - } - } - - void JustSummoned(Creature* summon) override - { - BossAI::JustSummoned(summon); - if (summon->GetEntry() == NPC_FLAME_OF_RAGNAROS) - { - summon->CastSpell((Unit*)nullptr, SPELL_INTENSE_HEAT, true, nullptr, nullptr, me->GetGUID()); - } - else if (summon->GetEntry() == NPC_SON_OF_FLAME) - { - DoZoneInCombat(summon); - } - } - - void SetGUID(ObjectGuid const& guid, int32 index) override - { - if (index == GO_LAVA_BURST) - { - if (_lavaBurstGUIDS.empty()) - { - extraEvents.ScheduleEvent(EVENT_LAVA_BURST_TRIGGER, 1ms); - } - - _lavaBurstGUIDS.insert(guid); - } - } - - void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override - { - summons.Despawn(summon); - if (events.IsInPhase(PHASE_SUBMERGED) && !summons.HasEntry(NPC_SON_OF_FLAME)) - { - HandleEmerge(); - } - } - - void JustEngagedWith(Unit* /*who*/) override - { - _JustEngagedWith(); - events.SetPhase(PHASE_EMERGED); - ScheduleCombatEvents(); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); extraEvents.Reset(); - me->SetFacingTo(DEATH_ORIENTATION); + extraEvents.SetPhase(PHASE_EMERGED); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToAll(false); + me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); + me->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE); } - void KilledUnit(Unit* victim) override + _hasYelledMagmaBurst = false; + _processingMagmaBurst = false; + _hasSubmergedOnce = false; + _isKnockbackEmoteAllowed = true; + me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); + me->SetControlled(true, UNIT_STATE_ROOT); + _lavaBurstGUIDS.clear(); + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType, SpellSchoolMask) override + { + if (events.IsInPhase(PHASE_SUBMERGED) && damage >= me->GetHealth()) { - if (roll_chance_i(25) && victim->IsPlayer()) + damage = 0; + } + } + + void DoAction(int32 action) override + { + if (action == ACTION_FINISH_RAGNAROS_INTRO) + { + extraEvents.SetPhase(PHASE_INTRO); + extraEvents.ScheduleEvent(EVENT_INTRO_SAY, 5s, 0, PHASE_INTRO); + } + } + + void JustSummoned(Creature* summon) override + { + BossAI::JustSummoned(summon); + if (summon->GetEntry() == NPC_FLAME_OF_RAGNAROS) + summon->CastSpell((Unit*)nullptr, SPELL_INTENSE_HEAT, true, nullptr, nullptr, me->GetGUID()); + else if (summon->GetEntry() == NPC_SON_OF_FLAME) + DoZoneInCombat(summon); + } + + void SetGUID(ObjectGuid const& guid, int32 index) override + { + if (index == GO_LAVA_BURST) + { + if (_lavaBurstGUIDS.empty()) + extraEvents.ScheduleEvent(EVENT_LAVA_BURST_TRIGGER, 1ms); + + _lavaBurstGUIDS.insert(guid); + } + } + + void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override + { + summons.Despawn(summon); + if (events.IsInPhase(PHASE_SUBMERGED) && !summons.HasEntry(NPC_SON_OF_FLAME)) + HandleEmerge(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + events.SetPhase(PHASE_EMERGED); + ScheduleCombatEvents(); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + extraEvents.Reset(); + me->SetFacingTo(DEATH_ORIENTATION); + } + + void KilledUnit(Unit* victim) override + { + if (roll_chance_i(25) && victim->IsPlayer()) + Talk(SAY_KILL); + } + + void AttackStart(Unit* target) override + { + if (target && me->Attack(target, true)) + DoStartNoMovement(target); + } + + void EnterEvadeMode(EvadeReason why) override + { + if (!me->GetThreatMgr().IsThreatListEmpty()) + { + if (!_processingMagmaBurst) { - Talk(SAY_KILL); + // Boss try to evade, but still got some targets on threat list - it means that none of these targets are in melee range - cast magma blast + _processingMagmaBurst = true; + events.ScheduleEvent(EVENT_MAGMA_BLAST, 4s, PHASE_EMERGED, PHASE_EMERGED); } } - - void AttackStart(Unit* target) override + else { - if (target && me->Attack(target, true)) - { - DoStartNoMovement(target); - } + BossAI::EnterEvadeMode(why); } + } - void EnterEvadeMode(EvadeReason why) override + bool CanAIAttack(Unit const* victim) const override + { + // Used for Magma Blast handling to force EnterEvadeMode if there are no melee targets + return me->IsWithinMeleeRange(victim); + } + + void UpdateAI(uint32 diff) override + { + if (!extraEvents.Empty()) { - if (!me->GetThreatMgr().IsThreatListEmpty()) - { - if (!_processingMagmaBurst) - { - // Boss try to evade, but still got some targets on threat list - it means that none of these targets are in melee range - cast magma blast - _processingMagmaBurst = true; - events.ScheduleEvent(EVENT_MAGMA_BLAST, 4s, PHASE_EMERGED, PHASE_EMERGED); - } - } - else - { - BossAI::EnterEvadeMode(why); - } - } + extraEvents.Update(diff); - bool CanAIAttack(Unit const* victim) const override - { - // Used for Magma Blast handling to force EnterEvadeMode if there are no melee targets - return me->IsWithinMeleeRange(victim); - } - - void UpdateAI(uint32 diff) override - { - if (!extraEvents.Empty()) - { - extraEvents.Update(diff); - - while (uint32 const eventId = extraEvents.ExecuteEvent()) - { - switch (eventId) - { - // Intro events - case EVENT_INTRO_SAY: - { - Talk(SAY_ARRIVAL5_RAG); - extraEvents.ScheduleEvent(EVENT_INTRO_MAKE_ATTACKABLE, 2500ms, 0, PHASE_INTRO); - break; - } - case EVENT_INTRO_MAKE_ATTACKABLE: - { - _isIntroDone = true; - extraEvents.SetPhase(PHASE_EMERGED); - me->RemoveAurasDueToSpell(SPELL_RAGNAROS_SUBMERGE_EFFECT); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->SetImmuneToAll(false); - me->SetReactState(REACT_AGGRESSIVE); - DoZoneInCombat(); - break; - } - // Submerge events - case EVENT_EMERGE: - { - HandleEmerge(); - break; - } - case EVENT_RESET_KNOCKBACK_EMOTE: - { - _isKnockbackEmoteAllowed = true; - break; - } - case EVENT_LAVA_BURST_TRIGGER: - { - if (!_lavaBurstGUIDS.empty()) - { - ObjectGuid lavaBurstGUID = Acore::Containers::SelectRandomContainerElement(_lavaBurstGUIDS); - - if (GameObject* go = ObjectAccessor::GetGameObject(*me, lavaBurstGUID)) - { - go->CastSpell(nullptr, SPELL_LAVA_BURST_TRAP); - go->SendCustomAnim(0); - } - - _lavaBurstGUIDS.erase(lavaBurstGUID); - extraEvents.Repeat(1s); - } - else - { - events.RescheduleEvent(EVENT_LAVA_BURST, 10s, PHASE_EMERGED, PHASE_EMERGED); - } - - break; - } - } - } - } - - if (!UpdateVictim()) - { - if (!_processingMagmaBurst) - { - return; - } - } - - events.Update(diff); - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - - // Base combat events - (mainly emerge phase) - while (uint32 const eventId = events.ExecuteEvent()) + while (uint32 const eventId = extraEvents.ExecuteEvent()) { switch (eventId) { - case EVENT_WRATH_OF_RAGNAROS: + // Intro events + case EVENT_INTRO_SAY: { - DoCastVictim(SPELL_WRATH_OF_RAGNAROS); - - if (urand(0, 1)) - { - Talk(SAY_WRATH); - } - events.Repeat(25s); + Talk(SAY_ARRIVAL5_RAG); + extraEvents.ScheduleEvent(EVENT_INTRO_MAKE_ATTACKABLE, 2500ms, 0, PHASE_INTRO); break; } - case EVENT_HAND_OF_RAGNAROS: + case EVENT_INTRO_MAKE_ATTACKABLE: { - DoCastSelf(SPELL_HAND_OF_RAGNAROS); - if (_isKnockbackEmoteAllowed) + _isIntroDone = true; + extraEvents.SetPhase(PHASE_EMERGED); + me->RemoveAurasDueToSpell(SPELL_RAGNAROS_SUBMERGE_EFFECT); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + me->SetImmuneToAll(false); + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(); + break; + } + // Submerge events + case EVENT_EMERGE: + { + HandleEmerge(); + break; + } + case EVENT_RESET_KNOCKBACK_EMOTE: + { + _isKnockbackEmoteAllowed = true; + break; + } + case EVENT_LAVA_BURST_TRIGGER: + { + if (!_lavaBurstGUIDS.empty()) { - Talk(SAY_KNOCKBACK); + ObjectGuid lavaBurstGUID = Acore::Containers::SelectRandomContainerElement(_lavaBurstGUIDS); + + if (GameObject* go = ObjectAccessor::GetGameObject(*me, lavaBurstGUID)) + { + go->CastSpell(nullptr, SPELL_LAVA_BURST_TRAP); + go->SendCustomAnim(0); + } + + _lavaBurstGUIDS.erase(lavaBurstGUID); + extraEvents.Repeat(1s); + } + else + { + events.RescheduleEvent(EVENT_LAVA_BURST, 10s, PHASE_EMERGED, PHASE_EMERGED); + } + + break; + } + } + } + } + + if (!UpdateVictim()) + { + if (!_processingMagmaBurst) + return; + } + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + // Base combat events - (mainly emerge phase) + while (uint32 const eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_WRATH_OF_RAGNAROS: + { + DoCastVictim(SPELL_WRATH_OF_RAGNAROS); + + if (urand(0, 1)) + Talk(SAY_WRATH); + events.Repeat(25s); + break; + } + case EVENT_HAND_OF_RAGNAROS: + { + DoCastSelf(SPELL_HAND_OF_RAGNAROS); + if (_isKnockbackEmoteAllowed) + { + Talk(SAY_KNOCKBACK); + _isKnockbackEmoteAllowed = false; + extraEvents.RescheduleEvent(EVENT_RESET_KNOCKBACK_EMOTE, 5s); + } + events.Repeat(20s); + break; + } + case EVENT_LAVA_BURST: + { + DoCastAOE(SPELL_LAVA_BURST); + break; + } + case EVENT_MAGMA_BLAST: + { + _processingMagmaBurst = false; + + if (!IsVictimWithinMeleeRange()) + { + DoCastRandomTarget(SPELL_MAGMA_BLAST); + + if (!_hasYelledMagmaBurst) + { + Talk(SAY_MAGMABURST); + _hasYelledMagmaBurst = true; + } + } + + break; + } + case EVENT_MIGHT_OF_RAGNAROS: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, [](Unit const* target) + { + return target->IsPlayer() && target->getPowerType() == POWER_MANA; + })) + { + if (me->CastSpell(target, SPELL_MIGHT_OF_RAGNAROS) == SPELL_CAST_OK && _isKnockbackEmoteAllowed) + { + Talk(SAY_KNOCKBACK, me); _isKnockbackEmoteAllowed = false; extraEvents.RescheduleEvent(EVENT_RESET_KNOCKBACK_EMOTE, 5s); } - events.Repeat(20s); - break; - } - case EVENT_LAVA_BURST: - { - DoCastAOE(SPELL_LAVA_BURST); - break; - } - case EVENT_MAGMA_BLAST: - { - _processingMagmaBurst = false; - - if (!IsVictimWithinMeleeRange()) - { - DoCastRandomTarget(SPELL_MAGMA_BLAST); - - if (!_hasYelledMagmaBurst) - { - Talk(SAY_MAGMABURST); - _hasYelledMagmaBurst = true; - } - } - - break; - } - case EVENT_MIGHT_OF_RAGNAROS: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, [](Unit const* target) - { - return target->IsPlayer() && target->getPowerType() == POWER_MANA; - })) - { - if (me->CastSpell(target, SPELL_MIGHT_OF_RAGNAROS) == SPELL_CAST_OK && _isKnockbackEmoteAllowed) - { - Talk(SAY_KNOCKBACK, me); - _isKnockbackEmoteAllowed = false; - extraEvents.RescheduleEvent(EVENT_RESET_KNOCKBACK_EMOTE, 5s); - } - } - events.Repeat(11s, 30s); - break; - } - case EVENT_SUBMERGE: - { - events.CancelEventGroup(PHASE_EMERGED); - events.SetPhase(PHASE_SUBMERGED); - extraEvents.SetPhase(PHASE_SUBMERGED); - me->SetReactState(REACT_PASSIVE); - me->InterruptNonMeleeSpells(false); - me->AttackStop(); - DoResetThreatList(); - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE); - me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_SUBMERGED); - DoCastSelf(SPELL_RAGNA_SUBMERGE_VISUAL, true); - //me->HandleEmoteCommand(EMOTE_ONESHOT_SUBMERGE); - - Talk(_hasSubmergedOnce ? SAY_REINFORCEMENTS2 : SAY_REINFORCEMENTS1); - - DoCastAOE(SPELL_SUMMON_SONS_FLAME); - - if (!_hasSubmergedOnce) - { - _hasSubmergedOnce = true; - } - - extraEvents.ScheduleEvent(EVENT_EMERGE, 90s, PHASE_SUBMERGED, PHASE_SUBMERGED); - break; } + events.Repeat(11s, 30s); + break; } - - if (me->HasUnitState(UNIT_STATE_CASTING)) + case EVENT_SUBMERGE: { - return; + events.CancelEventGroup(PHASE_EMERGED); + events.SetPhase(PHASE_SUBMERGED); + extraEvents.SetPhase(PHASE_SUBMERGED); + me->SetReactState(REACT_PASSIVE); + me->InterruptNonMeleeSpells(false); + me->AttackStop(); + DoResetThreatList(); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE); + me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_SUBMERGED); + DoCastSelf(SPELL_RAGNA_SUBMERGE_VISUAL, true); + //me->HandleEmoteCommand(EMOTE_ONESHOT_SUBMERGE); + + Talk(_hasSubmergedOnce ? SAY_REINFORCEMENTS2 : SAY_REINFORCEMENTS1); + + DoCastAOE(SPELL_SUMMON_SONS_FLAME); + + if (!_hasSubmergedOnce) + _hasSubmergedOnce = true; + + extraEvents.ScheduleEvent(EVENT_EMERGE, 90s, PHASE_SUBMERGED, PHASE_SUBMERGED); + break; } } - DoMeleeAttackIfReady(); - } - - private: - EventMap extraEvents; - bool _isIntroDone; - bool _hasYelledMagmaBurst; - bool _processingMagmaBurst; - bool _hasSubmergedOnce; - bool _isKnockbackEmoteAllowed; // Prevents possible text overlap - - GuidSet _lavaBurstGUIDS; - - void HandleEmerge() - { - if (events.IsInPhase(PHASE_EMERGED)) - { + if (me->HasUnitState(UNIT_STATE_CASTING)) return; - } - - events.SetPhase(PHASE_EMERGED); - ScheduleCombatEvents(); - extraEvents.CancelEventGroup(PHASE_SUBMERGED); - extraEvents.SetPhase(PHASE_EMERGED); - - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); - me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); - me->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE); - - me->RemoveAurasDueToSpell(SPELL_RAGNA_SUBMERGE_VISUAL); - - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) - { - AttackStart(target); - } } - void ScheduleCombatEvents() - { - events.RescheduleEvent(EVENT_ERUPTION, 15s, PHASE_EMERGED, PHASE_EMERGED); - events.RescheduleEvent(EVENT_WRATH_OF_RAGNAROS, 30s, PHASE_EMERGED, PHASE_EMERGED); - events.RescheduleEvent(EVENT_HAND_OF_RAGNAROS, 25s, PHASE_EMERGED, PHASE_EMERGED); - events.RescheduleEvent(EVENT_LAVA_BURST, 10s, PHASE_EMERGED, PHASE_EMERGED); - events.RescheduleEvent(EVENT_SUBMERGE, 180s, PHASE_EMERGED, PHASE_EMERGED); - events.RescheduleEvent(EVENT_MIGHT_OF_RAGNAROS, 11s, PHASE_EMERGED, PHASE_EMERGED); - } + DoMeleeAttackIfReady(); + } - bool IsVictimWithinMeleeRange() const - { - return me->GetVictim() && me->IsWithinMeleeRange(me->GetVictim()); - } - }; +private: + EventMap extraEvents; + bool _isIntroDone; + bool _hasYelledMagmaBurst; + bool _processingMagmaBurst; + bool _hasSubmergedOnce; + bool _isKnockbackEmoteAllowed; // Prevents possible text overlap - CreatureAI* GetAI(Creature* creature) const override + GuidSet _lavaBurstGUIDS; + + void HandleEmerge() { - return GetMoltenCoreAI(creature); + if (events.IsInPhase(PHASE_EMERGED)) + return; + + events.SetPhase(PHASE_EMERGED); + ScheduleCombatEvents(); + extraEvents.CancelEventGroup(PHASE_SUBMERGED); + extraEvents.SetPhase(PHASE_EMERGED); + + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); + me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); + me->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE); + + me->RemoveAurasDueToSpell(SPELL_RAGNA_SUBMERGE_VISUAL); + + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true)) + AttackStart(target); + } + + void ScheduleCombatEvents() + { + events.RescheduleEvent(EVENT_ERUPTION, 15s, PHASE_EMERGED, PHASE_EMERGED); + events.RescheduleEvent(EVENT_WRATH_OF_RAGNAROS, 30s, PHASE_EMERGED, PHASE_EMERGED); + events.RescheduleEvent(EVENT_HAND_OF_RAGNAROS, 25s, PHASE_EMERGED, PHASE_EMERGED); + events.RescheduleEvent(EVENT_LAVA_BURST, 10s, PHASE_EMERGED, PHASE_EMERGED); + events.RescheduleEvent(EVENT_SUBMERGE, 180s, PHASE_EMERGED, PHASE_EMERGED); + events.RescheduleEvent(EVENT_MIGHT_OF_RAGNAROS, 11s, PHASE_EMERGED, PHASE_EMERGED); + } + + bool IsVictimWithinMeleeRange() const + { + return me->GetVictim() && me->IsWithinMeleeRange(me->GetVictim()); } }; @@ -547,9 +510,7 @@ class spell_ragnaros_summon_sons_of_flame : public SpellScript if (Unit* caster = GetCaster()) { for (uint32 spell : RagnarosSoFSpells) - { caster->CastSpell(caster, spell, true); - } } } @@ -561,7 +522,7 @@ class spell_ragnaros_summon_sons_of_flame : public SpellScript void AddSC_boss_ragnaros() { - new boss_ragnaros(); + RegisterMoltenCoreCreatureAI(boss_ragnaros); RegisterSpellScript(spell_ragnaros_lava_burst_randomizer); RegisterSpellScript(spell_ragnaros_summon_sons_of_flame); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_shazzrah.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_shazzrah.cpp index 45c9f7ad1..dfac88db9 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_shazzrah.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_shazzrah.cpp @@ -41,70 +41,57 @@ enum Events EVENT_SHAZZRAH_GATE, }; -class boss_shazzrah : public CreatureScript +struct boss_shazzrah : public BossAI { -public: - boss_shazzrah() : CreatureScript("boss_shazzrah") { } + boss_shazzrah(Creature* creature) : BossAI(creature, DATA_SHAZZRAH) {} - struct boss_shazzrahAI : public BossAI + void JustEngagedWith(Unit* /*target*/) override { - boss_shazzrahAI(Creature* creature) : BossAI(creature, DATA_SHAZZRAH) {} + _JustEngagedWith(); + events.ScheduleEvent(EVENT_ARCANE_EXPLOSION, 2s, 4s); + events.ScheduleEvent(EVENT_SHAZZRAH_CURSE, 7s,11s); + events.ScheduleEvent(EVENT_MAGIC_GROUNDING, 14s, 19s); + events.ScheduleEvent(EVENT_COUNTERSPELL, 9s, 10s); + events.ScheduleEvent(EVENT_SHAZZRAH_GATE, 30s); + } - void JustEngagedWith(Unit* /*target*/) override + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_ARCANE_EXPLOSION, 2s, 4s); - events.ScheduleEvent(EVENT_SHAZZRAH_CURSE, 7s,11s); - events.ScheduleEvent(EVENT_MAGIC_GROUNDING, 14s, 19s); - events.ScheduleEvent(EVENT_COUNTERSPELL, 9s, 10s); - events.ScheduleEvent(EVENT_SHAZZRAH_GATE, 30s); - } - - void ExecuteEvent(uint32 eventId) override - { - switch (eventId) + case EVENT_ARCANE_EXPLOSION: { - case EVENT_ARCANE_EXPLOSION: - { - DoCastVictim(SPELL_ARCANE_EXPLOSION); - events.Repeat(4s, 5s); - break; - } - case EVENT_SHAZZRAH_CURSE: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_SHAZZRAH_CURSE)) - { - DoCast(target, SPELL_SHAZZRAH_CURSE); - } - events.Repeat(23s, 26s); - break; - } - case EVENT_MAGIC_GROUNDING: - { - DoCastSelf(SPELL_MAGIC_GROUNDING); - events.Repeat(7s, 9s); - break; - } - case EVENT_COUNTERSPELL: - { - DoCastAOE(SPELL_COUNTERSPELL); - events.Repeat(15s, 18s); - break; - } - case EVENT_SHAZZRAH_GATE: - { - DoCastAOE(SPELL_SHAZZRAH_GATE_DUMMY); - events.RescheduleEvent(EVENT_ARCANE_EXPLOSION, 3s, 6s); - events.Repeat(45s); - break; - } + DoCastVictim(SPELL_ARCANE_EXPLOSION); + events.Repeat(4s, 5s); + break; + } + case EVENT_SHAZZRAH_CURSE: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_SHAZZRAH_CURSE)) + DoCast(target, SPELL_SHAZZRAH_CURSE); + events.Repeat(23s, 26s); + break; + } + case EVENT_MAGIC_GROUNDING: + { + DoCastSelf(SPELL_MAGIC_GROUNDING); + events.Repeat(7s, 9s); + break; + } + case EVENT_COUNTERSPELL: + { + DoCastAOE(SPELL_COUNTERSPELL); + events.Repeat(15s, 18s); + break; + } + case EVENT_SHAZZRAH_GATE: + { + DoCastAOE(SPELL_SHAZZRAH_GATE_DUMMY); + events.RescheduleEvent(EVENT_ARCANE_EXPLOSION, 3s, 6s); + events.Repeat(45s); + break; } } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } }; @@ -128,30 +115,22 @@ class spell_shazzrah_gate_dummy : public SpellScript Player const* plrTarget = target->ToPlayer(); // Should not target non player targets if (!plrTarget) - { return true; - } // Should skip current victim if (caster->GetVictim() == plrTarget) - { return true; - } // Should not target enemies within melee range if (plrTarget->IsWithinMeleeRange(caster)) - { return true; - } return false; }); } if (!targets.empty()) - { Acore::Containers::RandomResize(targets, 1); - } } void HandleScript(SpellEffIndex /*effIndex*/) @@ -182,7 +161,7 @@ class spell_shazzrah_gate_dummy : public SpellScript void AddSC_boss_shazzrah() { - new boss_shazzrah(); + RegisterMoltenCoreCreatureAI(boss_shazzrah); // Spells RegisterSpellScript(spell_shazzrah_gate_dummy); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_sulfuron_harbinger.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_sulfuron_harbinger.cpp index edc39e8cc..3eb80a537 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_sulfuron_harbinger.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_sulfuron_harbinger.cpp @@ -47,172 +47,138 @@ enum Events EVENT_IMMOLATE, }; -class boss_sulfuron : public CreatureScript +struct boss_sulfuron : public BossAI { -public: - boss_sulfuron() : CreatureScript("boss_sulfuron") {} + boss_sulfuron(Creature* creature) : BossAI(creature, DATA_SULFURON) {} - struct boss_sulfuronAI : public BossAI + void JustEngagedWith(Unit* /*who*/) override { - boss_sulfuronAI(Creature* creature) : BossAI(creature, DATA_SULFURON) {} + _JustEngagedWith(); + events.ScheduleEvent(EVENT_DEMORALIZING_SHOUT, 6s, 20s); + events.ScheduleEvent(EVENT_INSPIRE, 7s, 10s); + events.ScheduleEvent(EVENT_KNOCKDOWN, 6s); + events.ScheduleEvent(EVENT_FLAMESPEAR, 2s); + } - void JustEngagedWith(Unit* /*who*/) override + void ExecuteEvent(uint32 eventId) override + { + switch (eventId) { - _JustEngagedWith(); - events.ScheduleEvent(EVENT_DEMORALIZING_SHOUT, 6s, 20s); - events.ScheduleEvent(EVENT_INSPIRE, 7s, 10s); - events.ScheduleEvent(EVENT_KNOCKDOWN, 6s); - events.ScheduleEvent(EVENT_FLAMESPEAR, 2s); - } - - void ExecuteEvent(uint32 eventId) override - { - switch (eventId) + case EVENT_DEMORALIZING_SHOUT: { - case EVENT_DEMORALIZING_SHOUT: - { - DoCastVictim(SPELL_DEMORALIZING_SHOUT); - events.Repeat(12s, 18s); - break; - } - case EVENT_INSPIRE: - { - std::list healers = DoFindFriendlyMissingBuff(45.0f, SPELL_INSPIRE); - if (!healers.empty()) - { - DoCast(Acore::Containers::SelectRandomContainerElement(healers), SPELL_INSPIRE); - } + DoCastVictim(SPELL_DEMORALIZING_SHOUT); + events.Repeat(12s, 18s); + break; + } + case EVENT_INSPIRE: + { + std::list healers = DoFindFriendlyMissingBuff(45.0f, SPELL_INSPIRE); + if (!healers.empty()) + DoCast(Acore::Containers::SelectRandomContainerElement(healers), SPELL_INSPIRE); - DoCastSelf(SPELL_INSPIRE); - events.Repeat(13s, 20s); - break; - } - case EVENT_KNOCKDOWN: - { - DoCastVictim(SPELL_KNOCKDOWN); - events.Repeat(10s, 20s); - break; - } - case EVENT_FLAMESPEAR: - { - DoCastRandomTarget(SPELL_FLAMESPEAR); - events.Repeat(12s, 16s); - break; - } + DoCastSelf(SPELL_INSPIRE); + events.Repeat(13s, 20s); + break; + } + case EVENT_KNOCKDOWN: + { + DoCastVictim(SPELL_KNOCKDOWN); + events.Repeat(10s, 20s); + break; + } + case EVENT_FLAMESPEAR: + { + DoCastRandomTarget(SPELL_FLAMESPEAR); + events.Repeat(12s, 16s); + break; } } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } }; -class npc_flamewaker_priest : public CreatureScript +struct npc_flamewaker_priest : public ScriptedAI { -public: - npc_flamewaker_priest() : CreatureScript("npc_flamewaker_priest") {} + npc_flamewaker_priest(Creature* creature) : ScriptedAI(creature) {} - struct npc_flamewaker_priestAI : public ScriptedAI + void Reset() override { - npc_flamewaker_priestAI(Creature* creature) : ScriptedAI(creature) {} + events.Reset(); + } - void Reset() override - { - events.Reset(); - } + void JustDied(Unit* /*killer*/) override + { + events.Reset(); + } - void JustDied(Unit* /*killer*/) override - { - events.Reset(); - } + void JustEngagedWith(Unit* /*who*/) override + { + events.ScheduleEvent(EVENT_DARK_STRIKE, 4s, 7s); + events.ScheduleEvent(EVENT_DARK_MENDING, 15s, 30s); + events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 2s, 4s); + events.ScheduleEvent(EVENT_IMMOLATE, 3500ms, 6s); + } - void JustEngagedWith(Unit* /*who*/) override - { - events.ScheduleEvent(EVENT_DARK_STRIKE, 4s, 7s); - events.ScheduleEvent(EVENT_DARK_MENDING, 15s, 30s); - events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 2s, 4s); - events.ScheduleEvent(EVENT_IMMOLATE, 3500ms, 6s); - } + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; - void UpdateAI(uint32 diff) override + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 const eventId = events.ExecuteEvent()) { - if (!UpdateVictim()) + switch (eventId) { - return; + case EVENT_DARK_STRIKE: + { + DoCastVictim(SPELL_DARK_STRIKE); + events.Repeat(4s, 7s); + break; + } + case EVENT_DARK_MENDING: + { + if (Unit* target = DoSelectLowestHpFriendly(60.0f, 1)) + { + if (target->GetGUID() != me->GetGUID()) + { + DoCast(target, SPELL_DARK_MENDING); + } + } + events.Repeat(15s, 20s); + break; + } + case EVENT_SHADOW_WORD_PAIN: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_SHADOW_WORD_PAIN)) + DoCast(target, SPELL_SHADOW_WORD_PAIN); + events.Repeat(2500ms, 5s); + break; + } + case EVENT_IMMOLATE: + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_IMMOLATE)) + DoCast(target, SPELL_IMMOLATE); + events.Repeat(5s, 7s); + break; + } } - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - { return; - } - - while (uint32 const eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_DARK_STRIKE: - { - DoCastVictim(SPELL_DARK_STRIKE); - events.Repeat(4s, 7s); - break; - } - case EVENT_DARK_MENDING: - { - if (Unit* target = DoSelectLowestHpFriendly(60.0f, 1)) - { - if (target->GetGUID() != me->GetGUID()) - { - DoCast(target, SPELL_DARK_MENDING); - } - } - events.Repeat(15s, 20s); - break; - } - case EVENT_SHADOW_WORD_PAIN: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_SHADOW_WORD_PAIN)) - { - DoCast(target, SPELL_SHADOW_WORD_PAIN); - } - events.Repeat(2500ms, 5s); - break; - } - case EVENT_IMMOLATE: - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_IMMOLATE)) - { - DoCast(target, SPELL_IMMOLATE); - } - events.Repeat(5s, 7s); - break; - } - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - { - return; - } - } - - DoMeleeAttackIfReady(); } - private: - EventMap events; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); + DoMeleeAttackIfReady(); } + +private: + EventMap events; }; void AddSC_boss_sulfuron() { - new boss_sulfuron(); - new npc_flamewaker_priest(); + RegisterMoltenCoreCreatureAI(boss_sulfuron); + RegisterMoltenCoreCreatureAI(npc_flamewaker_priest); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/instance_molten_core.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/instance_molten_core.cpp index e9163cb89..b51e49018 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/instance_molten_core.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/instance_molten_core.cpp @@ -53,384 +53,331 @@ MCBossObject const linkedBossObjData[MAX_MC_LINKED_BOSS_OBJ]= constexpr uint8 SAY_SPAWN = 1; -class instance_molten_core : public InstanceMapScript +struct instance_molten_core : public InstanceScript { -public: - instance_molten_core() : InstanceMapScript(MCScriptName, MAP_MOLTEN_CORE) {} - - struct instance_molten_core_InstanceMapScript : public InstanceScript + instance_molten_core(Map* map) : InstanceScript(map) { - instance_molten_core_InstanceMapScript(Map* map) : InstanceScript(map) - { - SetHeaders(DataHeader); - SetBossNumber(MAX_ENCOUNTER); - LoadMinionData(minionData); - } - - void OnPlayerEnter(Player* /*player*/) override - { - if (CheckMajordomoExecutus()) - { - SummonMajordomoExecutus(); - } - } - - void OnCreatureCreate(Creature* creature) override - { - switch (creature->GetEntry()) - { - case NPC_GOLEMAGG_THE_INCINERATOR: - { - _golemaggGUID = creature->GetGUID(); - break; - } - case NPC_CORE_RAGER: - { - _golemaggMinionsGUIDS.insert(creature->GetGUID()); - break; - } - case NPC_MAJORDOMO_EXECUTUS: - { - _majordomoExecutusGUID = creature->GetGUID(); - break; - } - case NPC_GARR: - { - _garrGUID = creature->GetGUID(); - break; - } - case NPC_RAGNAROS: - { - _ragnarosGUID = creature->GetGUID(); - break; - } - case NPC_FIRESWORN: - case NPC_FLAMEWALKER: - case NPC_FLAMEWALKER_PROTECTOR: - case NPC_FLAMEWALKER_PRIEST: - case NPC_FLAMEWALKER_HEALER: - case NPC_FLAMEWALKER_ELITE: - { - AddMinion(creature); - break; - } - } - } - - void OnCreatureRemove(Creature* creature) override - { - switch (creature->GetEntry()) - { - case NPC_FIRESWORN: - { - RemoveMinion(creature); - break; - } - case NPC_FLAMEWALKER: - case NPC_FLAMEWALKER_PROTECTOR: - case NPC_FLAMEWALKER_PRIEST: - case NPC_FLAMEWALKER_HEALER: - case NPC_FLAMEWALKER_ELITE: - { - RemoveMinion(creature); - break; - } - } - } - - void OnGameObjectCreate(GameObject* go) override - { - switch (go->GetEntry()) - { - case GO_CACHE_OF_THE_FIRELORD: - { - _cacheOfTheFirelordGUID = go->GetGUID(); - break; - } - case GO_CIRCLE_GEDDON: - case GO_CIRCLE_GARR: - case GO_CIRCLE_GEHENNAS: - case GO_CIRCLE_GOLEMAGG: - case GO_CIRCLE_MAGMADAR: - case GO_CIRCLE_SHAZZRAH: - case GO_CIRCLE_SULFURON: - { - for (uint8 i = 0; i < MAX_MC_LINKED_BOSS_OBJ; ++i) - { - if (linkedBossObjData[i].circleId != go->GetEntry()) - { - continue; - } - - if (GetBossState(linkedBossObjData[i].bossId) == DONE) - { - go->DespawnOrUnsummon(0ms, Seconds(WEEK)); - } - else - { - _circlesGUIDs[linkedBossObjData[i].bossId] = go->GetGUID(); - } - } - - break; - } - case GO_RUNE_KRESS: - case GO_RUNE_MOHN: - case GO_RUNE_BLAZ: - case GO_RUNE_MAZJ: - case GO_RUNE_ZETH: - case GO_RUNE_THERI: - case GO_RUNE_KORO: - { - for (uint8 i = 0; i < MAX_MC_LINKED_BOSS_OBJ; ++i) - { - if (linkedBossObjData[i].runeId != go->GetEntry()) - { - continue; - } - - if (GetBossState(linkedBossObjData[i].bossId) == DONE) - { - go->UseDoorOrButton(WEEK * IN_MILLISECONDS); - } - else - { - _runesGUIDs[linkedBossObjData[i].bossId] = go->GetGUID(); - } - } - break; - } - case GO_LAVA_STEAM: - { - _lavaSteamGUID = go->GetGUID(); - break; - } - case GO_LAVA_SPLASH: - { - _lavaSplashGUID = go->GetGUID(); - break; - } - case GO_LAVA_BURST: - { - if (Creature* ragnaros = instance->GetCreature(_ragnarosGUID)) - { - ragnaros->AI()->SetGUID(go->GetGUID(), GO_LAVA_BURST); - } - break; - } - } - } - - ObjectGuid GetGuidData(uint32 type) const override - { - switch (type) - { - case DATA_GOLEMAGG: - return _golemaggGUID; - case DATA_MAJORDOMO_EXECUTUS: - return _majordomoExecutusGUID; - case DATA_GARR: - return _garrGUID; - case DATA_LAVA_STEAM: - return _lavaSteamGUID; - case DATA_LAVA_SPLASH: - return _lavaSplashGUID; - case DATA_RAGNAROS: - return _ragnarosGUID; - } - - return ObjectGuid::Empty; - } - - bool SetBossState(uint32 bossId, EncounterState state) override - { - if (!InstanceScript::SetBossState(bossId, state)) - { - return false; - } - - if (bossId == DATA_MAJORDOMO_EXECUTUS && state == DONE) - { - if (GameObject* cache = instance->GetGameObject(_cacheOfTheFirelordGUID)) - { - cache->SetRespawnTime(7 * DAY); - cache->SetLootRecipient(instance); - } - } - else if (bossId == DATA_GOLEMAGG) - { - switch (state) - { - case NOT_STARTED: - case FAIL: - { - if (!_golemaggMinionsGUIDS.empty()) - { - for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) - { - Creature* minion = instance->GetCreature(minionGuid); - if (minion && minion->isDead()) - { - minion->Respawn(); - } - } - } - break; - } - case IN_PROGRESS: - { - if (!_golemaggMinionsGUIDS.empty()) - { - for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) - { - if (Creature* minion = instance->GetCreature(minionGuid)) - { - minion->AI()->DoZoneInCombat(nullptr, 150.0f); - } - } - } - break; - } - case DONE: - { - if (!_golemaggMinionsGUIDS.empty()) - { - for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) - { - if (Creature* minion = instance->GetCreature(minionGuid)) - { - minion->CastSpell(minion, SPELL_CORE_RAGER_QUIET_SUICIDE, true); - } - } - _golemaggMinionsGUIDS.clear(); - } - break; - } - default: - break; - } - } - // Perform needed checks for Majordomu - if (bossId < DATA_MAJORDOMO_EXECUTUS && state == DONE) - { - if (GameObject* circle = instance->GetGameObject(_circlesGUIDs[bossId])) - { - circle->DespawnOrUnsummon(0ms, Seconds(WEEK)); - _circlesGUIDs[bossId].Clear(); - } - - if (GameObject* rune = instance->GetGameObject(_runesGUIDs[bossId])) - { - rune->UseDoorOrButton(WEEK * IN_MILLISECONDS); - _runesGUIDs[bossId].Clear(); - } - - if (CheckMajordomoExecutus()) - { - SummonMajordomoExecutus(); - } - } - - return true; - } - - void DoAction(int32 action) override - { - if (action == ACTION_RESET_GOLEMAGG_ENCOUNTER) - { - if (Creature* golemagg = instance->GetCreature(_golemaggGUID)) - { - golemagg->AI()->EnterEvadeMode(); - } - - if (!_golemaggMinionsGUIDS.empty()) - { - for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) - { - if (Creature* minion = instance->GetCreature(minionGuid)) - { - minion->AI()->EnterEvadeMode(); - } - } - } - } - } - - void SummonMajordomoExecutus() - { - if (instance->GetCreature(_majordomoExecutusGUID)) - { - return; - } - - if (GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE) - { - if (Creature* creature = instance->SummonCreature(NPC_MAJORDOMO_EXECUTUS, MajordomoSummonPos)) - { - creature->AI()->Talk(SAY_SPAWN); - } - } - else - { - instance->SummonCreature(NPC_MAJORDOMO_EXECUTUS, MajordomoRagnaros); - } - } - - bool CheckMajordomoExecutus() const - { - if (GetBossState(DATA_RAGNAROS) == DONE) - { - return false; - } - - for (uint8 i = 0; i < DATA_MAJORDOMO_EXECUTUS; ++i) - { - if (i == DATA_LUCIFRON) - { - continue; - } - - if (GetBossState(i) != DONE) - { - return false; - } - } - - // Prevent spawning if Ragnaros is present - if (instance->GetCreature(_ragnarosGUID)) - { - return false; - } - - return true; - } - - private: - std::unordered_map _circlesGUIDs; - std::unordered_map _runesGUIDs; - - // Golemagg encounter related - ObjectGuid _golemaggGUID; - GuidSet _golemaggMinionsGUIDS; - - // Ragnaros encounter related - ObjectGuid _ragnarosGUID; - ObjectGuid _lavaSteamGUID; - ObjectGuid _lavaSplashGUID; - - ObjectGuid _majordomoExecutusGUID; - ObjectGuid _cacheOfTheFirelordGUID; - ObjectGuid _garrGUID; - ObjectGuid _magmadarGUID; - }; - - InstanceScript* GetInstanceScript(InstanceMap* map) const override - { - return new instance_molten_core_InstanceMapScript(map); + SetHeaders(DataHeader); + SetBossNumber(MAX_ENCOUNTER); + LoadMinionData(minionData); } + + void OnPlayerEnter(Player* /*player*/) override + { + if (CheckMajordomoExecutus()) + SummonMajordomoExecutus(); + } + + void OnCreatureCreate(Creature* creature) override + { + switch (creature->GetEntry()) + { + case NPC_GOLEMAGG_THE_INCINERATOR: + { + _golemaggGUID = creature->GetGUID(); + break; + } + case NPC_CORE_RAGER: + { + _golemaggMinionsGUIDS.insert(creature->GetGUID()); + break; + } + case NPC_MAJORDOMO_EXECUTUS: + { + _majordomoExecutusGUID = creature->GetGUID(); + break; + } + case NPC_GARR: + { + _garrGUID = creature->GetGUID(); + break; + } + case NPC_RAGNAROS: + { + _ragnarosGUID = creature->GetGUID(); + break; + } + case NPC_FIRESWORN: + case NPC_FLAMEWALKER: + case NPC_FLAMEWALKER_PROTECTOR: + case NPC_FLAMEWALKER_PRIEST: + case NPC_FLAMEWALKER_HEALER: + case NPC_FLAMEWALKER_ELITE: + { + AddMinion(creature); + break; + } + } + } + + void OnCreatureRemove(Creature* creature) override + { + switch (creature->GetEntry()) + { + case NPC_FIRESWORN: + { + RemoveMinion(creature); + break; + } + case NPC_FLAMEWALKER: + case NPC_FLAMEWALKER_PROTECTOR: + case NPC_FLAMEWALKER_PRIEST: + case NPC_FLAMEWALKER_HEALER: + case NPC_FLAMEWALKER_ELITE: + { + RemoveMinion(creature); + break; + } + } + } + + void OnGameObjectCreate(GameObject* go) override + { + switch (go->GetEntry()) + { + case GO_CACHE_OF_THE_FIRELORD: + { + _cacheOfTheFirelordGUID = go->GetGUID(); + break; + } + case GO_CIRCLE_GEDDON: + case GO_CIRCLE_GARR: + case GO_CIRCLE_GEHENNAS: + case GO_CIRCLE_GOLEMAGG: + case GO_CIRCLE_MAGMADAR: + case GO_CIRCLE_SHAZZRAH: + case GO_CIRCLE_SULFURON: + { + for (uint8 i = 0; i < MAX_MC_LINKED_BOSS_OBJ; ++i) + { + if (linkedBossObjData[i].circleId != go->GetEntry()) + continue; + + if (GetBossState(linkedBossObjData[i].bossId) == DONE) + go->DespawnOrUnsummon(0ms, Seconds(WEEK)); + else + _circlesGUIDs[linkedBossObjData[i].bossId] = go->GetGUID(); + } + + break; + } + case GO_RUNE_KRESS: + case GO_RUNE_MOHN: + case GO_RUNE_BLAZ: + case GO_RUNE_MAZJ: + case GO_RUNE_ZETH: + case GO_RUNE_THERI: + case GO_RUNE_KORO: + { + for (uint8 i = 0; i < MAX_MC_LINKED_BOSS_OBJ; ++i) + { + if (linkedBossObjData[i].runeId != go->GetEntry()) + continue; + + if (GetBossState(linkedBossObjData[i].bossId) == DONE) + go->UseDoorOrButton(WEEK * IN_MILLISECONDS); + else + _runesGUIDs[linkedBossObjData[i].bossId] = go->GetGUID(); + } + break; + } + case GO_LAVA_STEAM: + { + _lavaSteamGUID = go->GetGUID(); + break; + } + case GO_LAVA_SPLASH: + { + _lavaSplashGUID = go->GetGUID(); + break; + } + case GO_LAVA_BURST: + { + if (Creature* ragnaros = instance->GetCreature(_ragnarosGUID)) + ragnaros->AI()->SetGUID(go->GetGUID(), GO_LAVA_BURST); + break; + } + } + } + + ObjectGuid GetGuidData(uint32 type) const override + { + switch (type) + { + case DATA_GOLEMAGG: + return _golemaggGUID; + case DATA_MAJORDOMO_EXECUTUS: + return _majordomoExecutusGUID; + case DATA_GARR: + return _garrGUID; + case DATA_LAVA_STEAM: + return _lavaSteamGUID; + case DATA_LAVA_SPLASH: + return _lavaSplashGUID; + case DATA_RAGNAROS: + return _ragnarosGUID; + } + + return ObjectGuid::Empty; + } + + bool SetBossState(uint32 bossId, EncounterState state) override + { + if (!InstanceScript::SetBossState(bossId, state)) + return false; + + if (bossId == DATA_MAJORDOMO_EXECUTUS && state == DONE) + { + if (GameObject* cache = instance->GetGameObject(_cacheOfTheFirelordGUID)) + { + cache->SetRespawnTime(7 * DAY); + cache->SetLootRecipient(instance); + } + } + else if (bossId == DATA_GOLEMAGG) + { + switch (state) + { + case NOT_STARTED: + case FAIL: + { + if (!_golemaggMinionsGUIDS.empty()) + { + for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) + { + Creature* minion = instance->GetCreature(minionGuid); + if (minion && minion->isDead()) + minion->Respawn(); + } + } + break; + } + case IN_PROGRESS: + { + if (!_golemaggMinionsGUIDS.empty()) + { + for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) + { + if (Creature* minion = instance->GetCreature(minionGuid)) + minion->AI()->DoZoneInCombat(nullptr, 150.0f); + } + } + break; + } + case DONE: + { + if (!_golemaggMinionsGUIDS.empty()) + { + for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) + { + if (Creature* minion = instance->GetCreature(minionGuid)) + minion->CastSpell(minion, SPELL_CORE_RAGER_QUIET_SUICIDE, true); + } + _golemaggMinionsGUIDS.clear(); + } + break; + } + default: + break; + } + } + // Perform needed checks for Majordomu + if (bossId < DATA_MAJORDOMO_EXECUTUS && state == DONE) + { + if (GameObject* circle = instance->GetGameObject(_circlesGUIDs[bossId])) + { + circle->DespawnOrUnsummon(0ms, Seconds(WEEK)); + _circlesGUIDs[bossId].Clear(); + } + + if (GameObject* rune = instance->GetGameObject(_runesGUIDs[bossId])) + { + rune->UseDoorOrButton(WEEK * IN_MILLISECONDS); + _runesGUIDs[bossId].Clear(); + } + + if (CheckMajordomoExecutus()) + SummonMajordomoExecutus(); + } + + return true; + } + + void DoAction(int32 action) override + { + if (action == ACTION_RESET_GOLEMAGG_ENCOUNTER) + { + if (Creature* golemagg = instance->GetCreature(_golemaggGUID)) + golemagg->AI()->EnterEvadeMode(); + + if (!_golemaggMinionsGUIDS.empty()) + { + for (ObjectGuid const& minionGuid : _golemaggMinionsGUIDS) + { + if (Creature* minion = instance->GetCreature(minionGuid)) + minion->AI()->EnterEvadeMode(); + } + } + } + } + + void SummonMajordomoExecutus() + { + if (instance->GetCreature(_majordomoExecutusGUID)) + return; + + if (GetBossState(DATA_MAJORDOMO_EXECUTUS) != DONE) + { + if (Creature* creature = instance->SummonCreature(NPC_MAJORDOMO_EXECUTUS, MajordomoSummonPos)) + creature->AI()->Talk(SAY_SPAWN); + } + else + { + instance->SummonCreature(NPC_MAJORDOMO_EXECUTUS, MajordomoRagnaros); + } + } + + bool CheckMajordomoExecutus() const + { + if (GetBossState(DATA_RAGNAROS) == DONE) + return false; + + for (uint8 i = 0; i < DATA_MAJORDOMO_EXECUTUS; ++i) + { + if (i == DATA_LUCIFRON) + continue; + + if (GetBossState(i) != DONE) + return false; + } + + // Prevent spawning if Ragnaros is present + if (instance->GetCreature(_ragnarosGUID)) + return false; + + return true; + } + +private: + std::unordered_map _circlesGUIDs; + std::unordered_map _runesGUIDs; + + // Golemagg encounter related + ObjectGuid _golemaggGUID; + GuidSet _golemaggMinionsGUIDS; + + // Ragnaros encounter related + ObjectGuid _ragnarosGUID; + ObjectGuid _lavaSteamGUID; + ObjectGuid _lavaSplashGUID; + + ObjectGuid _majordomoExecutusGUID; + ObjectGuid _cacheOfTheFirelordGUID; + ObjectGuid _garrGUID; + ObjectGuid _magmadarGUID; }; void AddSC_instance_molten_core() { - new instance_molten_core(); + RegisterInstanceScript(instance_molten_core, MAP_MOLTEN_CORE); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.cpp index 117f06926..4b9548ecf 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.cpp @@ -46,75 +46,60 @@ enum Spells }; // Serrated Bites timer may be wrong -class npc_mc_core_hound : public CreatureScript +struct npc_mc_core_hound : public CreatureAI { -public: - npc_mc_core_hound() : CreatureScript("npc_mc_core_hound") {} - - struct npc_mc_core_houndAI : public CreatureAI + npc_mc_core_hound(Creature* creature) : + CreatureAI(creature), + instance(creature->GetInstanceScript()), + serratedBiteTimer(3000) { - npc_mc_core_houndAI(Creature* creature) : - CreatureAI(creature), - instance(creature->GetInstanceScript()), - serratedBiteTimer(3000) - { - } - - void Reset() override - { - serratedBiteTimer = 3000; - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override - { - // Prevent receiving any extra damage if Hound is playing dead - if (me->HasAura(SPELL_PLAY_DEAD)) - { - damage = 0; - return; - } - else if (me->GetHealth() <= damage) - { - damage = 0; - Talk(EMOTE_SMOLDERING); - DoCastSelf(SPELL_PLAY_DEAD); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - { - return; - } - - if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasAura(SPELL_PLAY_DEAD)) - { - return; - } - - if (serratedBiteTimer <= diff) - { - DoCastVictim(SPELL_SERRATED_BITE); - serratedBiteTimer = urand(5000, 6000); - } - else - { - serratedBiteTimer -= diff; - } - - DoMeleeAttackIfReady(); - } - - private: - InstanceScript* instance; - uint32 serratedBiteTimer; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetMoltenCoreAI(creature); } + + void Reset() override + { + serratedBiteTimer = 3000; + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override + { + // Prevent receiving any extra damage if Hound is playing dead + if (me->HasAura(SPELL_PLAY_DEAD)) + { + damage = 0; + return; + } + else if (me->GetHealth() <= damage) + { + damage = 0; + Talk(EMOTE_SMOLDERING); + DoCastSelf(SPELL_PLAY_DEAD); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasAura(SPELL_PLAY_DEAD)) + return; + + if (serratedBiteTimer <= diff) + { + DoCastVictim(SPELL_SERRATED_BITE); + serratedBiteTimer = urand(5000, 6000); + } + else + { + serratedBiteTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + +private: + InstanceScript* instance; + uint32 serratedBiteTimer; }; // 19822 Play Dead @@ -136,9 +121,7 @@ class spell_mc_play_dead_aura : public AuraScript { Creature* creatureTarget = GetTarget()->ToCreature(); if (!creatureTarget) - { return; - } creatureTarget->CastSpell(creatureTarget, SPELL_PLAY_DEAD_PACIFY, true); creatureTarget->SetDynamicFlag(UNIT_DYNFLAG_DEAD); @@ -154,9 +137,7 @@ class spell_mc_play_dead_aura : public AuraScript { Creature* creatureTarget = GetTarget()->ToCreature(); if (!creatureTarget) - { return; - } creatureTarget->RemoveAurasDueToSpell(SPELL_PLAY_DEAD_PACIFY); creatureTarget->RemoveDynamicFlag(UNIT_DYNFLAG_DEAD); @@ -166,9 +147,7 @@ class spell_mc_play_dead_aura : public AuraScript creatureTarget->SetReactState(REACT_AGGRESSIVE); if (!creatureTarget->IsInCombat()) - { return; - } bool shouldDie = true; std::list hounds; @@ -246,9 +225,7 @@ struct npc_lava_spawn : public ScriptedAI void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } _scheduler.Update(diff); @@ -262,7 +239,7 @@ private: void AddSC_molten_core() { // Creatures - new npc_mc_core_hound(); + RegisterMoltenCoreCreatureAI(npc_mc_core_hound); RegisterCreatureAI(npc_lava_spawn); // Spells diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.h index 0dea07421..ca58eee1d 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.h +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/molten_core.h @@ -127,4 +127,7 @@ inline AI* GetMoltenCoreAI(T* obj) return GetInstanceAI(obj, MCScriptName); } +#define RegisterMoltenCoreCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetMoltenCoreAI) +#define RegisterMoltenCoreGameObjectAI(ai_name) RegisterGameObjectAIWithFactory(ai_name, GetMoltenCoreAI) + #endif From d31e58d237584341db7d003e23e948706cf83fe0 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:28:40 -0500 Subject: [PATCH 041/132] fix(Core/SmartAI): Remove REACT_PASSIVE check from CanAIAttack (#25186) Co-authored-by: blinkysc --- src/server/game/AI/SmartScripts/SmartAI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 284a6e661..2c165c526 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -755,7 +755,7 @@ void SmartAI::MoveInLineOfSight(Unit* who) bool SmartAI::CanAIAttack(Unit const* /*who*/) const { - return !(me->GetReactState() == REACT_PASSIVE); + return true; } bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) From 9e666623c3c064cdc3ccca457a0e6ec9a1090159 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:39:34 -0500 Subject: [PATCH 042/132] fix(Core/Combat): Restore pet/owner combat propagation (#25166) Co-authored-by: blinkysc --- src/server/game/AI/CreatureAI.cpp | 2 +- src/server/game/Entities/Unit/Unit.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index b2535a446..161d11b14 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -199,7 +199,7 @@ void CreatureAI::OnOwnerCombatInteraction(Unit* target) return; if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true)) - me->EngageWithTarget(target); + AttackStart(target); } // Distract creature, if player gets too close while stealthed/prowling diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 0d94a956a..3f96d8049 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -11392,6 +11392,8 @@ void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro) target->EngageWithTarget(this); if (Unit* targetOwner = target->GetCharmerOrOwner()) targetOwner->EngageWithTarget(this); + if (Unit* myOwner = GetCharmerOrOwner()) + target->EngageWithTarget(myOwner); // Patch 3.0.8: All player spells which cause a creature to become aggressive // to you will now also immediately cause the creature to be tapped. From 83c97205951e7f00ba8688c8dfb246523cabb1a5 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 14:50:00 -0300 Subject: [PATCH 043/132] fix(DB/Conditions): Fix Ruthless Cunning banner target conditions (#25150) Co-authored-by: Claude Opus 4.6 --- .../rev_ruthless_cunning_conditions.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_ruthless_cunning_conditions.sql diff --git a/data/sql/updates/pending_db_world/rev_ruthless_cunning_conditions.sql b/data/sql/updates/pending_db_world/rev_ruthless_cunning_conditions.sql new file mode 100644 index 000000000..0b9f87610 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_ruthless_cunning_conditions.sql @@ -0,0 +1,12 @@ +-- +-- Fix Quest 9927 (Ruthless Cunning): banner spell 32307 conditions only checked for Kil'sorrow Deathsworn (17148) +-- Each ElseGroup should check a different Kil'sorrow creature so banners work on all applicable mobs +-- No double-banner check needed: SpellEffects.cpp already removes corpse after banner placement + +-- Remove old conditions for spell 32307 and insert corrected ones: CONDITION_NEAR_CREATURE (29) per mob +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 17 AND `SourceEntry` = 32307; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(17, 0, 32307, 0, 0, 29, 0, 17148, 5, 1, 0, 12, 0, '', 'Spell Place Kil''sorrow Banner requires nearby Kil''sorrow Deathsworn'), +(17, 0, 32307, 0, 1, 29, 0, 17147, 5, 1, 0, 12, 0, '', 'Spell Place Kil''sorrow Banner requires nearby Kil''sorrow Cultist'), +(17, 0, 32307, 0, 2, 29, 0, 17146, 5, 1, 0, 12, 0, '', 'Spell Place Kil''sorrow Banner requires nearby Kil''sorrow Spellbinder'), +(17, 0, 32307, 0, 3, 29, 0, 18391, 5, 1, 0, 12, 0, '', 'Spell Place Kil''sorrow Banner requires nearby Giselda the Crone'); From 2b63e2282f6dde5632c25119fc859fe2e10ab8b7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Mar 2026 17:51:05 +0000 Subject: [PATCH 044/132] chore(DB): import pending files Referenced commit(s): 83c97205951e7f00ba8688c8dfb246523cabb1a5 --- .../2026_03_22_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_ruthless_cunning_conditions.sql => db_world/2026_03_22_01.sql} (96%) diff --git a/data/sql/updates/pending_db_world/rev_ruthless_cunning_conditions.sql b/data/sql/updates/db_world/2026_03_22_01.sql similarity index 96% rename from data/sql/updates/pending_db_world/rev_ruthless_cunning_conditions.sql rename to data/sql/updates/db_world/2026_03_22_01.sql index 0b9f87610..76d2c005d 100644 --- a/data/sql/updates/pending_db_world/rev_ruthless_cunning_conditions.sql +++ b/data/sql/updates/db_world/2026_03_22_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_22_00 -> 2026_03_22_01 -- -- Fix Quest 9927 (Ruthless Cunning): banner spell 32307 conditions only checked for Kil'sorrow Deathsworn (17148) -- Each ElseGroup should check a different Kil'sorrow creature so banners work on all applicable mobs From 795e59dbcf4b25bbb9408c5c02dce7b7677b4906 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:43:50 -0300 Subject: [PATCH 045/132] fix(Scripts/Ulduar): fix Mimiron ACU unkillable while grounded by Magnetic Core (#25141) Co-authored-by: Claude Opus 4.6 --- .../Northrend/Ulduar/Ulduar/boss_mimiron.cpp | 513 +++++++++--------- 1 file changed, 263 insertions(+), 250 deletions(-) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp index 8958c9af8..600c59f66 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp @@ -269,30 +269,19 @@ struct boss_mimiron : public BossAI if (!me->IsAlive()) instance->SetBossState(BOSS_MIMIRON, DONE); - bIsEvading = false; + _isEvading = false; } - bool bIsEvading; - bool hardmode; - bool berserk; - bool bAchievProximityMine; - bool bAchievBombBot; - bool bAchievRocketStrike; - uint32 allowedFlameSpreadTime; - bool changeAllowedFlameSpreadTime; - uint8 minutesTalkNum; - uint32 outofCombatTimer; - void Reset() override { - hardmode = false; - berserk = false; - bAchievProximityMine = false; - bAchievBombBot = false; - bAchievRocketStrike = false; - allowedFlameSpreadTime = 0; - outofCombatTimer = 0; - changeAllowedFlameSpreadTime = false; + _hardmode = false; + _berserk = false; + _achievProximityMine = false; + _achievBombBot = false; + _achievRocketStrike = false; + _allowedFlameSpreadTime = 0; + _outOfCombatTimer = 0; + _changeAllowedFlameSpreadTime = false; ResetGameObjects(); me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); @@ -339,7 +328,7 @@ struct boss_mimiron : public BossAI } CloseDoorAndButton(); - if (!hardmode) + if (!_hardmode) { Talk(SAY_MKII_ACTIVATE); events.ScheduleEvent(EVENT_SIT_LMK2, 6s); @@ -354,10 +343,10 @@ struct boss_mimiron : public BossAI computer->AI()->Talk(TALK_COMPUTER_INITIATED); events.ScheduleEvent(EVENT_COMPUTER_SAY_MINUTES, 3s); - minutesTalkNum = Is25ManRaid() ? TALK_COMPUTER_TEN : TALK_COMPUTER_EIGHT; - for (uint32 i = 0; i < uint32(TALK_COMPUTER_ZERO - minutesTalkNum - 1); ++i) + _minutesTalkNum = Is25ManRaid() ? TALK_COMPUTER_TEN : TALK_COMPUTER_EIGHT; + for (uint32 i = 0; i < uint32(TALK_COMPUTER_ZERO - _minutesTalkNum - 1); ++i) events.ScheduleEvent(EVENT_COMPUTER_SAY_MINUTES, Milliseconds((i + 1) * 60000)); - events.ScheduleEvent(EVENT_COMPUTER_SAY_MINUTES, Milliseconds((TALK_COMPUTER_ZERO - minutesTalkNum) * 60000)); + events.ScheduleEvent(EVENT_COMPUTER_SAY_MINUTES, Milliseconds((TALK_COMPUTER_ZERO - _minutesTalkNum) * 60000)); } // ensure LMK2 is at proper position @@ -375,10 +364,10 @@ struct boss_mimiron : public BossAI { if (!me->IsInCombat()) { - outofCombatTimer += diff; - if (outofCombatTimer >= 10000) + _outOfCombatTimer += diff; + if (_outOfCombatTimer >= 10000) { - outofCombatTimer = 0; + _outOfCombatTimer = 0; if (Creature* c = GetLMK2()) me->CastSpell(c, RAND(SPELL_ENTER_VEHICLE_0, SPELL_ENTER_VEHICLE_1, SPELL_ENTER_VEHICLE_2, SPELL_ENTER_VEHICLE_4), true); } @@ -400,7 +389,7 @@ struct boss_mimiron : public BossAI break; case EVENT_COMPUTER_SAY_MINUTES: if (Creature* computer = me->SummonCreature(NPC_COMPUTER, 2746.7f, 2569.44f, 410.39f, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 1000)) - computer->AI()->Talk(minutesTalkNum++); + computer->AI()->Talk(_minutesTalkNum++); break; case EVENT_MIMIRON_SAY_HARDMODE: Talk(SAY_HARDMODE_ON); @@ -409,8 +398,8 @@ struct boss_mimiron : public BossAI break; case EVENT_SPAWN_FLAMES_INITIAL: { - if (changeAllowedFlameSpreadTime) - allowedFlameSpreadTime = GameTime::GetGameTime().count(); + if (_changeAllowedFlameSpreadTime) + _allowedFlameSpreadTime = GameTime::GetGameTime().count(); std::vector pg; Map::PlayerList const& pl = me->GetMap()->GetPlayers(); @@ -438,9 +427,9 @@ struct boss_mimiron : public BossAI } break; case EVENT_BERSERK: - berserk = true; + _berserk = true; Talk(SAY_BERSERK); - if (hardmode) + if (_hardmode) me->SummonCreature(33576, 2744.78f, 2569.47f, 364.32f, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 120000); events.ScheduleEvent(EVENT_BERSERK_2, 0ms); break; @@ -470,7 +459,7 @@ struct boss_mimiron : public BossAI case EVENT_SIT_LMK2_INTERVAL: if (Creature* LMK2 = GetLMK2()) { - if (hardmode) + if (_hardmode) { LMK2->CastSpell(LMK2, SPELL_EMERGENCY_MODE, true); if (Vehicle* veh = LMK2->GetVehicleKit()) @@ -550,7 +539,7 @@ struct boss_mimiron : public BossAI case EVENT_VX001_START_FIGHT: if (Creature* VX001 = GetVX001()) { - if (hardmode) + if (_hardmode) VX001->CastSpell(VX001, SPELL_EMERGENCY_MODE, true); VX001->AI()->SetData(1, 2); me->SetInCombatWithZone(); @@ -579,7 +568,7 @@ struct boss_mimiron : public BossAI EnterEvadeMode(EVADE_REASON_OTHER); break; case EVENT_SAY_VX001_DEAD: - changeAllowedFlameSpreadTime = true; + _changeAllowedFlameSpreadTime = true; Talk(SAY_VX001_DEATH); events.ScheduleEvent(EVENT_ENTER_ACU, 7s); break; @@ -599,7 +588,7 @@ struct boss_mimiron : public BossAI case EVENT_ACU_START_ATTACK: if (Creature* ACU = GetACU()) { - if (hardmode) + if (_hardmode) ACU->CastSpell(ACU, SPELL_EMERGENCY_MODE, true); ACU->AI()->SetData(1, 3); me->SetInCombatWithZone(); @@ -687,7 +676,7 @@ struct boss_mimiron : public BossAI LMK2->CastSpell(LMK2, SPELL_SELF_REPAIR, true); //LMK2->SetHealth( LMK2->GetMaxHealth()/2 ); VX001->CastSpell(VX001, SPELL_SELF_REPAIR, true); //VX001->SetHealth( VX001->GetMaxHealth()/2 ); ACU->CastSpell(ACU, SPELL_SELF_REPAIR, true); //ACU->SetHealth( ACU->GetMaxHealth()/2 ); - if (hardmode) + if (_hardmode) { LMK2->CastSpell(LMK2, SPELL_EMERGENCY_MODE, true); VX001->CastSpell(VX001, SPELL_EMERGENCY_MODE, true); @@ -751,7 +740,7 @@ struct boss_mimiron : public BossAI instance->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, NPC_LEVIATHAN_MKII, 1, me); - if (hardmode) + if (_hardmode) if (Creature* computer = me->SummonCreature(NPC_COMPUTER, 2746.7f, 2569.44f, 410.39f, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 1000)) computer->AI()->Talk(TALK_COMPUTER_TERMINATED); @@ -770,7 +759,7 @@ struct boss_mimiron : public BossAI me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); instance->SetBossState(BOSS_MIMIRON, DONE); // spawn chest - if (uint32 chestId = (hardmode ? RAID_MODE(GO_MIMIRON_CHEST_HARD, GO_MIMIRON_CHEST_HERO_HARD) : RAID_MODE(GO_MIMIRON_CHEST, GO_MIMIRON_CHEST_HERO))) + if (uint32 chestId = (_hardmode ? RAID_MODE(GO_MIMIRON_CHEST_HARD, GO_MIMIRON_CHEST_HERO_HARD) : RAID_MODE(GO_MIMIRON_CHEST, GO_MIMIRON_CHEST_HERO))) { if (GameObject* go = me->SummonGameObject(chestId, 2744.65f, 2569.46f, 364.397f, 0, 0, 0, 0, 0, 0)) { @@ -800,9 +789,9 @@ struct boss_mimiron : public BossAI void EnterEvadeMode(EvadeReason why) override { - if (bIsEvading) + if (_isEvading) return; - bIsEvading = true; + _isEvading = true; if (Creature* c = GetLMK2()) c->AI()->EnterEvadeMode(why); @@ -823,7 +812,7 @@ struct boss_mimiron : public BossAI me->ExitVehicle(); BossAI::EnterEvadeMode(why); - bIsEvading = false; + _isEvading = false; } void ResetGameObjects() @@ -907,16 +896,16 @@ struct boss_mimiron : public BossAI } break; case 7: - hardmode = true; + _hardmode = true; break; case 11: - bAchievProximityMine = true; + _achievProximityMine = true; break; case 12: - bAchievBombBot = true; + _achievBombBot = true; break; case 13: - bAchievRocketStrike = true; + _achievRocketStrike = true; break; } } @@ -926,20 +915,32 @@ struct boss_mimiron : public BossAI switch (id) { case 1: - return (hardmode ? 1 : 0); + return (_hardmode ? 1 : 0); case 2: - return (berserk ? 1 : 0); + return (_berserk ? 1 : 0); case 10: - return allowedFlameSpreadTime; + return _allowedFlameSpreadTime; case 11: - return (bAchievProximityMine ? 1 : 0); + return (_achievProximityMine ? 1 : 0); case 12: - return (bAchievBombBot ? 1 : 0); + return (_achievBombBot ? 1 : 0); case 13: - return (bAchievRocketStrike ? 1 : 0); + return (_achievRocketStrike ? 1 : 0); } return 0; } + +private: + bool _isEvading; + bool _hardmode; + bool _berserk; + bool _achievProximityMine; + bool _achievBombBot; + bool _achievRocketStrike; + uint32 _allowedFlameSpreadTime; + bool _changeAllowedFlameSpreadTime; + uint8 _minutesTalkNum; + uint32 _outOfCombatTimer; }; struct npc_ulduar_leviathan_mkii : public ScriptedAI @@ -947,17 +948,12 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI npc_ulduar_leviathan_mkii(Creature* pCreature) : ScriptedAI(pCreature) { instance = me->GetInstanceScript(); - bIsEvading = false; + _isEvading = false; } - InstanceScript* instance; - EventMap events; - bool bIsEvading; - uint8 Phase; - void Reset() override { - Phase = 0; + _phase = 0; if (Unit* c = GetS3()) c->ExitVehicle(); // this should never happen! if (Creature* c = me->SummonCreature(NPC_LEVIATHAN_MKII_CANNON, *me, TEMPSUMMON_MANUAL_DESPAWN)) @@ -967,7 +963,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI me->SetReactState(REACT_AGGRESSIVE); me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - events.Reset(); + _events.Reset(); } void SetData(uint32 id, uint32 value) override @@ -977,35 +973,35 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI switch (value) { case 0: - Phase = 0; - events.Reset(); + _phase = 0; + _events.Reset(); break; case 1: - Phase = 1; + _phase = 1; me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); if (Unit* target = SelectTargetFromPlayerList(75.0f)) AttackStart(target); DoZoneInCombat(); - events.Reset(); - events.ScheduleEvent(EVENT_SPELL_NAPALM_SHELL, 3s); - events.ScheduleEvent(EVENT_SPELL_PLASMA_BLAST, 10s); - events.ScheduleEvent(EVENT_SPELL_SHOCK_BLAST, 20s); - events.ScheduleEvent(EVENT_PROXIMITY_MINES_1, 6s); + _events.Reset(); + _events.ScheduleEvent(EVENT_SPELL_NAPALM_SHELL, 3s); + _events.ScheduleEvent(EVENT_SPELL_PLASMA_BLAST, 10s); + _events.ScheduleEvent(EVENT_SPELL_SHOCK_BLAST, 20s); + _events.ScheduleEvent(EVENT_PROXIMITY_MINES_1, 6s); if (Creature* c = GetMimiron()) if (c->AI()->GetData(1)) - events.ScheduleEvent(EVENT_FLAME_SUPPRESSION_50000, 60s); + _events.ScheduleEvent(EVENT_FLAME_SUPPRESSION_50000, 60s); break; case 4: me->SetReactState(REACT_AGGRESSIVE); DoResetThreatList(); - Phase = 4; + _phase = 4; me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); if (Unit* target = SelectTargetFromPlayerList(75.0f)) AttackStart(target); DoZoneInCombat(); - events.Reset(); - events.ScheduleEvent(EVENT_SPELL_SHOCK_BLAST, 20s); - events.ScheduleEvent(EVENT_PROXIMITY_MINES_1, 6s); + _events.Reset(); + _events.ScheduleEvent(EVENT_SPELL_SHOCK_BLAST, 20s); + _events.ScheduleEvent(EVENT_PROXIMITY_MINES_1, 6s); break; } } @@ -1019,7 +1015,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI if (me->GetReactState() == REACT_PASSIVE) return; me->SetReactState(REACT_PASSIVE); - if (Phase == 1) + if (_phase == 1) { if (!me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) { @@ -1037,7 +1033,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI c->AI()->SetData(0, 1); } } - else if (Phase == 4) + else if (_phase == 4) { if (!me->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE)) { @@ -1063,7 +1059,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI if (!UpdateVictim()) return; - events.Update(diff); + _events.Update(diff); if (!me->HasUnitState(UNIT_STATE_CASTING)) DoMeleeAttackIfReady(); @@ -1072,7 +1068,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI if (!cannon || cannon->HasUnitState(UNIT_STATE_CASTING) || me->HasUnitState(UNIT_STATE_CASTING) || me->HasSilenceAura()) return; - switch (events.ExecuteEvent()) + switch (_events.ExecuteEvent()) { case 0: break; @@ -1094,7 +1090,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI if (pTarget) cannon->CastSpell(pTarget, SPELL_NAPALM_SHELL, false); - events.Repeat(14s); + _events.Repeat(14s); } break; case EVENT_SPELL_PLASMA_BLAST: @@ -1103,12 +1099,12 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI Talk(EMOTE_PLASMA_BLAST); cannon->CastSpell(victim, SPELL_PLASMA_BLAST, false); } - events.Repeat(22s); + _events.Repeat(22s); break; case EVENT_SPELL_SHOCK_BLAST: me->CastSpell(me->GetVictim(), SPELL_SHOCK_BLAST, false); - events.Repeat(30s); - events.ScheduleEvent(EVENT_PROXIMITY_MINES_1, 8s); + _events.Repeat(30s); + _events.ScheduleEvent(EVENT_PROXIMITY_MINES_1, 8s); break; case EVENT_PROXIMITY_MINES_1: for (uint8 i = 0; i < 10; ++i) @@ -1129,7 +1125,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI if (who->IsPlayer()) if (Creature* c = GetMimiron()) { - if (Phase == 1) + if (_phase == 1) { c->AI()->Talk(SAY_MKII_SLAY); } @@ -1142,9 +1138,9 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI void EnterEvadeMode(EvadeReason why) override { - if (bIsEvading) + if (_isEvading) return; - bIsEvading = true; + _isEvading = true; me->RemoveAllAuras(); me->ExitVehicle(); @@ -1153,7 +1149,7 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI if (Creature* mimiron = GetMimiron()) mimiron->AI()->EnterEvadeMode(why); - bIsEvading = false; + _isEvading = false; } void PassengerBoarded(Unit* p, int8 /*seat*/, bool apply) override @@ -1182,6 +1178,12 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI me->SetReactState(REACT_AGGRESSIVE); } } + +private: + InstanceScript* instance; + EventMap _events; + bool _isEvading; + uint8 _phase; }; struct npc_ulduar_vx001 : public ScriptedAI @@ -1189,26 +1191,18 @@ struct npc_ulduar_vx001 : public ScriptedAI npc_ulduar_vx001(Creature* pCreature) : ScriptedAI(pCreature) { instance = me->GetInstanceScript(); - bIsEvading = false; + _isEvading = false; } - InstanceScript* instance; - EventMap events; - bool bIsEvading; - uint8 Phase; - bool fighting; - bool leftarm; - uint32 spinningUpOrientation; - uint16 spinningUpTimer; - void Reset() override { - Phase = 0; - fighting = false; - leftarm = false; - spinningUpTimer = 0; + _phase = 0; + _fighting = false; + _leftArm = false; + _spinningUpOrientation = 0; + _spinningUpTimer = 0; me->SetRegeneratingHealth(false); - events.Reset(); + _events.Reset(); } void AttackStart(Unit* /*who*/) override {} @@ -1220,41 +1214,41 @@ struct npc_ulduar_vx001 : public ScriptedAI switch (value) { case 0: - Phase = 0; - fighting = false; + _phase = 0; + _fighting = false; me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - events.Reset(); + _events.Reset(); break; case 2: - Phase = 2; - fighting = true; + _phase = 2; + _fighting = true; me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_SPELL_CAST_OMNI); me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - events.Reset(); - events.ScheduleEvent(EVENT_SPELL_HEAT_WAVE, 10s); - events.ScheduleEvent(EVENT_SPELL_ROCKET_STRIKE, 16s); - events.ScheduleEvent(EVENT_SPELL_RAPID_BURST, 0ms); - events.ScheduleEvent(EVENT_SPELL_SPINNING_UP, 30s); - events.ScheduleEvent(EVENT_REINSTALL_ROCKETS, 3s); + _events.Reset(); + _events.ScheduleEvent(EVENT_SPELL_HEAT_WAVE, 10s); + _events.ScheduleEvent(EVENT_SPELL_ROCKET_STRIKE, 16s); + _events.ScheduleEvent(EVENT_SPELL_RAPID_BURST, 0ms); + _events.ScheduleEvent(EVENT_SPELL_SPINNING_UP, 30s); + _events.ScheduleEvent(EVENT_REINSTALL_ROCKETS, 3s); if (Creature* c = GetMimiron()) if (c->AI()->GetData(1)) { - events.ScheduleEvent(EVENT_FLAME_SUPPRESSION_10, 7s); - events.ScheduleEvent(EVENT_FROST_BOMB, 1s); + _events.ScheduleEvent(EVENT_FLAME_SUPPRESSION_10, 7s); + _events.ScheduleEvent(EVENT_FROST_BOMB, 1s); } break; case 4: - Phase = 4; - fighting = true; + _phase = 4; + _fighting = true; me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - events.Reset(); - events.ScheduleEvent(EVENT_REINSTALL_ROCKETS, 3s); - events.ScheduleEvent(EVENT_SPELL_ROCKET_STRIKE, 16s); - events.ScheduleEvent(EVENT_HAND_PULSE, 1ms); - events.ScheduleEvent(EVENT_SPELL_SPINNING_UP, 30s); + _events.Reset(); + _events.ScheduleEvent(EVENT_REINSTALL_ROCKETS, 3s); + _events.ScheduleEvent(EVENT_SPELL_ROCKET_STRIKE, 16s); + _events.ScheduleEvent(EVENT_HAND_PULSE, 1ms); + _events.ScheduleEvent(EVENT_SPELL_SPINNING_UP, 30s); if (Creature* c = GetMimiron()) if (c->AI()->GetData(1)) - events.ScheduleEvent(EVENT_FROST_BOMB, 1s); + _events.ScheduleEvent(EVENT_FROST_BOMB, 1s); break; } } @@ -1262,7 +1256,7 @@ struct npc_ulduar_vx001 : public ScriptedAI uint32 GetData(uint32 /*id*/) const override { - return spinningUpOrientation; + return _spinningUpOrientation; } void DoAction(int32 action) override @@ -1283,7 +1277,7 @@ struct npc_ulduar_vx001 : public ScriptedAI if (me->GetReactState() == REACT_PASSIVE) return; me->SetReactState(REACT_PASSIVE); - if (Phase == 2) + if (_phase == 2) { if (!me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) { @@ -1298,7 +1292,7 @@ struct npc_ulduar_vx001 : public ScriptedAI c->AI()->SetData(0, 2); } } - else if (Phase == 4) + else if (_phase == 4) { if (!me->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE)) { @@ -1321,41 +1315,41 @@ struct npc_ulduar_vx001 : public ScriptedAI void UpdateAI(uint32 diff) override { - if (!fighting) + if (!_fighting) return; - events.Update(diff); + _events.Update(diff); - if (spinningUpTimer) // executed about a second after starting casting to ensure players can see the correct direction + if (_spinningUpTimer) // executed about a second after starting casting to ensure players can see the correct direction { - if (spinningUpTimer <= diff) + if (_spinningUpTimer <= diff) { - float angle = (spinningUpOrientation * 2 * M_PI) / 100.0f; + float angle = (_spinningUpOrientation * 2 * M_PI) / 100.0f; me->SetFacingTo(angle); - spinningUpTimer = 0; + _spinningUpTimer = 0; } else - spinningUpTimer -= diff; + _spinningUpTimer -= diff; } if (me->HasUnitState(UNIT_STATE_CASTING)) return; - switch (events.ExecuteEvent()) + switch (_events.ExecuteEvent()) { case 0: break; case EVENT_SPELL_HEAT_WAVE: me->CastSpell(me, SPELL_HEAT_WAVE, true); - events.Repeat(10s); + _events.Repeat(10s); break; case EVENT_SPELL_ROCKET_STRIKE: if (Vehicle* vk = me->GetVehicleKit()) { - for( int i = 0; i < (Phase / 2); ++i ) + for( int i = 0; i < (_phase / 2); ++i ) { - uint8 index = (Phase == 2 ? rand() % 2 : i); + uint8 index = (_phase == 2 ? rand() % 2 : i); if (Unit* r = vk->GetPassenger(5 + index)) if (Player* temp = SelectTargetFromPlayerList(100.0f)) { @@ -1364,15 +1358,15 @@ struct npc_ulduar_vx001 : public ScriptedAI Position exitPos = r->GetPosition(); exitPos.m_positionX += cos(me->GetOrientation()) * 2.35f; exitPos.m_positionY += std::sin(me->GetOrientation()) * 2.35f; - exitPos.m_positionZ += 2.0f * Phase; + exitPos.m_positionZ += 2.0f * _phase; r->_ExitVehicle(&exitPos); me->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, r->GetGUID()); if (r->IsCreature()) r->ToCreature()->AI()->SetData(0, 0); } } - events.Repeat(20s); - events.ScheduleEvent(EVENT_REINSTALL_ROCKETS, 10s); + _events.Repeat(20s); + _events.ScheduleEvent(EVENT_REINSTALL_ROCKETS, 10s); } break; case EVENT_REINSTALL_ROCKETS: @@ -1391,7 +1385,7 @@ struct npc_ulduar_vx001 : public ScriptedAI me->CastSpell(p, SPELL_RAPID_BURST, true); me->SetFacingToObject(p); } - events.Repeat(3200ms); + _events.Repeat(3200ms); break; case EVENT_HAND_PULSE: if (Player* p = SelectTargetFromPlayerList(80.0f)) @@ -1402,7 +1396,7 @@ struct npc_ulduar_vx001 : public ScriptedAI vb->SendMeleeAttackStop(); vb->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - if (!leftarm) + if (!_leftArm) { vb->HandleEmoteCommand(EMOTE_ONESHOT_CUSTOM_SPELL_03); me->CastSpell(p, SPELL_HAND_PULSE_R, false); @@ -1414,18 +1408,18 @@ struct npc_ulduar_vx001 : public ScriptedAI } } - leftarm = !leftarm; + _leftArm = !_leftArm; } - events.Repeat(1750ms); + _events.Repeat(1750ms); break; case EVENT_SPELL_SPINNING_UP: - events.Repeat(45s); + _events.Repeat(45s); if (Player* p = SelectTargetFromPlayerList(80.0f)) { float angle = me->GetAngle(p); - spinningUpOrientation = (uint32)((angle * 100.0f) / (2 * M_PI)); - spinningUpTimer = 1500; + _spinningUpOrientation = (uint32)((angle * 100.0f) / (2 * M_PI)); + _spinningUpTimer = 1500; me->SetFacingTo(angle); me->CastSpell(p, SPELL_SPINNING_UP, true); if (Unit* vehicle = me->GetVehicleBase()) @@ -1433,16 +1427,16 @@ struct npc_ulduar_vx001 : public ScriptedAI vehicle->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_CUSTOM_SPELL_01); vehicle->HandleEmoteCommand(EMOTE_STATE_CUSTOM_SPELL_01); } - events.RescheduleEvent((Phase == 2 ? EVENT_SPELL_RAPID_BURST : EVENT_HAND_PULSE), 14s + 500ms); + _events.RescheduleEvent((_phase == 2 ? EVENT_SPELL_RAPID_BURST : EVENT_HAND_PULSE), 14s + 500ms); } break; case EVENT_FLAME_SUPPRESSION_10: me->CastSpell(me, SPELL_FLAME_SUPPRESSANT_10yd, false); - events.Repeat(10s); + _events.Repeat(10s); break; case EVENT_FROST_BOMB: me->CastCustomSpell(SPELL_VX001_FROST_BOMB, SPELLVALUE_MAX_TARGETS, 1, (Unit*)nullptr, false); - events.Repeat(45s); + _events.Repeat(45s); break; } } @@ -1454,7 +1448,7 @@ struct npc_ulduar_vx001 : public ScriptedAI if (who->IsPlayer()) if (Creature* c = GetMimiron()) { - if (Phase == 2) + if (_phase == 2) { c->AI()->Talk(SAY_VX001_SLAY); } @@ -1467,9 +1461,9 @@ struct npc_ulduar_vx001 : public ScriptedAI void EnterEvadeMode(EvadeReason why) override { - if (bIsEvading) + if (_isEvading) return; - bIsEvading = true; + _isEvading = true; me->RemoveAllAuras(); me->ExitVehicle(); @@ -1478,7 +1472,7 @@ struct npc_ulduar_vx001 : public ScriptedAI if (Creature* mimiron = GetMimiron()) mimiron->AI()->EnterEvadeMode(why); - bIsEvading = false; + _isEvading = false; } void PassengerBoarded(Unit* p, int8 /*seat*/, bool apply) override @@ -1495,28 +1489,33 @@ struct npc_ulduar_vx001 : public ScriptedAI me->SetReactState(REACT_AGGRESSIVE); } } + +private: + InstanceScript* instance; + EventMap _events; + bool _isEvading; + bool _fighting; + bool _leftArm; + uint32 _spinningUpOrientation; + uint16 _spinningUpTimer; + uint8 _phase; }; struct npc_ulduar_aerial_command_unit : public ScriptedAI { - npc_ulduar_aerial_command_unit(Creature* pCreature) : ScriptedAI(pCreature), summons(me) + npc_ulduar_aerial_command_unit(Creature* pCreature) : ScriptedAI(pCreature), _summons(me) { instance = me->GetInstanceScript(); - bIsEvading = false; + _isEvading = false; me->SetDisableGravity(true); } - InstanceScript* instance; - EventMap events; - SummonList summons; - bool bIsEvading; - uint8 Phase; - void Reset() override { - Phase = 0; - events.Reset(); - summons.DespawnAll(); + _phase = 0; + _isDefeated = false; + _events.Reset(); + _summons.DespawnAll(); me->SetCombatMovement(false); /// @todo: research ACU behaviour } @@ -1527,31 +1526,31 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI switch (value) { case 0: - Phase = 0; - events.Reset(); + _phase = 0; + _events.Reset(); break; case 3: - Phase = 3; + _phase = 3; me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); DoZoneInCombat(); - events.Reset(); - events.ScheduleEvent(EVENT_SUMMON_BOMB_BOT, 15s); - events.ScheduleEvent(EVENT_SUMMON_ASSAULT_BOT, 1s); - events.ScheduleEvent(EVENT_SUMMON_JUNK_BOT, 10s); + _events.Reset(); + _events.ScheduleEvent(EVENT_SUMMON_BOMB_BOT, 15s); + _events.ScheduleEvent(EVENT_SUMMON_ASSAULT_BOT, 1s); + _events.ScheduleEvent(EVENT_SUMMON_JUNK_BOT, 10s); if (Creature* c = GetMimiron()) if (c->AI()->GetData(1)) - events.ScheduleEvent(EVENT_SUMMON_EMERGENCY_FIRE_BOTS, 0ms); + _events.ScheduleEvent(EVENT_SUMMON_EMERGENCY_FIRE_BOTS, 0ms); break; case 4: me->SetReactState(REACT_AGGRESSIVE); DoResetThreatList(); - Phase = 4; + _phase = 4; me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); if (Unit* target = SelectTargetFromPlayerList(75.0f)) AttackStart(target); DoZoneInCombat(); - events.Reset(); - events.ScheduleEvent(EVENT_SPELL_PLASMA_BALL, 0ms); + _events.Reset(); + _events.ScheduleEvent(EVENT_SPELL_PLASMA_BALL, 0ms); } } } @@ -1566,7 +1565,7 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI me->SetReactState(REACT_PASSIVE); me->SetDisableGravity(false); me->GetMotionMaster()->MoveFall(); - events.DelayEvents(25s); + _events.DelayEvents(25s); break; case DO_ENABLE_AERIAL: me->SetDisableGravity(true); @@ -1576,7 +1575,7 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI }, 2s); break; case 1337: - summons.DespawnAll(); + _summons.DespawnAll(); break; } } @@ -1586,10 +1585,11 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI if (damage >= me->GetHealth() || me->GetHealth() < 15000) { damage = 0; - if (me->GetReactState() == REACT_PASSIVE) + if (_isDefeated) return; + _isDefeated = true; me->SetReactState(REACT_PASSIVE); - if (Phase == 3) + if (_phase == 3) { if (!me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) { @@ -1608,7 +1608,7 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI c->AI()->SetData(0, 3); } } - else if (Phase == 4) + else if (_phase == 4) { if (!me->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE)) { @@ -1637,30 +1637,30 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI if (!UpdateVictim()) return; - events.Update(diff); + _events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; - switch (events.ExecuteEvent()) + switch (_events.ExecuteEvent()) { case 0: break; case EVENT_SUMMON_BOMB_BOT: me->CastSpell(me, SPELL_SUMMON_BOMB_BOT, false); - events.Repeat(15s); + _events.Repeat(15s); break; case EVENT_SUMMON_ASSAULT_BOT: if (GameObject* pad = me->FindNearestGameObject(RAND(194742, 194746, 194745), 200.0f)) if (Creature* trigger = me->SummonCreature(NPC_BOT_SUMMON_TRIGGER, *pad, TEMPSUMMON_TIMED_DESPAWN, 15000)) trigger->AI()->DoAction(2); - events.Repeat(30s); + _events.Repeat(30s); break; case EVENT_SUMMON_JUNK_BOT: if (GameObject* pad = me->FindNearestGameObject(RAND(194741, 194744, 194747), 200.0f)) if (Creature* trigger = me->SummonCreature(NPC_BOT_SUMMON_TRIGGER, *pad, TEMPSUMMON_TIMED_DESPAWN, 15000)) trigger->AI()->DoAction(1); - events.Repeat(10s); + _events.Repeat(10s); break; case EVENT_SUMMON_EMERGENCY_FIRE_BOTS: { @@ -1669,12 +1669,12 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI if (GameObject* pad = me->FindNearestGameObject(ids[i], 200.0f)) if (Creature* trigger = me->SummonCreature(NPC_BOT_SUMMON_TRIGGER, *pad, TEMPSUMMON_MANUAL_DESPAWN)) trigger->AI()->DoAction(3); - events.Repeat(45s); + _events.Repeat(45s); } break; } - DoSpellAttackIfReady(Phase == 3 ? SPELL_PLASMA_BALL_P1 : SPELL_PLASMA_BALL_P2); + DoSpellAttackIfReady(_phase == 3 ? SPELL_PLASMA_BALL_P1 : SPELL_PLASMA_BALL_P2); } void MoveInLineOfSight(Unit* /*mover*/) override {} @@ -1684,7 +1684,7 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI if (who->IsPlayer()) if (Creature* c = GetMimiron()) { - if (Phase == 3) + if (_phase == 3) { c->AI()->Talk(SAY_AERIAL_SLAY); } @@ -1697,9 +1697,9 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI void EnterEvadeMode(EvadeReason why) override { - if (bIsEvading) + if (_isEvading) return; - bIsEvading = true; + _isEvading = true; me->RemoveAllAuras(); me->ExitVehicle(); @@ -1708,44 +1708,49 @@ struct npc_ulduar_aerial_command_unit : public ScriptedAI if (Creature* mimiron = GetMimiron()) mimiron->AI()->EnterEvadeMode(why); - bIsEvading = false; + _isEvading = false; } void JustSummoned(Creature* s) override { - summons.Summon(s); + _summons.Summon(s); if (s->GetEntry() == NPC_BOMB_BOT) s->m_positionZ = 364.34f; } void SummonedCreatureDespawn(Creature* s) override { - summons.Despawn(s); + _summons.Despawn(s); } void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override { if (spell->Id == SPELL_SELF_REPAIR) { + _isDefeated = false; me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); me->SetReactState(REACT_AGGRESSIVE); } } + +private: + InstanceScript* instance; + EventMap _events; + SummonList _summons; + bool _isEvading; + bool _isDefeated; + uint8 _phase; }; struct npc_ulduar_proximity_mine : public ScriptedAI { npc_ulduar_proximity_mine(Creature* pCreature) : ScriptedAI(pCreature) { - exploded = false; - timer = 2500; - timer2 = 35000; + _exploded = false; + _timer = 2500; + _timer2 = 35000; } - bool exploded; - uint16 timer; - uint16 timer2; - void AttackStart(Unit* /*who*/) override {} void MoveInLineOfSight(Unit* /*who*/) override {} bool CanAIAttack(Unit const* /*target*/) const override { return false; } @@ -1753,30 +1758,35 @@ struct npc_ulduar_proximity_mine : public ScriptedAI // MoveInLineOfSight is checked every few yards, can't use it void UpdateAI(uint32 diff) override { - if (timer2 <= diff) + if (_timer2 <= diff) { - timer2 = 35000; - if (!exploded) + _timer2 = 35000; + if (!_exploded) { - exploded = true; + _exploded = true; me->CastSpell(me, SPELL_MINE_EXPLOSION, false); } } else - timer2 -= diff; + _timer2 -= diff; - if (timer <= diff) + if (_timer <= diff) { - timer = 500; - if (!exploded && SelectTargetFromPlayerList(1.9f)) + _timer = 500; + if (!_exploded && SelectTargetFromPlayerList(1.9f)) { - exploded = true; + _exploded = true; me->CastSpell(me, SPELL_MINE_EXPLOSION, false); } } else - timer -= diff; + _timer -= diff; } + +private: + bool _exploded; + uint16 _timer; + uint16 _timer2; }; class spell_ulduar_mimiron_mine_explosion : public SpellScript @@ -1833,13 +1843,10 @@ struct npc_ulduar_bot_summon_trigger : public NullCreatureAI { npc_ulduar_bot_summon_trigger(Creature* pCreature) : NullCreatureAI(pCreature) { } - uint32 timer; - uint8 option; - void Reset() override { - timer = 8000; - option = 0; + _timer = 8000; + _option = 0; } void DoAction(int32 param) override @@ -1848,29 +1855,29 @@ struct npc_ulduar_bot_summon_trigger : public NullCreatureAI { case 1: me->CastSpell(me, SPELL_BEAM_GREEN, true); - option = 1; + _option = 1; break; case 2: me->CastSpell(me, SPELL_BEAM_YELLOW, true); - option = 2; + _option = 2; break; case 3: me->CastSpell(me, SPELL_BEAM_BLUE, true); - option = 3; + _option = 3; break; } } void UpdateAI(uint32 diff) override { - if (timer <= diff) + if (_timer <= diff) { uint32 option_npcid[3] = {NPC_JUNK_BOT, NPC_ASSAULT_BOT, NPC_EMERGENCY_FIRE_BOT}; InstanceScript* instance = me->GetInstanceScript(); if (Creature* ACU = GetACU()) // ACU summons for easy removing - if (Creature* bot = ACU->SummonCreature( option_npcid[option - 1], *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 25000)) + if (Creature* bot = ACU->SummonCreature( option_npcid[_option - 1], *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 25000)) { - if (option < 3) + if (_option < 3) bot->SetInCombatWithZone(); if (Creature* m = GetMimiron()) if (m->AI()->GetData(1)) // hardmode @@ -1878,11 +1885,15 @@ struct npc_ulduar_bot_summon_trigger : public NullCreatureAI } me->DespawnOrUnsummon(500ms); - timer = 99999; + _timer = 99999; } else - timer -= diff; + _timer -= diff; } + +private: + uint32 _timer; + uint8 _option; }; // 64444 - Magnetic Core Summon @@ -2053,20 +2064,16 @@ struct npc_ulduar_flames_initial : public NullCreatureAI { npc_ulduar_flames_initial(Creature* pCreature) : NullCreatureAI(pCreature) { - CreateTime = GameTime::GetGameTime().count(); - events.Reset(); - events.ScheduleEvent(EVENT_FLAMES_SPREAD, 5750ms); + _createTime = GameTime::GetGameTime().count(); + _events.Reset(); + _events.ScheduleEvent(EVENT_FLAMES_SPREAD, 5750ms); if (Creature* flame = me->SummonCreature(NPC_FLAMES_SPREAD, me->GetPositionX(), me->GetPositionY(), 364.32f, 0.0f)) { - FlameList.push_back(flame->GetGUID()); + _flameList.push_back(flame->GetGUID()); flame->CastSpell(flame, SPELL_FLAMES_AURA, true); } } - GuidList FlameList; - EventMap events; - uint32 CreateTime; - void DoAction(int32 action) override { if (action == 1337) @@ -2077,7 +2084,7 @@ struct npc_ulduar_flames_initial : public NullCreatureAI { if (Creature* flame = me->SummonCreature(NPC_FLAMES_SPREAD, x, y, 364.32f, 0.0f)) { - FlameList.push_back(flame->GetGUID()); + _flameList.push_back(flame->GetGUID()); if (Creature* c = me->FindNearestCreature(NPC_FLAMES_SPREAD, 10.0f)) if (c->GetExactDist2d(flame->GetPositionX(), flame->GetPositionY()) <= 4.0f) return; @@ -2087,15 +2094,15 @@ struct npc_ulduar_flames_initial : public NullCreatureAI void RemoveFlame(ObjectGuid guid) { - FlameList.remove(guid); + _flameList.remove(guid); } void RemoveAll() { - for (ObjectGuid const& guid : FlameList) + for (ObjectGuid const& guid : _flameList) if (Creature* c = ObjectAccessor::GetCreature(*me, guid)) c->DespawnOrUnsummon(); - FlameList.clear(); + _flameList.clear(); me->DespawnOrUnsummon(); } @@ -2108,15 +2115,15 @@ struct npc_ulduar_flames_initial : public NullCreatureAI return; } - events.Update(diff); + _events.Update(diff); - switch (events.ExecuteEvent()) + switch (_events.ExecuteEvent()) { case 0: break; case EVENT_FLAMES_SPREAD: { - if (FlameList.empty()) + if (_flameList.empty()) { me->DespawnOrUnsummon(); return; @@ -2124,10 +2131,10 @@ struct npc_ulduar_flames_initial : public NullCreatureAI if (InstanceScript* instance = me->GetInstanceScript()) if (Creature* mimiron = GetMimiron()) - if (CreateTime < mimiron->AI()->GetData(10)) + if (_createTime < mimiron->AI()->GetData(10)) break; - Creature* last = ObjectAccessor::GetCreature(*me, FlameList.back()); + Creature* last = ObjectAccessor::GetCreature(*me, _flameList.back()); if (last) { float prevdist = 100.0f; @@ -2149,11 +2156,16 @@ struct npc_ulduar_flames_initial : public NullCreatureAI } } - events.Repeat(5750ms); + _events.Repeat(5750ms); } break; } } + +private: + GuidList _flameList; + EventMap _events; + uint32 _createTime; }; struct npc_ulduar_flames_spread : public NullCreatureAI @@ -2191,46 +2203,47 @@ struct npc_ulduar_emergency_fire_bot : public ScriptedAI { npc_ulduar_emergency_fire_bot(Creature* pCreature) : ScriptedAI(pCreature) { - events.Reset(); - events.ScheduleEvent(EVENT_EMERGENCY_BOT_CHECK, 1s); + _events.Reset(); + _events.ScheduleEvent(EVENT_EMERGENCY_BOT_CHECK, 1s); } - EventMap events; - void MoveInLineOfSight(Unit*) override {} void AttackStart(Unit*) override {} void MovementInform(uint32 type, uint32 id) override { if (type == POINT_MOTION_TYPE && id == 1) - events.ScheduleEvent(EVENT_EMERGENCY_BOT_ATTACK, 0ms); + _events.ScheduleEvent(EVENT_EMERGENCY_BOT_ATTACK, 0ms); } void UpdateAI(uint32 diff) override { - events.Update(diff); - switch (events.ExecuteEvent()) + _events.Update(diff); + switch (_events.ExecuteEvent()) { case 0: break; case EVENT_EMERGENCY_BOT_CHECK: - events.Repeat(15s); + _events.Repeat(15s); if (Creature* flame = me->FindNearestCreature(NPC_FLAMES_SPREAD, 150.0f, true)) { me->SetOrientation(me->GetAngle(flame->GetPositionX(), flame->GetPositionY())); float dist = me->GetExactDist2d(flame); if (dist <= 5.0f) - events.ScheduleEvent(EVENT_EMERGENCY_BOT_ATTACK, 0ms); + _events.ScheduleEvent(EVENT_EMERGENCY_BOT_ATTACK, 0ms); else me->GetMotionMaster()->MovePoint(1, me->GetPositionX() + (dist - 5.0f)*cos(me->GetOrientation()), me->GetPositionY() + (dist - 5.0f)*sin(me->GetOrientation()), 364.32f); } break; case EVENT_EMERGENCY_BOT_ATTACK: me->CastSpell((Unit*)nullptr, SPELL_WATER_SPRAY, false); - events.RescheduleEvent(EVENT_EMERGENCY_BOT_CHECK, 5s); + _events.RescheduleEvent(EVENT_EMERGENCY_BOT_CHECK, 5s); break; } } + +private: + EventMap _events; }; struct npc_ulduar_rocket_strike_trigger : public NullCreatureAI From 9c05983b2a03eb6d89575f9a1d75454a8bce69e7 Mon Sep 17 00:00:00 2001 From: sogladev Date: Sun, 22 Mar 2026 21:45:31 +0100 Subject: [PATCH 046/132] fix(Core/SmartAI): despawn follow if player goes out of range / offline (#24450) --- src/server/game/AI/SmartScripts/SmartAI.cpp | 57 +++++++++++++++------ src/server/game/AI/SmartScripts/SmartAI.h | 2 + 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 2c165c526..fc4481d50 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -109,6 +109,45 @@ void SmartAI::UpdateDespawn(const uint32 diff) mDespawnTime -= diff; } +void SmartAI::UpdateFollow(const uint32 diff) +{ + if (!mFollowGuid) + return; + + if (mFollowArrivedTimer < diff) + { + if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, mFollowArrivedAlive)) + StopFollow(true); + else + mFollowArrivedTimer = 1000; + } + else + mFollowArrivedTimer -= diff; + + if (mFollowGuid.IsPlayer()) + { + if (_followCheckTimer < diff) + { + bool shouldDespawn = false; + if (Player* player = ObjectAccessor::FindPlayer(mFollowGuid)) + { + float checkDist = me->GetInstanceScript() ? SMART_ESCORT_MAX_PLAYER_DIST * 2 : SMART_ESCORT_MAX_PLAYER_DIST; + if (!me->IsWithinDistInMap(player, checkDist)) + shouldDespawn = true; + } + else + shouldDespawn = true; + + if (shouldDespawn) + me->DespawnOrUnsummon(); + + _followCheckTimer = 1000; + } + else + _followCheckTimer -= diff; + } +} + WaypointData const* SmartAI::GetNextWayPoint() { if (!mWayPoints || mWayPoints->empty()) @@ -536,23 +575,7 @@ void SmartAI::UpdateAI(uint32 diff) GetScript()->OnUpdate(diff); UpdatePath(diff); UpdateDespawn(diff); - - //TODO move to void - if (mFollowGuid) - { - if (mFollowArrivedTimer < diff) - { - if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, mFollowArrivedAlive)) - { - StopFollow(true); - return; - } - - mFollowArrivedTimer = 1000; - } - else - mFollowArrivedTimer -= diff; - } + UpdateFollow(diff); if (!IsAIControlled()) { diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index 5165058bb..34e6aa658 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -222,6 +222,7 @@ private: bool mIsCharmed; uint32 mFollowCreditType; uint32 mFollowArrivedTimer; + uint32 _followCheckTimer; uint32 mFollowCredit; uint32 mFollowArrivedEntry; bool mFollowArrivedAlive; @@ -253,6 +254,7 @@ private: uint32 mDespawnTime; uint32 mDespawnState; void UpdateDespawn(const uint32 diff); + void UpdateFollow(const uint32 diff); uint32 mEscortInvokerCheckTimer; bool mJustReset; From fa9f28309b03f56ce3565b8397bb25e72dd2f25c Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:38:49 -0500 Subject: [PATCH 047/132] fix(Core/AI): Prevent guardians from switching targets on owner combat interaction (#25190) Co-authored-by: blinkysc --- src/server/game/AI/CreatureAI.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 161d11b14..f8c69e141 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -198,6 +198,10 @@ void CreatureAI::OnOwnerCombatInteraction(Unit* target) if (!target || !me->IsAlive()) return; + // Prevent guardian from disengaging from current target + if (me->GetVictim() && me->GetVictim()->IsAlive()) + return; + if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true)) AttackStart(target); } From 985b5922b97418b7f0f45edb8c622ee1a830185e Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:42:09 -0500 Subject: [PATCH 048/132] fix(DB/Scripts): CoS Brandon Eiredeck and citizens now talk (#24043) Co-authored-by: blinkysc Co-authored-by: sudlud --- .../rev_1764892137978064536.sql | 21 ++++++++++++++++++ .../culling_of_stratholme.cpp | 22 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1764892137978064536.sql diff --git a/data/sql/updates/pending_db_world/rev_1764892137978064536.sql b/data/sql/updates/pending_db_world/rev_1764892137978064536.sql new file mode 100644 index 000000000..43d7a304f --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1764892137978064536.sql @@ -0,0 +1,21 @@ +-- +-- Brandon Eiredeck (31023) should cower and say "Please, I'm just a delivery man!" +-- Agitated Stratholme Citizens (31126) near Brandon should chastise him about the grain +-- +DELETE FROM `creature_text` WHERE `CreatureID` = 31023; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(31023, 0, 0, 'Please, I''m just a delivery man!', 12, 0, 100, 431, 0, 0, 32044, 0, 'Brandon Eiredeck - Ambient'); + +-- Add group 3 for the Brandon Eiredeck crowd (different from Barthilas crowd) +DELETE FROM `creature_text` WHERE `CreatureID` = 31126 AND `GroupID` = 3; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(31126, 3, 0, 'I think it''s your grain that''s making us sick!', 12, 0, 100, 5, 0, 0, 32032, 0, 'Agitated Stratholme Citizen - Brandon Crowd'), +(31126, 3, 1, 'We were fine until we ate from this tainted grain of yours!', 12, 0, 100, 5, 0, 0, 32035, 0, 'Agitated Stratholme Citizen - Brandon Crowd'); + +-- Enable SmartAI for Brandon Eiredeck +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 31023; + +DELETE FROM `smart_scripts` WHERE `entryorguid` = 31023 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`, `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 +(31023, 0, 0, 0, 1, 0, 100, 0, 5000, 15000, 15000, 25000, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Brandon Eiredeck - OOC - Say Line 0'), +(31023, 0, 1, 0, 1, 0, 100, 0, 0, 0, 3000, 3000, 0, 0, 17, 431, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Brandon Eiredeck - OOC - Set Emote State Cower'); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp index 2df10021e..66b76a02e 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp @@ -175,6 +175,10 @@ enum Misc ACTION_FORCE_CHANGE_LOCK = 2, POINT_CHRONOS = 1, + + // Brandon Eiredeck crowd + NPC_BRANDON_EIREDECK = 31023, + SAY_BRANDON_CROWD_AMBIENT = 3, }; enum Events @@ -1521,12 +1525,17 @@ public: pInstance = me->GetInstanceScript(); if (!pInstance || pInstance->GetData(DATA_ARTHAS_EVENT) < COS_PROGRESS_FINISHED_CITY_INTRO) allowTimer++; + + isBrandonCrowd = me->GetDistance(2267.86f, 1144.93f, 138.403f) < 10.0f; + ambientTalkTimer = isBrandonCrowd ? urand(5000, 15000) : 0; } bool locked; uint32 changeTimer; InstanceScript* pInstance; uint32 allowTimer; + bool isBrandonCrowd; + uint32 ambientTalkTimer; void Reset() override { @@ -1587,6 +1596,19 @@ public: { ScriptedAI::UpdateAI(diff); + if (isBrandonCrowd && ambientTalkTimer) + { + if (ambientTalkTimer <= diff) + { + Talk(SAY_BRANDON_CROWD_AMBIENT); + ambientTalkTimer = urand(15000, 25000); + } + else + ambientTalkTimer -= diff; + + return; + } + if (allowTimer) { allowTimer += diff; From f1a027c250b3874fe0c414d11e345d5328a2b08b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Mar 2026 21:43:16 +0000 Subject: [PATCH 049/132] chore(DB): import pending files Referenced commit(s): 985b5922b97418b7f0f45edb8c622ee1a830185e --- .../rev_1764892137978064536.sql => db_world/2026_03_22_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1764892137978064536.sql => db_world/2026_03_22_02.sql} (98%) diff --git a/data/sql/updates/pending_db_world/rev_1764892137978064536.sql b/data/sql/updates/db_world/2026_03_22_02.sql similarity index 98% rename from data/sql/updates/pending_db_world/rev_1764892137978064536.sql rename to data/sql/updates/db_world/2026_03_22_02.sql index 43d7a304f..624279128 100644 --- a/data/sql/updates/pending_db_world/rev_1764892137978064536.sql +++ b/data/sql/updates/db_world/2026_03_22_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_22_01 -> 2026_03_22_02 -- -- Brandon Eiredeck (31023) should cower and say "Please, I'm just a delivery man!" -- Agitated Stratholme Citizens (31126) near Brandon should chastise him about the grain From 06ca4de21010975961006e2463fcfec1f742f87d Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 22 Mar 2026 18:53:26 -0300 Subject: [PATCH 050/132] refactor(Core/Scripts): remove redundant scheduler SetValidator calls (#25189) Co-authored-by: Claude Opus 4.6 (1M context) --- .../AlteracValley/boss_balinda.cpp | 7 +------ .../EasternKingdoms/Karazhan/boss_curator.cpp | 7 +------ .../Karazhan/boss_maiden_of_virtue.cpp | 7 +------ .../EasternKingdoms/Karazhan/boss_moroes.cpp | 4 ---- .../Karazhan/boss_tenris_mirkblood.cpp | 7 +------ .../Karazhan/boss_terestian_illhoof.cpp | 7 +------ .../ShadowfangKeep/boss_apothecary_hummel.cpp | 7 +------ .../SunwellPlateau/boss_kiljaeden.cpp | 9 -------- .../instance_sunwell_plateau.cpp | 7 +------ .../EasternKingdoms/ZulAman/boss_halazzi.cpp | 7 +------ .../EasternKingdoms/ZulAman/boss_hexlord.cpp | 7 +------ .../EasternKingdoms/ZulAman/boss_janalai.cpp | 7 +------ .../EasternKingdoms/ZulGurub/boss_thekal.cpp | 5 ----- .../EasternKingdoms/ZulGurub/boss_venoxis.cpp | 5 ----- .../BattleForMountHyjal/boss_anetheron.cpp | 4 ---- .../BattleForMountHyjal/boss_archimonde.cpp | 4 ---- .../BattleForMountHyjal/boss_azgalor.cpp | 4 ---- .../BattleForMountHyjal/boss_kazrogal.cpp | 7 +------ .../boss_rage_winterchill.cpp | 4 ---- .../BattleForMountHyjal/hyjal.cpp | 21 +++---------------- .../boss_captain_skarloc.cpp | 7 +------ src/server/scripts/Kalimdor/boss_azuregos.cpp | 7 +------ .../boss_krikthir_the_gatewatcher.cpp | 5 ----- .../ahnkahet/boss_prince_taldaram.cpp | 7 +------ .../scripts/Northrend/Gundrak/boss_eck.cpp | 7 +------ .../AuchenaiCrypts/boss_exarch_maladaar.cpp | 4 ---- .../boss_shirrak_the_dead_watcher.cpp | 7 +------ .../ManaTombs/boss_nexusprince_shaffar.cpp | 4 ---- .../Auchindoun/ManaTombs/boss_pandemonius.cpp | 7 +------ .../Auchindoun/ManaTombs/boss_tavarok.cpp | 7 +------ .../Auchindoun/SethekkHalls/boss_anzu.cpp | 4 ---- .../SethekkHalls/boss_darkweaver_syth.cpp | 7 +------ .../SethekkHalls/boss_talon_king_ikiss.cpp | 7 +------ .../boss_blackheart_the_inciter.cpp | 7 +------ .../ShadowLabyrinth/boss_murmur.cpp | 4 ---- .../Outland/BlackTemple/boss_supremus.cpp | 7 +------ .../boss_fathomlord_karathress.cpp | 5 ----- .../SerpentShrine/boss_lady_vashj.cpp | 5 ----- .../boss_leotheras_the_blind.cpp | 7 +------ .../boss_morogrim_tidewalker.cpp | 7 +------ .../SlavePens/boss_mennu_the_betrayer.cpp | 7 +------ .../SlavePens/boss_quagmirran.cpp | 7 +------ .../SlavePens/boss_rokmar_the_crackler.cpp | 7 +------ .../SteamVault/boss_mekgineer_steamrigger.cpp | 7 +------ .../underbog/boss_ghazan.cpp | 7 +------ .../underbog/boss_swamplord_muselek.cpp | 7 +------ .../underbog/boss_the_black_stalker.cpp | 7 +------ .../GruulsLair/boss_high_king_maulgar.cpp | 7 +------ .../BloodFurnace/boss_kelidan_the_breaker.cpp | 7 +------ .../BloodFurnace/boss_the_maker.cpp | 7 +------ .../boss_omor_the_unscarred.cpp | 4 ---- .../boss_watchkeeper_gargolmar.cpp | 4 ---- .../MagtheridonsLair/boss_magtheridon.cpp | 7 +------ .../ShatteredHalls/boss_nethekurse.cpp | 7 +------ .../ShatteredHalls/boss_warbringer_omrogg.cpp | 7 +------ .../boss_warchief_kargath_bladefist.cpp | 7 +------ .../Outland/TempestKeep/Eye/boss_alar.cpp | 4 ---- .../TempestKeep/Eye/boss_astromancer.cpp | 4 ---- .../Outland/TempestKeep/Eye/boss_kaelthas.cpp | 7 +------ .../TempestKeep/Eye/boss_void_reaver.cpp | 5 ----- 60 files changed, 42 insertions(+), 343 deletions(-) diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_balinda.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_balinda.cpp index 7020a484f..c823bb061 100644 --- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_balinda.cpp +++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_balinda.cpp @@ -43,12 +43,7 @@ enum Creatures struct boss_balinda : public ScriptedAI { boss_balinda(Creature* creature) : ScriptedAI(creature), summons(me), _hasCastIceBlock(false) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/Karazhan/boss_curator.cpp b/src/server/scripts/EasternKingdoms/Karazhan/boss_curator.cpp index e55b7dcdf..01afffe50 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/boss_curator.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/boss_curator.cpp @@ -45,12 +45,7 @@ enum Spells struct boss_curator : public BossAI { boss_curator(Creature* creature) : BossAI(creature, DATA_CURATOR) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/Karazhan/boss_maiden_of_virtue.cpp b/src/server/scripts/EasternKingdoms/Karazhan/boss_maiden_of_virtue.cpp index 93d9c0d3a..e6ccebfef 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/boss_maiden_of_virtue.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/boss_maiden_of_virtue.cpp @@ -39,12 +39,7 @@ enum Spells struct boss_maiden_of_virtue : public BossAI { boss_maiden_of_virtue(Creature* creature) : BossAI(creature, DATA_MAIDEN) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void JustEngagedWith(Unit* who) override { diff --git a/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp b/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp index 6bb780a83..8b6256ad8 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp @@ -75,10 +75,6 @@ struct boss_moroes : public BossAI boss_moroes(Creature* creature) : BossAI(creature, DATA_MOROES) { _activeGuests = 0; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void InitializeAI() override diff --git a/src/server/scripts/EasternKingdoms/Karazhan/boss_tenris_mirkblood.cpp b/src/server/scripts/EasternKingdoms/Karazhan/boss_tenris_mirkblood.cpp index 289b2d596..c1a6341bf 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/boss_tenris_mirkblood.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/boss_tenris_mirkblood.cpp @@ -74,12 +74,7 @@ enum Events struct boss_tenris_mirkblood : public BossAI { boss_tenris_mirkblood(Creature* creature) : BossAI(creature, DATA_MIRKBLOOD) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/Karazhan/boss_terestian_illhoof.cpp b/src/server/scripts/EasternKingdoms/Karazhan/boss_terestian_illhoof.cpp index 13b37f3e0..15bffcc2c 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/boss_terestian_illhoof.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/boss_terestian_illhoof.cpp @@ -147,12 +147,7 @@ private: struct boss_terestian_illhoof : public BossAI { boss_terestian_illhoof(Creature* creature) : BossAI(creature, DATA_TERESTIAN) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/ShadowfangKeep/boss_apothecary_hummel.cpp b/src/server/scripts/EasternKingdoms/ShadowfangKeep/boss_apothecary_hummel.cpp index 895fb283e..0efd989f1 100644 --- a/src/server/scripts/EasternKingdoms/ShadowfangKeep/boss_apothecary_hummel.cpp +++ b/src/server/scripts/EasternKingdoms/ShadowfangKeep/boss_apothecary_hummel.cpp @@ -87,12 +87,7 @@ public: struct boss_apothecary_hummelAI : public BossAI { boss_apothecary_hummelAI(Creature* creature) : BossAI(creature, DATA_APOTHECARY_HUMMEL), _deadCount(0), _isDead(false) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void sGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override { diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp index c74788d8f..63e45764c 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp @@ -126,10 +126,6 @@ struct npc_kiljaeden_controller : public NullCreatureAI npc_kiljaeden_controller(Creature* creature) : NullCreatureAI(creature), summons(me) { instance = creature->GetInstanceScript(); - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void ResetOrbs() @@ -233,11 +229,6 @@ struct boss_kiljaeden : public BossAI boss_kiljaeden(Creature* creature) : BossAI(creature, DATA_KILJAEDEN) { me->SetReactState(REACT_PASSIVE); - - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void InitializeAI() override diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/instance_sunwell_plateau.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/instance_sunwell_plateau.cpp index 5ae5e5059..b0407d303 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/instance_sunwell_plateau.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/instance_sunwell_plateau.cpp @@ -292,12 +292,7 @@ enum SunbladeArchMageSpells struct npc_sunblade_arch_mage : public ScriptedAI { npc_sunblade_arch_mage(Creature* creature) : ScriptedAI(creature) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_halazzi.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_halazzi.cpp index aa0653557..c5455dc4e 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_halazzi.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_halazzi.cpp @@ -86,12 +86,7 @@ enum Actions struct boss_halazzi : public BossAI { boss_halazzi(Creature* creature) : BossAI(creature, DATA_HALAZZI) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_hexlord.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_hexlord.cpp index 71811383b..e0a7967fc 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_hexlord.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_hexlord.cpp @@ -233,12 +233,7 @@ static PlayerAbilityStruct PlayerAbility[13][3] = struct boss_hexlord_malacrass : public BossAI { boss_hexlord_malacrass(Creature* creature) : BossAI(creature, DATA_HEXLORD) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_janalai.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_janalai.cpp index d8c63ecb4..2ace89da2 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_janalai.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_janalai.cpp @@ -120,12 +120,7 @@ enum Misc struct boss_janalai : public BossAI { boss_janalai(Creature* creature) : BossAI(creature, DATA_JANALAI) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/EasternKingdoms/ZulGurub/boss_thekal.cpp b/src/server/scripts/EasternKingdoms/ZulGurub/boss_thekal.cpp index 7140a1ae8..1ddca06ed 100644 --- a/src/server/scripts/EasternKingdoms/ZulGurub/boss_thekal.cpp +++ b/src/server/scripts/EasternKingdoms/ZulGurub/boss_thekal.cpp @@ -115,11 +115,6 @@ struct boss_thekal : public BossAI ); context.Repeat(5s, 25s); }); - - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void JustDied(Unit* /*killer*/) override diff --git a/src/server/scripts/EasternKingdoms/ZulGurub/boss_venoxis.cpp b/src/server/scripts/EasternKingdoms/ZulGurub/boss_venoxis.cpp index e706c472c..fa67c73e8 100644 --- a/src/server/scripts/EasternKingdoms/ZulGurub/boss_venoxis.cpp +++ b/src/server/scripts/EasternKingdoms/ZulGurub/boss_venoxis.cpp @@ -74,11 +74,6 @@ public: void InitializeAI() override { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - Reset(); } diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp index 145495965..0ac933683 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp @@ -50,10 +50,6 @@ public: boss_anetheron(Creature* creature) : BossAI(creature, DATA_ANETHERON) { _recentlySpoken = false; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void JustEngagedWith(Unit * who) override diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_archimonde.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_archimonde.cpp index f2f6c003d..9db55d36e 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_archimonde.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_archimonde.cpp @@ -186,10 +186,6 @@ struct boss_archimonde : public BossAI { boss_archimonde(Creature* creature) : BossAI(creature, DATA_ARCHIMONDE) { - scheduler.SetValidator([&] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_azgalor.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_azgalor.cpp index 63a1a3a6c..22024068e 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_azgalor.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_azgalor.cpp @@ -47,10 +47,6 @@ public: boss_azgalor(Creature* creature) : BossAI(creature, DATA_AZGALOR) { _recentlySpoken = false; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void JustEngagedWith(Unit * who) override diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_kazrogal.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_kazrogal.cpp index a5d03d912..889ccc178 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_kazrogal.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_kazrogal.cpp @@ -48,12 +48,7 @@ struct boss_kazrogal : public BossAI { public: boss_kazrogal(Creature* creature) : BossAI(creature, DATA_KAZROGAL) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_rage_winterchill.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_rage_winterchill.cpp index a2ce47ff3..2bdf44abf 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_rage_winterchill.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_rage_winterchill.cpp @@ -50,10 +50,6 @@ public: boss_rage_winterchill(Creature* creature) : BossAI(creature, DATA_WINTERCHILL) { _recentlySpoken = false; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void JustEngagedWith(Unit* who) override diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal.cpp index 8bad06221..9b8743654 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal.cpp @@ -352,12 +352,7 @@ class spell_cannibalize_heal : public SpellScript struct npc_hyjal_ground_trash : public ScriptedAI { npc_hyjal_ground_trash(Creature* creature) : ScriptedAI(creature) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { @@ -574,12 +569,7 @@ struct npc_hyjal_ground_trash : public ScriptedAI struct npc_hyjal_gargoyle : public ScriptedAI { npc_hyjal_gargoyle(Creature* creature) : ScriptedAI(creature) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { @@ -648,12 +638,7 @@ struct npc_hyjal_gargoyle : public ScriptedAI struct npc_hyjal_frost_wyrm : public ScriptedAI { npc_hyjal_frost_wyrm(Creature* creature) : ScriptedAI(creature) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/boss_captain_skarloc.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/boss_captain_skarloc.cpp index c75f00b1f..e3f9c5408 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/boss_captain_skarloc.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/boss_captain_skarloc.cpp @@ -53,12 +53,7 @@ const Position startPath[WAYPOINTS_COUNT] = struct boss_captain_skarloc : public BossAI { boss_captain_skarloc(Creature* creature) : BossAI(creature, DATA_CAPTAIN_SKARLOC), summons(me) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } SummonList summons; bool _spawnedAdds; diff --git a/src/server/scripts/Kalimdor/boss_azuregos.cpp b/src/server/scripts/Kalimdor/boss_azuregos.cpp index e6b320789..99941d209 100644 --- a/src/server/scripts/Kalimdor/boss_azuregos.cpp +++ b/src/server/scripts/Kalimdor/boss_azuregos.cpp @@ -53,12 +53,7 @@ public: struct boss_azuregosAI : public ScriptedAI { boss_azuregosAI(Creature* creature) : ScriptedAI(creature) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp index 2e0eac307..be33fad5a 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp @@ -60,11 +60,6 @@ public: _initTalk = false; _canTalk = true; _minionInCombat = false; - - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp index 89adac195..041d68360 100644 --- a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp @@ -172,12 +172,7 @@ private: struct boss_taldaram : public BossAI { boss_taldaram(Creature* pCreature) : BossAI(pCreature, DATA_PRINCE_TALDARAM), vanishDamage(0) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void InitializeAI() override { diff --git a/src/server/scripts/Northrend/Gundrak/boss_eck.cpp b/src/server/scripts/Northrend/Gundrak/boss_eck.cpp index c84207926..e21b5835c 100644 --- a/src/server/scripts/Northrend/Gundrak/boss_eck.cpp +++ b/src/server/scripts/Northrend/Gundrak/boss_eck.cpp @@ -43,12 +43,7 @@ Position const EckCombatStartPosition = { 1638.55f, 919.76f, 104.95f, 0.00f }; struct boss_eck : public BossAI { boss_eck(Creature* creature) : BossAI(creature, DATA_ECK_THE_FEROCIOUS) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void InitializeAI() override { diff --git a/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_exarch_maladaar.cpp b/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_exarch_maladaar.cpp index 67679c091..7413ed4f2 100644 --- a/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_exarch_maladaar.cpp +++ b/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_exarch_maladaar.cpp @@ -61,10 +61,6 @@ struct boss_exarch_maladaar : public BossAI boss_exarch_maladaar(Creature* creature) : BossAI(creature, DATA_EXARCH_MALADAAR) { _talked = false; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp b/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp index 03918e5e1..bb7bb9404 100644 --- a/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp +++ b/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp @@ -46,12 +46,7 @@ enum Misc struct boss_shirrak_the_dead_watcher : public BossAI { boss_shirrak_the_dead_watcher(Creature* creature) : BossAI(creature, DATA_SHIRRAK_THE_DEAD_WATCHER) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } ObjectGuid focusGUID; diff --git a/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_nexusprince_shaffar.cpp b/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_nexusprince_shaffar.cpp index 0986ba9c6..846f786a5 100644 --- a/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_nexusprince_shaffar.cpp +++ b/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_nexusprince_shaffar.cpp @@ -54,10 +54,6 @@ struct boss_nexusprince_shaffar : public BossAI boss_nexusprince_shaffar(Creature* creature) : BossAI(creature, DATA_NEXUSPRINCE_SHAFFAR), summons(me) { HasTaunted = false; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } SummonList summons; diff --git a/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_pandemonius.cpp b/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_pandemonius.cpp index 2612c690e..04fc2baa3 100644 --- a/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_pandemonius.cpp +++ b/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_pandemonius.cpp @@ -55,12 +55,7 @@ constexpr uint8 MAX_VOID_BLAST = 5; struct boss_pandemonius : public BossAI { boss_pandemonius(Creature* creature) : BossAI(creature, DATA_PANDEMONIUS) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void JustEngagedWith(Unit* who) override { diff --git a/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_tavarok.cpp b/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_tavarok.cpp index 2bacc2b4d..e12658381 100644 --- a/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_tavarok.cpp +++ b/src/server/scripts/Outland/Auchindoun/ManaTombs/boss_tavarok.cpp @@ -29,12 +29,7 @@ enum Spells struct boss_tavarok : public BossAI { boss_tavarok(Creature* creature) : BossAI(creature, DATA_TAVAROK) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp index c201d5a0a..c4add0c19 100644 --- a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp +++ b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp @@ -68,10 +68,6 @@ struct boss_anzu : public BossAI talkTimer = 1; me->ReplaceAllUnitFlags(UNIT_FLAG_NON_ATTACKABLE); me->AddAura(SPELL_SHADOWFORM, me); - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } const Position AnzuSpiritPos[3] = diff --git a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_darkweaver_syth.cpp b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_darkweaver_syth.cpp index fb3600458..0992d4da3 100644 --- a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_darkweaver_syth.cpp +++ b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_darkweaver_syth.cpp @@ -43,12 +43,7 @@ enum Spells struct boss_darkweaver_syth : public BossAI { boss_darkweaver_syth(Creature* creature) : BossAI(creature, DATA_DARKWEAVER_SYTH) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp index 51cb46a99..9e6372481 100644 --- a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp +++ b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp @@ -46,12 +46,7 @@ enum Spells struct boss_talon_king_ikiss : public BossAI { boss_talon_king_ikiss(Creature* creature) : BossAI(creature, DATA_IKISS), _spoken(false) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp index 155ad15fd..cf6fe6102 100644 --- a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp +++ b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp @@ -45,12 +45,7 @@ enum Npc struct boss_blackheart_the_inciter : public BossAI { boss_blackheart_the_inciter(Creature* creature) : BossAI(creature, DATA_BLACKHEARTTHEINCITEREVENT) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } bool InciteChaos; diff --git a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp index a9c1471e9..30969ea8a 100644 --- a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp +++ b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp @@ -59,10 +59,6 @@ struct boss_murmur : public BossAI boss_murmur(Creature* creature) : BossAI(creature, DATA_MURMUR) { me->SetCombatMovement(false); - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp b/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp index 5a5796538..a81ab01a9 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp @@ -48,12 +48,7 @@ enum Supremus struct boss_supremus : public BossAI { boss_supremus(Creature* creature) : BossAI(creature, DATA_SUPREMUS) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_fathomlord_karathress.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_fathomlord_karathress.cpp index 2e73787fa..353e357cf 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_fathomlord_karathress.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_fathomlord_karathress.cpp @@ -205,11 +205,6 @@ struct boss_fathomguard_sharkkis : public ScriptedAI { _instance = creature->GetInstanceScript(); SetBoundary(_instance->GetBossBoundary(DATA_FATHOM_LORD_KARATHRESS)); - - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp index 9fcbc68fc..286bb0c97 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp @@ -72,11 +72,6 @@ struct boss_lady_vashj : public BossAI { boss_lady_vashj(Creature* creature) : BossAI(creature, DATA_LADY_VASHJ) { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - _intro = false; } diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp index 5a1d6c0fa..be599ef83 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp @@ -249,12 +249,7 @@ private: struct npc_inner_demon : public ScriptedAI { npc_inner_demon(Creature* creature) : ScriptedAI(creature) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void IsSummonedBy(WorldObject* summoner) override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_morogrim_tidewalker.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_morogrim_tidewalker.cpp index cc337beee..a923b81a9 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_morogrim_tidewalker.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_morogrim_tidewalker.cpp @@ -55,12 +55,7 @@ const uint32 waterGlobuleIds[4] = {SPELL_SUMMON_WATER_GLOBULE_1, SPELL_SUMMON_WA struct boss_morogrim_tidewalker : public BossAI { boss_morogrim_tidewalker(Creature* creature) : BossAI(creature, DATA_MOROGRIM_TIDEWALKER) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_mennu_the_betrayer.cpp b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_mennu_the_betrayer.cpp index 547e0e442..2d210f3cd 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_mennu_the_betrayer.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_mennu_the_betrayer.cpp @@ -38,12 +38,7 @@ enum Text struct boss_mennu_the_betrayer : public BossAI { boss_mennu_the_betrayer(Creature* creature) : BossAI(creature, DATA_MENNU_THE_BETRAYER) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_quagmirran.cpp b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_quagmirran.cpp index 55fb6cd4d..75fe25dc3 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_quagmirran.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_quagmirran.cpp @@ -30,12 +30,7 @@ enum Spells struct boss_quagmirran : public BossAI { boss_quagmirran(Creature* creature) : BossAI(creature, DATA_QUAGMIRRAN) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void JustEngagedWith(Unit* /*who*/) override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_rokmar_the_crackler.cpp b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_rokmar_the_crackler.cpp index 5a33cdc19..e4e3aa967 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_rokmar_the_crackler.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_rokmar_the_crackler.cpp @@ -30,12 +30,7 @@ enum Spells struct boss_rokmar_the_crackler : public BossAI { explicit boss_rokmar_the_crackler(Creature* creature) : BossAI(creature, DATA_ROKMAR_THE_CRACKLER) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/SteamVault/boss_mekgineer_steamrigger.cpp b/src/server/scripts/Outland/CoilfangReservoir/SteamVault/boss_mekgineer_steamrigger.cpp index 5f9769e62..4d3595879 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SteamVault/boss_mekgineer_steamrigger.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SteamVault/boss_mekgineer_steamrigger.cpp @@ -41,12 +41,7 @@ enum MekgineerSteamrigger struct boss_mekgineer_steamrigger : public BossAI { boss_mekgineer_steamrigger(Creature* creature) : BossAI(creature, DATA_MEKGINEER_STEAMRIGGER) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void JustDied(Unit* /*killer*/) override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_ghazan.cpp b/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_ghazan.cpp index e0f7ef243..5b660a1d1 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_ghazan.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_ghazan.cpp @@ -35,12 +35,7 @@ enum eBlackStalker struct boss_ghazan : public BossAI { boss_ghazan(Creature* creature) : BossAI(creature, DATA_GHAZAN) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void InitializeAI() override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_swamplord_muselek.cpp b/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_swamplord_muselek.cpp index ee2e3d36f..1a4c19fe5 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_swamplord_muselek.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_swamplord_muselek.cpp @@ -47,12 +47,7 @@ enum Misc struct boss_swamplord_muselek : public BossAI { boss_swamplord_muselek(Creature* creature) : BossAI(creature, DATA_MUSELEK) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_the_black_stalker.cpp b/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_the_black_stalker.cpp index 1bb2ee4b5..5bb097451 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_the_black_stalker.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/underbog/boss_the_black_stalker.cpp @@ -54,12 +54,7 @@ enum eBlackStalker struct boss_the_black_stalker : public BossAI { boss_the_black_stalker(Creature* creature) : BossAI(creature, DATA_BLACK_STALKER) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void JustEngagedWith(Unit* /*who*/) override { diff --git a/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp b/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp index 2679eb6ca..5dd35b908 100644 --- a/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp +++ b/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp @@ -64,12 +64,7 @@ enum HighKingMaulgar struct boss_high_king_maulgar : public BossAI { boss_high_king_maulgar(Creature* creature) : BossAI(creature, DATA_MAULGAR) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_kelidan_the_breaker.cpp b/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_kelidan_the_breaker.cpp index 41f77e9bb..acba63305 100644 --- a/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_kelidan_the_breaker.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_kelidan_the_breaker.cpp @@ -53,12 +53,7 @@ enum Actions struct boss_kelidan_the_breaker : public BossAI { boss_kelidan_the_breaker(Creature* creature) : BossAI(creature, DATA_KELIDAN) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_the_maker.cpp b/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_the_maker.cpp index 7bb30a7c7..691a04724 100644 --- a/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_the_maker.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/BloodFurnace/boss_the_maker.cpp @@ -35,12 +35,7 @@ enum Spells struct boss_the_maker : public BossAI { boss_the_maker(Creature* creature) : BossAI(creature, DATA_THE_MAKER) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void JustEngagedWith(Unit* /*who*/) override { diff --git a/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_omor_the_unscarred.cpp b/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_omor_the_unscarred.cpp index d5a01b296..914aee1bf 100644 --- a/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_omor_the_unscarred.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_omor_the_unscarred.cpp @@ -42,10 +42,6 @@ struct boss_omor_the_unscarred : public BossAI boss_omor_the_unscarred(Creature* creature) : BossAI(creature, DATA_OMOR_THE_UNSCARRED) { me->SetCombatMovement(false); - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_watchkeeper_gargolmar.cpp b/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_watchkeeper_gargolmar.cpp index 448bdfa2b..3f64181e4 100644 --- a/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_watchkeeper_gargolmar.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_watchkeeper_gargolmar.cpp @@ -48,10 +48,6 @@ struct boss_watchkeeper_gargolmar : public BossAI boss_watchkeeper_gargolmar(Creature* creature) : BossAI(creature, DATA_WATCHKEEPER_GARGOLMAR) { _taunted = false; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp index f64c0fff5..83c7abefc 100644 --- a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp @@ -79,12 +79,7 @@ enum Actions struct boss_magtheridon : public BossAI { boss_magtheridon(Creature* creature) : BossAI(creature, DATA_MAGTHERIDON) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_nethekurse.cpp b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_nethekurse.cpp index 5d87557be..50adf9ba2 100644 --- a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_nethekurse.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_nethekurse.cpp @@ -100,12 +100,7 @@ PeonRoleplay PeonRoleplayData[3] = struct boss_grand_warlock_nethekurse : public BossAI { boss_grand_warlock_nethekurse(Creature* creature) : BossAI(creature, DATA_NETHEKURSE) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void Reset() override { diff --git a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warbringer_omrogg.cpp b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warbringer_omrogg.cpp index 66fe098cd..ff6af2ceb 100644 --- a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warbringer_omrogg.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warbringer_omrogg.cpp @@ -66,12 +66,7 @@ enum Phase struct boss_warbringer_omrogg : public BossAI { boss_warbringer_omrogg(Creature* creature) : BossAI(creature, DATA_OMROGG) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void HandleHeadTalk(HeadYells yell) { diff --git a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp index d275d6956..984e3d9bd 100644 --- a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp @@ -69,12 +69,7 @@ Position const kargathRespawnPos = { 231.25f, -83.6449f, 5.02341f }; struct boss_warchief_kargath_bladefist : public BossAI { boss_warchief_kargath_bladefist(Creature* creature) : BossAI(creature, DATA_KARGATH) - { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + { } void InitializeAI() override { diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp index 910d8b8cc..c7b7d1d72 100644 --- a/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp +++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp @@ -98,10 +98,6 @@ struct boss_alar : public BossAI boss_alar(Creature* creature) : BossAI(creature, DATA_ALAR) { me->SetCombatMovement(false); - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp index d691dde4b..54235dda0 100644 --- a/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp +++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp @@ -67,10 +67,6 @@ struct boss_high_astromancer_solarian : public BossAI boss_high_astromancer_solarian(Creature* creature) : BossAI(creature, DATA_ASTROMANCER) { callForHelpRange = 105.0f; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); } void Reset() override diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp index 31390d5a3..8328802bd 100644 --- a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp +++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp @@ -725,12 +725,7 @@ private: struct advisor_baseAI : public ScriptedAI { - advisor_baseAI(Creature* creature) : ScriptedAI(creature) { - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - } + advisor_baseAI(Creature* creature) : ScriptedAI(creature) { } virtual void ScheduleEvents() {} diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_void_reaver.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_void_reaver.cpp index 6ccf91dca..3c4582d46 100644 --- a/src/server/scripts/Outland/TempestKeep/Eye/boss_void_reaver.cpp +++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_void_reaver.cpp @@ -42,11 +42,6 @@ struct boss_void_reaver : public BossAI boss_void_reaver(Creature* creature) : BossAI(creature, DATA_REAVER) { callForHelpRange = 105.0f; - scheduler.SetValidator([this] - { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); - me->ApplySpellImmune(0, IMMUNITY_DISPEL, DISPEL_POISON, true); me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEALTH_LEECH, true); me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_POWER_DRAIN, true); From 0e0ff86e3e88ad836d1fde23128f8720e1d045aa Mon Sep 17 00:00:00 2001 From: Gultask <100873791+Gultask@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:33:42 -0300 Subject: [PATCH 051/132] fix(DB/SAI): Only despawn Terokk on evade if there are Skyguard Ace nearby (#25118) --- .../updates/pending_db_world/rev_1773864671057223800.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1773864671057223800.sql diff --git a/data/sql/updates/pending_db_world/rev_1773864671057223800.sql b/data/sql/updates/pending_db_world/rev_1773864671057223800.sql new file mode 100644 index 000000000..e2313cbfb --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1773864671057223800.sql @@ -0,0 +1,8 @@ +-- +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 22) AND (`SourceGroup` = 11) AND (`SourceEntry` = 21838) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 29) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23377) AND (`ConditionValue2` = 90) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(22, 11, 21838, 0, 0, 29, 1, 23377, 90, 0, 0, 0, 0, '', 'Terokk will only Despawn on Evade if Skyguard Ace is nearby.'); + +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 22) AND (`SourceGroup` = 12) AND (`SourceEntry` = 21838) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 29) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23377) AND (`ConditionValue2` = 90) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(22, 12, 21838, 0, 0, 29, 1, 23377, 90, 0, 0, 0, 0, '', 'Terokk will only Despawn on Evade if Skyguard Ace is nearby.'); From a90570a2dee7ae3c5333f22a1f8217cead5769fe Mon Sep 17 00:00:00 2001 From: sogladev Date: Sun, 22 Mar 2026 23:36:35 +0100 Subject: [PATCH 052/132] fix(Core/Spell): spell immunity system and new separate immunities table (#24956) Co-authored-by: ariel- Co-authored-by: Keader Co-authored-by: Shauren --- .../rev_1772292641644296192.sql | 23 + .../rev_1772402317724411598.sql | 37 ++ .../Database/Implementation/WorldDatabase.cpp | 2 +- .../game/Entities/Creature/Creature.cpp | 74 ++- src/server/game/Entities/Creature/Creature.h | 8 +- .../game/Entities/Creature/CreatureData.h | 4 +- src/server/game/Entities/Pet/Pet.cpp | 2 +- src/server/game/Entities/Totem/Totem.cpp | 4 +- src/server/game/Entities/Totem/Totem.h | 2 +- src/server/game/Entities/Unit/Unit.cpp | 424 ++++++------- src/server/game/Entities/Unit/Unit.h | 28 +- src/server/game/Globals/ObjectMgr.cpp | 120 ++-- .../game/Spells/Auras/SpellAuraEffects.cpp | 395 +----------- src/server/game/Spells/Auras/SpellAuras.cpp | 6 +- src/server/game/Spells/Spell.cpp | 246 ++++---- src/server/game/Spells/Spell.h | 18 +- src/server/game/Spells/SpellEffects.cpp | 8 +- src/server/game/Spells/SpellInfo.cpp | 594 ++++++++++++++++-- src/server/game/Spells/SpellInfo.h | 48 +- src/server/game/Spells/SpellMgr.cpp | 89 ++- src/server/game/Spells/SpellMgr.h | 21 + src/server/game/World/World.cpp | 3 + src/server/scripts/Commands/cs_npc.cpp | 42 +- src/server/shared/SharedDefines.h | 40 +- src/test/mocks/TestCreature.cpp | 1 - .../server/game/Spells/SpellImmunityTest.cpp | 330 ++++++++++ 26 files changed, 1655 insertions(+), 914 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1772292641644296192.sql create mode 100644 data/sql/updates/pending_db_world/rev_1772402317724411598.sql create mode 100644 src/test/server/game/Spells/SpellImmunityTest.cpp diff --git a/data/sql/updates/pending_db_world/rev_1772292641644296192.sql b/data/sql/updates/pending_db_world/rev_1772292641644296192.sql new file mode 100644 index 000000000..d34f54d4d --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1772292641644296192.sql @@ -0,0 +1,23 @@ +-- +CREATE TABLE `creature_immunities` ( + `ID` int NOT NULL, + `SchoolMask` tinyint NOT NULL DEFAULT '0', + `DispelTypeMask` smallint NOT NULL DEFAULT '0', + `MechanicsMask` bigint NOT NULL DEFAULT '0', + `Effects` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `Auras` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `ImmuneAoE` tinyint(1) NOT NULL DEFAULT '0', + `ImmuneChain` tinyint(1) NOT NULL DEFAULT '0', + `Comment` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +ALTER TABLE `creature_template` ADD `CreatureImmunitiesId` int NOT NULL DEFAULT '0' AFTER `RegenHealth`; + +UPDATE `creature_template` SET `CreatureImmunitiesId`=COALESCE((SELECT ci.`ID` FROM `creature_immunities` ci WHERE ci.`SchoolMask`=`spell_school_immune_mask` AND ci.`MechanicsMask`=`mechanic_immune_mask`*2),0); + +ALTER TABLE `creature_template` + DROP `spell_school_immune_mask`, + DROP `mechanic_immune_mask`; + +ALTER TABLE `creature_template` DROP COLUMN `scale`; diff --git a/data/sql/updates/pending_db_world/rev_1772402317724411598.sql b/data/sql/updates/pending_db_world/rev_1772402317724411598.sql new file mode 100644 index 000000000..aeaac02a2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1772402317724411598.sql @@ -0,0 +1,37 @@ +-- + +DELETE FROM `creature_immunities` WHERE `ID` IN (95,96,315,477,679,878,1537,1557,1614,1615,1630,1632,1664,1676,1682,1693,1694,1695,1733); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(95, 0, 0, 32, '', '', 0, 0, 'Immune to Fear'), +(96, 0, 0, 1234599598, '', '5,7,12,26,33', 0, 0, 'Immune to CC (Free Friend, Uncontrollable Frenzy, Warlord\'s Presence)'), +(315, 127, 0, 0, '', '', 0, 0, 'Immune to damage'), +(477, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(679, 0, 0, 1234599078, '', '5,7,12,26,33', 0, 0, 'Immune to CC'), +(878, 0, 0, 4096, '', '12', 0, 0, 'Immune to Stun'), +(1537, 0, 0, 32, '', '7', 0, 0, 'Immune to Fear'), +(1557, 0, 0, 1234599078, '98,124,144,145', '', 0, 0, 'Immune CC+Stun+Move'), +(1614, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(1615, 0, 0, 1301707942, '68', '5,7,12,26,27,33,60', 0, 0, 'Immune to CC+Interrupt'), +(1630, 0, 0, 69954075814, '114', '5,7,11,12,26,33', 0, 0, 'Immune to CC+Taunt'), +(1632, 0, 0, 584472182, '', '', 0, 0, 'Immune to hard CC'), +(1664, 0, 0, 2176, '', '', 0, 0, 'Immune to slow'), +(1676, 0, 0, 570425344, '', '', 0, 0, 'Immune to immunities'), +(1682, 0, 0, 4096, '', '', 0, 0, 'Immune to Stun'), +(1693, 0, 0, 584472182, '68', '', 0, 0, 'Immune to hard CC+Interrupt'), +(1694, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(1695, 0, 0, 4096, '', '12', 0, 0, 'Immune to Stun'), +(1733, 0, 0, 1234599078, '98,124,144,145', '', 0, 0, 'Immune CC+Stun+Move'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (1762,1771,1794,1799,1808,1835,1887,1893,1944,1964,1971); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(1762, 0, 4, 0, '', '', 0, 0, 'Immune to Curses'), +(1771, 0, 0, 0, '', '', 1, 0, 'Immune to AoE'), +(1794, 0, 0, 67108864, '68', '', 0, 0, 'Immune to Interrupts'), +(1799, 0, 0, 67109376, '68', '27,60', 0, 0, 'Immune to Interrupts+Silence'), +(1808, 0, 0, 0, '98,124,144,145', '', 0, 0, 'Immune to knockback'), +(1835, 0, 0, 1075183616, '', '', 0, 0, 'Immune to polymorph+banish+shackle+sap'), +(1887, 0, 0, 0, '98,124,144,145', '', 0, 0, 'Immune to knockback'), +(1893, 0, 0, 1091728546, '', '', 0, 0, 'Immune to Charm+Fear+Freeze+Horror+Knockout+Polymorph+Root+Sap+Shackle+Sleep+Stun'), +(1944, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(1964, 0, 0, 67109376, '68', '27,60', 0, 0, 'Immune to Interrupts+Silence'), +(1971, 0, 0, 4096, '', '', 0, 0, 'Immune to Stun'); diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index ea97d9a8c..32b568b77 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH); diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 815644e68..d714343c9 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -643,7 +643,7 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele SetControlled(true, UNIT_STATE_ROOT); UpdateMovementFlags(); - LoadSpellTemplateImmunity(); + LoadTemplateImmunities(cInfo->CreatureImmunitiesId); if (updateAI) { @@ -2132,39 +2132,55 @@ void Creature::InitializeReactState() SetReactState(REACT_AGGRESSIVE); } -bool Creature::HasMechanicTemplateImmunity(uint32 mask) const +bool Creature::HasMechanicTemplateImmunity(uint64 mask) const { - return !GetOwnerGUID().IsPlayer() && (GetCreatureTemplate()->MechanicImmuneMask & mask); + if (GetOwnerGUID().IsPlayer()) + return false; + + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(_creatureImmunitiesId)) + return (immunities->Mechanic.to_ullong() & mask) != 0; + + // no custom immunity entry => no mechanic immunity + return false; } -void Creature::LoadSpellTemplateImmunity() +void Creature::LoadTemplateImmunities(int32 creatureImmunitiesId) { // uint32 max used for "spell id", the immunity system will not perform SpellInfo checks against invalid spells // used so we know which immunities were loaded from template - static uint32 const placeholderSpellId = std::numeric_limits::max(); + static uint32 constexpr placeholderSpellId = std::numeric_limits::max(); - // unapply template immunities (in case we're updating entry) - for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i) + auto applyCreatureImmunities = [this](CreatureImmunities const* immunities, bool apply) { - ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, i, false); - } + if (!immunities) + return; + for (std::size_t i = 0; i < immunities->School.size(); ++i) + if (immunities->School[i]) + ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, apply); + for (std::size_t i = 0; i < immunities->DispelType.size(); ++i) + if (immunities->DispelType[i]) + ApplySpellImmune(placeholderSpellId, IMMUNITY_DISPEL, i, apply); + for (std::size_t i = 0; i < immunities->Mechanic.size(); ++i) + if (immunities->Mechanic[i]) + ApplySpellImmune(placeholderSpellId, IMMUNITY_MECHANIC, i, apply); + for (SpellEffects effect : immunities->Effect) + ApplySpellImmune(placeholderSpellId, IMMUNITY_EFFECT, effect, apply); + for (AuraType aura : immunities->Aura) + ApplySpellImmune(placeholderSpellId, IMMUNITY_STATE, aura, apply); + }; - // don't inherit immunities for hunter pets - if (GetOwnerGUID().IsPlayer() && IsHunterPet()) - { - return; - } + // unapply old template if any + if (CreatureImmunities const* oldImmunities = sSpellMgr->GetCreatureImmunities(_creatureImmunitiesId)) + applyCreatureImmunities(oldImmunities, false); - if (uint8 mask = GetCreatureTemplate()->SpellSchoolImmuneMask) + // apply requested immunities + if (CreatureImmunities const* newImmunities = sSpellMgr->GetCreatureImmunities(creatureImmunitiesId)) { - for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i) - { - if (mask & (1 << i)) - { - ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, true); - } - } + _creatureImmunitiesId = creatureImmunitiesId; + applyCreatureImmunities(newImmunities, true); } + else + _creatureImmunitiesId = 0; } bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) @@ -2179,11 +2195,13 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) // Xinef: this should exclude self casts... // Spells that don't have effectMechanics. - if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Mechanic - 1))) + if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Mechanic)) return true; - // This check must be done instead of 'if (GetCreatureTemplate()->MechanicImmuneMask & (1 << (spellInfo->Mechanic - 1)))' for not break - // the check of mechanic immunity on DB (tested) because GetCreatureTemplate()->MechanicImmuneMask and m_spellImmune[IMMUNITY_MECHANIC] don't have same data. + // The above helper uses the creature_immunities table rather than a + // simple mask on creature_template. We can't rely on the old mask field + // (which has been removed) because it no longer exists and did not always + // match the runtime immunity set stored in m_spellImmune. bool immunedToAllEffects = true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (spellInfo->Effects[i].IsEffect() && !IsImmunedToSpellEffect(spellInfo, i)) @@ -2197,16 +2215,16 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) return Unit::IsImmunedToSpell(spellInfo, spell); } -bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const +bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { // Xinef: this should exclude self casts... - if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Effects[index].Mechanic - 1))) + if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Effects[index].Mechanic)) return true; if (GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL && spellInfo->Effects[index].Effect == SPELL_EFFECT_HEAL) return true; - return Unit::IsImmunedToSpellEffect(spellInfo, index); + return Unit::IsImmunedToSpellEffect(spellInfo, index, caster); } SpellInfo const* Creature::reachWithSpellAttack(Unit* victim) diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 0669d6e45..2218ab8ec 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -105,12 +105,11 @@ public: bool isCanInteractWithBattleMaster(Player* player, bool msg) const; bool CanResetTalents(Player* player) const; bool CanCreatureAttack(Unit const* victim, bool skipDistCheck = false) const; - void LoadSpellTemplateImmunity(); bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr) override; - [[nodiscard]] bool HasMechanicTemplateImmunity(uint32 mask) const; + [[nodiscard]] bool HasMechanicTemplateImmunity(uint64 mask) const; // redefine Unit::IsImmunedToSpell - bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override; + bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const override; // redefine Unit::IsImmunedToSpellEffect [[nodiscard]] bool isElite() const { @@ -186,6 +185,8 @@ public: void UpdateAttackPowerAndDamage(bool ranged = false) override; void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex) override; + void LoadTemplateImmunities(int32 creatureImmunitiesId); + void LoadSparringPct(); [[nodiscard]] float GetSparringPct() const { return _sparringPct; } @@ -486,6 +487,7 @@ protected: SpellSchoolMask m_meleeDamageSchoolMask; uint32 m_originalEntry; + int32 _creatureImmunitiesId; uint32 _gossipMenuId; bool m_moveInLineOfSightDisabled; diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index f94ae69f5..1ba2226a7 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -202,7 +202,6 @@ struct CreatureTemplate float speed_swim; float speed_flight; float detection_range; // Detection Range for Line of Sight aggro - float scale; uint32 rank; uint32 dmgschool; float DamageModifier; @@ -237,8 +236,7 @@ struct CreatureTemplate bool RacialLeader; uint32 movementId; bool RegenHealth; - uint32 MechanicImmuneMask; - uint8 SpellSchoolImmuneMask; + int32 CreatureImmunitiesId; uint32 flags_extra; uint32 ScriptID; WorldPacket queryData; // pussywizard diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 95e4d878d..23df0fa0f 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -491,7 +491,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c } // must be after SetMinion (owner guid check) - //LoadTemplateImmunities(); + LoadTemplateImmunities(0); //LoadMechanicTemplateImmunity(); m_loading = false; }); diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index 9f28ac743..20253a0c3 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -179,7 +179,7 @@ void Totem::UnSummon(Milliseconds msTime) AddObjectToRemoveList(); } -bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const +bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { // xinef: immune to all positive spells, except of stoneclaw totem absorb, sentry totem bind sight and intervene // totems positive spells have unit_caster target @@ -209,5 +209,5 @@ bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) con break; } - return Creature::IsImmunedToSpellEffect(spellInfo, index); + return Creature::IsImmunedToSpellEffect(spellInfo, index, caster); } diff --git a/src/server/game/Entities/Totem/Totem.h b/src/server/game/Entities/Totem/Totem.h index 3149e6398..08583d027 100644 --- a/src/server/game/Entities/Totem/Totem.h +++ b/src/server/game/Entities/Totem/Totem.h @@ -70,7 +70,7 @@ public: void UpdateAttackPowerAndDamage(bool /*ranged*/) override {} void UpdateDamagePhysical(WeaponAttackType /*attType*/) override {} - bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override; + bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const override; protected: TotemType m_type; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 3f96d8049..49a437543 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -72,6 +72,7 @@ #include "Vehicle.h" #include "World.h" #include "WorldPacket.h" +#include #include float baseMoveSpeed[MAX_MOVE_TYPE] = @@ -372,9 +373,6 @@ Unit::Unit() : WorldObject(), m_transform = 0; m_canModifyStats = false; - for (uint8 i = 0; i < MAX_SPELL_IMMUNITY; ++i) - m_spellImmune[i].clear(); - for (uint8 i = 0; i < UNIT_MOD_END; ++i) { m_auraFlatModifiersGroup[i][BASE_VALUE] = 0.0f; @@ -891,6 +889,29 @@ bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint return false; } +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster /*= nullptr*/) +{ + if (!spellInfo) + return false; + + for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!(effectMask & (1u << i))) + continue; + + if (IsImmunedToSpellEffect(spellInfo, i, caster)) + return true; + } + + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + if (IsImmunedToSchool(spellInfo)) + return true; + } + + return false; +} + bool Unit::HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura) const { AuraEffectList const& auras = GetAuraEffectsByType(type); @@ -2075,7 +2096,7 @@ void Unit::DealDamageShieldDamage(Unit* victim) } // ...or immuned - if (IsImmunedToDamageOrSchool(i_spellProto)) + if (IsImmunedToDamage(victim, i_spellProto)) { victim->SendSpellDamageImmune(this, i_spellProto->Id); continue; @@ -3203,7 +3224,7 @@ void Unit::SendMeleeAttackStop(Unit* victim) bool Unit::isSpellBlocked(Unit* victim, SpellInfo const* spellProto, WeaponAttackType attackType) { // These spells can't be blocked - if (spellProto && spellProto->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE)) + if (spellProto && (spellProto->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE) || spellProto->HasAttribute(SPELL_ATTR3_ALWAYS_HIT))) return false; if (victim->HasIgnoreHitDirectionAura() || victim->HasInArc(M_PI, this)) @@ -3571,6 +3592,9 @@ SpellMissInfo Unit::MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo // Resist SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool CanReflect) { + if (spell->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + return SPELL_MISS_NONE; + // Check for immune if (victim->IsImmunedToSpell(spell)) return SPELL_MISS_IMMUNE; @@ -3581,11 +3605,6 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool Ca && (!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell return SPELL_MISS_NONE; - // Check for immune - // xinef: check for school immunity only - if (victim->IsImmunedToSchool(spell)) - return SPELL_MISS_IMMUNE; - if (this == victim) return SPELL_MISS_NONE; @@ -3650,13 +3669,17 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, Spell const* spell, bool CanRef return SPELL_MISS_NONE; } - // Check for immune - // xinef: check for school immunity only - if (victim->IsImmunedToSchool(spell)) + // Check for immune to spell effects (includes school, mechanics, state, dispel immunities) + if (victim->IsImmunedToSpell(spellInfo, MAX_EFFECT_MASK, this)) { return SPELL_MISS_IMMUNE; } + // Damage immunity is only checked if the spell has damage effects, this immunity must not prevent aura apply + // returns SPELL_MISS_IMMUNE in that case, for other spells, the SMSG_SPELL_GO must show hit + if (spellInfo->HasOnlyDamageEffects() && victim->IsImmunedToDamage(this, spellInfo)) + return SPELL_MISS_IMMUNE; + if (this == victim) { return SPELL_MISS_NONE; @@ -5447,7 +5470,7 @@ void Unit::RemoveMovementImpairingAuras(bool withRoot) } } -void Unit::RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemode, uint32 except) +void Unit::RemoveAurasWithMechanic(uint64 mechanic_mask, AuraRemoveMode removemode, uint32 except) { for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { @@ -5466,7 +5489,7 @@ void Unit::RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemo void Unit::RemoveAurasByShapeShift() { - uint32 mechanic_mask = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT); + uint64 mechanic_mask = (UI64LIT(1) << MECHANIC_SNARE) | (UI64LIT(1) << MECHANIC_ROOT); for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { Aura const* aura = iter->second->GetBase(); @@ -6015,17 +6038,17 @@ bool Unit::HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid) return false; } -bool Unit::HasAuraWithMechanic(uint32 mechanicMask) const +bool Unit::HasAuraWithMechanic(uint64 mechanicMask) const { for (AuraApplicationMap::const_iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end(); ++iter) { SpellInfo const* spellInfo = iter->second->GetBase()->GetSpellInfo(); - if (spellInfo->Mechanic && (mechanicMask & (1 << spellInfo->Mechanic))) + if (spellInfo->Mechanic && (mechanicMask & (UI64LIT(1) << spellInfo->Mechanic))) return true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (iter->second->HasEffect(i) && spellInfo->Effects[i].Effect && spellInfo->Effects[i].Mechanic) - if (mechanicMask & (1 << spellInfo->Effects[i].Mechanic)) + if (mechanicMask & (UI64LIT(1) << spellInfo->Effects[i].Mechanic)) return true; } @@ -8833,7 +8856,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui }); } - if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask()) + if (uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask()) { int32 modifierMax = 0; int32 modifierMin = 0; @@ -8848,7 +8871,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui if (!caster || caster->GetGUID() != (*i)->GetCasterGUID()) continue; - if (mechanicMask & uint32(1 << (*i)->GetMiscValue())) + if (mechanicMask & uint64(UI64LIT(1) << (*i)->GetMiscValue())) { if ((*i)->GetAmount() > 0) { @@ -9739,107 +9762,90 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) return AdvertisedBenefit; } -bool Unit::IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const +uint32 Unit::GetDamageImmunityMask() const { - if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) - { - return false; - } - - // If m_immuneToDamage type contain magic, IMMUNE damage. - SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; - for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) - if ((itr->type & meleeSchoolMask) == meleeSchoolMask) - return true; - - return false; + uint32 mask = 0; + SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; + for (auto const& [immunitySchoolMask, _] : damageList) + mask |= immunitySchoolMask; + return mask; } -bool Unit::IsImmunedToDamage(SpellInfo const* spellInfo) const +bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const { - if (!spellInfo) - { - return false; - } - - if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) - { - return false; - } - - if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - return false; - } - - uint32 schoolMask = spellInfo->GetSchoolMask(); if (schoolMask == SPELL_SCHOOL_MASK_NONE) - { return false; - } - // If m_immuneToDamage type contain magic, IMMUNE damage. - SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; - for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) - if ((itr->type & schoolMask) == schoolMask) + // If m_immuneToSchool type contains all requested schools, IMMUNE damage. + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto const& itr : schoolList) + if (IsImmuneMaskFully(SpellSchoolMask(itr.first), schoolMask)) return true; - return false; + // If m_immuneToDamage type contains all requested schools, IMMUNE damage. + SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; + return std::ranges::any_of(damageList, [schoolMask](auto const& immune) + { + return Unit::IsImmuneMaskFully(SpellSchoolMask(immune.first), schoolMask); + }); } -bool Unit::IsImmunedToDamage(Spell const* spell) const +bool Unit::IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const { - SpellInfo const* spellInfo = spell->GetSpellInfo(); if (!spellInfo) - { return false; - } - if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) - { + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) return false; - } - if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - return false; - } - - uint32 schoolMask = spell->GetSpellSchoolMask(); + SpellSchoolMask schoolMask = SpellSchoolMask(spellInfo->GetSchoolMask()); if (schoolMask == SPELL_SCHOOL_MASK_NONE) - { return false; - } - // If m_immuneToDamage type contain magic, IMMUNE damage. - SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; - for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) + auto hasImmunity = [&](SpellImmuneContainer const& container) { - if ((itr->type & schoolMask) == schoolMask) + uint32 schoolImmunityMask = 0; + for (auto const& [immunitySchoolMask, immunityAuraId] : container) { - return true; + SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); + if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this)) + continue; + + if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo)) + continue; + + schoolImmunityMask |= immunitySchoolMask; } - } - return false; -} - -bool Unit::IsImmunedToSchool(SpellSchoolMask meleeSchoolMask) const -{ - if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) - { - return false; - } + // We need to be immune to all types + return (schoolImmunityMask & schoolMask) == schoolMask; + }; // If m_immuneToSchool type contain this school type, IMMUNE damage. - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) - if ((itr->type & meleeSchoolMask) == meleeSchoolMask) - return true; + if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL])) + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + if (hasImmunity(m_spellImmune[IMMUNITY_DAMAGE])) + return true; return false; } +bool Unit::IsImmunedToSchool(SpellSchoolMask schoolMask) const +{ + if (schoolMask == SPELL_SCHOOL_MASK_NONE) + return false; + + // Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered by at least one immunity + // (e.g., Anti-Magic Shell provides school immunity to all magic schools) + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + return std::ranges::any_of(schoolList, [schoolMask](auto const& immune) + { + return (immune.first & schoolMask) == schoolMask; + }); +} + bool Unit::IsImmunedToSchool(SpellInfo const* spellInfo) const { if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) @@ -9853,10 +9859,10 @@ bool Unit::IsImmunedToSchool(SpellInfo const* spellInfo) const if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) { - // If m_immuneToSchool type contain this school type, IMMUNE damage. - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) - if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId))) + // Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered and spell can't pierce + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + if ((itr->first & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) return true; } @@ -9879,11 +9885,11 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) { - // If m_immuneToSchool type contain this school type, IMMUNE damage. - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) + // Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered and spell can't pierce + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) { - if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId))) + if ((itr->first & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) { return true; } @@ -9893,19 +9899,49 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const return false; } -bool Unit::IsImmunedToDamageOrSchool(SpellSchoolMask meleeSchoolMask) const +bool Unit::IsImmunedToDamageOrSchool(SpellSchoolMask schoolMask) const { - if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) + if (schoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } - return IsImmunedToDamage(meleeSchoolMask) || IsImmunedToSchool(meleeSchoolMask); + return IsImmunedToDamage(schoolMask) || IsImmunedToSchool(schoolMask); } -bool Unit::IsImmunedToDamageOrSchool(SpellInfo const* spellInfo) const +bool Unit::IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spellInfo) const { - return IsImmunedToDamage(spellInfo) || IsImmunedToSchool(spellInfo); + if (!spellInfo) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + return false; + + SpellSchoolMask schoolMask = SpellSchoolMask(spellInfo->GetSchoolMask()); + if (schoolMask == SPELL_SCHOOL_MASK_NONE) + return false; + + auto hasImmunity = [&](SpellImmuneContainer const& container) + { + uint32 schoolImmunityMask = 0; + for (auto const& [immunitySchoolMask, immunityAuraId] : container) + { + SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); + if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this)) + continue; + + schoolImmunityMask |= immunitySchoolMask; + } + + // We need to be immune to all types + return (schoolImmunityMask & schoolMask) == schoolMask; + }; + + // If m_immuneToSchool type contain this school type, IMMUNE periodic tick. + if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL])) + return true; + + return false; } bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) @@ -9914,39 +9950,35 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) return false; // Single spell immunity. - SpellImmuneList const& idList = m_spellImmune[IMMUNITY_ID]; - for (SpellImmuneList::const_iterator itr = idList.begin(); itr != idList.end(); ++itr) - if (itr->type == spellInfo->Id) - return true; + SpellImmuneContainer const& idList = m_spellImmune[IMMUNITY_ID]; + if (idList.count(spellInfo->Id) > 0) + return true; // xinef: my special immunity, if spellid is not on this list it means npc is immune - SpellImmuneList const& allowIdList = m_spellImmune[IMMUNITY_ALLOW_ID]; + SpellImmuneContainer const& allowIdList = m_spellImmune[IMMUNITY_ALLOW_ID]; if (!allowIdList.empty()) { - for (SpellImmuneList::const_iterator itr = allowIdList.begin(); itr != allowIdList.end(); ++itr) - if (itr->type == spellInfo->Id) - return false; - return true; + if (allowIdList.count(spellInfo->Id) == 0) + return true; + return false; } if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) return false; - if (spellInfo->Dispel) + if (uint32 dispel = spellInfo->Dispel) { - SpellImmuneList const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; - for (SpellImmuneList::const_iterator itr = dispelList.begin(); itr != dispelList.end(); ++itr) - if (itr->type == spellInfo->Dispel) - return true; + SpellImmuneContainer const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; + if (dispelList.count(dispel) > 0) + return true; } // Spells that don't have effectMechanics. - if (spellInfo->Mechanic) + if (uint32 mechanic = spellInfo->Mechanic) { - SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; - for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) - if (itr->type == spellInfo->Mechanic) - return true; + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (mechanicList.count(mechanic) > 0) + return true; } bool immuneToAllEffects = true; @@ -9971,7 +10003,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. return true; - if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask(); if (spell) @@ -9981,16 +10013,24 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (spellSchoolMask != SPELL_SCHOOL_MASK_NONE) { - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) { - SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->spellId); - if (((itr->type & spellSchoolMask) == spellSchoolMask) - && (!immuneSpellInfo || immuneSpellInfo->IsPositive()) && !spellInfo->IsPositive() - && !spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second); + if (!(itr->first & spellSchoolMask)) + continue; + + if (immuneSpellInfo && !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) { - return true; + Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; + if (spellCaster && spellCaster->IsFriendlyTo(this)) + continue; } + + if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + continue; + + return true; } } } @@ -9998,7 +10038,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) return false; } -bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const +bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { if (!spellInfo || !spellInfo->Effects[index].IsEffect()) return false; @@ -10010,12 +10050,16 @@ bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) cons if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) return false; + // Special-case handling for Vezax's Aura of Despair. + static constexpr uint32 SPELL_AURA_OF_DESPAIR_1 = 62692; + static constexpr uint32 SPELL_AURA_OF_DESPAIR_2 = 64848; + //If m_immuneToEffect type contain this effect type, IMMUNE effect. uint32 effect = spellInfo->Effects[index].Effect; - SpellImmuneList const& effectList = m_spellImmune[IMMUNITY_EFFECT]; - for (SpellImmuneList::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr) + SpellImmuneContainer const& effectList = m_spellImmune[IMMUNITY_EFFECT]; + for (SpellImmuneContainer::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr) { - if (itr->type == effect && (itr->spellId != 62692 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) + if (itr->first == effect && (itr->second != SPELL_AURA_OF_DESPAIR_1 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) { return true; } @@ -10023,41 +10067,32 @@ bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) cons if (uint32 mechanic = spellInfo->Effects[index].Mechanic) { - SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; - for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) - if (itr->type == mechanic) - return true; + auto const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (mechanicList.count(mechanic) > 0) + return true; } - if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) + if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) { - SpellImmuneList const& list = m_spellImmune[IMMUNITY_STATE]; - for (SpellImmuneList::const_iterator itr = list.begin(); itr != list.end(); ++itr) + if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) { - if (itr->type == aura && (itr->spellId != 64848 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) + SpellImmuneContainer const& list = m_spellImmune[IMMUNITY_STATE]; + for (SpellImmuneContainer::const_iterator itr = list.begin(); itr != list.end(); ++itr) { - if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) - { - if (itr->blockType == SPELL_BLOCK_TYPE_ALL || spellInfo->IsPositive()) // xinef: added for pet scaling - { - return true; - } - } - } - } - - if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - // Check for immune to application of harmful magical effects - AuraEffectList const& immuneAuraApply = GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL); - for (AuraEffectList::const_iterator iter = immuneAuraApply.begin(); iter != immuneAuraApply.end(); ++iter) - { - if (/*(spellInfo->Dispel == DISPEL_MAGIC || spellInfo->Dispel == DISPEL_CURSE || spellInfo->Dispel == DISPEL_DISEASE) &&*/ // Magic debuff, xinef: all kinds? - ((*iter)->GetMiscValue() & spellInfo->GetSchoolMask()) && // Check school - !spellInfo->IsPositiveEffect(index) && // Harmful - spellInfo->Effects[index].Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA) // Not Persistent area auras - { + if (itr->first == aura && (itr->second != SPELL_AURA_OF_DESPAIR_2 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) return true; + } + + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + // Check for immune to application of harmful magical effects + for (AuraEffect* immuneAuraApply : GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL)) + { + if (!(immuneAuraApply->GetMiscValue() & spellInfo->GetSchoolMask())) // Check school + continue; + + if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || (caster && !IsFriendlyTo(caster))) // Harmful + return true; } } } @@ -10286,17 +10321,17 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT }); // Mod damage from spell mechanic - uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask(); + uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask(); // Shred, Maul - "Effects which increase Bleed damage also increase Shred damage" if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[0] & 0x00008800) - mechanicMask |= (1 << MECHANIC_BLEED); + mechanicMask |= (UI64LIT(1) << MECHANIC_BLEED); if (mechanicMask) { TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask](AuraEffect const* aurEff) -> bool { - if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + if (mechanicMask & uint64(UI64LIT(1) << (aurEff->GetMiscValue()))) return true; return false; }); @@ -10364,51 +10399,17 @@ private: uint32 _type; }; -void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType) +void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType /*blockType*/) { if (apply) - { - // xinef: immunities with spellId 0 are intended to be applied only once (script purposes mosty) - if (spellId == 0 && std::find_if(m_spellImmune[op].begin(), m_spellImmune[op].end(), spellIdImmunityPredicate(type)) != m_spellImmune[op].end()) - return; - - SpellImmune immune; - immune.spellId = spellId; - immune.type = type; - immune.blockType = blockType; - m_spellImmune[op].push_back(std::move(immune)); - } + m_spellImmune[op].emplace(type, spellId); else { - for (SpellImmuneList::iterator itr = m_spellImmune[op].begin(); itr != m_spellImmune[op].end(); ++itr) + auto bounds = m_spellImmune[op].equal_range(type); + for (auto itr = bounds.first; itr != bounds.second;) { - if (itr->spellId == spellId && itr->type == type) - { - m_spellImmune[op].erase(itr); - break; - } - } - } -} - -void Unit::ApplySpellDispelImmunity(SpellInfo const* spellProto, DispelType type, bool apply) -{ - ApplySpellImmune(spellProto->Id, IMMUNITY_DISPEL, type, apply); - - if (apply && spellProto->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) - { - // Create dispel mask by dispel type - uint32 dispelMask = SpellInfo::GetDispelMask(type); - // Dispel all existing auras vs current dispel type - AuraApplicationMap& auras = GetAppliedAuras(); - for (AuraApplicationMap::iterator itr = auras.begin(); itr != auras.end();) - { - SpellInfo const* spell = itr->second->GetBase()->GetSpellInfo(); - if (spell->GetDispelMask() & dispelMask) - { - // Dispel aura - RemoveAura(itr); - } + if (itr->second == spellId) + itr = m_spellImmune[op].erase(itr); else ++itr; } @@ -11174,8 +11175,9 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) { if (Creature* creature = ToCreature()) { - uint32 immuneMask = creature->GetCreatureTemplate()->MechanicImmuneMask; - if (immuneMask & (1 << (MECHANIC_SNARE - 1)) || immuneMask & (1 << (MECHANIC_DAZE - 1))) + // use creature helper which now consults creature_immunities table + if (creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE) || + creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_DAZE)) break; } @@ -11196,7 +11198,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) if (creature && !IsPet() && !(IsControlledByPlayer() && IsVehicle()) - && !(creature->HasMechanicTemplateImmunity(MECHANIC_SNARE)) + && !(creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE)) && !(creature->IsDungeonBoss())) { // 1.6% for each % under 30. @@ -15075,7 +15077,7 @@ Aura* Unit::AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target) { if (!(effMask & (1 << i))) continue; - if (target->IsImmunedToSpellEffect(spellInfo, i)) + if (target->IsImmunedToSpellEffect(spellInfo, i, this)) effMask &= ~(1 << i); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 286548115..b784c702e 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1408,7 +1408,7 @@ public: void RemoveAurasWithInterruptFlags(uint32 flag, uint32 except = 0, bool isAutoshot = false); void RemoveAurasWithAttribute(uint32 flags); void RemoveAurasWithFamily(SpellFamilyNames family, uint32 familyFlag1, uint32 familyFlag2, uint32 familyFlag3, ObjectGuid casterGUID); - void RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemode = AURA_REMOVE_BY_DEFAULT, uint32 except = 0); + void RemoveAurasWithMechanic(uint64 mechanic_mask, AuraRemoveMode removemode = AURA_REMOVE_BY_DEFAULT, uint32 except = 0); void RemoveMovementImpairingAuras(bool withRoot); void RemoveAurasByShapeShift(); @@ -1500,7 +1500,7 @@ public: bool HasNegativeAuraWithInterruptFlag(uint32 flag, ObjectGuid guid = ObjectGuid::Empty); [[nodiscard]] bool HasVisibleAuraType(AuraType auraType) const; bool HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid = ObjectGuid::Empty); - [[nodiscard]] bool HasAuraWithMechanic(uint32 mechanicMask) const; + [[nodiscard]] bool HasAuraWithMechanic(uint64 mechanicMask) const; [[nodiscard]] bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const; @@ -1636,17 +1636,22 @@ public: // Spells immunities void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType = SPELL_BLOCK_TYPE_ALL); - void ApplySpellDispelImmunity(SpellInfo const* spellProto, DispelType type, bool apply); virtual bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr); - [[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const; - [[nodiscard]] bool IsImmunedToDamage(SpellInfo const* spellInfo) const; - [[nodiscard]] bool IsImmunedToDamage(Spell const* spell) const; - [[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask meleeSchoolMask) const; + bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster = nullptr); + [[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask schoolMask) const; + [[nodiscard]] bool IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const; + [[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask schoolMask) const; + + static bool IsImmuneMaskFully(SpellSchoolMask immuneMask, SpellSchoolMask schoolMask) { return (immuneMask & schoolMask) == schoolMask; } + + [[nodiscard]] uint32 GetSchoolImmunityMask() const; + [[nodiscard]] uint32 GetDamageImmunityMask() const; + [[nodiscard]] bool IsImmunedToSchool(SpellInfo const* spellInfo) const; [[nodiscard]] bool IsImmunedToSchool(Spell const* spell) const; - [[nodiscard]] bool IsImmunedToDamageOrSchool(SpellSchoolMask meleeSchoolMask) const; - bool IsImmunedToDamageOrSchool(SpellInfo const* spellInfo) const; - virtual bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const; + [[nodiscard]] bool IsImmunedToDamageOrSchool(SpellSchoolMask schoolMask) const; + [[nodiscard]] bool IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spellInfo) const; + virtual bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const; // Critic chances bool isBlockCritical(); @@ -2080,7 +2085,8 @@ public: float m_modAttackSpeedPct[3]; - SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY]; + typedef std::unordered_multimap SpellImmuneContainer; + SpellImmuneContainer m_spellImmune[MAX_SPELL_IMMUNITY]; uint32 m_lastSanctuaryTime; // pet auras diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index c3d639022..5d9ca3dc8 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -522,18 +522,18 @@ void ObjectMgr::LoadCreatureTemplates() // 0 1 2 3 4 5 6 7 8 QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, " -// 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - "gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, " -// 23 24 25 26 27 28 29 30 31 32 +// 9 10 11 12 13 14 15 16 17 18 19 20 21 + "gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, " +// 22 23 24 25 26 27 28 29 30 31 "DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, " -// 33 34 35 36 37 +// 32 33 34 35 36 "type, type_flags, lootid, pickpocketloot, skinloot, " -// 38 39 40 41 42 43 44 45 46 47 +// 37 38 39 40 41 42 43 44 45 46 "PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, " -// 48 49 50 51 52 53 54 55 56 57 58 59 - "ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, " -// 60 61 62 - "spell_school_immune_mask, flags_extra, ScriptName " +// 47 48 49 50 51 52 53 54 55 56 57 + "ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, " +// 58 59 60 + "CreatureImmunitiesId, flags_extra, ScriptName " "FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId ORDER BY entry DESC;"); if (!result) @@ -619,24 +619,23 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook) creatureTemplate.speed_swim = fields[17].Get(); creatureTemplate.speed_flight = fields[18].Get(); creatureTemplate.detection_range = fields[19].Get(); - creatureTemplate.scale = fields[20].Get(); - creatureTemplate.rank = uint32(fields[21].Get()); - creatureTemplate.dmgschool = uint32(fields[22].Get()); - creatureTemplate.DamageModifier = fields[23].Get(); - creatureTemplate.BaseAttackTime = fields[24].Get(); - creatureTemplate.RangeAttackTime = fields[25].Get(); - creatureTemplate.BaseVariance = fields[26].Get(); - creatureTemplate.RangeVariance = fields[27].Get(); - creatureTemplate.unit_class = uint32(fields[28].Get()); - creatureTemplate.unit_flags = fields[29].Get(); - creatureTemplate.unit_flags2 = fields[30].Get(); - creatureTemplate.dynamicflags = fields[31].Get(); - creatureTemplate.family = uint32(fields[32].Get()); - creatureTemplate.type = uint32(fields[33].Get()); - creatureTemplate.type_flags = fields[34].Get(); - creatureTemplate.lootid = fields[35].Get(); - creatureTemplate.pickpocketLootId = fields[36].Get(); - creatureTemplate.SkinLootId = fields[37].Get(); + creatureTemplate.rank = uint32(fields[20].Get()); + creatureTemplate.dmgschool = uint32(fields[21].Get()); + creatureTemplate.DamageModifier = fields[22].Get(); + creatureTemplate.BaseAttackTime = fields[23].Get(); + creatureTemplate.RangeAttackTime = fields[24].Get(); + creatureTemplate.BaseVariance = fields[25].Get(); + creatureTemplate.RangeVariance = fields[26].Get(); + creatureTemplate.unit_class = uint32(fields[27].Get()); + creatureTemplate.unit_flags = fields[28].Get(); + creatureTemplate.unit_flags2 = fields[29].Get(); + creatureTemplate.dynamicflags = fields[30].Get(); + creatureTemplate.family = uint32(fields[31].Get()); + creatureTemplate.type = uint32(fields[32].Get()); + creatureTemplate.type_flags = fields[33].Get(); + creatureTemplate.lootid = fields[34].Get(); + creatureTemplate.pickpocketLootId = fields[35].Get(); + creatureTemplate.SkinLootId = fields[36].Get(); for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) { @@ -648,49 +647,56 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook) creatureTemplate.spells[i] = 0; } - creatureTemplate.PetSpellDataId = fields[38].Get(); - creatureTemplate.VehicleId = fields[39].Get(); - creatureTemplate.mingold = fields[40].Get(); - creatureTemplate.maxgold = fields[41].Get(); - creatureTemplate.AIName = fields[42].Get(); - creatureTemplate.MovementType = uint32(fields[43].Get()); - if (!fields[44].IsNull()) + creatureTemplate.PetSpellDataId = fields[37].Get(); + creatureTemplate.VehicleId = fields[38].Get(); + creatureTemplate.mingold = fields[39].Get(); + creatureTemplate.maxgold = fields[40].Get(); + creatureTemplate.AIName = fields[41].Get(); + creatureTemplate.MovementType = uint32(fields[42].Get()); + if (!fields[43].IsNull()) { - creatureTemplate.Movement.Ground = static_cast(fields[44].Get()); + creatureTemplate.Movement.Ground = static_cast(fields[43].Get()); } - creatureTemplate.Movement.Swim = fields[45].Get(); - if (!fields[46].IsNull()) + creatureTemplate.Movement.Swim = fields[44].Get(); + if (!fields[45].IsNull()) { - creatureTemplate.Movement.Flight = static_cast(fields[46].Get()); + creatureTemplate.Movement.Flight = static_cast(fields[45].Get()); } - creatureTemplate.Movement.Rooted = fields[47].Get(); + creatureTemplate.Movement.Rooted = fields[46].Get(); + if (!fields[47].IsNull()) + { + creatureTemplate.Movement.Chase = static_cast(fields[47].Get()); + } if (!fields[48].IsNull()) { - creatureTemplate.Movement.Chase = static_cast(fields[48].Get()); + creatureTemplate.Movement.Random = static_cast(fields[48].Get()); } if (!fields[49].IsNull()) { - creatureTemplate.Movement.Random = static_cast(fields[49].Get()); - } - if (!fields[50].IsNull()) - { - creatureTemplate.Movement.InteractionPauseTimer = fields[50].Get(); + creatureTemplate.Movement.InteractionPauseTimer = fields[49].Get(); } - creatureTemplate.HoverHeight = fields[51].Get(); - creatureTemplate.ModHealth = fields[52].Get(); - creatureTemplate.ModMana = fields[53].Get(); - creatureTemplate.ModArmor = fields[54].Get(); - creatureTemplate.ModExperience = fields[55].Get(); - creatureTemplate.RacialLeader = fields[56].Get(); - creatureTemplate.movementId = fields[57].Get(); - creatureTemplate.RegenHealth = fields[58].Get(); - creatureTemplate.MechanicImmuneMask = fields[59].Get(); - creatureTemplate.SpellSchoolImmuneMask = fields[60].Get(); - creatureTemplate.flags_extra = fields[61].Get(); - creatureTemplate.ScriptID = GetScriptId(fields[62].Get()); + creatureTemplate.HoverHeight = fields[50].Get(); + creatureTemplate.ModHealth = fields[51].Get(); + creatureTemplate.ModMana = fields[52].Get(); + creatureTemplate.ModArmor = fields[53].Get(); + creatureTemplate.ModExperience = fields[54].Get(); + creatureTemplate.RacialLeader = fields[55].Get(); + creatureTemplate.movementId = fields[56].Get(); + creatureTemplate.RegenHealth = fields[57].Get(); + creatureTemplate.CreatureImmunitiesId = fields[58].Get(); + creatureTemplate.flags_extra = fields[59].Get(); + creatureTemplate.ScriptID = GetScriptId(fields[60].Get()); + + // Warn about deprecated immunity flags that should be moved to `creature_immunities` table + if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_NO_TAUNT) + LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit NO_TAUNT (0x100) set. This will be migrated to the `creature_immunities` table in a future update.", entry); + if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_AVOID_AOE) + LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit AVOID_AOE (0x400000) set. This will be migrated to the `creature_immunities` table in a future update.", entry); + if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK) + LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit IMMUNITY_KNOCKBACK (0x40000000) set. This will be migrated to the `creature_immunities` table in a future update.", entry); // useful if the creature template load is being triggered from outside this class if (triggerHook) diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index e7c030cc3..5506a9461 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -1223,7 +1223,7 @@ bool AuraEffect::CheckEffectProc(AuraApplication* aurApp, ProcEventInfo& eventIn case SPELL_AURA_MECHANIC_IMMUNITY: case SPELL_AURA_MOD_MECHANIC_RESISTANCE: // compare mechanic - if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (1 << GetMiscValue()))) + if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (UI64LIT(1) << GetMiscValue()))) return false; break; case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: @@ -3933,312 +3933,7 @@ void AuraEffect::HandleModStateImmunityMask(AuraApplication const* aurApp, uint8 return; Unit* target = aurApp->GetTarget(); - std::list aura_immunity_list; - uint32 mechanic_immunity_list = 0; - int32 miscVal = GetMiscValue(); - - switch (miscVal) - { - case 96: - case 1615: - { - if (!GetAmount()) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_CHARM); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 679: - { - if (GetId() == 57742) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 1557: - { - if (GetId() == 64187) - { - mechanic_immunity_list = (1 << MECHANIC_STUN); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - } - else - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 1614: - case 1694: - { - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT); - break; - } - case 1630: - { - if (!GetAmount()) - { - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT); - } - else - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 477: - case 1733: - case 1632: - { - if (!GetAmount()) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN) | (1 << MECHANIC_BANISH); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_BANISH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 878: - { - if (GetAmount() == 1) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_DISORIENTED) | (1 << MECHANIC_FREEZE); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - } - break; - } - default: - break; - } - - if (aura_immunity_list.empty()) - { - // Roots, OK - if (GetMiscValue() & (1 << 0)) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - } - // Taunt, OK - if (GetMiscValue() & (1 << 1)) - { - aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT); - } - // Crowd-Control auras? - if (GetMiscValue() & (1 << 2)) - { - mechanic_immunity_list = (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - } - // Interrupt, OK - if (GetMiscValue() & (1 << 3)) - { - mechanic_immunity_list = (1 << MECHANIC_INTERRUPT); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_INTERRUPT, apply); - } - // Transform? - if (GetMiscValue() & (1 << 4)) - { - aura_immunity_list.push_back(SPELL_AURA_TRANSFORM); - } - // Stun auras breakable by damage (Incapacitate effects), OK - if (GetMiscValue() & (1 << 5)) - { - mechanic_immunity_list = (1 << MECHANIC_KNOCKOUT); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_KNOCKOUT, apply); - } - // // Slowing effects - if (GetMiscValue() & (1 << 6)) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - } - // Charm auras?, 90% - if ((GetMiscValue() & (1 << 7))) - { - mechanic_immunity_list = (1 << MECHANIC_CHARM); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_CHARM); - aura_immunity_list.push_back(SPELL_AURA_MOD_POSSESS); - } - // UNK - // if ((GetMiscValue() & (1 << 8))) - // { - // } - // Fear, OK - if (GetMiscValue() & (1 << 9)) - { - mechanic_immunity_list = (1 << MECHANIC_FEAR); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - // Stuns, OK - if (GetMiscValue() & (1 << 10)) - { - mechanic_immunity_list = (1 << MECHANIC_STUN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - } - } - - // apply immunities - for (std::list ::iterator iter = aura_immunity_list.begin(); iter != aura_immunity_list.end(); ++iter) - target->ApplySpellImmune(GetId(), IMMUNITY_STATE, *iter, apply); - - // Patch 3.0.3 Bladestorm now breaks all snares and roots on the warrior when activated. - if (GetId() == 46924) - { - // Knockback and hex - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, apply); - } - - if (apply && GetSpellInfo()->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) - { - target->RemoveAurasWithMechanic(mechanic_immunity_list, AURA_REMOVE_BY_DEFAULT, GetId()); - for (std::list ::iterator iter = aura_immunity_list.begin(); iter != aura_immunity_list.end(); ++iter) - target->RemoveAurasByType(*iter); - } + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); } void AuraEffect::HandleModMechanicImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -4247,67 +3942,7 @@ void AuraEffect::HandleModMechanicImmunity(AuraApplication const* aurApp, uint8 return; Unit* target = aurApp->GetTarget(); - uint32 mechanic = 0; - - switch (GetId()) - { - case 46924: // BladeStorm - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - break; - case 34471: // The Beast Within - case 19574: // Bestial Wrath - case 38484: // Bestial Wrath - case 40081: // Free friend (Black Temple) - mechanic = IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_KNOCKOUT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_BANISH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SHACKLE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DAZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - break; - case 42292: // PvP trinket - case 59752: // Every Man for Himself - case 65547: // PvP trinket for Faction Champions (ToC 25) - case 53490: // Bullheaded - case 46227: // Medalion of Immunity - mechanic = IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; - target->RemoveAurasByType(SPELL_AURA_PREVENTS_FLEEING); // xinef: Patch 2.3.0 PvP Trinkets: Insignia of the Alliance, Insignia of the Horde, Medallion of the Alliance, and Medallion of the Horde now clear the debuff from Judgement of Justice. - // Actually we should apply immunities here, too, but the aura has only 100 ms duration, so there is practically no point - break; - case 54508: // Demonic Empowerment - mechanic = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT); - target->RemoveAurasByType(SPELL_AURA_MOD_STUN); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - break; - default: - if (GetMiscValue() < 1) - return; - mechanic = 1 << GetMiscValue(); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, GetMiscValue(), apply); - break; - } - - if (apply && GetSpellInfo()->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) - { - // Xinef: exception for purely snare mechanic (eg. hands of freedom)! - if (mechanic == (1 << MECHANIC_SNARE)) - target->RemoveMovementImpairingAuras(false); - else - target->RemoveAurasWithMechanic(mechanic, AURA_REMOVE_BY_DEFAULT, GetId()); - } + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); } void AuraEffect::HandleAuraModEffectImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -4415,7 +4050,7 @@ void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 m Unit* target = aurApp->GetTarget(); - target->ApplySpellImmune(GetId(), IMMUNITY_DAMAGE, GetMiscValue(), apply); + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); if (apply) target->GetThreatMgr().EvaluateSuppressed(); @@ -4427,8 +4062,7 @@ void AuraEffect::HandleAuraModDispelImmunity(AuraApplication const* aurApp, uint return; Unit* target = aurApp->GetTarget(); - - target->ApplySpellDispelImmunity(m_spellInfo, DispelType(GetMiscValue()), (apply)); + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); } /*********************************************************/ @@ -6643,7 +6277,7 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const if (!target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo()) || target->IsTotem()) + if (target->IsImmunedToDamage(caster, GetSpellInfo()) || target->IsTotem()) { SendTickImmune(target, caster); return; @@ -6795,7 +6429,7 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c if (!target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo())) + if (target->IsImmunedToDamage(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -6905,7 +6539,7 @@ void AuraEffect::HandlePeriodicHealthFunnelAuraTick(Unit* target, Unit* caster) if (!caster || !caster->IsAlive() || !target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -6934,7 +6568,7 @@ void AuraEffect::HandlePeriodicHealAurasTick(Unit* target, Unit* caster) const if (!target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7073,7 +6707,7 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con if (!caster || !caster->IsAlive() || !target->IsAlive() || !target->HasActivePowerType(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo())) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7148,7 +6782,7 @@ void AuraEffect::HandleObsModPowerAuraTick(Unit* target, Unit* caster) const if (!target->IsAlive() || !target->GetMaxPower(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7181,7 +6815,7 @@ void AuraEffect::HandlePeriodicEnergizeAuraTick(Unit* target, Unit* caster) cons if (!target->IsAlive() || !target->GetMaxPower(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7212,7 +6846,7 @@ void AuraEffect::HandlePeriodicPowerBurnAuraTick(Unit* target, Unit* caster) con if (!caster || !target->IsAlive() || !target->HasActivePowerType(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo())) + if (target->IsImmunedToDamage(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7306,7 +6940,8 @@ void AuraEffect::HandleProcTriggerDamageAuraProc(AuraApplication* aurApp, ProcEv Unit* triggerTarget = target == eventInfo.GetActor() ? eventInfo.GetActionTarget() : eventInfo.GetActor(); if (!triggerTarget) return; - if (triggerTarget->HasUnitState(UNIT_STATE_ISOLATED) || triggerTarget->IsImmunedToDamageOrSchool(GetSpellInfo())) + + if (triggerTarget->IsImmunedToDamage(target, GetSpellInfo())) { SendTickImmune(triggerTarget, target); return; diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index e4b59781e..3ca928b47 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -552,7 +552,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply) if (IsArea()) for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) { - if ((existing->second & (1 << effIndex)) && existing->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex)) + if ((existing->second & (1 << effIndex)) && existing->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex, GetCaster())) existing->second &= ~(1 << effIndex); } @@ -594,7 +594,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply) // check target immunities for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) { - if ((itr->second & (1 << effIndex)) && itr->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex)) + if ((itr->second & (1 << effIndex)) && itr->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex, GetCaster())) itr->second &= ~(1 << effIndex); } if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo()) || !CanBeAppliedOn(itr->first)) @@ -2822,7 +2822,7 @@ void UnitAura::FillTargetMap(std::map& targets, Unit* caster) { float radius = GetSpellInfo()->Effects[effIndex].CalcRadius(caster); - if (!GetUnitOwner()->HasUnitState(UNIT_STATE_ISOLATED)) + if (!GetUnitOwner()->HasAuraState(AURA_STATE_BANISHED, GetSpellInfo(), caster)) { switch (GetSpellInfo()->Effects[effIndex].Effect) { diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index a901be115..4283d5a13 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1315,7 +1315,7 @@ void Spell::SelectImplicitAreaTargets(SpellEffIndex effIndex, SpellImplicitTarge // Xinef: the distance should be increased by caster size, it is neglected in latter calculations std::list targets; float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod; - SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions); + SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions, Acore::WorldObjectSpellAreaTargetSearchReason::Area); CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType); @@ -2091,12 +2091,12 @@ WorldObject* Spell::SearchNearbyTarget(float range, SpellTargetObjectTypes objec return target; } -void Spell::SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList) +void Spell::SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason) { uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList); if (!containerTypeMask) return; - Acore::WorldObjectSpellAreaTargetCheck check(range, position, m_caster, referer, m_spellInfo, selectionType, condList); + Acore::WorldObjectSpellAreaTargetCheck check(range, position, m_caster, referer, m_spellInfo, selectionType, condList, searchReason); Acore::WorldObjectListSearcher searcher(m_caster, targets, check, containerTypeMask); SearchTargets > (searcher, containerTypeMask, m_caster, position, range); } @@ -2142,7 +2142,7 @@ void Spell::SearchChainTargets(std::list& targets, uint32 chainTar WorldObject* chainSource = m_spellInfo->HasAttribute(SPELL_ATTR2_CHAIN_FROM_CASTER) ? m_caster : target; std::list tempTargets; - SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, condList); + SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, condList, Acore::WorldObjectSpellAreaTargetSearchReason::Chain); tempTargets.remove(target); // remove targets which are always invalid for chain spells @@ -2310,7 +2310,7 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*= // Check for effect immune skip if immuned for (uint32 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) - if (target->IsImmunedToSpellEffect(m_spellInfo, effIndex)) + if (target->IsImmunedToSpellEffect(m_spellInfo, effIndex, m_caster)) effectMask &= ~(1 << effIndex); ObjectGuid targetGUID = target->GetGUID(); @@ -2777,83 +2777,93 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) // Fill base damage struct (unitTarget - is real spell target) SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo, m_spellSchoolMask); - // Add bonuses and fill damageInfo struct - // Dancing Rune Weapon... - if (m_caster->GetEntry() == 27893) + // Check damage immunity + if (unitTarget->IsImmunedToDamage(caster, m_spellInfo)) { - if (Unit* owner = m_caster->GetOwner()) - owner->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); + m_damage = 0; + + // no packet found in sniffs } else - caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); - - // xinef: override miss info after absorb / block calculations - if (missInfo == SPELL_MISS_NONE && damageInfo.damage == 0) { - //if (damageInfo.absorb > 0) - // missInfo = SPELL_MISS_ABSORB; - if (damageInfo.blocked) - missInfo = SPELL_MISS_BLOCK; - } - - // Xinef: override with forced crit, only visual result - if (GetSpellValue()->ForcedCritResult) - { - damageInfo.HitInfo |= SPELL_HIT_TYPE_CRIT; - } - - Unit::DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb); - - // xinef: health leech handling - if (m_spellInfo->HasEffect(SPELL_EFFECT_HEALTH_LEECH)) - { - uint8 effIndex = EFFECT_0; - for (; effIndex < MAX_SPELL_EFFECTS; ++effIndex) - if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_HEALTH_LEECH) - break; - - float healMultiplier = m_spellInfo->Effects[effIndex].CalcValueMultiplier(m_originalCaster, this); - - // get max possible damage, don't count overkill for heal - uint32 healthGain = uint32(-unitTarget->GetHealthGain(-int32(damageInfo.damage)) * healMultiplier); - - if (m_caster->IsAlive()) + // Add bonuses and fill damageInfo struct + // Dancing Rune Weapon... + if (m_caster->GetEntry() == 27893) { - healthGain = m_caster->SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, HEAL, effIndex); - healthGain = m_caster->SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, HEAL); - - HealInfo healInfo(m_caster, m_caster, healthGain, m_spellInfo, m_spellInfo->GetSchoolMask()); - m_caster->HealBySpell(healInfo); + if (Unit* owner = m_caster->GetOwner()) + owner->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); } + else + caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); + + // xinef: override miss info after absorb / block calculations + if (missInfo == SPELL_MISS_NONE && damageInfo.damage == 0) + { + //if (damageInfo.absorb > 0) + // missInfo = SPELL_MISS_ABSORB; + if (damageInfo.blocked) + missInfo = SPELL_MISS_BLOCK; + } + + // Xinef: override with forced crit, only visual result + if (GetSpellValue()->ForcedCritResult) + { + damageInfo.HitInfo |= SPELL_HIT_TYPE_CRIT; + } + + Unit::DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb); + + // xinef: health leech handling + if (m_spellInfo->HasEffect(SPELL_EFFECT_HEALTH_LEECH)) + { + uint8 effIndex = EFFECT_0; + for (; effIndex < MAX_SPELL_EFFECTS; ++effIndex) + if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_HEALTH_LEECH) + break; + + float healMultiplier = m_spellInfo->Effects[effIndex].CalcValueMultiplier(m_originalCaster, this); + + // get max possible damage, don't count overkill for heal + uint32 healthGain = uint32(-unitTarget->GetHealthGain(-int32(damageInfo.damage)) * healMultiplier); + + if (m_caster->IsAlive()) + { + healthGain = m_caster->SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, HEAL, effIndex); + healthGain = m_caster->SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, HEAL); + + HealInfo healInfo(m_caster, m_caster, healthGain, m_spellInfo, m_spellInfo->GetSchoolMask()); + m_caster->HealBySpell(healInfo); + } + } + + // Send log damage message to client + caster->SendSpellNonMeleeDamageLog(&damageInfo); + // Xinef: send info to target about reflect + if (reflectedSpell) + effectUnit->SendSpellNonMeleeReflectLog(&damageInfo, effectUnit); + + procVictim |= PROC_FLAG_TAKEN_DAMAGE; + + caster->DealSpellDamage(&damageInfo, true, this); + + // do procs after damage, eg healing effects + // no need to check if target is alive, done in procdamageandspell + + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (canEffectTrigger) + { + DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, missInfo); + uint32 hitMask = m_procEx | dmgInfo.GetHitMask(); + Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + m_triggeredByAuraSpell.effectIndex, this, &dmgInfo); + + if (caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 && + m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) + caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, dmgInfo.GetHitMask()); + } + + m_damage = damageInfo.damage; } - - // Send log damage message to client - caster->SendSpellNonMeleeDamageLog(&damageInfo); - // Xinef: send info to target about reflect - if (reflectedSpell) - effectUnit->SendSpellNonMeleeReflectLog(&damageInfo, effectUnit); - - procVictim |= PROC_FLAG_TAKEN_DAMAGE; - - caster->DealSpellDamage(&damageInfo, true, this); - - // do procs after damage, eg healing effects - // no need to check if target is alive, done in procdamageandspell - - // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) - if (canEffectTrigger) - { - DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, missInfo); - uint32 hitMask = m_procEx | dmgInfo.GetHitMask(); - Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, - m_triggeredByAuraSpell.effectIndex, this, &dmgInfo); - - if (caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 && - m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) - caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, dmgInfo.GetHitMask()); - } - - m_damage = damageInfo.damage; } // Passive spell hits/misses or active spells only misses (only triggers) else @@ -2939,7 +2949,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA return SPELL_MISS_EVADE; // For delayed spells immunity may be applied between missile launch and hit - check immunity for that case - if (m_spellInfo->Speed && ((m_damage > 0 && unit->IsImmunedToDamage(this)) || unit->IsImmunedToSchool(this) || unit->IsImmunedToSpell(m_spellInfo, this))) + if (m_spellInfo->Speed && ((m_damage > 0 && unit->IsImmunedToDamage(m_caster, m_spellInfo)) || unit->IsImmunedToSchool(this) || unit->IsImmunedToSpell(m_spellInfo, this))) { return SPELL_MISS_IMMUNE; } @@ -2950,7 +2960,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA { if (effectMask & (1 << effectNumber)) { - if (unit->IsImmunedToSpellEffect(m_spellInfo, effectNumber)) + if (unit->IsImmunedToSpellEffect(m_spellInfo, effectNumber, m_caster)) effectMask &= ~(1 << effectNumber); // Xinef: Buggs out polymorph // Xinef: And this is checked in MagicSpellHitResult, why we check resistance twice? @@ -3096,14 +3106,6 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA if (m_spellAura) { - // Prevent aura application if target is banished and immuned - if (m_targets.GetUnitTarget() && m_targets.GetUnitTarget()->IsImmunedToDamageOrSchool(m_spellAura->GetSpellInfo()) - && m_targets.GetUnitTarget()->HasUnitState(UNIT_STATE_ISOLATED)) - { - m_spellAura->Remove(); - return SPELL_MISS_IMMUNE; - } - // Set aura stack amount to desired value if (m_spellValue->AuraStackAmount > 1) { @@ -5606,7 +5608,7 @@ void Spell::HandleEffects(Unit* pUnitTarget, Item* pItemTarget, GameObject* pGOT } } -SpellCastResult Spell::CheckCast(bool strict) +SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*param2*/) { // check death state if (!m_caster->IsAlive() && !m_spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE) && !(m_spellInfo->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD) || (IsTriggered() && !m_triggeredByAuraSpell))) @@ -6015,10 +6017,9 @@ SpellCastResult Spell::CheckCast(bool strict) // xinef: Enraged Regeneration: While this is active, the warrior is blocked from using abilities that trigger being enraged (which would do nothing and waste the cooldowns). if (m_spellInfo->Mechanic && m_spellInfo->IsSelfCast()) { - SpellImmuneList const& mechanicList = m_caster->m_spellImmune[IMMUNITY_MECHANIC]; - for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) - if (itr->type == m_spellInfo->Mechanic) - return SPELL_FAILED_DAMAGE_IMMUNE; + auto const& mechanicList = m_caster->m_spellImmune[IMMUNITY_MECHANIC]; + if (mechanicList.count(m_spellInfo->Mechanic) > 0) + return SPELL_FAILED_DAMAGE_IMMUNE; } } @@ -7158,7 +7159,7 @@ SpellCastResult Spell::CheckPower() return SPELL_CAST_OK; } -SpellCastResult Spell::CheckItems() +SpellCastResult Spell::CheckItems(uint32* param1, uint32* param2) { Player* player = m_caster->ToPlayer(); if (!player) @@ -7566,21 +7567,29 @@ SpellCastResult Spell::CheckItems() } case SPELL_EFFECT_PROSPECTING: { - if (!m_targets.GetItemTarget()) + Item* item = m_targets.GetItemTarget(); + if (!item) return SPELL_FAILED_CANT_BE_PROSPECTED; //ensure item is a prospectable ore - if (!(m_targets.GetItemTarget()->GetTemplate()->HasFlag(ITEM_FLAG_IS_PROSPECTABLE))) + if (!(item->GetTemplate()->HasFlag(ITEM_FLAG_IS_PROSPECTABLE))) return SPELL_FAILED_CANT_BE_PROSPECTED; //prevent prospecting in trade slot - if (m_targets.GetItemTarget()->GetOwnerGUID() != m_caster->GetGUID()) + if (item->GetOwnerGUID() != m_caster->GetGUID()) return SPELL_FAILED_CANT_BE_PROSPECTED; //Check for enough skill in jewelcrafting - uint32 item_prospectingskilllevel = m_targets.GetItemTarget()->GetTemplate()->RequiredSkillRank; + uint32 item_prospectingskilllevel = item->GetTemplate()->RequiredSkillRank; if (item_prospectingskilllevel > player->GetSkillValue(SKILL_JEWELCRAFTING)) return SPELL_FAILED_LOW_CASTLEVEL; //make sure the player has the required ores in inventory - if (m_targets.GetItemTarget()->GetCount() < 5) + if (item->GetCount() < 5) + { + if (param1 && param2) + { + *param1 = item->GetEntry(); + *param2 = 5; + } return SPELL_FAILED_NEED_MORE_ITEMS; + } if (!LootTemplates_Prospecting.HaveLootFor(m_targets.GetItemTargetEntry())) return SPELL_FAILED_CANT_BE_PROSPECTED; @@ -7589,21 +7598,29 @@ SpellCastResult Spell::CheckItems() } case SPELL_EFFECT_MILLING: { - if (!m_targets.GetItemTarget()) + Item* item = m_targets.GetItemTarget(); + if (!item) return SPELL_FAILED_CANT_BE_MILLED; //ensure item is a millable herb - if (!(m_targets.GetItemTarget()->GetTemplate()->HasFlag(ITEM_FLAG_IS_MILLABLE))) + if (!(item->GetTemplate()->HasFlag(ITEM_FLAG_IS_MILLABLE))) return SPELL_FAILED_CANT_BE_MILLED; //prevent milling in trade slot - if (m_targets.GetItemTarget()->GetOwnerGUID() != m_caster->GetGUID()) + if (item->GetOwnerGUID() != m_caster->GetGUID()) return SPELL_FAILED_CANT_BE_MILLED; //Check for enough skill in inscription - uint32 item_millingskilllevel = m_targets.GetItemTarget()->GetTemplate()->RequiredSkillRank; + uint32 item_millingskilllevel = item->GetTemplate()->RequiredSkillRank; if (item_millingskilllevel > player->GetSkillValue(SKILL_INSCRIPTION)) return SPELL_FAILED_LOW_CASTLEVEL; //make sure the player has the required herbs in inventory - if (m_targets.GetItemTarget()->GetCount() < 5) + if (item->GetCount() < 5) + { + if (param1 && param2) + { + *param1 = item->GetEntry(); + *param2 = 5; + } return SPELL_FAILED_NEED_MORE_ITEMS; + } if (!LootTemplates_Milling.HaveLootFor(m_targets.GetItemTargetEntry())) return SPELL_FAILED_CANT_BE_MILLED; @@ -9063,8 +9080,8 @@ namespace Acore } WorldObjectSpellAreaTargetCheck::WorldObjectSpellAreaTargetCheck(float range, Position const* position, Unit* caster, - Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList) - : WorldObjectSpellTargetCheck(caster, referer, spellInfo, selectionType, condList), _range(range), _position(position) + Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason) + : WorldObjectSpellTargetCheck(caster, referer, spellInfo, selectionType, condList), _range(range), _position(position), _searchReason(searchReason) { } @@ -9077,8 +9094,27 @@ namespace Acore } else if (!target->IsWithinDist3d(_position, _range)) return false; - else if (target->IsCreature() && target->ToCreature()->IsAvoidingAOE()) // pussywizard - return false; + else if (Creature* c = target->ToCreature()) + { + if (c->IsAvoidingAOE()) // pussywizard + return false; + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(c->GetCreatureTemplate()->CreatureImmunitiesId)) + { + switch (_searchReason) + { + case Acore::WorldObjectSpellAreaTargetSearchReason::Area: + if (immunities->ImmuneAoE) + return false; + break; + case Acore::WorldObjectSpellAreaTargetSearchReason::Chain: + if (immunities->ImmuneChain) + return false; + break; + default: + break; + } + } + } return WorldObjectSpellTargetCheck::operator ()(target); } diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 700137f17..40ebfaa6f 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -38,6 +38,15 @@ class SpellEvent; class ByteBuffer; class BasicEvent; +namespace Acore +{ + enum class WorldObjectSpellAreaTargetSearchReason + { + Area, + Chain + }; +} + #define SPELL_CHANNEL_UPDATE_INTERVAL (1 * IN_MILLISECONDS) #define TRAJECTORY_MISSILE_SIZE 3.0f @@ -444,7 +453,7 @@ public: template void SearchTargets(SEARCHER& searcher, uint32 containerMask, Unit* referer, Position const* pos, float radius); WorldObject* SearchNearbyTarget(float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList = nullptr); - void SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList); + void SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason = Acore::WorldObjectSpellAreaTargetSearchReason::Area); void SearchChainTargets(std::list& targets, uint32 chainTargets, WorldObject* target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, SpellTargetSelectionCategories selectCategory, ConditionList* condList, bool isChainHeal); SpellCastResult prepare(SpellCastTargets const* targets, AuraEffect const* triggeredByAura = nullptr); @@ -460,7 +469,7 @@ public: void TakeReagents(); void TakeCastItem(); - SpellCastResult CheckCast(bool strict); + SpellCastResult CheckCast(bool strict, uint32* param1 = nullptr, uint32* param2 = nullptr); SpellCastResult CheckPetCast(Unit* target); // handlers @@ -472,7 +481,7 @@ public: void OnSpellLaunch(); - SpellCastResult CheckItems(); + SpellCastResult CheckItems(uint32* param1 = nullptr, uint32* param2 = nullptr); SpellCastResult CheckSpellFocus(); SpellCastResult CheckRange(bool strict); SpellCastResult CheckPower(); @@ -827,8 +836,9 @@ namespace Acore { float _range; Position const* _position; + Acore::WorldObjectSpellAreaTargetSearchReason _searchReason; WorldObjectSpellAreaTargetCheck(float range, Position const* position, Unit* caster, - Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList); + Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason = Acore::WorldObjectSpellAreaTargetSearchReason::Area); bool operator()(WorldObject* target); }; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 7c47c9d41..e8f6a6aa3 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -856,7 +856,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex effIndex) SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo(); // Pounce Bleed shouldn't be removed by Cloak of Shadows. - if (spell->GetAllEffectsMechanicMask() & 1 << MECHANIC_BLEED) + if (spell->GetAllEffectsMechanicMask() & (UI64LIT(1) << MECHANIC_BLEED)) return; bool dmgClassNone = false; @@ -1889,7 +1889,7 @@ void Spell::EffectEnergize(SpellEffIndex effIndex) if (!unitTarget->IsAlive()) return; - if (unitTarget->HasUnitState(UNIT_STATE_ISOLATED)) + if (unitTarget->IsImmunedToAuraPeriodicTick(m_caster, m_spellInfo)) { m_caster->SendSpellDamageImmune(unitTarget, GetSpellInfo()->Id); return; @@ -3681,7 +3681,7 @@ void Spell::EffectHealMaxHealth(SpellEffIndex /*effIndex*/) if (!unitTarget || !unitTarget->IsAlive()) return; - if (unitTarget->HasUnitState(UNIT_STATE_ISOLATED)) + if (unitTarget->IsImmunedToAuraPeriodicTick(m_caster, m_spellInfo)) { m_caster->SendSpellDamageImmune(unitTarget, GetSpellInfo()->Id); return; @@ -5152,7 +5152,7 @@ void Spell::EffectDispelMechanic(SpellEffIndex effIndex) continue; if (roll_chance_i(aura->CalcDispelChance(unitTarget, !unitTarget->IsFriendlyTo(m_caster)))) { - if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1 << mechanic))) + if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (UI64LIT(1) << mechanic))) { dispel_list.push(std::make_pair(aura->GetId(), aura->GetCasterGUID())); diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 86eb2d4dd..1775e9ec1 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -605,6 +605,14 @@ SpellTargetObjectTypes SpellEffectInfo::GetUsedTargetObjectType() const return _data[Effect].UsedTargetObjectType; } +ImmunityInfo const* SpellEffectInfo::GetImmunityInfo() const +{ + if (!_spellInfo) + return nullptr; + + return _spellInfo->GetImmunityInfo(EffectIndex); +} + std::array SpellEffectInfo::_data = { { // implicit target type used target object type @@ -915,6 +923,29 @@ bool SpellInfo::HasAreaAuraEffect() const return false; } +bool SpellInfo::HasOnlyDamageEffects() const +{ + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (Effects[i].IsEffect()) + { + switch (Effects[i].Effect) + { + case SPELL_EFFECT_SCHOOL_DAMAGE: + case SPELL_EFFECT_HEALTH_LEECH: + case SPELL_EFFECT_POWER_DRAIN: + case SPELL_EFFECT_POWER_BURN: + case SPELL_EFFECT_NORMALIZED_WEAPON_DMG: + case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE: + continue; + default: + return false; + } + } + } + return true; +} + bool SpellInfo::IsExplicitDiscovery() const { return ((Effects[0].Effect == SPELL_EFFECT_CREATE_RANDOM_ITEM @@ -1328,54 +1359,25 @@ bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const return IsAffected(affectSpell->SpellFamilyName, mod->mask); } -bool SpellInfo::CanPierceImmuneAura(SpellInfo const* aura) const +bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const { - // aura can't be pierced - if (!aura || aura->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) - { - return false; - } - - // these spells pierce all avalible spells (Resurrection Sickness for example) - if (HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + // Dispels other auras on immunity, check if this spell makes the unit immune to aura + if (HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT) && CanSpellProvideImmunityAgainstAura(auraSpellInfo)) return true; - // these spells (Cyclone for example) can pierce all... - if (HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - if (aura->Mechanic != MECHANIC_IMMUNE_SHIELD && - aura->Mechanic != MECHANIC_INVULNERABILITY && - aura->Mechanic != MECHANIC_BANISH) - { - return true; - } - - } - return false; } -bool SpellInfo::CanDispelAura(SpellInfo const* aura) const +bool SpellInfo::CanDispelAura(SpellInfo const* auraSpellInfo) const { // Xinef: Passive auras cannot be dispelled - if (aura->IsPassive()) + if (auraSpellInfo->IsPassive()) return false; // These auras (like Divine Shield) can't be dispelled - if (aura->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + if (auraSpellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) return false; - // These spells (like Mass Dispel) can dispell all auras - if (HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) - return true; - - // These auras (Cyclone for example) are not dispelable - if ((aura->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && aura->Mechanic != MECHANIC_NONE) - || aura->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - return false; - } - return true; } @@ -1666,6 +1668,12 @@ SpellCastResult SpellInfo::CheckTarget(Unit const* caster, WorldObject const* ta if (AttributesEx & SPELL_ATTR1_ONLY_PEACEFUL_TARGETS && (unitTarget->IsInCombat() || unitTarget->IsPetInCombat())) return SPELL_FAILED_TARGET_AFFECTING_COMBAT; + if (HasAttribute(SPELL_ATTR3_NOT_ON_AOE_IMMUNE)) + if (auto creature = unitTarget->ToCreature()) + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(creature->GetCreatureTemplate()->CreatureImmunitiesId)) + if (immunities->ImmuneAoE) + return SPELL_FAILED_BAD_TARGETS; + // only spells with SPELL_ATTR3_ONLY_ON_GHOSTS can target ghosts if (IsRequiringDeadTarget()) { @@ -1888,35 +1896,35 @@ SpellSchoolMask SpellInfo::GetSchoolMask() const return SpellSchoolMask(SchoolMask); } -uint32 SpellInfo::GetAllEffectsMechanicMask() const +uint64 SpellInfo::GetAllEffectsMechanicMask() const { - uint32 mask = 0; + uint64 mask = 0; if (Mechanic) - mask |= 1 << Mechanic; + mask |= UI64LIT(1) << Mechanic; for (int i = 0; i < MAX_SPELL_EFFECTS; ++i) if (Effects[i].IsEffect() && Effects[i].Mechanic) - mask |= 1 << Effects[i].Mechanic; + mask |= UI64LIT(1) << Effects[i].Mechanic; return mask; } -uint32 SpellInfo::GetEffectMechanicMask(uint8 effIndex) const +uint64 SpellInfo::GetEffectMechanicMask(uint8 effIndex) const { - uint32 mask = 0; + uint64 mask = 0; if (Mechanic) - mask |= 1 << Mechanic; + mask |= UI64LIT(1) << Mechanic; if (Effects[effIndex].IsEffect() && Effects[effIndex].Mechanic) - mask |= 1 << Effects[effIndex].Mechanic; + mask |= UI64LIT(1) << Effects[effIndex].Mechanic; return mask; } -uint32 SpellInfo::GetSpellMechanicMaskByEffectMask(uint32 effectMask) const +uint64 SpellInfo::GetSpellMechanicMaskByEffectMask(uint32 effectMask) const { - uint32 mask = 0; + uint64 mask = 0; if (Mechanic) - mask |= 1 << Mechanic; + mask |= UI64LIT(1) << Mechanic; for (int i = 0; i < MAX_SPELL_EFFECTS; ++i) if ((effectMask & (1 << i)) && Effects[i].Mechanic) - mask |= 1 << Effects[i].Mechanic; + mask |= UI64LIT(1) << Effects[i].Mechanic; return mask; } @@ -2000,9 +2008,13 @@ AuraStateType SpellInfo::LoadAuraState() const return AURA_STATE_ENRAGE; // Bleeding aura state - if (GetAllEffectsMechanicMask() & 1 << MECHANIC_BLEED) + if (GetAllEffectsMechanicMask() & (UI64LIT(1) << MECHANIC_BLEED)) return AURA_STATE_BLEEDING; + // Banished aura state + if (Mechanic == MECHANIC_BANISH) + return AURA_STATE_BANISHED; + if (GetSchoolMask() & SPELL_SCHOOL_MASK_FROST) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (Effects[i].IsAura() && (Effects[i].ApplyAuraName == SPELL_AURA_MOD_STUN @@ -2213,6 +2225,492 @@ SpellSpecificType SpellInfo::LoadSpellSpecific() const return SPELL_SPECIFIC_NORMAL; } +// immunity helper functions moved from SpellAuraEffects and global switches +void SpellInfo::_LoadImmunityInfo() +{ + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + SpellEffectInfo& effectInfo = Effects[i]; + if (!effectInfo.Effect) + continue; + + uint32 schoolImmunityMask = 0; + uint32 applyHarmfulAuraImmunityMask = 0; + uint64 mechanicImmunityMask = 0; + uint32 dispelImmunityMask = 0; + uint32 damageImmunityMask = 0; + + int32 miscVal = effectInfo.MiscValue; + int32 amount = effectInfo.CalcValue(); + + ImmunityInfo& immuneInfo = _immunityInfo[i]; + + switch (effectInfo.ApplyAuraName) + { + case SPELL_AURA_MECHANIC_IMMUNITY_MASK: + { + switch (miscVal) + { + case 27: + mechanicImmunityMask |= (1ULL << MECHANIC_SILENCE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_SILENCE); + break; + case 96: + case 1615: + { + if (amount) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 679: + { + if (Id == 57742) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + } + break; + } + case 1557: + { + if (Id == 64187) + { + mechanicImmunityMask |= (1ULL << MECHANIC_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + } + else + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 1614: + case 1694: + { + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_ATTACK_ME); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_TAUNT); + break; + } + case 1630: + { + if (Id == 64112) + { + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_ATTACK_ME); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_TAUNT); + } + else + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 477: + case 1733: + { + if (!amount) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_KNOCK_BACK); + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_KNOCK_BACK_DEST); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 878: + { + if (Id == 66092) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_DISORIENTED) | (1ULL << MECHANIC_FREEZE); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + } + break; + } + default: + break; + } + + if (immuneInfo.AuraTypeImmune.empty()) + { + if (miscVal & (1 << 10)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + if (miscVal & (1 << 1)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_TRANSFORM); + + if (miscVal & (1 << 6)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + if (miscVal & (1 << 0)) + { + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + if (miscVal & (1 << 2)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + if (miscVal & (1 << 9)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + if (miscVal & (1 << 7)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DISARM); + } + break; + } + case SPELL_AURA_MECHANIC_IMMUNITY: + { + switch (Id) + { + case 34471: // The Beast Within + case 19574: // Bestial Wrath + case 42292: // PvP trinket + case 46227: // Medallion of Immunity + case 59752: // Every Man for Himself + case 53490: // Bullheaded + case 65547: // PvP Trinket + case 134946: // Supremacy of the Alliance + case 134956: // Supremacy of the Horde + case 195710: // Honorable Medallion + case 208683: // Gladiator's Medallion + mechanicImmunityMask |= IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; + break; + case 54508: // Demonic Empowerment + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) | (1ULL << MECHANIC_STUN); + break; + default: + if (miscVal < 1) + break; + + mechanicImmunityMask |= 1ULL << miscVal; + break; + } + break; + } + case SPELL_AURA_EFFECT_IMMUNITY: + { + immuneInfo.SpellEffectImmune.insert(static_cast(miscVal)); + break; + } + case SPELL_AURA_STATE_IMMUNITY: + { + immuneInfo.AuraTypeImmune.insert(static_cast(miscVal)); + break; + } + case SPELL_AURA_SCHOOL_IMMUNITY: + { + schoolImmunityMask |= uint32(miscVal); + break; + } + case SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL: + { + applyHarmfulAuraImmunityMask |= uint32(miscVal); + break; + } + case SPELL_AURA_DAMAGE_IMMUNITY: + { + damageImmunityMask |= uint32(miscVal); + break; + } + case SPELL_AURA_DISPEL_IMMUNITY: + { + dispelImmunityMask = uint32(miscVal); + break; + } + default: + break; + } + + immuneInfo.SchoolImmuneMask = schoolImmunityMask; + immuneInfo.ApplyHarmfulAuraImmuneMask = applyHarmfulAuraImmunityMask; + immuneInfo.MechanicImmuneMask = mechanicImmunityMask; + immuneInfo.DispelImmuneMask = dispelImmunityMask; + immuneInfo.DamageSchoolMask = damageImmunityMask; + } +} + +void SpellInfo::ApplyAllSpellImmunitiesTo(Unit* target, SpellEffectInfo const* effect, bool apply) const +{ + ImmunityInfo const* immuneInfo = nullptr; + if (effect) + { + uint8 effIndex = effect->EffectIndex; + if (effIndex < MAX_SPELL_EFFECTS) + immuneInfo = &_immunityInfo[effIndex]; + } + + if (!immuneInfo) + return; + + if (uint32 schoolImmunity = immuneInfo->SchoolImmuneMask) + { + target->ApplySpellImmune(Id, IMMUNITY_SCHOOL, schoolImmunity, apply); + + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) +{ + target->RemoveAppliedAuras([this, target, schoolImmunity](AuraApplication const* aurApp) -> bool + { + SpellInfo const* auraSpellInfo = aurApp->GetBase()->GetSpellInfo(); + if (auraSpellInfo->Id == Id) // Don't remove self + return false; + if (auraSpellInfo->IsPassive()) // Don't remove passive auras + return false; + if (!(auraSpellInfo->GetSchoolMask() & schoolImmunity)) // Check for school mask + return false; + if (!CanDispelAura(auraSpellInfo)) + return false; + if (!HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) + { + Unit* existingAuraCaster = aurApp->GetBase()->GetCaster(); + if (existingAuraCaster && existingAuraCaster->IsFriendlyTo(target)) // Don't remove friendly auras + return false; + } + return true; + }); + } + } + + if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask) + { + for (uint32 i = 0; i < MAX_MECHANIC; ++i) + if (mechanicImmunity & (1ULL << i)) + target->ApplySpellImmune(Id, IMMUNITY_MECHANIC, i, apply); + + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + target->RemoveAurasWithMechanic(mechanicImmunity, AURA_REMOVE_BY_DEFAULT, Id); + } + + if (uint32 dispelImmunity = immuneInfo->DispelImmuneMask) + { + target->ApplySpellImmune(Id, IMMUNITY_DISPEL, dispelImmunity, apply); + + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + { + target->RemoveAppliedAuras([dispelImmunity](AuraApplication const* aurApp) -> bool + { + SpellInfo const* spellInfo = aurApp->GetBase()->GetSpellInfo(); + if (spellInfo->Dispel == dispelImmunity) + return true; + + return false; + }); + } + } + + if (uint32 damageImmunity = immuneInfo->DamageSchoolMask) + target->ApplySpellImmune(Id, IMMUNITY_DAMAGE, damageImmunity, apply); + + for (AuraType auraType : immuneInfo->AuraTypeImmune) + { + target->ApplySpellImmune(Id, IMMUNITY_STATE, auraType, apply); + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + { + target->RemoveAppliedAuras([this, auraType](AuraApplication const* aurApp) -> bool + { + Aura const* aura = aurApp->GetBase(); + if (!aura->GetSpellInfo()->HasAura(auraType)) + return false; + return CanDispelAura(aura->GetSpellInfo()); + }); + } + } + + for (SpellEffects effectType : immuneInfo->SpellEffectImmune) + target->ApplySpellImmune(Id, IMMUNITY_EFFECT, effectType, apply); +} + +bool SpellInfo::CanSpellProvideImmunityAgainstAura(SpellInfo const* auraSpellInfo) const +{ + if (!auraSpellInfo) + return false; + + for (SpellEffectInfo const& effectInfo : Effects) + { + if (!effectInfo.Effect) + continue; + + ImmunityInfo const* immuneInfo = effectInfo.GetImmunityInfo(); + if (!immuneInfo) + continue; + + if (!auraSpellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + if (uint32 schoolImmunity = immuneInfo->SchoolImmuneMask) + if ((auraSpellInfo->SchoolMask & schoolImmunity) != 0) + return true; + } + + if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask) + if ((mechanicImmunity & (1ULL << auraSpellInfo->Mechanic)) != 0) + return true; + + if (uint32 dispelImmunity = immuneInfo->DispelImmuneMask) + if (auraSpellInfo->Dispel == dispelImmunity) + return true; + + bool immuneToAllEffects = true; + for (SpellEffectInfo const& auraSpellEffectInfo : auraSpellInfo->Effects) + { + if (!auraSpellEffectInfo.Effect) + continue; + + uint32 effectName = auraSpellEffectInfo.Effect; + if (!effectName) + continue; + + if (immuneInfo->SpellEffectImmune.find(static_cast(effectName)) == immuneInfo->SpellEffectImmune.cend()) + { + immuneToAllEffects = false; + break; + } + + if (uint32 mechanic = auraSpellEffectInfo.Mechanic) + { + if (!(immuneInfo->MechanicImmuneMask & (1ULL << mechanic))) + { + immuneToAllEffects = false; + break; + } + } + + if (uint32 auraName = auraSpellEffectInfo.ApplyAuraName) + { + bool isImmuneToAuraEffectApply = false; + if (immuneInfo->AuraTypeImmune.find(static_cast(auraName)) != immuneInfo->AuraTypeImmune.cend()) + isImmuneToAuraEffectApply = true; + + if (!isImmuneToAuraEffectApply && !auraSpellInfo->IsPositiveEffect(auraSpellEffectInfo.EffectIndex) && + !auraSpellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + if (uint32 applyHarmfulAuraImmuneMask = immuneInfo->ApplyHarmfulAuraImmuneMask) + if ((auraSpellInfo->SchoolMask & applyHarmfulAuraImmuneMask) != 0) + isImmuneToAuraEffectApply = true; + } + + if (!isImmuneToAuraEffectApply) + { + immuneToAllEffects = false; + break; + } + } + } + + if (immuneToAllEffects) + return true; + } + + return false; +} + +// based on client sub_007FDFA0 +bool SpellInfo::CanSpellCastOverrideAuraEffect(AuraEffect const* aurEff) const +{ + if (!HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return false; + + if (aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + SpellEffectInfo const* aurEffInfo = &aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()]; + + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + SpellEffectInfo const& effectInfo = Effects[i]; + if (!effectInfo.Effect) + continue; + + if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) + continue; + + uint32 const miscValue = static_cast(effectInfo.MiscValue); + switch (effectInfo.ApplyAuraName) + { + case SPELL_AURA_STATE_IMMUNITY: + if (miscValue != aurEffInfo->ApplyAuraName) + continue; + break; + case SPELL_AURA_SCHOOL_IMMUNITY: + case SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL: + if (aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES) || !(aurEff->GetSpellInfo()->SchoolMask & miscValue)) + continue; + break; + case SPELL_AURA_DISPEL_IMMUNITY: + if (miscValue != aurEff->GetSpellInfo()->Dispel) + continue; + break; + case SPELL_AURA_MECHANIC_IMMUNITY: + if (miscValue != aurEff->GetSpellInfo()->Mechanic) + { + if (miscValue != aurEffInfo->Mechanic) + continue; + } + break; + default: + continue; + } + + return true; + } + + return false; +} + float SpellInfo::GetMinRange(bool positive) const { if (!RangeEntry) diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 70f0097f7..b87b5e37d 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -22,6 +22,7 @@ #include "Object.h" #include "SharedDefines.h" #include "SpellAuraDefines.h" +#include #include "Util.h" class Unit; @@ -213,6 +214,26 @@ enum SpellCustomAttributes uint32 GetTargetFlagMask(SpellTargetObjectTypes objType); +struct SpellDiminishInfo +{ + DiminishingGroup DiminishGroup = DIMINISHING_NONE; + DiminishingReturnsType DiminishReturnType = DRTYPE_NONE; + int32 DiminishMaxLevel = 3; // DIMINISHING_LEVEL_IMMUNE + int32 DiminishDurationLimit = 0; +}; + +struct AC_GAME_API ImmunityInfo +{ + uint32 SchoolImmuneMask = 0; + uint32 ApplyHarmfulAuraImmuneMask = 0; + uint64 MechanicImmuneMask = 0; + uint32 DispelImmuneMask = 0; + uint32 DamageSchoolMask = 0; + + boost::container::flat_set AuraTypeImmune; + boost::container::flat_set SpellEffectImmune; +}; + class SpellImplicitTargetInfo { private: @@ -302,6 +323,8 @@ public: SpellEffectImplicitTargetTypes GetImplicitTargetType() const; SpellTargetObjectTypes GetUsedTargetObjectType() const; + [[nodiscard]] ImmunityInfo const* GetImmunityInfo() const; + private: struct StaticData { @@ -310,6 +333,7 @@ private: }; static std::array _data; + }; class AC_GAME_API SpellInfo @@ -412,6 +436,7 @@ public: bool HasAura(AuraType aura) const; bool HasAnyAura() const; bool HasAreaAuraEffect() const; + bool HasOnlyDamageEffects() const; inline bool HasAttribute(SpellAttr0 attribute) const { return (Attributes & attribute) != 0; } inline bool HasAttribute(SpellAttr1 attribute) const { return (AttributesEx & attribute) != 0; } @@ -473,8 +498,12 @@ public: bool IsAffectedBySpellMods() const; bool IsAffectedBySpellMod(SpellModifier const* mod) const; - bool CanPierceImmuneAura(SpellInfo const* aura) const; - bool CanDispelAura(SpellInfo const* aura) const; + bool CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const; + bool CanDispelAura(SpellInfo const* auraSpellInfo) const; + + void ApplyAllSpellImmunitiesTo(Unit* target, SpellEffectInfo const* effect, bool apply) const; + bool CanSpellProvideImmunityAgainstAura(SpellInfo const* auraSpellInfo) const; + bool CanSpellCastOverrideAuraEffect(AuraEffect const* aurEff) const; bool IsSingleTarget() const; bool IsAuraExclusiveBySpecificWith(SpellInfo const* spellInfo) const; @@ -490,11 +519,12 @@ public: bool ValidateAttribute6SpellDamageMods(Unit const* caster, const AuraEffect* auraEffect, bool isDot) const; SpellSchoolMask GetSchoolMask() const; - uint32 GetAllEffectsMechanicMask() const; - uint32 GetEffectMechanicMask(uint8 effIndex) const; - uint32 GetSpellMechanicMaskByEffectMask(uint32 effectMask) const; + uint64 GetAllEffectsMechanicMask() const; + uint64 GetEffectMechanicMask(uint8 effIndex) const; + uint64 GetSpellMechanicMaskByEffectMask(uint32 effectMask) const; Mechanics GetEffectMechanic(uint8 effIndex) const; bool HasAnyEffectMechanic() const; + [[nodiscard]] ImmunityInfo const* GetImmunityInfo(uint8 effIndex) const { return effIndex < MAX_SPELL_EFFECTS ? &_immunityInfo[effIndex] : nullptr; } uint32 GetDispelMask() const; static uint32 GetDispelMask(DispelType type); uint32 GetExplicitTargetMask() const; @@ -541,7 +571,13 @@ public: // unloading helpers void _UnloadImplicitTargetConditionLists(); -private: + // immunity helpers + void _LoadImmunityInfo(); + + SpellDiminishInfo _diminishInfoNonTriggered; + SpellDiminishInfo _diminishInfoTriggered; + + ImmunityInfo _immunityInfo[MAX_SPELL_EFFECTS]; std::array& _GetEffects() { return Effects; } SpellEffectInfo& _GetEffect(SpellEffIndex index) { ASSERT(index < Effects.size()); return Effects[index]; } }; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 42bf4ed7d..af70ec147 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -16,6 +16,7 @@ */ #include "SpellMgr.h" +#include "Log.h" #include "BattlefieldMgr.h" #include "BattlegroundIC.h" #include "Chat.h" @@ -29,6 +30,7 @@ #include "Spell.h" #include "SpellAuraDefines.h" #include "SpellInfo.h" +#include "Tokenize.h" #include "World.h" bool IsPrimaryProfessionSkill(uint32 skill) @@ -53,6 +55,53 @@ bool IsPartOfSkillLine(uint32 skillId, uint32 spellId) return false; } +CreatureImmunities const* SpellMgr::GetCreatureImmunities(int32 creatureImmunitiesId) const +{ + return Acore::Containers::MapGetValuePtr(mCreatureImmunities, creatureImmunitiesId); +} + +void SpellMgr::LoadCreatureImmunities() +{ + mCreatureImmunities.clear(); + if (QueryResult result = WorldDatabase.Query("SELECT ID, SchoolMask, DispelTypeMask, MechanicsMask, Effects, Auras, ImmuneAoE, ImmuneChain FROM creature_immunities")) + { + do + { + Field* fields = result->Fetch(); + int32 id = fields[0].Get(); + uint8 school = fields[1].Get(); + uint16 dispelType = fields[2].Get(); + uint64 mechanics = fields[3].Get(); + CreatureImmunities& immunities = mCreatureImmunities[id]; + immunities.School = school; + immunities.DispelType = dispelType; + immunities.Mechanic = mechanics; + immunities.ImmuneAoE = fields[6].Get(); + immunities.ImmuneChain = fields[7].Get(); + { + std::string effects = fields[4].Get(); + for (std::string_view token : Acore::Tokenize(effects, ',', false)) + { + if (Optional val = Acore::StringTo(token); val && *val < uint32(TOTAL_SPELL_EFFECTS)) + immunities.Effect.push_back(SpellEffects(*val)); + else + LOG_ERROR("sql.sql", "Invalid effect type in `Effects` {} for creature immunities {}, skipped", token, id); + } + } + { + std::string auras = fields[5].Get(); + for (std::string_view token : Acore::Tokenize(auras, ',', false)) + { + if (Optional val = Acore::StringTo(token); val && *val < TOTAL_AURAS) + immunities.Aura.push_back(AuraType(*val)); + else + LOG_ERROR("sql.sql", "Invalid aura type in `Auras` {} for creature immunities {}, skipped", token, id); + } + } + } while (result->NextRow()); + } +} + DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo const* spellproto, bool triggered) { if (spellproto->IsPositive()) @@ -212,29 +261,29 @@ DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo const* spellproto, } // Lastly - Set diminishing depending on mechanic - uint32 mechanic = spellproto->GetAllEffectsMechanicMask(); - if (mechanic & (1 << MECHANIC_CHARM)) + uint64 mechanic = spellproto->GetAllEffectsMechanicMask(); + if (mechanic & (1ULL << MECHANIC_CHARM)) return DIMINISHING_MIND_CONTROL; - if (mechanic & (1 << MECHANIC_SILENCE)) + if (mechanic & (1ULL << MECHANIC_SILENCE)) return DIMINISHING_SILENCE; - if (mechanic & (1 << MECHANIC_SLEEP)) + if (mechanic & (1ULL << MECHANIC_SLEEP)) return DIMINISHING_SLEEP; - if (mechanic & ((1 << MECHANIC_SAPPED) | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_SHACKLE))) + if (mechanic & ((1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_SHACKLE))) return DIMINISHING_DISORIENT; // Mechanic Knockout, except Blast Wave - if (mechanic & (1 << MECHANIC_KNOCKOUT) && spellproto->SpellIconID != 292) + if (mechanic & (1ULL << MECHANIC_KNOCKOUT) && spellproto->SpellIconID != 292) return DIMINISHING_DISORIENT; - if (mechanic & (1 << MECHANIC_DISARM)) + if (mechanic & (1ULL << MECHANIC_DISARM)) return DIMINISHING_DISARM; - if (mechanic & (1 << MECHANIC_FEAR)) + if (mechanic & (1ULL << MECHANIC_FEAR)) return DIMINISHING_FEAR; - if (mechanic & (1 << MECHANIC_STUN)) + if (mechanic & (1ULL << MECHANIC_STUN)) return triggered ? DIMINISHING_STUN : DIMINISHING_CONTROLLED_STUN; - if (mechanic & (1 << MECHANIC_BANISH)) + if (mechanic & (1ULL << MECHANIC_BANISH)) return DIMINISHING_BANISH; - if (mechanic & (1 << MECHANIC_ROOT)) + if (mechanic & (1ULL << MECHANIC_ROOT)) return triggered ? DIMINISHING_ROOT : DIMINISHING_CONTROLLED_ROOT; - if (mechanic & (1 << MECHANIC_HORROR)) + if (mechanic & (1ULL << MECHANIC_HORROR)) return DIMINISHING_HORROR; return DIMINISHING_NONE; @@ -2876,6 +2925,8 @@ void SpellMgr::LoadSpellInfoStore() } } + LoadCreatureImmunities(); + LOG_INFO("server.loading", ">> Loaded Spell Custom Attributes in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } @@ -3714,3 +3765,17 @@ void SpellMgr::LoadSpellInfoCustomAttributes() LOG_INFO("server.loading", ">> Loaded SpellInfo Custom Attributes in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } + +void SpellMgr::LoadSpellInfoImmunities() +{ + uint32 oldMSTime = getMSTime(); + + for (SpellInfo* spellInfo : mSpellInfoMap) + { + if (!spellInfo) + continue; + spellInfo->_LoadImmunityInfo(); + } + + LOG_INFO("server.loading", ">> Loaded SpellInfo immunity infos in {} ms", GetMSTimeDiffToNow(oldMSTime)); +} diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index ccedf1bff..32f34b030 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -25,6 +25,9 @@ #include "SharedDefines.h" #include "Unit.h" +#include +#include + class SpellInfo; class Player; class Unit; @@ -557,6 +560,18 @@ typedef std::multimap SpellsRequiringSpellMap; typedef std::pair SpellsRequiringSpellMapBounds; // Spell learning properties (accessed using SpellMgr functions) +struct CreatureImmunities +{ + std::bitset School; + std::bitset DispelType; + std::bitset Mechanic; + std::vector Effect; + std::vector Aura; + bool ImmuneAoE = false; + bool ImmuneChain = false; +}; + +typedef std::unordered_map CreatureImmunitiesMap; struct SpellLearnSkillNode { uint16 skill; @@ -634,6 +649,9 @@ private: public: static SpellMgr* instance(); + // creature immunity definitions loaded from DB + CreatureImmunities const* GetCreatureImmunities(int32 creatureImmunitiesId) const; + // Spell correctness for client using static bool ComputeIsSpellValid(SpellInfo const* spellInfo, bool msg = true); static bool IsSpellValid(SpellInfo const* spellInfo); @@ -772,10 +790,12 @@ public: void LoadPetDefaultSpells(); void LoadSpellAreas(); void LoadSpellInfoStore(); + void LoadCreatureImmunities(); void LoadSpellCooldownOverrides(); void UnloadSpellInfoStore(); void UnloadSpellInfoImplicitTargetConditionLists(); void LoadSpellInfoCustomAttributes(); + void LoadSpellInfoImmunities(); void LoadSpellInfoCorrections(); void LoadSpellSpecificAndAuraState(); void LoadSpellJumpDistances(); @@ -792,6 +812,7 @@ private: SpellGroupStackMap mSpellGroupStack; SameEffectStackMap mSpellSameEffectStack; SpellProcMap mSpellProcMap; + CreatureImmunitiesMap mCreatureImmunities; SpellBonusMap mSpellBonusMap; SpellThreatMap mSpellThreatMap; SpellMixologyMap mSpellMixologyMap; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 0db7ffd3c..b11b77c60 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -436,6 +436,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Spell Jump Distances..."); sSpellMgr->LoadSpellJumpDistances(); + LOG_INFO("server.loading", "Loading SpellInfo Immunity infos..."); + sSpellMgr->LoadSpellInfoImmunities(); + LOG_INFO("server.loading", "Loading Player Totem models..."); sObjectMgr->LoadPlayerTotemModels(); diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index ce6b78213..6eb781317 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -655,8 +655,17 @@ public: CreatureTemplate const* cInfo = target->GetCreatureTemplate(); uint32 faction = target->GetFaction(); uint32 npcflags = target->GetNpcFlags(); - uint32 mechanicImmuneMask = cInfo->MechanicImmuneMask; - uint32 spellSchoolImmuneMask = cInfo->SpellSchoolImmuneMask; + uint64 mechanicImmuneMask = 0; + uint32 spellSchoolImmuneMask = 0; + + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(cInfo->CreatureImmunitiesId)) + { + mechanicImmuneMask = immunities->Mechanic.to_ullong(); + for (std::size_t j = 0; j < immunities->School.size(); ++j) + if (immunities->School[j]) + spellSchoolImmuneMask |= (1u << j); + } + uint32 displayid = target->GetDisplayId(); uint32 nativeid = target->GetNativeDisplayId(); uint32 entry = target->GetEntry(); @@ -673,6 +682,7 @@ public: int64 curRespawnDelay = target->GetRespawnTimeEx() - GameTime::GetGameTime().count(); if (curRespawnDelay < 0) curRespawnDelay = 0; + std::string curRespawnDelayStr = secsToTimeString(uint64(curRespawnDelay), true); std::string defRespawnDelayStr = secsToTimeString(target->GetRespawnDelay(), true); @@ -689,31 +699,19 @@ public: handler->PSendSysMessage(LANG_NPCINFO_POSITION, float(target->GetPositionX()), float(target->GetPositionY()), float(target->GetPositionZ())); handler->PSendSysMessage(LANG_NPCINFO_AIINFO, target->GetAIName(), target->GetScriptName()); - for (uint8 i = 0; i < NPCFLAG_COUNT; i++) - { - if (npcflags & npcFlagTexts[i].flag) - { - handler->PSendSysMessage(npcFlagTexts[i].text, npcFlagTexts[i].flag); - } - } + for (auto npcFlagText : npcFlagTexts) + if (npcflags & npcFlagText.flag) + handler->PSendSysMessage(npcFlagText.text, npcFlagText.flag); - handler->PSendSysMessage(LANG_NPCINFO_MECHANIC_IMMUNE, mechanicImmuneMask); + handler->PSendSysMessage(LANG_NPCINFO_MECHANIC_IMMUNE, Acore::StringFormat("0x{:X}", mechanicImmuneMask).c_str()); for (uint8 i = 1; i < MAX_MECHANIC; ++i) - { - if (mechanicImmuneMask & (1 << (mechanicImmunes[i].flag - 1))) - { + if (mechanicImmuneMask & (UI64LIT(1) << i)) handler->PSendSysMessage(mechanicImmunes[i].text, mechanicImmunes[i].flag); - } - } handler->PSendSysMessage(LANG_NPCINFO_SPELL_SCHOOL_IMMUNE, spellSchoolImmuneMask); - for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i) - { - if (spellSchoolImmuneMask & (1 << spellSchoolImmunes[i].flag)) - { - handler->PSendSysMessage(spellSchoolImmunes[i].text, spellSchoolImmunes[i].flag); - } - } + for (auto spellSchoolImmune : spellSchoolImmunes) + if (spellSchoolImmuneMask & (1 << spellSchoolImmune.flag)) + handler->PSendSysMessage(spellSchoolImmune.text, spellSchoolImmune.flag); return true; } diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 87b6c6ab3..23d31745d 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -420,7 +420,7 @@ enum SpellAttr1 : uint32 SPELL_ATTR1_TOGGLE_FAR_SIGHT = 0x00002000, // TITLE Farsight aura (client only) SPELL_ATTR1_TRACK_TARGET_IN_CHANNEL = 0x00004000, // TITLE Track target while channeling DESCRIPTION While channeling, adjust facing to face target SPELL_ATTR1_IMMUNITY_PURGES_EFFECT = 0x00008000, // TITLE Immunity cancels preapplied auras DESCRIPTION For immunity spells, cancel all auras that this spell would make you immune to when the spell is applied - SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS = 0x00010000, // TITLE Unaffected by school immunities DESCRIPTION Will not pierce Divine Shield, Ice Block and other full invulnerabilities + SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS = 0x00010000, // TITLE Immunity to Hostile & Friendly Effects DESCRIPTION Immunity applied by this aura will also be checked for friendly spells (school immunity only) - used by Cyclone for example to cause friendly spells and healing over time to be immune SPELL_ATTR1_NO_AUTOCAST_AI = 0x00020000, // TITLE Cannot be autocast by pet DESCRIPTION (AI) SPELL_ATTR1_PREVENTS_ANIM = 0x00040000, // TITLE NYI, auras apply UNIT_FLAG_PREVENT_EMOTES_FROM_CHAT_TEXT SPELL_ATTR1_EXCLUDE_CASTER = 0x00080000, // TITLE Cannot be self-cast @@ -1285,7 +1285,7 @@ enum AuraStateType //AURA_STATE_UNKNOWN6 = 6, // | not used AURA_STATE_HUNTER_PARRY = 7, // C | //AURA_STATE_UNKNOWN7 = 7, // c | creature cheap shot / focused bursts spells - //AURA_STATE_UNKNOWN8 = 8, // t| test spells + AURA_STATE_BANISHED = 8, // c t| banished //AURA_STATE_UNKNOWN9 = 9, // | AURA_STATE_WARRIOR_VICTORY_RUSH = 10, // C | warrior victory rush //AURA_STATE_UNKNOWN11 = 11, // C t| 60348 - Maelstrom Ready!, test spells @@ -1342,17 +1342,34 @@ enum Mechanics : uint32 MECHANIC_IMMUNE_SHIELD = 29, // Divine (Blessing) Shield/Protection and Ice Block MECHANIC_SAPPED = 30, MECHANIC_ENRAGED = 31, - MAX_MECHANIC = 32 // SKIP + MECHANIC_WOUNDED = 32, + MECHANIC_INFECTED_2 = 33, + MECHANIC_INFECTED_3 = 34, + MECHANIC_INFECTED_4 = 35, + MECHANIC_TAUNTED = 36, + MAX_MECHANIC = 37 // SKIP }; // Used for spell 42292 Immune Movement Impairment and Loss of Control (0x49967ca6) -#define IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK (\ - (1<speed_run = 1.14286f; _fakeCreatureTemplate->speed_swim = 1.0f; _fakeCreatureTemplate->speed_flight = 1.0f; - _fakeCreatureTemplate->scale = 1.0f; _fakeCreatureTemplate->DamageModifier = 1.0f; _fakeCreatureTemplate->BaseAttackTime = 2000; _fakeCreatureTemplate->RangeAttackTime = 2000; diff --git a/src/test/server/game/Spells/SpellImmunityTest.cpp b/src/test/server/game/Spells/SpellImmunityTest.cpp new file mode 100644 index 000000000..342e2818f --- /dev/null +++ b/src/test/server/game/Spells/SpellImmunityTest.cpp @@ -0,0 +1,330 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellImmunityTest.cpp + * @brief Tests for spell immunity mechanics + */ + +#include "gtest/gtest.h" + +#include +#include +#include "Unit.h" // needed for SpellSchoolMask and mask helper + +namespace +{ + enum EffectType : uint8_t + { + EFFECT_NONE, + EFFECT_SCHOOL_DAMAGE, + EFFECT_HEALTH_LEECH, + EFFECT_POWER_DRAIN, + EFFECT_POWER_BURN, + EFFECT_NORMALIZED_WEAPON_DMG, + EFFECT_WEAPON_PERCENT_DAMAGE, + EFFECT_APPLY_AURA, + EFFECT_DUMMY + }; + + enum AuraType : uint8_t + { + AURA_NONE, + AURA_MOD_DECREASE_SPEED, + AURA_PERIODIC_DAMAGE, + AURA_TRANSFORM, + AURA_MOD_STUN + }; + + struct EffectDesc + { + EffectType effect = EFFECT_NONE; + AuraType aura = AURA_NONE; + }; + + struct SpellDesc + { + std::array effects{}; + }; + + bool IsDamageEffect(EffectType effect) + { + switch (effect) + { + case EFFECT_SCHOOL_DAMAGE: + case EFFECT_HEALTH_LEECH: + case EFFECT_POWER_DRAIN: + case EFFECT_POWER_BURN: + case EFFECT_NORMALIZED_WEAPON_DMG: + case EFFECT_WEAPON_PERCENT_DAMAGE: + return true; + default: + return false; + } + } + + bool HasOnlyDamageEffects(SpellDesc const& spell) + { + bool hasAny = false; + + for (EffectDesc const& e : spell.effects) + { + if (e.effect == EFFECT_NONE) + continue; + + hasAny = true; + if (!IsDamageEffect(e.effect)) + return false; + } + + return hasAny; + } + + // Helper to classify spells which apply a stun aura + bool IsStunSpell(SpellDesc const& spell) + { + for (EffectDesc const& e : spell.effects) + { + if (e.effect == EFFECT_APPLY_AURA && e.aura == AURA_MOD_STUN) + return true; + } + return false; + } + + // The last parameter defaults to false to avoid updating existing tests + // that don't care about mechanic immunities. Bladestorm grants a + // specific mechanic immunity (including stun) that should block + // a spell like Lethargy. + SpellMissInfo ComputeSpellHitResult(bool isImmunedToSpell, bool isImmunedToDamage, + SpellDesc const& spell, bool immuneToStun = false) + { + // Mirrors current core ordering: + // 1) full spell immunity check + // 2) mechanic immunity check (e.g. bladestorm vs stun) + // 3) damage immunity only for damage-only spells + if (isImmunedToSpell) + return SPELL_MISS_IMMUNE; + + if (immuneToStun && IsStunSpell(spell)) + return SPELL_MISS_IMMUNE; + + if (HasOnlyDamageEffects(spell) && isImmunedToDamage) + return SPELL_MISS_IMMUNE; + + return SPELL_MISS_NONE; + } + + struct EffectApplyResult + { + bool damageApplied = false; + bool slowApplied = false; + }; + + EffectApplyResult ApplyEffectsWithMovementImmunity(SpellDesc const& spell, bool immuneToMovementImpairing) + { + EffectApplyResult result; + + for (EffectDesc const& e : spell.effects) + { + if (e.effect == EFFECT_NONE) + continue; + + if (IsDamageEffect(e.effect)) + result.damageApplied = true; + + if (e.effect == EFFECT_APPLY_AURA && e.aura == AURA_MOD_DECREASE_SPEED) + { + if (!immuneToMovementImpairing) + result.slowApplied = true; + } + } + + return result; + } + + SpellDesc MakeDamageOnlySpell() + { + SpellDesc spell; + spell.effects[0] = { EFFECT_SCHOOL_DAMAGE, AURA_NONE }; + return spell; + } + + SpellDesc MakeFrostboltLikeSpell() + { + SpellDesc spell; + spell.effects[0] = { EFFECT_SCHOOL_DAMAGE, AURA_NONE }; + spell.effects[1] = { EFFECT_APPLY_AURA, AURA_MOD_DECREASE_SPEED }; + return spell; + } + + SpellDesc MakeCycloneLikeSpell() + { + SpellDesc spell; + spell.effects[0] = { EFFECT_APPLY_AURA, AURA_TRANSFORM }; + return spell; + } + + SpellDesc MakeSlowOnlySpell() + { + // represents effects such as Frost Trap or Desecration, which only slow + SpellDesc spell; + spell.effects[0] = { EFFECT_APPLY_AURA, AURA_MOD_DECREASE_SPEED }; + return spell; + } +} + +TEST(SpellImmunityTest, HasOnlyDamageEffects_TrueForPureDamage) +{ + SpellDesc spell = MakeDamageOnlySpell(); + EXPECT_TRUE(HasOnlyDamageEffects(spell)); +} + +TEST(SpellImmunityTest, HasOnlyDamageEffects_FalseForDamagePlusAura) +{ + SpellDesc spell = MakeFrostboltLikeSpell(); + EXPECT_FALSE(HasOnlyDamageEffects(spell)); +} + +TEST(SpellImmunityTest, SpellImmunity_BlocksAllSpells) +{ + SpellDesc damageOnly = MakeDamageOnlySpell(); + SpellDesc cycloneLike = MakeCycloneLikeSpell(); + + EXPECT_EQ(ComputeSpellHitResult(true, false, damageOnly), SPELL_MISS_IMMUNE); + EXPECT_EQ(ComputeSpellHitResult(true, false, cycloneLike), SPELL_MISS_IMMUNE); +} + +TEST(SpellImmunityTest, DamageImmunity_BlocksDamageOnlySpell) +{ + SpellDesc damageOnly = MakeDamageOnlySpell(); + + EXPECT_EQ(ComputeSpellHitResult(false, true, damageOnly), SPELL_MISS_IMMUNE); +} + +// Specific case for spell ID 16621 "Self Invulnerability". +// This aura grants immunity to melee/physical damage only. A rogue +// using Sinister Strike (a physical melee attack) should be completely +// blocked by the effect. The test uses the same simplified damage-only +// spell description as above but documents the physical context. +TEST(SpellImmunityTest, SelfInvulnerability_BlocksMeleeDamage) +{ + SpellDesc meleeAttack = MakeDamageOnlySpell(); + // simulate physical damage coming from a rogue melee ability + + EXPECT_EQ(ComputeSpellHitResult(false, true, meleeAttack), SPELL_MISS_IMMUNE); +} + +TEST(SpellImmunityTest, DamageImmunity_DoesNotMissMixedSpell) +{ + // This is the key fix: damage immunity must not force SPELL_MISS_IMMUNE + // for mixed spells that include non-damage effects. + SpellDesc frostboltLike = MakeFrostboltLikeSpell(); + + EXPECT_EQ(ComputeSpellHitResult(false, true, frostboltLike), SPELL_MISS_NONE); +} + +TEST(SpellImmunityTest, HandOfFreedomStyle_MovementImmunity_AllowsDamageBlocksSlow) +{ + SpellDesc frostboltLike = MakeFrostboltLikeSpell(); + + EffectApplyResult result = ApplyEffectsWithMovementImmunity(frostboltLike, true); + + EXPECT_TRUE(result.damageApplied); + EXPECT_FALSE(result.slowApplied); +} + +TEST(SpellImmunityTest, NoMovementImmunity_FrostboltStyle_AppliesDamageAndSlow) +{ + SpellDesc frostboltLike = MakeFrostboltLikeSpell(); + + EffectApplyResult result = ApplyEffectsWithMovementImmunity(frostboltLike, false); + + EXPECT_TRUE(result.damageApplied); + EXPECT_TRUE(result.slowApplied); +} + +TEST(SpellImmunityTest, CycloneLikeSpell_DivineShieldStyle_Immune) +{ + SpellDesc cycloneLike = MakeCycloneLikeSpell(); + + EXPECT_EQ(ComputeSpellHitResult(true, false, cycloneLike), SPELL_MISS_IMMUNE); +} + +// Regression test for issue #10671: +// Divine Shield (full spell immunity) should block purely slowing spells +// such as Hunter Frost Trap or DK Desecration. Previously the effect was +// applied because the core only checked damage-only spells when deciding +// immunity based on damage or spell state. +TEST(SpellImmunityTest, SpellImmunity_BlocksSlowOnlySpell) +{ + SpellDesc slowOnly = MakeSlowOnlySpell(); + EXPECT_EQ(ComputeSpellHitResult(true, false, slowOnly), SPELL_MISS_IMMUNE); +} + +// Ensure that damage-only immunity (e.g. from Hand of Protection) does not +// accidentally prevent slow-only spells. This covers the regression when +// Divine Shield was incorrectly modelled as damage immunity only. +TEST(SpellImmunityTest, DamageImmunity_DoesNotBlockSlowOnlySpell) +{ + SpellDesc slowOnly = MakeSlowOnlySpell(); + EXPECT_EQ(ComputeSpellHitResult(false, true, slowOnly), SPELL_MISS_NONE); +} + +// New coverage for school-mask logic. These exercises the helper used by +// Unit::IsImmunedToDamage to ensure broad masks are handled correctly. +TEST(SpellImmunityTest, ImmunityMask_PartialOverlapDoesNotCount) +{ + SpellSchoolMask immune = SPELL_SCHOOL_MASK_FROST; + SpellSchoolMask checkAll = SPELL_SCHOOL_MASK_ALL; + + // a frost-only immunity should *not* make you immune to all damage + EXPECT_FALSE(Unit::IsImmuneMaskFully(immune, checkAll)); +} + +TEST(SpellImmunityTest, ImmunityMask_FullCoverageAccepted) +{ + SpellSchoolMask immune = SPELL_SCHOOL_MASK_MAGIC; // holy+spell + SpellSchoolMask checkMagic = SPELL_SCHOOL_MASK_MAGIC; + SpellSchoolMask checkSpell = SPELL_SCHOOL_MASK_SPELL; + + EXPECT_TRUE(Unit::IsImmuneMaskFully(immune, checkMagic)); + EXPECT_TRUE(Unit::IsImmuneMaskFully(immune, checkSpell)); +} + +TEST(SpellImmunityTest, ImmunityMask_SupersetMatches) +{ + SpellSchoolMask immune = SPELL_SCHOOL_MASK_ALL; + SpellSchoolMask checkMagic = SPELL_SCHOOL_MASK_MAGIC; + + EXPECT_TRUE(Unit::IsImmuneMaskFully(immune, checkMagic)); +} + +// Bladestorm grants a mechanic immunity mask which includes stuns (e.g. +// Lethargy 69133). The following test mirrors that behaviour by +// modelling a simple spell that applies a stun aura and exercising the +// new `immuneToStun` flag in ComputeSpellHitResult. +TEST(SpellImmunityTest, Bladestorm_ImmuneToStun) +{ + SpellDesc stunSpell; + stunSpell.effects[0] = {EFFECT_APPLY_AURA, AURA_MOD_STUN}; + + // without any special immunity the stun should land + EXPECT_EQ(ComputeSpellHitResult(false, false, stunSpell), SPELL_MISS_NONE); + + // with a bladestorm‑style stun immunity it is blocked + EXPECT_EQ(ComputeSpellHitResult(false, false, stunSpell, true), SPELL_MISS_IMMUNE); +} From 57db75a474f33dc1555686c45d05d8007afa94da Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:37:31 -0500 Subject: [PATCH 053/132] fix(Core/Spells): Do not add PROC_HIT_NORMAL/PROC_HIT_CRITICAL if damage is fully nullified (#25105) Co-authored-by: blinkysc Co-authored-by: ariel- --- src/server/game/Entities/Unit/Unit.cpp | 36 +++++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 49a437543..2070ea04d 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -126,6 +126,28 @@ DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo, uint8 damageIndex) m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.attackType), m_absorb(dmgInfo.damages[damageIndex].absorb), m_resist(dmgInfo.damages[damageIndex].resist), m_block(dmgInfo.blocked_amount), m_cleanDamage(dmgInfo.cleanDamage), m_hitMask(0) { + switch (dmgInfo.TargetState) + { + case VICTIMSTATE_IS_IMMUNE: + m_hitMask |= PROC_HIT_IMMUNE; + break; + case VICTIMSTATE_BLOCKS: + m_hitMask |= PROC_HIT_FULL_BLOCK; + break; + } + + if (dmgInfo.HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB)) + m_hitMask |= PROC_HIT_ABSORB; + + if (dmgInfo.HitInfo & HITINFO_FULL_RESIST) + m_hitMask |= PROC_HIT_FULL_RESIST; + + if (m_block) + m_hitMask |= PROC_HIT_BLOCK; + + bool const damageNullified = (dmgInfo.HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 + || (m_hitMask & (PROC_HIT_IMMUNE | PROC_HIT_FULL_BLOCK)) != 0; + switch (dmgInfo.hitOutCome) { case MELEE_HIT_MISS: @@ -141,25 +163,19 @@ DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo, uint8 damageIndex) m_hitMask |= PROC_HIT_EVADE; break; case MELEE_HIT_BLOCK: - m_hitMask |= PROC_HIT_BLOCK; - [[fallthrough]]; case MELEE_HIT_CRUSHING: case MELEE_HIT_GLANCING: case MELEE_HIT_NORMAL: - m_hitMask |= PROC_HIT_NORMAL; + if (!damageNullified) + m_hitMask |= PROC_HIT_NORMAL; break; case MELEE_HIT_CRIT: - m_hitMask |= PROC_HIT_CRITICAL; + if (!damageNullified) + m_hitMask |= PROC_HIT_CRITICAL; break; default: break; } - - if (dmgInfo.damages[damageIndex].absorb) - m_hitMask |= PROC_HIT_ABSORB; - - if (dmgInfo.blocked_amount) - m_hitMask |= (dmgInfo.damages[damageIndex].damage == dmgInfo.blocked_amount) ? PROC_HIT_FULL_BLOCK : PROC_HIT_BLOCK; } DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, uint32 hitMask) From 80b6984274b750f8d94fbeb9049b419eacbefc24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Mar 2026 22:39:50 +0000 Subject: [PATCH 054/132] chore(DB): import pending files Referenced commit(s): 0e0ff86e3e88ad836d1fde23128f8720e1d045aa --- .../rev_1772292641644296192.sql => db_world/2026_03_22_03.sql} | 1 + .../rev_1772402317724411598.sql => db_world/2026_03_22_04.sql} | 1 + .../rev_1773864671057223800.sql => db_world/2026_03_22_05.sql} | 1 + 3 files changed, 3 insertions(+) rename data/sql/updates/{pending_db_world/rev_1772292641644296192.sql => db_world/2026_03_22_03.sql} (95%) rename data/sql/updates/{pending_db_world/rev_1772402317724411598.sql => db_world/2026_03_22_04.sql} (98%) rename data/sql/updates/{pending_db_world/rev_1773864671057223800.sql => db_world/2026_03_22_05.sql} (97%) diff --git a/data/sql/updates/pending_db_world/rev_1772292641644296192.sql b/data/sql/updates/db_world/2026_03_22_03.sql similarity index 95% rename from data/sql/updates/pending_db_world/rev_1772292641644296192.sql rename to data/sql/updates/db_world/2026_03_22_03.sql index d34f54d4d..7c3a2d276 100644 --- a/data/sql/updates/pending_db_world/rev_1772292641644296192.sql +++ b/data/sql/updates/db_world/2026_03_22_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_22_02 -> 2026_03_22_03 -- CREATE TABLE `creature_immunities` ( `ID` int NOT NULL, diff --git a/data/sql/updates/pending_db_world/rev_1772402317724411598.sql b/data/sql/updates/db_world/2026_03_22_04.sql similarity index 98% rename from data/sql/updates/pending_db_world/rev_1772402317724411598.sql rename to data/sql/updates/db_world/2026_03_22_04.sql index aeaac02a2..c07b7420d 100644 --- a/data/sql/updates/pending_db_world/rev_1772402317724411598.sql +++ b/data/sql/updates/db_world/2026_03_22_04.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_22_03 -> 2026_03_22_04 -- DELETE FROM `creature_immunities` WHERE `ID` IN (95,96,315,477,679,878,1537,1557,1614,1615,1630,1632,1664,1676,1682,1693,1694,1695,1733); diff --git a/data/sql/updates/pending_db_world/rev_1773864671057223800.sql b/data/sql/updates/db_world/2026_03_22_05.sql similarity index 97% rename from data/sql/updates/pending_db_world/rev_1773864671057223800.sql rename to data/sql/updates/db_world/2026_03_22_05.sql index e2313cbfb..2d77e73f9 100644 --- a/data/sql/updates/pending_db_world/rev_1773864671057223800.sql +++ b/data/sql/updates/db_world/2026_03_22_05.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_22_04 -> 2026_03_22_05 -- DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 22) AND (`SourceGroup` = 11) AND (`SourceEntry` = 21838) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 29) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23377) AND (`ConditionValue2` = 90) AND (`ConditionValue3` = 0); INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES From 27b0ecc6dcd036e771dc6da4644ff2548e73a091 Mon Sep 17 00:00:00 2001 From: Takenbacon Date: Sun, 22 Mar 2026 18:33:04 -0700 Subject: [PATCH 055/132] refactor(Core/Collision): Store collision data on map context (#25049) --- src/common/Collision/Management/IVMapMgr.h | 20 +- .../Collision/Management/MMapFactory.cpp | 56 ---- src/common/Collision/Management/MMapFactory.h | 45 --- src/common/Collision/Management/MMapMgr.cpp | 241 ++------------- src/common/Collision/Management/MMapMgr.h | 72 ++--- src/common/Collision/Management/VMapMgr2.cpp | 274 +----------------- src/common/Collision/Management/VMapMgr2.h | 56 +--- .../Collision/Management/WorldModelStore.cpp | 43 +++ .../{IMMAPMgr.h => WorldModelStore.h} | 42 +-- src/common/Collision/Maps/MapTree.cpp | 101 ++----- src/common/Collision/Maps/MapTree.h | 10 +- .../Collision/Models/GameObjectModel.cpp | 11 +- src/common/Collision/Models/GameObjectModel.h | 5 +- src/common/Collision/Models/ModelInstance.cpp | 2 +- src/common/Collision/Models/ModelInstance.h | 8 +- src/server/game/Conditions/DisableMgr.cpp | 15 +- src/server/game/Grids/GridTerrainLoader.cpp | 31 +- src/server/game/Grids/GridTerrainLoader.h | 15 +- src/server/game/Grids/MapGridManager.cpp | 14 +- src/server/game/Maps/Map.cpp | 57 ++-- src/server/game/Maps/Map.h | 22 +- src/server/game/Maps/MapCollisionData.cpp | 183 ++++++++++++ src/server/game/Maps/MapCollisionData.h | 100 +++++++ .../MovementGenerators/PathGenerator.cpp | 7 +- src/server/game/Spells/Spell.cpp | 9 +- src/server/game/World/World.cpp | 19 +- src/server/game/World/WorldConfig.cpp | 2 + src/server/game/World/WorldConfig.h | 2 + src/server/scripts/Commands/cs_misc.cpp | 3 +- src/server/scripts/Commands/cs_mmaps.cpp | 28 +- .../SunwellPlateau/boss_muru.cpp | 3 +- src/tools/mmaps_generator/TerrainBuilder.cpp | 23 +- 32 files changed, 527 insertions(+), 992 deletions(-) delete mode 100644 src/common/Collision/Management/MMapFactory.cpp delete mode 100644 src/common/Collision/Management/MMapFactory.h create mode 100644 src/common/Collision/Management/WorldModelStore.cpp rename src/common/Collision/Management/{IMMAPMgr.h => WorldModelStore.h} (55%) create mode 100644 src/server/game/Maps/MapCollisionData.cpp create mode 100644 src/server/game/Maps/MapCollisionData.h diff --git a/src/common/Collision/Management/IVMapMgr.h b/src/common/Collision/Management/IVMapMgr.h index 410743f76..4af323372 100644 --- a/src/common/Collision/Management/IVMapMgr.h +++ b/src/common/Collision/Management/IVMapMgr.h @@ -31,6 +31,8 @@ This is the minimum interface to the VMapMamager. namespace VMAP { + class StaticMapTree; + enum VMAP_LOAD_RESULT { VMAP_LOAD_RESULT_ERROR, @@ -88,20 +90,8 @@ namespace VMAP virtual ~IVMapMgr() = default; - virtual int loadMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0; - virtual LoadResult existsMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0; - virtual void unloadMap(unsigned int pMapId, int x, int y) = 0; - virtual void unloadMap(unsigned int pMapId) = 0; - - virtual bool isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags) = 0; - virtual float getHeight(unsigned int pMapId, float x, float y, float z, float maxSearchDist) = 0; - /** - test if we hit an object. return true if we hit one. rx, ry, rz will hold the hit position or the dest position, if no intersection was found - return a position, that is pReduceDist closer to the origin - */ - virtual bool GetObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float pModifyDist) = 0; /** send debug commands */ @@ -123,12 +113,6 @@ namespace VMAP [[nodiscard]] bool isMapLoadingEnabled() const { return (iEnableLineOfSightCalc || iEnableHeightCalc ); } [[nodiscard]] virtual std::string getDirFileName(unsigned int pMapId, int x, int y) const = 0; - - /** - Query world model area info. - \param z gets adjusted to the ground height for which this are info is valid - */ - virtual bool GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional reqLiquidType, AreaAndLiquidData& data) const = 0; }; } diff --git a/src/common/Collision/Management/MMapFactory.cpp b/src/common/Collision/Management/MMapFactory.cpp deleted file mode 100644 index 4409bcc77..000000000 --- a/src/common/Collision/Management/MMapFactory.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "MMapFactory.h" -#include -namespace MMAP -{ - // ######################## MMapFactory ######################## - // our global singleton copy - MMapMgr* g_MMapMgr = nullptr; - bool MMapFactory::forbiddenMaps[1000] = {0}; - - MMapMgr* MMapFactory::createOrGetMMapMgr() - { - if (!g_MMapMgr) - { - g_MMapMgr = new MMapMgr(); - } - - return g_MMapMgr; - } - - void MMapFactory::InitializeDisabledMaps() - { - memset(&forbiddenMaps, 0, sizeof(forbiddenMaps)); - int32 f[] = {616 /*EoE*/, 649 /*ToC25*/, 650 /*ToC5*/, -1}; - uint32 i = 0; - while (f[i] >= 0) - { - forbiddenMaps[f[i++]] = true; - } - } - - void MMapFactory::clear() - { - if (g_MMapMgr) - { - delete g_MMapMgr; - g_MMapMgr = nullptr; - } - } -} diff --git a/src/common/Collision/Management/MMapFactory.h b/src/common/Collision/Management/MMapFactory.h deleted file mode 100644 index 923954ab4..000000000 --- a/src/common/Collision/Management/MMapFactory.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef _MMAP_FACTORY_H -#define _MMAP_FACTORY_H - -#include "MMapMgr.h" - -namespace MMAP -{ - enum MMAP_LOAD_RESULT - { - MMAP_LOAD_RESULT_ERROR, - MMAP_LOAD_RESULT_OK, - MMAP_LOAD_RESULT_IGNORED, - }; - - // static class - // holds all mmap global data - // access point to MMapMgr singleton - class MMapFactory - { - public: - static MMapMgr* createOrGetMMapMgr(); - static void clear(); - static void InitializeDisabledMaps(); - static bool forbiddenMaps[1000]; - }; -} - -#endif diff --git a/src/common/Collision/Management/MMapMgr.cpp b/src/common/Collision/Management/MMapMgr.cpp index d3c450097..9944995e4 100644 --- a/src/common/Collision/Management/MMapMgr.cpp +++ b/src/common/Collision/Management/MMapMgr.cpp @@ -24,63 +24,8 @@ namespace MMAP { // ######################## MMapMgr ######################## - MMapMgr::~MMapMgr() + std::shared_ptr MMapMgr::LoadNavMesh(uint32 mapId) { - for (MMapDataSet::iterator i = loadedMMaps.begin(); i != loadedMMaps.end(); ++i) - { - delete i->second; - } - - // by now we should not have maps loaded - // if we had, tiles in MMapData->mmapLoadedTiles, their actual data is lost! - } - - void MMapMgr::InitializeThreadUnsafe(const std::vector& mapIds) - { - // the caller must pass the list of all mapIds that will be used in the VMapMgr2 lifetime - for (const uint32& mapId : mapIds) - { - loadedMMaps.emplace(mapId, nullptr); - } - - thread_safe_environment = false; - } - - MMapDataSet::const_iterator MMapMgr::GetMMapData(uint32 mapId) const - { - // return the iterator if found or end() if not found/NULL - MMapDataSet::const_iterator itr = loadedMMaps.find(mapId); - if (itr != loadedMMaps.cend() && !itr->second) - { - itr = loadedMMaps.cend(); - } - - return itr; - } - - bool MMapMgr::loadMapData(uint32 mapId) - { - // we already have this map loaded? - MMapDataSet::iterator itr = loadedMMaps.find(mapId); - if (itr != loadedMMaps.end()) - { - if (itr->second) - { - return true; - } - } - else - { - if (thread_safe_environment) - { - itr = loadedMMaps.insert(MMapDataSet::value_type(mapId, nullptr)).first; - } - else - { - ABORT("Invalid mapId {} passed to MMapMgr after startup in thread unsafe environment", mapId); - } - } - // load and init dtNavMesh - read parameters from file std::string fileName = Acore::StringFormat(MAP_FILE_NAME_FORMAT, sConfigMgr->GetOption("DataDir", "."), mapId); @@ -88,7 +33,7 @@ namespace MMAP if (!file) { LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not open mmap file '{}'", fileName); - return false; + return nullptr; } dtNavMeshParams params; @@ -97,7 +42,7 @@ namespace MMAP if (count != 1) { LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read params from file '{}'", fileName); - return false; + return nullptr; } dtNavMesh* mesh = dtAllocNavMesh(); @@ -106,15 +51,13 @@ namespace MMAP { dtFreeNavMesh(mesh); LOG_ERROR("maps", "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {:03} from file {}", mapId, fileName); - return false; + return nullptr; } LOG_DEBUG("maps", "MMAP:loadMapData: Loaded {:03}.mmap", mapId); - // store inside our map list - MMapData* mmap_data = new MMapData(mesh); - itr->second = mmap_data; - return true; + std::shared_ptr navMesh = std::shared_ptr(mesh, NavMeshDeleter()); + return navMesh; } uint32 MMapMgr::packTileID(int32 x, int32 y) @@ -122,26 +65,8 @@ namespace MMAP return uint32(x << 16 | y); } - bool MMapMgr::loadMap(uint32 mapId, int32 x, int32 y) + bool MMapMgr::LoadTile(dtNavMesh* navMesh, uint32 mapId, int32 x, int32 y) { - // make sure the mmap is loaded and ready to load tiles - if (!loadMapData(mapId)) - { - return false; - } - - // get this mmap data - MMapData* mmap = loadedMMaps[mapId]; - ASSERT(mmap->navMesh); - - // check if we already have this tile loaded - uint32 packedGridPos = packTileID(x, y); - if (mmap->loadedTileRefs.find(packedGridPos) != mmap->loadedTileRefs.end()) - { - LOG_ERROR("maps", "MMAP:loadMap: Asked to load already loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y); - return false; - } - // load this tile :: mmaps/MMMXXYY.mmtile std::string fileName = Acore::StringFormat(TILE_FILE_NAME_FORMAT, sConfigMgr->GetOption("DataDir", "."), mapId, x, y); FILE* file = fopen(fileName.c_str(), "rb"); @@ -184,10 +109,8 @@ namespace MMAP dtTileRef tileRef = 0; // memory allocated for data is now managed by detour, and will be deallocated when the tile is removed - if (dtStatusSucceed(mmap->navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef))) + if (dtStatusSucceed(navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef))) { - mmap->loadedTileRefs.insert(std::pair(packedGridPos, tileRef)); - ++loadedTiles; dtMeshHeader* header = (dtMeshHeader*)data; LOG_DEBUG("maps", "MMAP:loadMap: Loaded mmtile {:03}[{:02},{:02}] into {:03}[{:02},{:02}]", mapId, x, y, mapId, header->x, header->y); return true; @@ -198,149 +121,19 @@ namespace MMAP return false; } - bool MMapMgr::unloadMap(uint32 mapId, int32 x, int32 y) + ManagedNavMeshQuery MMapMgr::CreateNavMeshQuery(dtNavMesh* navMesh) { - // check if we have this map loaded - MMapDataSet::const_iterator itr = GetMMapData(mapId); - if (itr == loadedMMaps.end()) - { - // file may not exist, therefore not loaded - LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map. {:03}{:02}{:02}.mmtile", mapId, x, y); - return false; - } - - MMapData* mmap = itr->second; - - // check if we have this tile loaded - uint32 packedGridPos = packTileID(x, y); - if (mmap->loadedTileRefs.find(packedGridPos) == mmap->loadedTileRefs.end()) - { - // file may not exist, therefore not loaded - LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y); - return false; - } - - dtTileRef tileRef = mmap->loadedTileRefs[packedGridPos]; - - // unload, and mark as non loaded - if (dtStatusFailed(mmap->navMesh->removeTile(tileRef, nullptr, nullptr))) - { - // this is technically a memory leak - // if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used - // we cannot recover from this error - assert out - LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:03}{:02}{:02}.mmtile from navmesh", mapId, x, y); - ABORT(); - } - - mmap->loadedTileRefs.erase(packedGridPos); - --loadedTiles; - LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:03}[{:02},{:02}] from {:03}", mapId, x, y, mapId); - return true; - } - - bool MMapMgr::unloadMap(uint32 mapId) - { - MMapDataSet::iterator itr = loadedMMaps.find(mapId); - if (itr == loadedMMaps.end() || !itr->second) - { - // file may not exist, therefore not loaded - LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map {:03}", mapId); - return false; - } - - // unload all tiles from given map - MMapData* mmap = itr->second; - for (auto& i : mmap->loadedTileRefs) - { - uint32 x = (i.first >> 16); - uint32 y = (i.first & 0x0000FFFF); - - if (dtStatusFailed(mmap->navMesh->removeTile(i.second, nullptr, nullptr))) - { - LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:03}{:02}{:02}.mmtile from navmesh", mapId, x, y); - } - else - { - --loadedTiles; - LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:03}[{:02},{:02}] from {:03}", mapId, x, y, mapId); - } - } - - delete mmap; - itr->second = nullptr; - LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded {:03}.mmap", mapId); - - return true; - } - - bool MMapMgr::unloadMapInstance(uint32 mapId, uint32 instanceId) - { - // check if we have this map loaded - MMapDataSet::const_iterator itr = GetMMapData(mapId); - if (itr == loadedMMaps.end()) - { - // file may not exist, therefore not loaded - LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map {:03}", mapId); - return false; - } - - MMapData* mmap = itr->second; - if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end()) - { - LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {:03} instanceId {}", mapId, instanceId); - return false; - } - - dtNavMeshQuery* query = mmap->navMeshQueries[instanceId]; - - dtFreeNavMeshQuery(query); - mmap->navMeshQueries.erase(instanceId); - LOG_DEBUG("maps", "MMAP:unloadMapInstance: Unloaded mapId {:03} instanceId {}", mapId, instanceId); - - return true; - } - - dtNavMesh const* MMapMgr::GetNavMesh(uint32 mapId) - { - MMapDataSet::const_iterator itr = GetMMapData(mapId); - if (itr == loadedMMaps.end()) + // allocate mesh query + dtNavMeshQuery* query = dtAllocNavMeshQuery(); + ASSERT(query); + + if (dtStatusFailed(query->init(navMesh, 1024))) { + dtFreeNavMeshQuery(query); return nullptr; } - return itr->second->navMesh; - } - - dtNavMeshQuery const* MMapMgr::GetNavMeshQuery(uint32 mapId, uint32 instanceId) - { - MMapDataSet::const_iterator itr = GetMMapData(mapId); - if (itr == loadedMMaps.end()) - { - return nullptr; - } - - MMapData* mmap = itr->second; - if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end()) - { - // check again after acquiring mutex - if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end()) - { - // allocate mesh query - dtNavMeshQuery* query = dtAllocNavMeshQuery(); - ASSERT(query); - - if (dtStatusFailed(query->init(mmap->navMesh, 1024))) - { - dtFreeNavMeshQuery(query); - LOG_ERROR("maps", "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {:03} instanceId {}", mapId, instanceId); - return nullptr; - } - - LOG_DEBUG("maps", "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {:03} instanceId {}", mapId, instanceId); - mmap->navMeshQueries.insert(std::pair(instanceId, query)); - } - } - - return mmap->navMeshQueries[instanceId]; + ManagedNavMeshQuery navMeshQuery = ManagedNavMeshQuery(query); + return navMeshQuery; } } diff --git a/src/common/Collision/Management/MMapMgr.h b/src/common/Collision/Management/MMapMgr.h index 823142cde..3b02d280a 100644 --- a/src/common/Collision/Management/MMapMgr.h +++ b/src/common/Collision/Management/MMapMgr.h @@ -22,8 +22,7 @@ #include "DetourAlloc.h" #include "DetourExtended.h" #include "DetourNavMesh.h" -#include -#include +#include // memory management inline void* dtCustomAlloc(std::size_t size, dtAllocHint /*hint*/) @@ -39,67 +38,40 @@ inline void dtCustomFree(void* ptr) // move map related classes namespace MMAP { + enum MMAP_LOAD_RESULT + { + MMAP_LOAD_RESULT_ERROR, + MMAP_LOAD_RESULT_OK, + MMAP_LOAD_RESULT_IGNORED, + }; + static char const* const MAP_FILE_NAME_FORMAT = "{}/mmaps/{:03}.mmap"; static char const* const TILE_FILE_NAME_FORMAT = "{}/mmaps/{:03}{:02}{:02}.mmtile"; - typedef std::unordered_map MMapTileSet; - typedef std::unordered_map NavMeshQuerySet; - - // dummy struct to hold map's mmap data - struct MMapData + struct NavMeshDeleter { - MMapData(dtNavMesh* mesh) : navMesh(mesh) { } - - ~MMapData() - { - for (auto& navMeshQuerie : navMeshQueries) - { - dtFreeNavMeshQuery(navMeshQuerie.second); - } - - if (navMesh) - { - dtFreeNavMesh(navMesh); - } - } - - // we have to use single dtNavMeshQuery for every instance, since those are not thread safe - NavMeshQuerySet navMeshQueries; // instanceId to query - dtNavMesh* navMesh; - MMapTileSet loadedTileRefs; // maps [map grid coords] to [dtTile] + void operator()(dtNavMesh* navMesh) noexcept { dtFreeNavMesh(navMesh); } }; - typedef std::unordered_map MMapDataSet; + struct NavMeshQueryDeleter + { + void operator()(dtNavMeshQuery* query) noexcept { dtFreeNavMeshQuery(query); } + }; + + using ManagedNavMeshQuery = std::unique_ptr; - // singleton class - // holds all all access to mmap loading unloading and meshes class MMapMgr { public: - MMapMgr() = default; - ~MMapMgr(); + MMapMgr() = default; + ~MMapMgr() = default; - void InitializeThreadUnsafe(const std::vector& mapIds); - bool loadMap(uint32 mapId, int32 x, int32 y); - bool unloadMap(uint32 mapId, int32 x, int32 y); - bool unloadMap(uint32 mapId); - bool unloadMapInstance(uint32 mapId, uint32 instanceId); - - // the returned [dtNavMeshQuery const*] is NOT threadsafe - dtNavMeshQuery const* GetNavMeshQuery(uint32 mapId, uint32 instanceId); - dtNavMesh const* GetNavMesh(uint32 mapId); - - [[nodiscard]] uint32 getLoadedTilesCount() const { return loadedTiles; } - [[nodiscard]] uint32 getLoadedMapsCount() const { return loadedMMaps.size(); } + static std::shared_ptr LoadNavMesh(uint32 mapId); + static bool LoadTile(dtNavMesh* navMesh, uint32 mapId, int32 x, int32 y); + static ManagedNavMeshQuery CreateNavMeshQuery(dtNavMesh* navMesh); private: - bool loadMapData(uint32 mapId); - uint32 packTileID(int32 x, int32 y); - [[nodiscard]] MMapDataSet::const_iterator GetMMapData(uint32 mapId) const; - - MMapDataSet loadedMMaps; - uint32 loadedTiles{0}; - bool thread_safe_environment{true}; + static uint32 packTileID(int32 x, int32 y); }; } diff --git a/src/common/Collision/Management/VMapMgr2.cpp b/src/common/Collision/Management/VMapMgr2.cpp index 7639c9c6b..24e4745d7 100644 --- a/src/common/Collision/Management/VMapMgr2.cpp +++ b/src/common/Collision/Management/VMapMgr2.cpp @@ -17,11 +17,8 @@ #include "VMapMgr2.h" #include "Errors.h" -#include "Log.h" #include "MapDefines.h" #include "MapTree.h" -#include "ModelInstance.h" -#include "WorldModel.h" #include #include #include @@ -35,34 +32,13 @@ namespace VMAP { GetLiquidFlagsPtr = &GetLiquidFlagsDummy; IsVMAPDisabledForPtr = &IsVMAPDisabledForDummy; - thread_safe_environment = true; } VMapMgr2::~VMapMgr2() { - for (InstanceTreeMap::iterator i = iInstanceMapTrees.begin(); i != iInstanceMapTrees.end(); ++i) - { - delete i->second; - } - - for (ModelFileMap::iterator i = iLoadedModelFiles.begin(); i != iLoadedModelFiles.end(); ++i) - { - delete i->second.getModel(); - } } - void VMapMgr2::InitializeThreadUnsafe(const std::vector& mapIds) - { - // the caller must pass the list of all mapIds that will be used in the VMapMgr2 lifetime - for (const uint32& mapId : mapIds) - { - iInstanceMapTrees.emplace(mapId, nullptr); - } - - thread_safe_environment = false; - } - - Vector3 VMapMgr2::convertPositionToInternalRep(float x, float y, float z) const + Vector3 VMapMgr2::convertPositionToInternalRep(float x, float y, float z) { Vector3 pos; const float mid = 0.5f * MAX_NUMBER_OF_GRIDS * SIZE_OF_GRIDS; @@ -73,18 +49,6 @@ namespace VMAP return pos; } - InstanceTreeMap::const_iterator VMapMgr2::GetMapTree(uint32 mapId) const - { - // return the iterator if found or end() if not found/NULL - InstanceTreeMap::const_iterator itr = iInstanceMapTrees.find(mapId); - if (itr != iInstanceMapTrees.cend() && !itr->second) - { - itr = iInstanceMapTrees.cend(); - } - - return itr; - } - // move to MapTree too? std::string VMapMgr2::getMapFileName(unsigned int mapId) { @@ -95,245 +59,9 @@ namespace VMAP return fname.str(); } - int VMapMgr2::loadMap(const char* basePath, unsigned int mapId, int x, int y) - { - int result = VMAP_LOAD_RESULT_IGNORED; - if (isMapLoadingEnabled()) - { - if (_loadMap(mapId, basePath, x, y)) - { - result = VMAP_LOAD_RESULT_OK; - } - else - { - result = VMAP_LOAD_RESULT_ERROR; - } - } - - return result; - } - - // load one tile (internal use only) - bool VMapMgr2::_loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY) - { - InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId); - if (instanceTree == iInstanceMapTrees.end()) - { - if (thread_safe_environment) - { - instanceTree = iInstanceMapTrees.insert(InstanceTreeMap::value_type(mapId, nullptr)).first; - } - else - ABORT("Invalid mapId {} tile [{}, {}] passed to VMapMgr2 after startup in thread unsafe environment", - mapId, tileX, tileY); - } - - if (!instanceTree->second) - { - std::string mapFileName = getMapFileName(mapId); - StaticMapTree* newTree = new StaticMapTree(mapId, basePath); - if (!newTree->InitMap(mapFileName, this)) - { - delete newTree; - return false; - } - instanceTree->second = newTree; - } - - return instanceTree->second->LoadMapTile(tileX, tileY, this); - } - - void VMapMgr2::unloadMap(unsigned int mapId) - { - InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId); - if (instanceTree != iInstanceMapTrees.end() && instanceTree->second) - { - instanceTree->second->UnloadMap(this); - if (instanceTree->second->numLoadedTiles() == 0) - { - delete instanceTree->second; - instanceTree->second = nullptr; - } - } - } - - void VMapMgr2::unloadMap(unsigned int mapId, int x, int y) - { - InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId); - if (instanceTree != iInstanceMapTrees.end() && instanceTree->second) - { - instanceTree->second->UnloadMapTile(x, y, this); - if (instanceTree->second->numLoadedTiles() == 0) - { - delete instanceTree->second; - instanceTree->second = nullptr; - } - } - } - - bool VMapMgr2::isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags) - { -#if defined(ENABLE_VMAP_CHECKS) - if (!isLineOfSightCalcEnabled() || IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS)) - { - return true; - } -#endif - - InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); - if (instanceTree != iInstanceMapTrees.end()) - { - Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1); - Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2); - if (pos1 != pos2) - { - return instanceTree->second->isInLineOfSight(pos1, pos2, ignoreFlags); - } - } - - return true; - } - - /** - get the hit position and return true if we hit something - otherwise the result pos will be the dest pos - */ - bool VMapMgr2::GetObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) - { -#if defined(ENABLE_VMAP_CHECKS) - if (isLineOfSightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS)) -#endif - { - InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); - if (instanceTree != iInstanceMapTrees.end()) - { - Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1); - Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2); - Vector3 resultPos; - bool result = instanceTree->second->GetObjectHitPos(pos1, pos2, resultPos, modifyDist); - resultPos = convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z); - rx = resultPos.x; - ry = resultPos.y; - rz = resultPos.z; - return result; - } - } - - rx = x2; - ry = y2; - rz = z2; - - return false; - } - - /** - get height or INVALID_HEIGHT if no height available - */ - - float VMapMgr2::getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist) - { -#if defined(ENABLE_VMAP_CHECKS) - if (isHeightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_HEIGHT)) -#endif - { - InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); - if (instanceTree != iInstanceMapTrees.end()) - { - Vector3 pos = convertPositionToInternalRep(x, y, z); - float height = instanceTree->second->getHeight(pos, maxSearchDist); - if (height >= G3D::finf()) - { - return height = VMAP_INVALID_HEIGHT_VALUE; // No height - } - - return height; - } - } - - return VMAP_INVALID_HEIGHT_VALUE; - } - - bool VMapMgr2::GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional reqLiquidType, AreaAndLiquidData& data) const - { - InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId); - if (instanceTree != iInstanceMapTrees.end()) - { - LocationInfo info; - Vector3 pos = convertPositionToInternalRep(x, y, z); - if (instanceTree->second->GetLocationInfo(pos, info)) - { - data.floorZ = info.ground_Z; - if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS)) - { - uint32 liquidType = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc - float liquidLevel; - if (!reqLiquidType || (GetLiquidFlagsPtr(liquidType) & *reqLiquidType)) - if (info.hitInstance->GetLiquidLevel(pos, info, liquidLevel)) - data.liquidInfo.emplace(liquidType, liquidLevel); - } - - if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_AREAFLAG)) - data.areaInfo.emplace(info.hitModel->GetWmoID(), info.hitInstance->adtId, info.rootId, info.hitModel->GetMogpFlags(), info.hitInstance->ID); - return true; - } - } - - return false; - } - - WorldModel* VMapMgr2::acquireModelInstance(const std::string& basepath, const std::string& filename, uint32 flags/* Only used when creating the model */) - { - //! Critical section, thread safe access to iLoadedModelFiles - std::lock_guard lock(LoadedModelFilesLock); - - ModelFileMap::iterator model = iLoadedModelFiles.find(filename); - if (model == iLoadedModelFiles.end()) - { - WorldModel* worldmodel = new WorldModel(); - if (!worldmodel->readFile(basepath + filename + ".vmo")) - { - LOG_ERROR("maps", "VMapMgr2: could not load '{}{}.vmo'", basepath, filename); - delete worldmodel; - return nullptr; - } - LOG_DEBUG("maps", "VMapMgr2: loading file '{}{}'", basepath, filename); - - worldmodel->Flags = flags; - - model = iLoadedModelFiles.insert(std::pair(filename, ManagedModel())).first; - model->second.setModel(worldmodel); - } - - return model->second.getModel(); - } - - void VMapMgr2::releaseModelInstance(const std::string& filename) - { - //! Critical section, thread safe access to iLoadedModelFiles - std::lock_guard lock(LoadedModelFilesLock); - - ModelFileMap::iterator model = iLoadedModelFiles.find(filename); - if (model == iLoadedModelFiles.end()) - { - LOG_ERROR("maps", "VMapMgr2: trying to unload non-loaded file '{}'", filename); - return; - } - if (model->second.decRefCount() == 0) - { - LOG_DEBUG("maps", "VMapMgr2: unloading file '{}'", filename); - delete model->second.getModel(); - iLoadedModelFiles.erase(model); - } - } - LoadResult VMapMgr2::existsMap(const char* basePath, unsigned int mapId, int x, int y) { return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y); } - void VMapMgr2::GetInstanceMapTree(InstanceTreeMap& instanceMapTree) - { - instanceMapTree = iInstanceMapTrees; - } - } // namespace VMAP diff --git a/src/common/Collision/Management/VMapMgr2.h b/src/common/Collision/Management/VMapMgr2.h index 5fc4106d2..3c221b170 100644 --- a/src/common/Collision/Management/VMapMgr2.h +++ b/src/common/Collision/Management/VMapMgr2.h @@ -19,9 +19,6 @@ #define _VMAPMANAGER2_H #include "IVMapMgr.h" -#include -#include -#include //=========================================================== @@ -46,24 +43,6 @@ namespace G3D namespace VMAP { - class StaticMapTree; - class WorldModel; - - class ManagedModel - { - public: - ManagedModel() { } - void setModel(WorldModel* model) { iModel = model; } - WorldModel* getModel() { return iModel; } - int decRefCount() { return --iRefCount; } - protected: - WorldModel* iModel{nullptr}; - int iRefCount{0}; - }; - - typedef std::unordered_map InstanceTreeMap; - typedef std::unordered_map ModelFileMap; - enum DisableTypes { VMAP_DISABLE_AREAFLAG = 0x1, @@ -75,58 +54,25 @@ namespace VMAP class VMapMgr2 : public IVMapMgr { protected: - // Tree to check collision - ModelFileMap iLoadedModelFiles; - InstanceTreeMap iInstanceMapTrees; - bool thread_safe_environment; - - // Mutex for iLoadedModelFiles - std::mutex LoadedModelFilesLock; - - bool _loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY); - /* void _unloadMap(uint32 pMapId, uint32 x, uint32 y); */ - static uint32 GetLiquidFlagsDummy(uint32) { return 0; } static bool IsVMAPDisabledForDummy(uint32 /*entry*/, uint8 /*flags*/) { return false; } - InstanceTreeMap::const_iterator GetMapTree(uint32 mapId) const; - public: // public for debug - [[nodiscard]] G3D::Vector3 convertPositionToInternalRep(float x, float y, float z) const; + static G3D::Vector3 convertPositionToInternalRep(float x, float y, float z); static std::string getMapFileName(unsigned int mapId); VMapMgr2(); ~VMapMgr2() override; - void InitializeThreadUnsafe(const std::vector& mapIds); - - int loadMap(const char* pBasePath, unsigned int mapId, int x, int y) override; - - void unloadMap(unsigned int mapId, int x, int y) override; - void unloadMap(unsigned int mapId) override; - - bool isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags) override ; - /** - fill the hit pos and return true, if an object was hit - */ - bool GetObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) override; - float getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist) override; - bool processCommand(char* /*command*/) override { return false; } // for debug and extensions - bool GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional reqLiquidType, AreaAndLiquidData& data) const override; - - WorldModel* acquireModelInstance(const std::string& basepath, const std::string& filename, uint32 flags); - void releaseModelInstance(const std::string& filename); - // what's the use of this? o.O [[nodiscard]] std::string getDirFileName(unsigned int mapId, int /*x*/, int /*y*/) const override { return getMapFileName(mapId); } LoadResult existsMap(const char* basePath, unsigned int mapId, int x, int y) override; - void GetInstanceMapTree(InstanceTreeMap& instanceMapTree); typedef uint32(*GetLiquidFlagsFn)(uint32 liquidType); GetLiquidFlagsFn GetLiquidFlagsPtr; diff --git a/src/common/Collision/Management/WorldModelStore.cpp b/src/common/Collision/Management/WorldModelStore.cpp new file mode 100644 index 000000000..af78a01d5 --- /dev/null +++ b/src/common/Collision/Management/WorldModelStore.cpp @@ -0,0 +1,43 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Log.h" +#include "WorldModelStore.h" + +std::shared_ptr WorldModelStore::AcquireModelInstance(std::string const& basepath, std::string const& filename, uint32 flags/* Only used when creating the model */) +{ + //! Critical section, thread safe access + std::lock_guard lock(_lock); + + ModelFileMap::iterator model = _loadedModels.find(filename); + if (model == _loadedModels.end()) + { + std::shared_ptr worldmodel = std::make_shared(); + LOG_DEBUG("maps", "WorldModelStore: loading file '{}{}'", basepath, filename); + if (!worldmodel->readFile(basepath + filename + ".vmo")) + { + LOG_ERROR("maps", "WorldModelStore: could not load '{}{}.vmo'", basepath, filename); + return nullptr; + } + + worldmodel->Flags = flags; + + model = _loadedModels.insert(std::pair>(filename, worldmodel)).first; + } + + return model->second; +} diff --git a/src/common/Collision/Management/IMMAPMgr.h b/src/common/Collision/Management/WorldModelStore.h similarity index 55% rename from src/common/Collision/Management/IMMAPMgr.h rename to src/common/Collision/Management/WorldModelStore.h index 49452c4a7..a175e0d1f 100644 --- a/src/common/Collision/Management/IMMAPMgr.h +++ b/src/common/Collision/Management/WorldModelStore.h @@ -15,32 +15,32 @@ * with this program. If not, see . */ -#ifndef _IMMAPMANAGER_H -#define _IMMAPMANAGER_H +#ifndef _WORLDMODELSTORE_H +#define _WORLDMODELSTORE_H -// Interface for IMMapManger -namespace MMAP +#include "WorldModel.h" +#include +#include +#include + +class WorldModelStore { - enum MMAP_LOAD_RESULT +public: + static WorldModelStore* instance() { - MMAP_LOAD_RESULT_ERROR, - MMAP_LOAD_RESULT_OK, - MMAP_LOAD_RESULT_IGNORED, - }; + static WorldModelStore instance; + return &instance; + } - class IMMapMgr - { - private: - bool iEnablePathFinding; + std::shared_ptr AcquireModelInstance(std::string const& basepath, std::string const& filename, uint32 flags); - public: - IMMapMgr() : iEnablePathFinding(true) {} - virtual ~IMMapMgr(void) {} +private: + typedef std::unordered_map> ModelFileMap; + ModelFileMap _loadedModels; - //Enabled/Disabled Pathfinding - void setEnablePathFinding(bool value) { iEnablePathFinding = value; } - bool isEnablePathFinding() const { return (iEnablePathFinding); } - }; -} + std::mutex _lock; +}; + +#define sWorldModelStore WorldModelStore::instance() #endif diff --git a/src/common/Collision/Maps/MapTree.cpp b/src/common/Collision/Maps/MapTree.cpp index bb9577d4e..877b17085 100644 --- a/src/common/Collision/Maps/MapTree.cpp +++ b/src/common/Collision/Maps/MapTree.cpp @@ -22,6 +22,7 @@ #include "ModelInstance.h" #include "VMapDefinitions.h" #include "VMapMgr2.h" +#include "WorldModelStore.h" #include #include #include @@ -259,7 +260,7 @@ namespace VMAP //========================================================= - bool StaticMapTree::InitMap(const std::string& fname, VMapMgr2* vm) + bool StaticMapTree::InitMap(const std::string& fname) { //VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : initializing StaticMapTree '{}'", fname); bool success = false; @@ -291,13 +292,12 @@ namespace VMAP #endif if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn)) { - WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags); + std::shared_ptr model = sWorldModelStore->AcquireModelInstance(iBasePath, spawn.name, spawn.flags); //VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : loading {}", spawn.name); if (model) { // assume that global model always is the first and only tree value (could be improved...) iTreeValues[0] = ModelInstance(spawn, model); - iLoadedSpawns[0] = 1; } else { @@ -312,23 +312,14 @@ namespace VMAP //========================================================= - void StaticMapTree::UnloadMap(VMapMgr2* vm) + void StaticMapTree::UnloadMap() { - for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i) - { - iTreeValues[i->first].setUnloaded(); - for (uint32 refCount = 0; refCount < i->second; ++refCount) - { - vm->releaseModelInstance(iTreeValues[i->first].name); - } - } - iLoadedSpawns.clear(); iLoadedTiles.clear(); } //========================================================= - bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm) + bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY) { if (!iIsTiled) { @@ -367,10 +358,11 @@ namespace VMAP if (result) { // acquire model instance - WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags); + std::shared_ptr model = sWorldModelStore->AcquireModelInstance(iBasePath, spawn.name, spawn.flags); if (!model) { LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [{}, {}]", tileX, tileY); + // why do we continue to try to load if the model was unsuccessful here? } // update tree @@ -378,22 +370,22 @@ namespace VMAP if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1) { - if (!iLoadedSpawns.count(referencedVal)) + if (referencedVal >= iNTreeValues) { -#if defined(VMAP_DEBUG) - if (referencedVal > iNTreeValues) - { - LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : invalid tree element ({}/{})", referencedVal, iNTreeValues); - continue; - } -#endif - iTreeValues[referencedVal] = ModelInstance(spawn, model); - iLoadedSpawns[referencedVal] = 1; + LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : invalid tree element ({}/{})", referencedVal, iNTreeValues); + continue; } + + // This looks odd and is confusing, took some research to figure it out: + // the first WorldModel will create a "groupmodel" of all other same-models in the tile + // we don't actually care about anything else + if (!iTreeValues[referencedVal].getWorldModel()) + { + iTreeValues[referencedVal] = ModelInstance(spawn, model); + } +#if defined(VMAP_DEBUG) else { - ++iLoadedSpawns[referencedVal]; -#if defined(VMAP_DEBUG) if (iTreeValues[referencedVal].ID != spawn.ID) { LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node"); @@ -402,8 +394,8 @@ namespace VMAP { LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID={}", spawn.ID); } -#endif } +#endif } else { @@ -427,7 +419,7 @@ namespace VMAP //========================================================= - void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm) + void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY) { uint32 tileID = packTileID(tileX, tileY); loadedTileMap::iterator tile = iLoadedTiles.find(tileID); @@ -436,57 +428,6 @@ namespace VMAP LOG_ERROR("maps", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:{} X:{} Y:{}", iMapID, tileX, tileY); return; } - if (tile->second) // file associated with tile - { - std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY); - FILE* tf = fopen(tilefile.c_str(), "rb"); - if (tf) - { - bool result = true; - char chunk[8]; - if (!readChunk(tf, chunk, VMAP_MAGIC, 8)) - { - result = false; - } - uint32 numSpawns; - if (fread(&numSpawns, sizeof(uint32), 1, tf) != 1) - { - result = false; - } - for (uint32 i = 0; i < numSpawns && result; ++i) - { - // read model spawns - ModelSpawn spawn; - result = ModelSpawn::readFromFile(tf, spawn); - if (result) - { - // release model instance - vm->releaseModelInstance(spawn.name); - - // update tree - uint32 referencedNode; - - if (fread(&referencedNode, sizeof(uint32), 1, tf) != 1) - { - result = false; - } - else - { - if (!iLoadedSpawns.count(referencedNode)) - { - LOG_ERROR("maps", "StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '{}' (ID:{})", spawn.name, spawn.ID); - } - else if (--iLoadedSpawns[referencedNode] == 0) - { - iTreeValues[referencedNode].setUnloaded(); - iLoadedSpawns.erase(referencedNode); - } - } - } - } - fclose(tf); - } - } iLoadedTiles.erase(tile); METRIC_EVENT("map_events", "UnloadMapTile", diff --git a/src/common/Collision/Maps/MapTree.h b/src/common/Collision/Maps/MapTree.h index 09e426e3f..88f0b8302 100644 --- a/src/common/Collision/Maps/MapTree.h +++ b/src/common/Collision/Maps/MapTree.h @@ -60,8 +60,6 @@ namespace VMAP // some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed // empty tiles have no tile file, hence map with bool instead of just a set (consistency check) loadedTileMap iLoadedTiles; - // stores to invalidate tree values, unload map, and to be able to report errors - loadedSpawnMap iLoadedSpawns; std::string iBasePath; private: @@ -81,10 +79,10 @@ namespace VMAP [[nodiscard]] float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const; bool GetLocationInfo(const G3D::Vector3& pos, LocationInfo& info) const; - bool InitMap(const std::string& fname, VMapMgr2* vm); - void UnloadMap(VMapMgr2* vm); - bool LoadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm); - void UnloadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm); + bool InitMap(const std::string& fname); + void UnloadMap(); + bool LoadMapTile(uint32 tileX, uint32 tileY); + void UnloadMapTile(uint32 tileX, uint32 tileY); [[nodiscard]] bool isTiled() const { return iIsTiled; } [[nodiscard]] uint32 numLoadedTiles() const { return iLoadedTiles.size(); } void GetModelInstances(ModelInstance*& models, uint32& count); diff --git a/src/common/Collision/Models/GameObjectModel.cpp b/src/common/Collision/Models/GameObjectModel.cpp index eebc7e62d..6a28164a2 100644 --- a/src/common/Collision/Models/GameObjectModel.cpp +++ b/src/common/Collision/Models/GameObjectModel.cpp @@ -24,6 +24,7 @@ #include "VMapFactory.h" #include "VMapMgr2.h" #include "WorldModel.h" +#include "WorldModelStore.h" using G3D::Vector3; using G3D::Ray; @@ -101,14 +102,6 @@ void LoadGameObjectModelList(std::string const& dataPath) LOG_INFO("server.loading", " "); } -GameObjectModel::~GameObjectModel() -{ - if (iModel) - { - VMAP::VMapFactory::createOrGetVMapMgr()->releaseModelInstance(name); - } -} - bool GameObjectModel::initialize(std::unique_ptr modelOwner, std::string const& dataPath) { ModelList::const_iterator it = model_list.find(modelOwner->GetDisplayId()); @@ -125,7 +118,7 @@ bool GameObjectModel::initialize(std::unique_ptr model return false; } - iModel = VMAP::VMapFactory::createOrGetVMapMgr()->acquireModelInstance(dataPath + "vmaps/", it->second.name, + iModel = sWorldModelStore->AcquireModelInstance(dataPath + "vmaps/", it->second.name, it->second.isWmo ? VMAP::ModelFlags::MOD_WORLDSPAWN : VMAP::ModelFlags::MOD_M2); if (!iModel) diff --git a/src/common/Collision/Models/GameObjectModel.h b/src/common/Collision/Models/GameObjectModel.h index 5938a5c1d..695a5ee43 100644 --- a/src/common/Collision/Models/GameObjectModel.h +++ b/src/common/Collision/Models/GameObjectModel.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace VMAP { @@ -58,7 +59,7 @@ public: [[nodiscard]] const G3D::AABox& GetBounds() const { return iBound; } - ~GameObjectModel(); + ~GameObjectModel() = default; [[nodiscard]] const G3D::Vector3& GetPosition() const { return iPos; } @@ -86,7 +87,7 @@ private: G3D::Vector3 iPos; float iInvScale{0}; float iScale{0}; - VMAP::WorldModel* iModel{nullptr}; + std::shared_ptr iModel; std::unique_ptr owner; bool isWmo{false}; }; diff --git a/src/common/Collision/Models/ModelInstance.cpp b/src/common/Collision/Models/ModelInstance.cpp index c5b6fbfdb..ecc97e85f 100644 --- a/src/common/Collision/Models/ModelInstance.cpp +++ b/src/common/Collision/Models/ModelInstance.cpp @@ -24,7 +24,7 @@ using G3D::Ray; namespace VMAP { - ModelInstance::ModelInstance(const ModelSpawn& spawn, WorldModel* model): ModelSpawn(spawn), iModel(model) + ModelInstance::ModelInstance(const ModelSpawn& spawn, std::shared_ptr model): ModelSpawn(spawn), iModel(model) { iInvRot = G3D::Matrix3::fromEulerAnglesZYX(G3D::pi() * iRot.y / 180.f, G3D::pi() * iRot.x / 180.f, G3D::pi() * iRot.z / 180.f).inverse(); iInvScale = 1.f / iScale; diff --git a/src/common/Collision/Models/ModelInstance.h b/src/common/Collision/Models/ModelInstance.h index 3476a514d..f8c0ecbb1 100644 --- a/src/common/Collision/Models/ModelInstance.h +++ b/src/common/Collision/Models/ModelInstance.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace VMAP { @@ -63,16 +64,15 @@ namespace VMAP { public: ModelInstance() { } - ModelInstance(const ModelSpawn& spawn, WorldModel* model); - void setUnloaded() { iModel = nullptr; } + ModelInstance(const ModelSpawn& spawn, std::shared_ptr model); bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const; bool GetLocationInfo(const G3D::Vector3& p, LocationInfo& info) const; bool GetLiquidLevel(const G3D::Vector3& p, LocationInfo& info, float& liqHeight) const; - WorldModel* getWorldModel() { return iModel; } + WorldModel* getWorldModel() { return iModel.get(); } protected: G3D::Matrix3 iInvRot; float iInvScale{0.0f}; - WorldModel* iModel{nullptr}; + std::shared_ptr iModel; }; } // namespace VMAP diff --git a/src/server/game/Conditions/DisableMgr.cpp b/src/server/game/Conditions/DisableMgr.cpp index c02c7c681..812b9668d 100644 --- a/src/server/game/Conditions/DisableMgr.cpp +++ b/src/server/game/Conditions/DisableMgr.cpp @@ -17,7 +17,6 @@ #include "DisableMgr.h" #include "GameEventMgr.h" -#include "MMapFactory.h" #include "ObjectMgr.h" #include "OutdoorPvP.h" #include "Player.h" @@ -448,5 +447,17 @@ bool DisableMgr::IsPathfindingEnabled(Map const* map) if (!map) return false; - return !MMAP::MMapFactory::forbiddenMaps[map->GetId()] && (sWorld->getBoolConfig(CONFIG_ENABLE_MMAPS) ? true : map->IsBattlegroundOrArena()); + // Still not an ideal solution but removes the extra awful forbiddenMaps hack + // int32 f[] = {616 /*EoE*/, 649 /*ToC25*/, 650 /*ToC5*/, -1}; + switch (map->GetId()) + { + case 616: // EoE + case 649: // ToC25 + case 650: // ToC5 + return false; + default: + break; + } + + return (sWorld->getBoolConfig(CONFIG_ENABLE_MMAPS) ? true : map->IsBattlegroundOrArena()); } diff --git a/src/server/game/Grids/GridTerrainLoader.cpp b/src/server/game/Grids/GridTerrainLoader.cpp index ecf9b0941..5b9598522 100644 --- a/src/server/game/Grids/GridTerrainLoader.cpp +++ b/src/server/game/Grids/GridTerrainLoader.cpp @@ -1,6 +1,6 @@ -#include "DisableMgr.h" #include "GridTerrainLoader.h" -#include "MMapFactory.h" +#include "IVMapMgr.h" +#include "Map.h" #include "MMapMgr.h" #include "ScriptMgr.h" #include "VMapFactory.h" @@ -9,6 +9,7 @@ void GridTerrainLoader::LoadTerrain() { LoadMap(); + if (_map->GetInstanceId() == 0) { LoadVMap(); @@ -18,13 +19,10 @@ void GridTerrainLoader::LoadTerrain() void GridTerrainLoader::LoadMap() { - // Instances will point to the parent maps terrain data + // Instances will point to the parent maps terrain data, no need to load anything. if (_map->GetInstanceId() != 0) { - // load grid map for base map Map* parentMap = const_cast(_map->GetParent()); - - // GetGridTerrainData will create the parent map grid _grid.SetTerrainData(parentMap->GetGridTerrainDataSharedPtr(GridCoord(_grid.GetX(), _grid.GetY()))); return; } @@ -51,7 +49,7 @@ void GridTerrainLoader::LoadMap() void GridTerrainLoader::LoadVMap() { - int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap((sWorld->GetDataPath() + "vmaps").c_str(), _map->GetId(), _grid.GetX(), _grid.GetY()); + int const vmapLoadResult = _map->GetMapCollisionData().LoadVMapTile(_grid.GetX(), _grid.GetY()); switch (vmapLoadResult) { case VMAP::VMAP_LOAD_RESULT_OK: @@ -66,15 +64,14 @@ void GridTerrainLoader::LoadVMap() LOG_DEBUG("maps", "Ignored VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})", _map->GetMapName(), _map->GetId(), _grid.GetX(), _grid.GetY(), _grid.GetX(), _grid.GetY()); break; + default: + break; } } void GridTerrainLoader::LoadMMap() { - if (!DisableMgr::IsPathfindingEnabled(_map)) - return; - - int mmapLoadResult = MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(_map->GetId(), _grid.GetX(), _grid.GetY()); + int const mmapLoadResult = _map->GetMapCollisionData().LoadMMapTile(_grid.GetX(), _grid.GetY()); switch (mmapLoadResult) { case MMAP::MMAP_LOAD_RESULT_OK: @@ -89,6 +86,8 @@ void GridTerrainLoader::LoadMMap() LOG_DEBUG("maps", "Ignored MMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})", _map->GetMapName(), _map->GetId(), _grid.GetX(), _grid.GetY(), _grid.GetX(), _grid.GetY()); break; + default: + break; } } @@ -145,13 +144,3 @@ bool GridTerrainLoader::ExistVMap(uint32 mapid, int gx, int gy) return true; } - -void GridTerrainUnloader::UnloadTerrain() -{ - // Only parent maps manage terrain data - if (_map->GetInstanceId() != 0) - return; - - VMAP::VMapFactory::createOrGetVMapMgr()->unloadMap(_map->GetId(), _grid.GetX(), _grid.GetY()); - MMAP::MMapFactory::createOrGetMMapMgr()->unloadMap(_map->GetId(), _grid.GetX(), _grid.GetY()); -} diff --git a/src/server/game/Grids/GridTerrainLoader.h b/src/server/game/Grids/GridTerrainLoader.h index e1d6019bf..99b4b89ef 100644 --- a/src/server/game/Grids/GridTerrainLoader.h +++ b/src/server/game/Grids/GridTerrainLoader.h @@ -20,6 +20,8 @@ #include "GridDefines.h" +class Map; + class GridTerrainLoader { public: @@ -40,17 +42,4 @@ private: Map* _map; }; -class GridTerrainUnloader -{ -public: - GridTerrainUnloader(MapGridType& grid, Map* map) - : _grid(grid), _map(map) { } - - void UnloadTerrain(); - -private: - MapGridType& _grid; - Map* _map; -}; - #endif diff --git a/src/server/game/Grids/MapGridManager.cpp b/src/server/game/Grids/MapGridManager.cpp index 21ca5bfed..7821bfe2d 100644 --- a/src/server/game/Grids/MapGridManager.cpp +++ b/src/server/game/Grids/MapGridManager.cpp @@ -8,9 +8,19 @@ void MapGridManager::CreateGrid(uint16 const x, uint16 const y) if (IsGridCreated(x, y)) return; + // If we are an instance, ensure parent map has already created the grid before proceeding. + // Note: grid loading is locked and is safe across multiple map threads in the + // event of multiple child maps attempting to create the same parent map grid + if (_map->GetInstanceId() != 0) + { + Map* parentMap = const_cast(_map->GetParent()); + parentMap->EnsureGridCreated(GridCoord(x, y)); + } + std::unique_ptr grid = std::make_unique(x, y); grid->link(_map); + // Terrain is loading during create (should/can we move this to LoadGrid?) GridTerrainLoader loader(*grid, _map); loader.LoadTerrain(); @@ -19,6 +29,7 @@ void MapGridManager::CreateGrid(uint16 const x, uint16 const y) ++_createdGridsCount; } +// Loads objects, terrain already loaded in CreateGrid bool MapGridManager::LoadGrid(uint16 const x, uint16 const y) { MapGridType* grid = GetGrid(x, y); @@ -55,9 +66,6 @@ void MapGridManager::UnloadGrid(uint16 const x, uint16 const y) grid->VisitAllCells(visitor); } - GridTerrainUnloader terrainUnloader(*grid, _map); - terrainUnloader.UnloadTerrain(); - _mapGrid[x][y] = nullptr; } diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 27d0b8fed..89cdd4bbf 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -32,7 +32,6 @@ #include "MapInstanced.h" #include "Metric.h" #include "MiscPackets.h" -#include "MMapFactory.h" #include "Object.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" @@ -62,13 +61,12 @@ Map::~Map() if (!m_scriptSchedule.empty()) sScriptMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size()); - - MMAP::MMapFactory::createOrGetMMapMgr()->unloadMapInstance(GetId(), i_InstanceId); } Map::Map(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) : - _mapGridManager(this), i_mapEntry(sMapStore.LookupEntry(id)), i_spawnMode(SpawnMode), i_InstanceId(InstanceId), - m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE), _instanceResetPeriod(0), + _mapGridManager(this), i_mapEntry(sMapStore.LookupEntry(id)), _mapCollisionData(*this, _parent), + i_spawnMode(SpawnMode), i_InstanceId(InstanceId), m_unloadTimer(0), + m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE), _instanceResetPeriod(0), _transportsUpdateIter(_transports.end()), i_scriptLock(false), _defaultLight(GetDefaultMapLight(id)) { m_parentMap = (_parent ? _parent : this); @@ -432,7 +430,7 @@ void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone) void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/) { if (t_diff) - _dynamicTree.update(t_diff); + _mapCollisionData.GetDynamicTree().update(t_diff); // Update world sessions and players for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter) @@ -1074,9 +1072,9 @@ void Map::UnloadAll() std::shared_ptr Map::GetGridTerrainDataSharedPtr(GridCoord const& gridCoord) { - // ensure GridMap is created - EnsureGridCreated(gridCoord); - return _mapGridManager.GetGrid(gridCoord.x_coord, gridCoord.y_coord)->GetTerrainDataSharedPtr(); + MapGridType* grid = _mapGridManager.GetGrid(gridCoord.x_coord, gridCoord.y_coord); + ASSERT(grid); // Grid should always exist during this call + return grid->GetTerrainDataSharedPtr(); } GridTerrainData* Map::GetGridTerrainData(GridCoord const& gridCoord) @@ -1153,10 +1151,7 @@ float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float float vmapHeight = VMAP_INVALID_HEIGHT_VALUE; if (checkVMap) - { - VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr(); - vmapHeight = vmgr->getHeight(GetId(), x, y, z, maxSearchDist); // look from a bit higher pos to find the floor - } + vmapHeight = _mapCollisionData.GetStaticTree().getHeight(x, y, z, maxSearchDist); // look from a bit higher pos to find the floor // mapHeight set for any above raw ground Z or <= INVALID_HEIGHT // vmapheight set for any under Z value or <= INVALID_HEIGHT @@ -1204,12 +1199,11 @@ static inline bool IsInWMOInterior(uint32 mogpFlags) bool Map::GetAreaInfo(uint32 phaseMask, float x, float y, float z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const { float check_z = z; - VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr(); VMAP::AreaAndLiquidData vdata; VMAP::AreaAndLiquidData ddata; - bool hasVmapAreaInfo = vmgr->GetAreaAndLiquidData(GetId(), x, y, z, {}, vdata) && vdata.areaInfo.has_value(); - bool hasDynamicAreaInfo = _dynamicTree.GetAreaAndLiquidData(x, y, z, phaseMask, {}, ddata) && ddata.areaInfo.has_value(); + bool hasVmapAreaInfo = _mapCollisionData.GetStaticTree().GetAreaAndLiquidData(x, y, z, {}, vdata) && vdata.areaInfo.has_value(); + bool hasDynamicAreaInfo = _mapCollisionData.GetDynamicTree().GetAreaAndLiquidData(x, y, z, phaseMask, {}, ddata) && ddata.areaInfo.has_value(); auto useVmap = [&] { check_z = vdata.floorZ; groupId = vdata.areaInfo->groupId; adtId = vdata.areaInfo->adtId; rootId = vdata.areaInfo->rootId; flags = vdata.areaInfo->mogpFlags; }; auto useDyn = [&] { check_z = ddata.floorZ; groupId = ddata.areaInfo->groupId; adtId = ddata.areaInfo->adtId; rootId = ddata.areaInfo->rootId; flags = ddata.areaInfo->mogpFlags; }; if (hasVmapAreaInfo) @@ -1300,10 +1294,9 @@ LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z, LiquidData liquidData; liquidData.Status = LIQUID_MAP_NO_WATER; - VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr(); VMAP::AreaAndLiquidData vmapData; bool useGridLiquid = true; - if (vmgr->GetAreaAndLiquidData(GetId(), x, y, z, ReqLiquidType, vmapData) && vmapData.liquidInfo) + if (_mapCollisionData.GetStaticTree().GetAreaAndLiquidData(x, y, z, ReqLiquidType, vmapData) && vmapData.liquidInfo) { useGridLiquid = !vmapData.areaInfo || !IsInWMOInterior(vmapData.areaInfo->mogpFlags); LOG_DEBUG("maps", "GetLiquidStatus(): vmap liquid level: {} ground: {} type: {}", vmapData.liquidInfo->level, vmapData.floorZ, vmapData.liquidInfo->type); @@ -1383,11 +1376,10 @@ void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y { GridTerrainData* gmap = GetGridTerrainData(x, y); - VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr(); VMAP::AreaAndLiquidData vmapData; // VMAP::AreaAndLiquidData dynData; VMAP::AreaAndLiquidData* wmoData = nullptr; - vmgr->GetAreaAndLiquidData(GetId(), x, y, z, reqLiquidType, vmapData); + _mapCollisionData.GetStaticTree().GetAreaAndLiquidData(x, y, z, reqLiquidType, vmapData); // _dynamicTree.GetAreaAndLiquidData(x, y, z, phaseMask, reqLiquidType, dynData); uint32 gridAreaId = 0; @@ -1548,7 +1540,7 @@ bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, floa } } - if ((checks & LINEOFSIGHT_CHECK_VMAP) && !VMAP::VMapFactory::createOrGetVMapMgr()->isInLineOfSight(GetId(), x1, y1, z1, x2, y2, z2, ignoreFlags)) + if ((checks & LINEOFSIGHT_CHECK_VMAP) && !_mapCollisionData.GetStaticTree().isInLineOfSight(x1, y1, z1, x2, y2, z2, ignoreFlags)) { return false; } @@ -1561,7 +1553,7 @@ bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, floa ignoreFlags = VMAP::ModelIgnoreFlags::M2; } - if (!_dynamicTree.isInLineOfSight(x1, y1, z1, x2, y2, z2, phasemask, ignoreFlags)) + if (!_mapCollisionData.GetDynamicTree().isInLineOfSight(x1, y1, z1, x2, y2, z2, phasemask, ignoreFlags)) { return false; } @@ -1570,25 +1562,11 @@ bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, floa return true; } -bool Map::GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) -{ - G3D::Vector3 startPos(x1, y1, z1); - G3D::Vector3 dstPos(x2, y2, z2); - - G3D::Vector3 resultPos; - bool result = _dynamicTree.GetObjectHitPos(phasemask, startPos, dstPos, resultPos, modifyDist); - - rx = resultPos.x; - ry = resultPos.y; - rz = resultPos.z; - return result; -} - float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const { float h1, h2; h1 = GetHeight(x, y, z, vmap, maxSearchDist); - h2 = _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask); + h2 = _mapCollisionData.GetDynamicTree().getHeight(x, y, z, maxSearchDist, phasemask); return std::max(h1, h2); } @@ -3050,8 +3028,7 @@ bool Map::CheckCollisionAndGetValidCoords(WorldObject const* source, float start // Unit is not on the ground, check for potential collision via vmaps if (notOnGround) { - bool col = VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos(source->GetMapId(), - startX, startY, startZ + halfHeight, + bool col = _mapCollisionData.GetStaticTree().GetObjectHitPos(startX, startY, startZ + halfHeight, destX, destY, destZ + halfHeight, destX, destY, destZ, -CONTACT_DISTANCE); @@ -3065,7 +3042,7 @@ bool Map::CheckCollisionAndGetValidCoords(WorldObject const* source, float start } // check dynamic collision - bool col = source->GetMap()->GetObjectHitPos(source->GetPhaseMask(), + bool col = _mapCollisionData.GetDynamicTree().GetObjectHitPos(source->GetPhaseMask(), startX, startY, startZ + halfHeight, destX, destY, destZ + halfHeight, destX, destY, destZ, -CONTACT_DISTANCE); diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 035ba0dce..e6027a717 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -27,6 +27,7 @@ #include "GameObjectModel.h" #include "GridDefines.h" #include "GridRefMgr.h" +#include "MapCollisionData.h" #include "MapGridManager.h" #include "MapRefMgr.h" #include "ObjectDefines.h" @@ -230,8 +231,6 @@ public: [[nodiscard]] Map const* GetParent() const { return m_parentMap; } - // pussywizard: movemaps, mmaps - [[nodiscard]] std::shared_mutex& GetMMapLock() const { return *(const_cast(&MMapLock)); } // pussywizard: std::unordered_set i_objectsForDelayedVisibility; void HandleDelayedVisibility(); @@ -389,15 +388,14 @@ public: bool CanReachPositionAndGetValidCoords(WorldObject const* source, float &destX, float &destY, float &destZ, bool failOnCollision = true, bool failOnSlopes = true) const; bool CanReachPositionAndGetValidCoords(WorldObject const* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision = true, bool failOnSlopes = true) const; bool CheckCollisionAndGetValidCoords(WorldObject const* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision = true) const; - void Balance() { _dynamicTree.balance(); } - void RemoveGameObjectModel(const GameObjectModel& model) { _dynamicTree.remove(model); } - void InsertGameObjectModel(const GameObjectModel& model) { _dynamicTree.insert(model); } - [[nodiscard]] bool ContainsGameObjectModel(const GameObjectModel& model) const { return _dynamicTree.contains(model);} - [[nodiscard]] DynamicMapTree const& GetDynamicMapTree() const { return _dynamicTree; } - bool GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist); + void Balance() { _mapCollisionData.GetDynamicTree().balance(); } + void RemoveGameObjectModel(const GameObjectModel& model) { _mapCollisionData.GetDynamicTree().remove(model); } + void InsertGameObjectModel(const GameObjectModel& model) { _mapCollisionData.GetDynamicTree().insert(model); } + [[nodiscard]] bool ContainsGameObjectModel(const GameObjectModel& model) const { return _mapCollisionData.GetDynamicTree().contains(model);} + [[nodiscard]] DynamicMapTree const& GetDynamicMapTree() const { return _mapCollisionData.GetDynamicTree(); } [[nodiscard]] float GetGameObjectFloor(uint32 phasemask, float x, float y, float z, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { - return _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask); + return _mapCollisionData.GetDynamicTree().getHeight(x, y, z, maxSearchDist, phasemask); } /* RESPAWN TIMES @@ -524,6 +522,9 @@ public: return 0; }; + MapCollisionData& GetMapCollisionData() { return _mapCollisionData; } + MapCollisionData const& GetMapCollisionData() const { return _mapCollisionData; } + private: template void InitializeObject(T* obj); @@ -551,15 +552,14 @@ protected: void AddToGrid(T* object, Cell const& cell); std::mutex Lock; - std::shared_mutex MMapLock; MapGridManager _mapGridManager; MapEntry const* i_mapEntry; + MapCollisionData _mapCollisionData; uint8 i_spawnMode; uint32 i_InstanceId; uint32 m_unloadTimer; float m_VisibleDistance; - DynamicMapTree _dynamicTree; time_t _instanceResetPeriod; // pussywizard MapRefMgr m_mapRefMgr; diff --git a/src/server/game/Maps/MapCollisionData.cpp b/src/server/game/Maps/MapCollisionData.cpp new file mode 100644 index 000000000..bdce5edb2 --- /dev/null +++ b/src/server/game/Maps/MapCollisionData.cpp @@ -0,0 +1,183 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "DBCStores.h" +#include "DetourNavMeshQuery.h" +#include "DisableMgr.h" +#include "MapCollisionData.h" +#include "MapTree.h" +#include "ModelInstance.h" +#include "VMapFactory.h" +#include "VMapMgr2.h" +#include "World.h" +#include "WorldModel.h" + +#include + +MapCollisionData::MapCollisionData(Map const& map, Map const* parentMap) : + _map(map), _staticVMapData(map.GetId()) +{ + if (parentMap) + { + // If we have a parent map, point our static tree and mmap nav mesh to the parent maps + _staticVMapData._staticTree = parentMap->GetMapCollisionData().GetStaticTreeSharedPtr(); + _mmapData._navMesh = parentMap->GetMapCollisionData().GetMMapNavMeshSharedPtr(); + } + else + { + // If we are a base map create a new static tree and mmap nav mesh + std::string const mapFileName = VMAP::VMapMgr2::getMapFileName(map.GetId()); + std::shared_ptr newTree = std::make_shared(map.GetId(), (sWorld->GetDataPath() + "vmaps")); + if (newTree->InitMap(mapFileName)) + _staticVMapData._staticTree = newTree; + + _mmapData._navMesh = MMAP::MMapMgr::LoadNavMesh(map.GetId()); + } +} + +int MapCollisionData::LoadVMapTile(uint32 tileX, uint32 tileY) +{ + if (!VMAP::VMapFactory::createOrGetVMapMgr()->isMapLoadingEnabled() || !_staticVMapData._staticTree) + return VMAP::VMAP_LOAD_RESULT_IGNORED; + + if (!_staticVMapData._staticTree->LoadMapTile(tileX, tileY)) + return VMAP::VMAP_LOAD_RESULT_ERROR; + + return VMAP::VMAP_LOAD_RESULT_OK; +} + +int MapCollisionData::LoadMMapTile(uint32 tileX, uint32 tileY) +{ + if (!DisableMgr::IsPathfindingEnabled(&_map) || !_mmapData._navMesh) + return MMAP::MMAP_LOAD_RESULT_IGNORED; + + return MMAP::MMapMgr::LoadTile(_mmapData._navMesh.get(), _map.GetId(), tileX, tileY); +} + +bool StaticVMapCollisionData::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, VMAP::ModelIgnoreFlags ignoreFlags) const +{ +#if defined(ENABLE_VMAP_CHECKS) + if (!sWorld->getBoolConfig(CONFIG_VMAP_ENABLE_LOS) || DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_LOS)) + return true; +#endif + + if (!_staticTree) + return true; + + G3D::Vector3 const pos1 = VMAP::VMapMgr2::convertPositionToInternalRep(x1, y1, z1); + G3D::Vector3 const pos2 = VMAP::VMapMgr2::convertPositionToInternalRep(x2, y2, z2); + if (pos1 != pos2) + return _staticTree->isInLineOfSight(pos1, pos2, ignoreFlags); + + return true; +} + +bool StaticVMapCollisionData::GetObjectHitPos(float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const +{ +#if defined(ENABLE_VMAP_CHECKS) + if (sWorld->getBoolConfig(CONFIG_VMAP_ENABLE_LOS) && !DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_LOS)) +#endif + { + if (_staticTree) + { + G3D::Vector3 const pos1 = VMAP::VMapMgr2::convertPositionToInternalRep(x1, y1, z1); + G3D::Vector3 const pos2 = VMAP::VMapMgr2::convertPositionToInternalRep(x2, y2, z2); + G3D::Vector3 resultPos; + bool result = _staticTree->GetObjectHitPos(pos1, pos2, resultPos, modifyDist); + resultPos = VMAP::VMapMgr2::convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z); + rx = resultPos.x; + ry = resultPos.y; + rz = resultPos.z; + return result; + } + } + + rx = x2; + ry = y2; + rz = z2; + + return false; +} + +float StaticVMapCollisionData::getHeight(float x, float y, float z, float maxSearchDist) const +{ +#if defined(ENABLE_VMAP_CHECKS) + if (sWorld->getBoolConfig(CONFIG_VMAP_ENABLE_HEIGHT) && !DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_HEIGHT)) +#endif + { + if (_staticTree) + { + G3D::Vector3 const pos = VMAP::VMapMgr2::convertPositionToInternalRep(x, y, z); + float height = _staticTree->getHeight(pos, maxSearchDist); + if (height >= G3D::finf()) + return VMAP_INVALID_HEIGHT_VALUE; // No height + + return height; + } + } + + return VMAP_INVALID_HEIGHT_VALUE; +} + +bool StaticVMapCollisionData::GetAreaAndLiquidData(float x, float y, float z, Optional reqLiquidType, VMAP::AreaAndLiquidData& data) const +{ + if (_staticTree) + { + VMAP::LocationInfo info; + G3D::Vector3 const pos = VMAP::VMapMgr2::convertPositionToInternalRep(x, y, z); + if (_staticTree->GetLocationInfo(pos, info)) + { + data.floorZ = info.ground_Z; + if (!DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_LIQUIDSTATUS)) + { + uint32 liquidType = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc + float liquidLevel; + if (!reqLiquidType || (GetLiquidFlags(liquidType) & *reqLiquidType)) + if (info.hitInstance->GetLiquidLevel(pos, info, liquidLevel)) + data.liquidInfo.emplace(liquidType, liquidLevel); + } + + if (!DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_AREAFLAG)) + data.areaInfo.emplace(info.hitModel->GetWmoID(), info.hitInstance->adtId, info.rootId, info.hitModel->GetMogpFlags(), info.hitInstance->ID); + return true; + } + } + + return false; +} + +bool DynamicVMapCollisionData::GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const +{ + G3D::Vector3 startPos(x1, y1, z1); + G3D::Vector3 dstPos(x2, y2, z2); + + G3D::Vector3 resultPos; + bool result = DynamicMapTree::GetObjectHitPos(phasemask, startPos, dstPos, resultPos, modifyDist); + + rx = resultPos.x; + ry = resultPos.y; + rz = resultPos.z; + return result; +} + +dtNavMeshQuery const* MMapData::GetNavMeshQuery() +{ + if (_navMesh && !_navMeshQuery) + _navMeshQuery = MMAP::MMapMgr::CreateNavMeshQuery(_navMesh.get()); + + return _navMeshQuery.get(); +} diff --git a/src/server/game/Maps/MapCollisionData.h b/src/server/game/Maps/MapCollisionData.h new file mode 100644 index 000000000..bb8a3cdac --- /dev/null +++ b/src/server/game/Maps/MapCollisionData.h @@ -0,0 +1,100 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef MAP_COLLISION_DATA_H +#define MAP_COLLISION_DATA_H + +#include "DynamicTree.h" +#include "MapTree.h" +#include "MMapMgr.h" +#include "IVMapMgr.h" + +class Map; + +namespace VMAP +{ + enum class ModelIgnoreFlags : uint32; +} + +// Simple wrappers for vmap static & dynamic trees to enable clear separation of use for increased readability +class StaticVMapCollisionData +{ + friend class MapCollisionData; + +public: + StaticVMapCollisionData(uint32 mapId) : _mapId(mapId) {} + + bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, VMAP::ModelIgnoreFlags ignoreFlags) const; + bool GetObjectHitPos(float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const; + float getHeight(float x, float y, float z, float maxSearchDist) const; + bool GetAreaAndLiquidData(float x, float y, float z, Optional reqLiquidType, VMAP::AreaAndLiquidData& data) const; +protected: + // _staticTree is a shared_ptr as it will point to a parent maps static tree (if exists) to save on memory + std::shared_ptr _staticTree; + uint32 _mapId; +}; + +class DynamicVMapCollisionData : public DynamicMapTree +{ +public: + bool GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const; +}; + +class MMapData +{ + friend class MapCollisionData; + +public: + dtNavMesh const* GetNavMesh() const { return _navMesh.get(); } + dtNavMeshQuery const* GetNavMeshQuery(); + +protected: + // _navMesh is a shared_ptr as it will point to a parent maps nav mesh (if exists) to save on memory + std::shared_ptr _navMesh; + // navMeshQuery is not thread safe and needs its own instance per map + MMAP::ManagedNavMeshQuery _navMeshQuery; +}; + +// Map collision data holders (dynamic&static vmap, mmaps) +class MapCollisionData +{ +public: + MapCollisionData(Map const& map, Map const* parentMap); + ~MapCollisionData() = default; + + int LoadVMapTile(uint32 tileX, uint32 tileY); + int LoadMMapTile(uint32 tileX, uint32 tileY); + + DynamicVMapCollisionData& GetDynamicTree() { return _dynamicVMapData; } + DynamicVMapCollisionData const& GetDynamicTree() const { return _dynamicVMapData; } + StaticVMapCollisionData& GetStaticTree() { return _staticVMapData; } + StaticVMapCollisionData const& GetStaticTree() const { return _staticVMapData; } + MMapData& GetMMapData() { return _mmapData; } + MMapData const& GetMMapData() const { return _mmapData; } + + std::shared_ptr const GetStaticTreeSharedPtr() const { return _staticVMapData._staticTree; } + std::shared_ptr const GetMMapNavMeshSharedPtr() const { return _mmapData._navMesh; } + +private: + Map const& _map; + + DynamicVMapCollisionData _dynamicVMapData; + StaticVMapCollisionData _staticVMapData; + MMapData _mmapData; +}; + +#endif diff --git a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp index 5ddbc2c45..607428cb6 100644 --- a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp @@ -20,7 +20,6 @@ #include "DetourCommon.h" #include "Geometry.h" #include "Log.h" -#include "MMapFactory.h" #include "MMapMgr.h" #include "Map.h" #include "Metric.h" @@ -34,12 +33,10 @@ PathGenerator::PathGenerator(WorldObject const* owner) : { memset(_pathPolyRefs, 0, sizeof(_pathPolyRefs)); - uint32 mapId = _source->GetMapId(); //if (sDisableMgr->IsPathfindingEnabled(_sourceUnit->FindMap())) { - MMAP::MMapMgr* mmap = MMAP::MMapFactory::createOrGetMMapMgr(); - _navMesh = mmap->GetNavMesh(mapId); - _navMeshQuery = mmap->GetNavMeshQuery(mapId, _source->GetInstanceId()); + _navMesh = _source->GetMap()->GetMapCollisionData().GetMMapData().GetNavMesh(); + _navMeshQuery = _source->GetMap()->GetMapCollisionData().GetMMapData().GetNavMeshQuery(); } CreateFilter(); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 4283d5a13..4830f5387 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1414,7 +1414,6 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici { float distance = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); Map* map = m_caster->GetMap(); - uint32 mapid = m_caster->GetMapId(); uint32 phasemask = m_caster->GetPhaseMask(); float collisionHeight = m_caster->GetCollisionHeight(); float destz = 0.0f, startx = 0.0f, starty = 0.0f, startz = 0.0f, starto = 0.0f; @@ -1556,9 +1555,9 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici //LOG_ERROR("spells", "total path > than distance in 3D , need to move back a bit for save distance, total path = {}, overdistance = {}", totalpath, overdistance); } - bool col = VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos(mapid, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f); + bool col = m_caster->GetMap()->GetMapCollisionData().GetStaticTree().GetObjectHitPos(prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f); // check dynamic collision - bool dcol = m_caster->GetMap()->GetObjectHitPos(phasemask, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f); + bool dcol = m_caster->GetMap()->GetMapCollisionData().GetDynamicTree().GetObjectHitPos(phasemask, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f); // collision occured if (col || dcol || (overdistance > 0.0f && !map->IsInWater(phasemask, tstX, tstY, ground, collisionHeight)) || (fabs(prevZ - tstZ) > maxtravelDistZ && (tstZ > prevZ))) @@ -1620,9 +1619,9 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici else { float z = pos.GetPositionZ(); - bool col = VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos(mapid, pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f); + bool col = m_caster->GetMap()->GetMapCollisionData().GetStaticTree().GetObjectHitPos(pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f); // check dynamic collision - bool dcol = m_caster->GetMap()->GetObjectHitPos(phasemask, pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f); + bool dcol = m_caster->GetMap()->GetMapCollisionData().GetDynamicTree().GetObjectHitPos(phasemask, pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f); // collision occured if (col || dcol) diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index b11b77c60..2f87bbb00 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -60,7 +60,6 @@ #include "LootItemStorage.h" #include "LootMgr.h" #include "M2Stores.h" -#include "MMapFactory.h" #include "MapMgr.h" #include "Metric.h" #include "MotdMgr.h" @@ -139,7 +138,6 @@ World::~World() delete command; VMAP::VMapFactory::clear(); - MMAP::MMapFactory::clear(); } std::unique_ptr& getWorldInstance() @@ -284,9 +282,9 @@ void World::LoadConfigSettings(bool reload) } bool const enableIndoor = getBoolConfig(CONFIG_VMAP_INDOOR_CHECK); - bool const enableLOS = sConfigMgr->GetOption("vmap.enableLOS", true); + bool const enableLOS = getBoolConfig(CONFIG_VMAP_ENABLE_LOS); bool const enablePetLOS = getBoolConfig(CONFIG_PET_LOS); - bool const enableHeight = sConfigMgr->GetOption("vmap.enableHeight", true); + bool const enableHeight = getBoolConfig(CONFIG_VMAP_ENABLE_HEIGHT); if (!enableHeight) LOG_ERROR("server.loading", "VMap height checking disabled! Creatures movements and other various things WILL be broken! Expect no support."); @@ -294,8 +292,6 @@ void World::LoadConfigSettings(bool reload) VMAP::VMapFactory::createOrGetVMapMgr()->setEnableHeightCalc(enableHeight); LOG_INFO("server.loading", "WORLD: VMap support included. LineOfSight:{}, getHeight:{}, indoorCheck:{} PetLOS:{}", enableLOS, enableHeight, enableIndoor, enablePetLOS); - MMAP::MMapFactory::InitializeDisabledMaps(); - // call ScriptMgr if we're reloading the configuration sScriptMgr->OnAfterConfigLoad(reload); } @@ -392,17 +388,6 @@ void World::SetInitialWorldSettings() // Load IP Location Database sIPLocation->Load(); - std::vector mapIds; - for (auto const& map : sMapStore) - { - mapIds.emplace_back(map->MapID); - } - - vmmgr2->InitializeThreadUnsafe(mapIds); - - MMAP::MMapMgr* mmmgr = MMAP::MMapFactory::createOrGetMMapMgr(); - mmmgr->InitializeThreadUnsafe(mapIds); - LOG_INFO("server.loading", "Loading Game Graveyard..."); sGraveyard->LoadGraveyardFromDB(); diff --git a/src/server/game/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp index d754951d3..131f734ad 100644 --- a/src/server/game/World/WorldConfig.cpp +++ b/src/server/game/World/WorldConfig.cpp @@ -523,6 +523,8 @@ void WorldConfig::BuildConfigCache() SetConfigValue(CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT, "Respawn.DynamicMinimumGameObject", 10); SetConfigValue(CONFIG_VMAP_INDOOR_CHECK, "vmap.enableIndoorCheck", true); + SetConfigValue(CONFIG_VMAP_ENABLE_LOS, "vmap.enableLOS", true); + SetConfigValue(CONFIG_VMAP_ENABLE_HEIGHT, "vmap.enableHeight", true); SetConfigValue(CONFIG_PET_LOS, "vmap.petLOS", true); SetConfigValue(CONFIG_VMAP_BLIZZLIKE_PVP_LOS, "vmap.BlizzlikePvPLOS", true); diff --git a/src/server/game/World/WorldConfig.h b/src/server/game/World/WorldConfig.h index 6356e4111..9626be8bf 100644 --- a/src/server/game/World/WorldConfig.h +++ b/src/server/game/World/WorldConfig.h @@ -80,6 +80,8 @@ enum ServerConfigs CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN, CONFIG_CREATURE_REPOSITION_AGAINST_NPCS, CONFIG_VMAP_INDOOR_CHECK, + CONFIG_VMAP_ENABLE_LOS, + CONFIG_VMAP_ENABLE_HEIGHT, CONFIG_PET_LOS, CONFIG_START_CUSTOM_SPELLS, CONFIG_START_ALL_EXPLORED, diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index bfa883c25..bc7307086 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -36,7 +36,6 @@ #include "Language.h" #include "MapMgr.h" #include "MiscPackets.h" -#include "MMapFactory.h" #include "MovementGenerator.h" #include "ObjectAccessor.h" #include "Pet.h" @@ -630,7 +629,7 @@ public: uint32 haveMap = GridTerrainLoader::ExistMap(object->GetMapId(), cell.GridX(), cell.GridY()) ? 1 : 0; uint32 haveVMap = GridTerrainLoader::ExistVMap(object->GetMapId(), cell.GridX(), cell.GridY()) ? 1 : 0; - uint32 haveMMAP = MMAP::MMapFactory::createOrGetMMapMgr()->GetNavMesh(handler->GetSession()->GetPlayer()->GetMapId()) ? 1 : 0; + uint32 haveMMAP = handler->GetSession()->GetPlayer()->GetMap()->GetMapCollisionData().GetMMapData().GetNavMesh() ? 1 : 0; if (haveVMap) { diff --git a/src/server/scripts/Commands/cs_mmaps.cpp b/src/server/scripts/Commands/cs_mmaps.cpp index f41c9cb1e..91a164a11 100644 --- a/src/server/scripts/Commands/cs_mmaps.cpp +++ b/src/server/scripts/Commands/cs_mmaps.cpp @@ -28,7 +28,6 @@ #include "CommandScript.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" -#include "MMapFactory.h" #include "MMapMgr.h" #include "Map.h" #include "PathGenerator.h" @@ -62,7 +61,8 @@ public: static bool HandleMmapPathCommand(ChatHandler* handler, Optional param) { - if (!MMAP::MMapFactory::createOrGetMMapMgr()->GetNavMesh(handler->GetSession()->GetPlayer()->GetMapId())) + Map* map = handler->GetSession()->GetPlayer()->GetMap(); + if (!map->GetMapCollisionData().GetMMapData().GetNavMesh()) { handler->PSendSysMessage("NavMesh not loaded for current map."); return true; @@ -173,9 +173,11 @@ public: handler->PSendSysMessage("gridloc [{}, {}]", gridCoord.x_coord, gridCoord.y_coord); + Map* map = handler->GetSession()->GetPlayer()->GetMap(); + // calculate navmesh tile location - dtNavMesh const* navmesh = MMAP::MMapFactory::createOrGetMMapMgr()->GetNavMesh(handler->GetSession()->GetPlayer()->GetMapId()); - dtNavMeshQuery const* navmeshquery = MMAP::MMapFactory::createOrGetMMapMgr()->GetNavMeshQuery(handler->GetSession()->GetPlayer()->GetMapId(), player->GetInstanceId()); + dtNavMesh const* navmesh = map->GetMapCollisionData().GetMMapData().GetNavMesh(); + dtNavMeshQuery const* navmeshquery = map->GetMapCollisionData().GetMMapData().GetNavMeshQuery(); if (!navmesh || !navmeshquery) { handler->PSendSysMessage("NavMesh not loaded for current map."); @@ -225,9 +227,9 @@ public: static bool HandleMmapLoadedTilesCommand(ChatHandler* handler) { - uint32 mapid = handler->GetSession()->GetPlayer()->GetMapId(); - dtNavMesh const* navmesh = MMAP::MMapFactory::createOrGetMMapMgr()->GetNavMesh(mapid); - dtNavMeshQuery const* navmeshquery = MMAP::MMapFactory::createOrGetMMapMgr()->GetNavMeshQuery(mapid, handler->GetSession()->GetPlayer()->GetInstanceId()); + Map* map = handler->GetSession()->GetPlayer()->GetMap(); + dtNavMesh const* navmesh = map->GetMapCollisionData().GetMMapData().GetNavMesh(); + dtNavMeshQuery const* navmeshquery = map->GetMapCollisionData().GetMMapData().GetNavMeshQuery(); if (!navmesh || !navmeshquery) { handler->PSendSysMessage("NavMesh not loaded for current map."); @@ -250,12 +252,14 @@ public: static bool HandleMmapStatsCommand(ChatHandler* handler) { - handler->PSendSysMessage("mmap stats:"); - //handler->PSendSysMessage(" global mmap pathfinding is {}abled", sDisableMgr->IsPathfindingEnabled(mapId) ? "en" : "dis"); - MMAP::MMapMgr* manager = MMAP::MMapFactory::createOrGetMMapMgr(); - handler->PSendSysMessage(" {} maps loaded with {} tiles overall", manager->getLoadedMapsCount(), manager->getLoadedTilesCount()); + Map* map = handler->GetSession()->GetPlayer()->GetMap(); - dtNavMesh const* navmesh = manager->GetNavMesh(handler->GetSession()->GetPlayer()->GetMapId()); + //handler->PSendSysMessage("mmap stats:"); + //handler->PSendSysMessage(" global mmap pathfinding is {}abled", sDisableMgr->IsPathfindingEnabled(mapId) ? "en" : "dis"); + //MMAP::MMapMgr* manager = MMAP::MMapFactory::createOrGetMMapMgr(); + //handler->PSendSysMessage(" {} maps loaded with {} tiles overall", manager->getLoadedMapsCount(), manager->getLoadedTilesCount()); + + dtNavMesh const* navmesh = map->GetMapCollisionData().GetMMapData().GetNavMesh(); if (!navmesh) { handler->PSendSysMessage("NavMesh not loaded for current map."); diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp index d8fc79846..414d3467e 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp @@ -434,8 +434,7 @@ class spell_entropius_black_hole_effect : public SpellScript float targetX = baseX + maxDist * cos(angle); float targetY = baseY + maxDist * sin(angle); float hitX, hitY, hitZ; - if (VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos( - unit->GetMapId(), + if (unit->GetMap()->GetMapCollisionData().GetStaticTree().GetObjectHitPos( baseX, baseY, z, targetX, targetY, z, hitX, hitY, hitZ, diff --git a/src/tools/mmaps_generator/TerrainBuilder.cpp b/src/tools/mmaps_generator/TerrainBuilder.cpp index 6fb539b0f..c85a7933e 100644 --- a/src/tools/mmaps_generator/TerrainBuilder.cpp +++ b/src/tools/mmaps_generator/TerrainBuilder.cpp @@ -676,24 +676,20 @@ namespace MMAP /**************************************************************************/ bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData) { - IVMapMgr* vmapMgr = new VMapMgr2(); - int result = vmapMgr->loadMap(m_vmapsPath.c_str(), mapID, tileX, tileY); + std::string const mapFileName = VMapMgr2::getMapFileName(mapID); + std::unique_ptr staticTree = std::make_unique(mapID, m_vmapsPath); + if (!staticTree->InitMap(mapFileName)) + return false; + + staticTree->LoadMapTile(tileX, tileY); + bool retval = false; do { - if (result == VMAP_LOAD_RESULT_ERROR) - break; - - InstanceTreeMap instanceTrees; - ((VMapMgr2*)vmapMgr)->GetInstanceMapTree(instanceTrees); - - if (!instanceTrees[mapID]) - break; - ModelInstance* models = nullptr; uint32 count = 0; - instanceTrees[mapID]->GetModelInstances(models, count); + staticTree->GetModelInstances(models, count); if (!models) break; @@ -829,9 +825,6 @@ namespace MMAP } } while (false); - vmapMgr->unloadMap(mapID, tileX, tileY); - delete vmapMgr; - return retval; } From 97571744604da4fa6095ef50591882cc7c1dfeb8 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:30:22 -0300 Subject: [PATCH 056/132] feat(Core/Spawns): introduce SpawnData base struct and spawn group infrastructure (#25152) Co-authored-by: r00ty-tc Co-authored-by: Claude Opus 4.6 --- .../game/Entities/Creature/CreatureData.h | 14 +--- .../game/Entities/GameObject/GameObjectData.h | 24 ++---- src/server/game/Globals/ObjectMgr.cpp | 20 +++++ src/server/game/Globals/ObjectMgr.h | 10 +++ src/server/game/Maps/SpawnData.h | 79 +++++++++++++++++++ 5 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 src/server/game/Maps/SpawnData.h diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 1ba2226a7..a79d7f8f0 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -22,6 +22,7 @@ #include "DatabaseEnv.h" #include "ItemTemplate.h" #include "LootMgr.h" +#include "SpawnData.h" #include "Unit.h" #include @@ -365,32 +366,23 @@ typedef std::unordered_map EquipmentInfoContainerInternal; typedef std::unordered_map EquipmentInfoContainer; // from `creature` table -struct CreatureData +struct CreatureData : public SpawnData { - CreatureData() = default; + CreatureData() : SpawnData(SPAWN_TYPE_CREATURE) {} uint32 id1{0}; // entry in creature_template uint32 id2{0}; // entry in creature_template uint32 id3{0}; // entry in creature_template - uint16 mapid{0}; - uint32 phaseMask{0}; uint32 displayid{0}; int8 equipmentId{0}; - float posX{0.0f}; - float posY{0.0f}; - float posZ{0.0f}; - float orientation{0.0f}; uint32 spawntimesecs{0}; float wander_distance{0.0f}; uint32 currentwaypoint{0}; uint32 curhealth{0}; uint32 curmana{0}; uint8 movementType{0}; - uint8 spawnMask{0}; uint32 npcflag{0}; uint32 unit_flags{0}; // enum UnitFlags mask values uint32 dynamicflags{0}; - uint32 ScriptId; - bool dbData{true}; }; struct CreatureModelInfo diff --git a/src/server/game/Entities/GameObject/GameObjectData.h b/src/server/game/Entities/GameObject/GameObjectData.h index 39c1a6e28..ab03b0098 100644 --- a/src/server/game/Entities/GameObject/GameObjectData.h +++ b/src/server/game/Entities/GameObject/GameObjectData.h @@ -19,6 +19,7 @@ #define GAMEOBJECTDATA_H #include "SharedDefines.h" +#include "SpawnData.h" #include #include #include @@ -710,24 +711,15 @@ enum GOState }; // from `gameobject` -struct GameObjectData +struct GameObjectData : public SpawnData { - explicit GameObjectData() = default; - uint32 id{ 0 }; // entry in gamobject_template - uint16 mapid{ 0 }; - uint32 phaseMask{ 0 }; - float posX{ 0.0f }; - float posY{ 0.0f }; - float posZ{ 0.0f }; - float orientation{ 0.0f }; + GameObjectData() : SpawnData(SPAWN_TYPE_GAMEOBJECT) {} + uint32 id{0}; // entry in gameobject_template G3D::Quat rotation; - int32 spawntimesecs{ 0 }; - uint32 ScriptId; - uint32 animprogress{ 0 }; - GOState go_state{ GO_STATE_ACTIVE }; - uint8 spawnMask{ 0 }; - uint8 artKit{ 0 }; - bool dbData{ true }; + int32 spawntimesecs{0}; + uint32 animprogress{0}; + GOState go_state{GO_STATE_ACTIVE}; + uint8 artKit{0}; }; #endif // GameObjectData_h__ diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 5d9ca3dc8..f1e100bba 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -310,6 +310,9 @@ ObjectMgr::ObjectMgr(): { _playerClassInfo[i] = nullptr; } + + // Initialize default spawn group + _spawnGroupDataStore[0] = {0, "Default Group", 0, SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM)}; } ObjectMgr::~ObjectMgr() @@ -2400,6 +2403,7 @@ void ObjectMgr::LoadCreatures() data.unit_flags = fields[21].Get(); data.dynamicflags = fields[22].Get(); data.ScriptId = GetScriptId(fields[23].Get()); + data.spawnGroupId = 0; if (!data.ScriptId) data.ScriptId = cInfo->ScriptID; @@ -2578,6 +2582,7 @@ CreatureData const* ObjectMgr::LoadCreatureDataFromDB(ObjectGuid::LowType spawnI creatureData.unit_flags = fields[19].Get(); creatureData.dynamicflags = fields[20].Get(); creatureData.ScriptId = GetScriptId(fields[21].Get()); + creatureData.spawnGroupId = 0; if (!creatureData.ScriptId) creatureData.ScriptId = cInfo->ScriptID; @@ -2760,6 +2765,7 @@ ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, float x, fl data.phaseMask = PHASEMASK_NORMAL; data.artKit = goinfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT ? 21 : 0; data.dbData = false; + data.spawnGroupId = 0; AddGameobjectToGrid(spawnId, &data); @@ -2915,6 +2921,7 @@ void ObjectMgr::LoadGameobjects() data.rotation.w = fields[10].Get(); data.spawntimesecs = fields[11].Get(); data.ScriptId = GetScriptId(fields[18].Get()); + data.spawnGroupId = 0; if (!data.ScriptId) data.ScriptId = gInfo->ScriptId; @@ -8683,6 +8690,19 @@ void ObjectMgr::DeleteGOData(ObjectGuid::LowType guid) _gameObjectDataStore.erase(guid); } +SpawnData const* ObjectMgr::GetSpawnData(SpawnObjectType type, ObjectGuid::LowType spawnId) const +{ + switch (type) + { + case SPAWN_TYPE_CREATURE: + return GetCreatureData(spawnId); + case SPAWN_TYPE_GAMEOBJECT: + return GetGameObjectData(spawnId); + default: + return nullptr; + } +} + void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, std::string const& table, bool starter, bool go) { uint32 oldMSTime = getMSTime(); diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index cbf2ff4e8..39bdbe59f 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -506,6 +506,7 @@ struct AcoreString typedef std::map LinkedRespawnContainer; typedef std::unordered_map CreatureDataContainer; typedef std::unordered_map GameObjectDataContainer; +typedef std::unordered_map SpawnGroupDataContainer; typedef std::map > TempSummonDataContainer; typedef std::map > GameObjectSummonDataContainer; typedef std::unordered_map CreatureLocaleContainer; @@ -1269,6 +1270,14 @@ public: if (itr == _gameObjectDataStore.end()) return nullptr; return &itr->second; } + [[nodiscard]] SpawnData const* GetSpawnData(SpawnObjectType type, ObjectGuid::LowType spawnId) const; + + [[nodiscard]] SpawnGroupTemplateData const* GetSpawnGroupData(uint32 groupId) const + { + auto itr = _spawnGroupDataStore.find(groupId); + return itr != _spawnGroupDataStore.end() ? &itr->second : nullptr; + } + [[nodiscard]] CreatureLocale const* GetCreatureLocale(uint32 entry) const { CreatureLocaleContainer::const_iterator itr = _creatureLocaleStore.find(entry); @@ -1643,6 +1652,7 @@ private: LinkedRespawnContainer _linkedRespawnStore; CreatureLocaleContainer _creatureLocaleStore; GameObjectDataContainer _gameObjectDataStore; + SpawnGroupDataContainer _spawnGroupDataStore; GameObjectLocaleContainer _gameObjectLocaleStore; GameObjectTemplateContainer _gameObjectTemplateStore; GameObjectTemplateAddonContainer _gameObjectTemplateAddonStore; diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h new file mode 100644 index 000000000..b1136203d --- /dev/null +++ b/src/server/game/Maps/SpawnData.h @@ -0,0 +1,79 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_SPAWNDATA_H +#define AZEROTHCORE_SPAWNDATA_H + +#include "Define.h" +#include + +enum SpawnObjectType : uint8 +{ + SPAWN_TYPE_CREATURE = 0, + SPAWN_TYPE_GAMEOBJECT = 1, + + SPAWN_TYPE_MAX +}; + +enum SpawnObjectTypeMask : uint8 +{ + SPAWN_TYPEMASK_CREATURE = (1 << SPAWN_TYPE_CREATURE), + SPAWN_TYPEMASK_GAMEOBJECT = (1 << SPAWN_TYPE_GAMEOBJECT), + + SPAWN_TYPEMASK_ALL = SPAWN_TYPEMASK_CREATURE | SPAWN_TYPEMASK_GAMEOBJECT +}; + +enum SpawnGroupFlags : uint32 +{ + SPAWNGROUP_FLAG_NONE = 0x00, + SPAWNGROUP_FLAG_SYSTEM = 0x01, + SPAWNGROUP_FLAG_MANUAL_SPAWN = 0x04, + SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE = 0x08, + SPAWNGROUP_FLAG_ESCORTQUESTNPC = 0x10, + + SPAWNGROUP_FLAG_ALL = SPAWNGROUP_FLAG_SYSTEM | + SPAWNGROUP_FLAG_MANUAL_SPAWN | SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE | + SPAWNGROUP_FLAG_ESCORTQUESTNPC +}; + +struct SpawnGroupTemplateData +{ + uint32 groupId; + std::string name; + uint16 mapid; + SpawnGroupFlags flags; +}; + +struct SpawnData +{ + SpawnObjectType const type; + uint16 mapid{0}; + uint32 phaseMask{0}; + float posX{0.0f}; + float posY{0.0f}; + float posZ{0.0f}; + float orientation{0.0f}; + uint8 spawnMask{0}; + uint32 ScriptId{0}; + bool dbData{true}; + uint32 spawnGroupId{0}; + +protected: + SpawnData(SpawnObjectType t) : type(t) {} +}; + +#endif // AZEROTHCORE_SPAWNDATA_H From 757f6127ab4f848276f1d3b5b98a28e2bd2aa970 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 22 Mar 2026 23:55:41 -0500 Subject: [PATCH 057/132] fix(Core/Tests): Add missing GetDataPath mock to combat tests (#25198) Co-authored-by: blinkysc --- src/test/server/game/Combat/CombatManagerTest.cpp | 2 ++ src/test/server/game/Combat/ThreatManagerTest.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/test/server/game/Combat/CombatManagerTest.cpp b/src/test/server/game/Combat/CombatManagerTest.cpp index 537d68665..f060a83d6 100644 --- a/src/test/server/game/Combat/CombatManagerTest.cpp +++ b/src/test/server/game/Combat/CombatManagerTest.cpp @@ -42,6 +42,8 @@ protected: ON_CALL(*_worldMock, getIntConfig(_)).WillByDefault(Return(0)); ON_CALL(*_worldMock, getFloatConfig(_)).WillByDefault(Return(1.0f)); ON_CALL(*_worldMock, getBoolConfig(_)).WillByDefault(Return(false)); + static std::string emptyString; + ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString)); sWorld.reset(_worldMock); diff --git a/src/test/server/game/Combat/ThreatManagerTest.cpp b/src/test/server/game/Combat/ThreatManagerTest.cpp index 69a708340..df29e0495 100644 --- a/src/test/server/game/Combat/ThreatManagerTest.cpp +++ b/src/test/server/game/Combat/ThreatManagerTest.cpp @@ -42,6 +42,8 @@ protected: ON_CALL(*_worldMock, getIntConfig(_)).WillByDefault(Return(0)); ON_CALL(*_worldMock, getFloatConfig(_)).WillByDefault(Return(1.0f)); ON_CALL(*_worldMock, getBoolConfig(_)).WillByDefault(Return(false)); + static std::string emptyString; + ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString)); sWorld.reset(_worldMock); From cbc4301adeae850797fbbdba46a3245a89f32581 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Mon, 23 Mar 2026 05:10:29 -0500 Subject: [PATCH 058/132] fix(DB/Scripts): CoS Stephanie Sindree and citizen now talk (#24045) Co-authored-by: blinkysc Co-authored-by: sudlud --- .../rev_1764894567538544452.sql | 12 ++++ .../culling_of_stratholme.cpp | 57 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1764894567538544452.sql diff --git a/data/sql/updates/pending_db_world/rev_1764894567538544452.sql b/data/sql/updates/pending_db_world/rev_1764894567538544452.sql new file mode 100644 index 000000000..a16068747 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1764894567538544452.sql @@ -0,0 +1,12 @@ +-- Add creature_text for Stephanie Sindree (31019) +DELETE FROM `creature_text` WHERE `CreatureID` = 31019; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(31019, 0, 0, 'It wasn''t my food!', 12, 0, 100, 6, 0, 0, 31838, 0, 'Stephanie Sindree - Stephanie Crowd'), +(31019, 1, 0, 'Please, it''s not my fault! If... if I give you a refund, maybe you can speak to a healer? I don''t know what you expect me to do!', 12, 0, 100, 20, 0, 0, 31840, 0, 'Stephanie Sindree - Stephanie Crowd'); + +-- Add creature_text for Agitated Stratholme Citizen (31126) - Stephanie Crowd +DELETE FROM `creature_text` WHERE `CreatureID` = 31126 AND `GroupID` IN (4, 5, 6); +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(31126, 4, 0, 'My wife and children are sick in bed because of the poison you sold me!', 12, 0, 100, 5, 0, 0, 31837, 0, 'Agitated Stratholme Citizen - Stephanie Crowd'), +(31126, 5, 0, 'Don''t try to weasel out of this!', 12, 0, 100, 25, 0, 0, 31839, 0, 'Agitated Stratholme Citizen - Stephanie Crowd'), +(31126, 6, 0, 'That... no, keep your filthy money! It won''t help my family!', 12, 0, 100, 5, 0, 0, 31842, 0, 'Agitated Stratholme Citizen - Stephanie Crowd'); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp index 66b76a02e..7165323ce 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp @@ -176,6 +176,14 @@ enum Misc POINT_CHRONOS = 1, + // Stephanie Sindree crowd + NPC_STEPHANIE_SINDREE = 31019, + SAY_STEPHANIE_CROWD_1 = 4, + SAY_STEPHANIE_CROWD_2 = 5, + SAY_STEPHANIE_CROWD_3 = 6, + SAY_STEPHANIE_RESPONSE_1 = 0, + SAY_STEPHANIE_RESPONSE_2 = 1, + // Brandon Eiredeck crowd NPC_BRANDON_EIREDECK = 31023, SAY_BRANDON_CROWD_AMBIENT = 3, @@ -1526,6 +1534,10 @@ public: if (!pInstance || pInstance->GetData(DATA_ARTHAS_EVENT) < COS_PROGRESS_FINISHED_CITY_INTRO) allowTimer++; + isStephanieCrowd = me->GetEntry() == NPC_CITY_MAN3 && me->GetDistance(2149.56f, 1339.46f, 132.531f) < 10.0f; + stephanieDialogueTimer = isStephanieCrowd ? urand(5000, 15000) : 0; + stephanieDialoguePhase = 0; + isBrandonCrowd = me->GetDistance(2267.86f, 1144.93f, 138.403f) < 10.0f; ambientTalkTimer = isBrandonCrowd ? urand(5000, 15000) : 0; } @@ -1534,6 +1546,9 @@ public: uint32 changeTimer; InstanceScript* pInstance; uint32 allowTimer; + bool isStephanieCrowd; + uint32 stephanieDialogueTimer; + uint8 stephanieDialoguePhase; bool isBrandonCrowd; uint32 ambientTalkTimer; @@ -1596,6 +1611,48 @@ public: { ScriptedAI::UpdateAI(diff); + if (isStephanieCrowd && stephanieDialogueTimer) + { + if (stephanieDialogueTimer <= diff) + { + Creature* stephanie = me->FindNearestCreature(NPC_STEPHANIE_SINDREE, 10.0f); + switch (stephanieDialoguePhase) + { + case 0: + Talk(SAY_STEPHANIE_CROWD_1); + stephanieDialogueTimer = 5000; + stephanieDialoguePhase = 1; + break; + case 1: + if (stephanie) + stephanie->AI()->Talk(SAY_STEPHANIE_RESPONSE_1); + stephanieDialogueTimer = 5000; + stephanieDialoguePhase = 2; + break; + case 2: + Talk(SAY_STEPHANIE_CROWD_2); + stephanieDialogueTimer = 5000; + stephanieDialoguePhase = 3; + break; + case 3: + if (stephanie) + stephanie->AI()->Talk(SAY_STEPHANIE_RESPONSE_2); + stephanieDialogueTimer = 8000; + stephanieDialoguePhase = 4; + break; + case 4: + Talk(SAY_STEPHANIE_CROWD_3); + stephanieDialogueTimer = urand(45000, 60000); + stephanieDialoguePhase = 0; + break; + } + } + else + stephanieDialogueTimer -= diff; + + return; + } + if (isBrandonCrowd && ambientTalkTimer) { if (ambientTalkTimer <= diff) From 4f363ac4bf6e71c4247372b9ea8c21d312491198 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Mar 2026 10:11:34 +0000 Subject: [PATCH 059/132] chore(DB): import pending files Referenced commit(s): cbc4301adeae850797fbbdba46a3245a89f32581 --- .../rev_1764894567538544452.sql => db_world/2026_03_23_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1764894567538544452.sql => db_world/2026_03_23_00.sql} (97%) diff --git a/data/sql/updates/pending_db_world/rev_1764894567538544452.sql b/data/sql/updates/db_world/2026_03_23_00.sql similarity index 97% rename from data/sql/updates/pending_db_world/rev_1764894567538544452.sql rename to data/sql/updates/db_world/2026_03_23_00.sql index a16068747..729fd3d10 100644 --- a/data/sql/updates/pending_db_world/rev_1764894567538544452.sql +++ b/data/sql/updates/db_world/2026_03_23_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_22_05 -> 2026_03_23_00 -- Add creature_text for Stephanie Sindree (31019) DELETE FROM `creature_text` WHERE `CreatureID` = 31019; INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES From 3da6e3019699e5d9f8eb3875f9def993d113a138 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:02:15 -0300 Subject: [PATCH 060/132] fix(Core/Scripts): skip areatrigger scripts for gamemasters (#25196) Co-authored-by: Claude Opus 4.6 (1M context) --- src/server/game/Handlers/MiscHandler.cpp | 3 ++- .../BlackrockMountain/BlackrockSpire/boss_the_beast.cpp | 6 ------ src/server/scripts/Kalimdor/zone_felwood.cpp | 2 +- .../FrozenHalls/HallsOfReflection/halls_of_reflection.cpp | 3 --- .../Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp | 3 +-- .../scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp | 4 ++-- 6 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index bfe454add..e5ae429c0 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -732,7 +732,8 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recv_data) if (player->isDebugAreaTriggers) ChatHandler(this).PSendSysMessage(LANG_DEBUG_AREATRIGGER_REACHED, triggerId); - if (sScriptMgr->OnAreaTrigger(player, atEntry)) + // Skip areatrigger scripts for GMs unless debug areatriggers is enabled + if ((!player->IsGameMaster() || player->isDebugAreaTriggers) && sScriptMgr->OnAreaTrigger(player, atEntry)) return; if (player->IsAlive()) diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp index 37d773328..fcb9e2f69 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp @@ -246,9 +246,6 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*at*/) override { - if (player->IsGameMaster()) - return false; - if (InstanceScript* instance = player->GetInstanceScript()) { if (Creature* beast = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THE_BEAST))) @@ -268,9 +265,6 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*at*/) override { - if (player->IsGameMaster()) - return false; - if (InstanceScript* instance = player->GetInstanceScript()) { if (Creature* beast = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THE_BEAST))) diff --git a/src/server/scripts/Kalimdor/zone_felwood.cpp b/src/server/scripts/Kalimdor/zone_felwood.cpp index 93f33e72d..26d3cec1f 100644 --- a/src/server/scripts/Kalimdor/zone_felwood.cpp +++ b/src/server/scripts/Kalimdor/zone_felwood.cpp @@ -127,7 +127,7 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*trigger*/) override { - if (player->IsGameMaster() || !player->IsAlive()) + if (!player->IsAlive()) return false; // Handle Call Ancients event start - The area trigger summons 3 ancients diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp index 266550e9f..95b2c4f5c 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp @@ -1463,9 +1463,6 @@ public: bool OnTrigger(Player* player, const AreaTrigger* /*at*/) override { - if (player->IsGameMaster()) - return false; - InstanceScript* inst = player->GetInstanceScript(); if (!inst) return false; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp index a80ff3250..9b6926c96 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp @@ -1176,8 +1176,7 @@ public: { if (InstanceScript* instance = player->GetInstanceScript()) if (instance->GetBossState(DATA_LADY_DEATHWHISPER) != DONE) - if (!player->IsGameMaster()) - if (Creature* ladyDeathwhisper = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_LADY_DEATHWHISPER))) + if (Creature* ladyDeathwhisper = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_LADY_DEATHWHISPER))) ladyDeathwhisper->AI()->DoAction(ACTION_START_INTRO); return true; } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp index 94ca3e5c4..90e53cf4a 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp @@ -3592,7 +3592,7 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*areaTrigger*/) override { if (InstanceScript* instance = player->GetInstanceScript()) - if (instance->GetBossState(DATA_SINDRAGOSA_GAUNTLET) == NOT_STARTED && !player->IsGameMaster()) + if (instance->GetBossState(DATA_SINDRAGOSA_GAUNTLET) == NOT_STARTED) if (Creature* gauntlet = ObjectAccessor::GetCreature(*player, instance->GetGuidData(NPC_SINDRAGOSA_GAUNTLET))) gauntlet->AI()->DoAction(ACTION_START_GAUNTLET); return true; @@ -3607,7 +3607,7 @@ public: bool OnTrigger(Player* player, AreaTrigger const* /*areaTrigger*/) override { if (InstanceScript* instance = player->GetInstanceScript()) - if (instance->GetData(DATA_PUTRICIDE_TRAP_STATE) == NOT_STARTED && !player->IsGameMaster()) + if (instance->GetData(DATA_PUTRICIDE_TRAP_STATE) == NOT_STARTED) if (Creature* trap = ObjectAccessor::GetCreature(*player, instance->GetGuidData(NPC_PUTRICADES_TRAP))) trap->AI()->DoAction(ACTION_START_GAUNTLET); return true; From 4201acddd5ec4da85403a4bdbd045bd2319f6562 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Mon, 23 Mar 2026 08:08:14 -0500 Subject: [PATCH 061/132] feat(Core/Movement): port smooth waypoint movement from Cataclysm Preservation Project (#25106) Co-authored-by: blinkysc Co-authored-by: Ovahlord Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Kitzunu --- .../rev_smooth_waypoint_movement.sql | 15 + .../Database/Implementation/WorldDatabase.cpp | 2 +- src/server/game/AI/CreatureAI.h | 7 + .../game/AI/ScriptedAI/ScriptedEscortAI.h | 1 + src/server/game/AI/SmartScripts/SmartAI.cpp | 88 ++- src/server/game/AI/SmartScripts/SmartAI.h | 11 +- .../game/AI/SmartScripts/SmartScript.cpp | 8 +- .../game/AI/SmartScripts/SmartScriptMgr.cpp | 36 +- .../game/AI/SmartScripts/SmartScriptMgr.h | 13 +- .../game/Entities/Creature/Creature.cpp | 27 + src/server/game/Entities/Creature/Creature.h | 9 + src/server/game/Movement/MotionMaster.cpp | 6 +- .../WaypointMovementGenerator.cpp | 582 ++++++++++++------ .../WaypointMovementGenerator.h | 58 +- src/server/game/Movement/Spline/MoveSpline.h | 1 + .../game/Movement/Spline/MoveSplineInit.cpp | 19 + .../game/Movement/Spline/MoveSplineInit.h | 1 + .../game/Movement/Waypoints/WaypointDefines.h | 72 +++ .../game/Movement/Waypoints/WaypointMgr.cpp | 168 +++-- .../game/Movement/Waypoints/WaypointMgr.h | 39 +- src/server/game/World/World.cpp | 3 + .../BlackrockDepths/blackrock_depths.cpp | 2 + .../EasternKingdoms/Karazhan/karazhan.cpp | 1 + .../EasternKingdoms/zone_arathi_highlands.cpp | 1 + .../EasternKingdoms/zone_ghostlands.cpp | 1 + .../EasternKingdoms/zone_hinterlands.cpp | 1 + .../zone_redridge_mountains.cpp | 1 + .../zone_silverpine_forest.cpp | 1 + .../EasternKingdoms/zone_stormwind_city.cpp | 2 + .../EasternKingdoms/zone_undercity.cpp | 2 + .../scripts/EasternKingdoms/zone_westfall.cpp | 1 + .../scripts/EasternKingdoms/zone_wetlands.cpp | 1 + .../culling_of_stratholme.cpp | 1 + .../EscapeFromDurnholdeKeep/old_hillsbrad.cpp | 2 + .../Kalimdor/RuinsOfAhnQiraj/boss_rajaxx.cpp | 1 + .../scripts/Kalimdor/zone_ashenvale.cpp | 1 + .../scripts/Kalimdor/zone_azuremyst_isle.cpp | 1 + .../scripts/Kalimdor/zone_darkshore.cpp | 1 + src/server/scripts/Kalimdor/zone_desolace.cpp | 1 + .../scripts/Kalimdor/zone_moonglade.cpp | 1 + .../Kalimdor/zone_stonetalon_mountains.cpp | 1 + src/server/scripts/Kalimdor/zone_tanaris.cpp | 1 + .../scripts/Kalimdor/zone_the_barrens.cpp | 2 + .../Kalimdor/zone_thousand_needles.cpp | 2 + .../scripts/Kalimdor/zone_ungoro_crater.cpp | 1 + .../scripts/Kalimdor/zone_winterspring.cpp | 1 + .../RubySanctum/boss_general_zarithrian.cpp | 1 + .../boss_argent_challenge.cpp | 1 + .../TrialOfTheChampion/boss_black_knight.cpp | 1 + .../boss_grand_champions.cpp | 1 + .../IcecrownCitadel/icecrown_citadel.cpp | 1 + .../HallsOfLightning/boss_bjarngrim.cpp | 1 + .../Ulduar/HallsOfStone/brann_bronzebeard.cpp | 1 + .../Ulduar/Ulduar/boss_flame_leviathan.cpp | 1 + .../Northrend/Ulduar/Ulduar/boss_thorim.cpp | 2 + .../Ulduar/Ulduar/boss_yoggsaron.cpp | 1 + .../Northrend/VioletHold/violet_hold.cpp | 2 + .../scripts/Northrend/zone_borean_tundra.cpp | 3 + .../scripts/Northrend/zone_grizzly_hills.cpp | 1 + .../scripts/Northrend/zone_howling_fjord.cpp | 2 + .../scripts/Northrend/zone_icecrown.cpp | 1 + .../scripts/Northrend/zone_sholazar_basin.cpp | 1 + .../scripts/Northrend/zone_storm_peaks.cpp | 2 + .../Outland/zone_hellfire_peninsula.cpp | 2 + src/server/scripts/Outland/zone_nagrand.cpp | 2 + .../scripts/Outland/zone_netherstorm.cpp | 3 + .../scripts/Outland/zone_shattrath_city.cpp | 1 + .../scripts/Outland/zone_terokkar_forest.cpp | 1 + src/server/scripts/World/npcs_special.cpp | 1 + 69 files changed, 844 insertions(+), 386 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_smooth_waypoint_movement.sql create mode 100644 src/server/game/Movement/Waypoints/WaypointDefines.h diff --git a/data/sql/updates/pending_db_world/rev_smooth_waypoint_movement.sql b/data/sql/updates/pending_db_world/rev_smooth_waypoint_movement.sql new file mode 100644 index 000000000..085777f2d --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_smooth_waypoint_movement.sql @@ -0,0 +1,15 @@ +-- Add velocity and smoothTransition columns to waypoint_data +ALTER TABLE `waypoint_data` + ADD COLUMN `velocity` FLOAT NOT NULL DEFAULT 0 AFTER `orientation`, + ADD COLUMN `smoothTransition` TINYINT NOT NULL DEFAULT 0 AFTER `delay`; + +-- Create waypoint_data_addon table for custom spline points +CREATE TABLE IF NOT EXISTS `waypoint_data_addon` ( + `PathID` INT UNSIGNED NOT NULL, + `PointID` INT UNSIGNED NOT NULL, + `SplinePointIndex` INT UNSIGNED NOT NULL, + `PositionX` FLOAT NOT NULL DEFAULT 0, + `PositionY` FLOAT NOT NULL DEFAULT 0, + `PositionZ` FLOAT NOT NULL DEFAULT 0, + PRIMARY KEY (`PathID`, `PointID`, `SplinePointIndex`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 32b568b77..128c0c7c0 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -52,7 +52,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_UPD_WAYPOINT_DATA_WPGUID, "UPDATE waypoint_data SET wpguid = ? WHERE id = ? and point = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_WAYPOINT_DATA_MAX_ID, "SELECT MAX(id) FROM waypoint_data", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_DATA_MAX_POINT, "SELECT MAX(point) FROM waypoint_data WHERE id = ?", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID, "SELECT point, position_x, position_y, position_z, orientation, move_type, delay, action, action_chance FROM waypoint_data WHERE id = ? ORDER BY point", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID, "SELECT point, position_x, position_y, position_z, orientation, velocity, delay, smoothTransition, move_type, action, action_chance FROM waypoint_data WHERE id = ? ORDER BY point", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_BY_ID, "SELECT point, position_x, position_y, position_z FROM waypoint_data WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_FIRST_BY_ID, "SELECT position_x, position_y, position_z FROM waypoint_data WHERE point = 1 AND id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_LAST_BY_ID, "SELECT position_x, position_y, position_z, orientation FROM waypoint_data WHERE id = ? ORDER BY point DESC LIMIT 1", CONNECTION_SYNCH); diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 6509e4c0e..06d250a38 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -186,6 +186,13 @@ public: // Called at MovePath End virtual void PathEndReached(uint32 /*pathId*/) {} + /// == Waypoints system ============================= + + virtual void WaypointPathStarted(uint32 /*pathId*/) { } + virtual void WaypointStarted(uint32 /*nodeId*/, uint32 /*pathId*/) { } + virtual void WaypointReached(uint32 /*nodeId*/, uint32 /*pathId*/) { } + virtual void WaypointPathEnded(uint32 /*nodeId*/, uint32 /*pathId*/) { } + void OnCharmed(bool apply) override; // Called at reaching home after evade diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h index a1e2aeee4..83039da38 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h @@ -87,6 +87,7 @@ public: void GenerateWaypointArray(Movement::PointsArray* points); + using CreatureAI::WaypointReached; virtual void WaypointReached(uint32 pointId) = 0; virtual void WaypointStart(uint32 /*pointId*/) {} diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index fc4481d50..80a65b113 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -148,27 +148,28 @@ void SmartAI::UpdateFollow(const uint32 diff) } } -WaypointData const* SmartAI::GetNextWayPoint() +WaypointNode const* SmartAI::GetNextWayPoint() { - if (!mWayPoints || mWayPoints->empty()) + if (!mWayPoints || mWayPoints->Nodes.empty()) return nullptr; mCurrentWPID++; - auto itr = mWayPoints->find(mCurrentWPID); - if (itr != mWayPoints->end()) - { - mLastWP = &(*itr).second; - if (mLastWP->id != mCurrentWPID) - LOG_ERROR("scripts.ai.sai", "SmartAI::GetNextWayPoint: Got not expected waypoint id {}, expected {}", mLastWP->id, mCurrentWPID); + // mCurrentWPID is 1-based for SmartAI escort paths + if (mCurrentWPID > mWayPoints->Nodes.size()) + return nullptr; - return &(*itr).second; - } - return nullptr; + // Nodes are 0-indexed, mCurrentWPID is 1-based + WaypointNode const& node = mWayPoints->Nodes[mCurrentWPID - 1]; + mLastWP = &node; + if (mLastWP->Id != mCurrentWPID) + LOG_ERROR("scripts.ai.sai", "SmartAI::GetNextWayPoint: Got not expected waypoint id {}, expected {}", mLastWP->Id, mCurrentWPID); + + return mLastWP; } void SmartAI::GenerateWayPointArray(Movement::PointsArray* points) { - if (!mWayPoints || mWayPoints->empty()) + if (!mWayPoints || mWayPoints->Nodes.empty()) return; // Flying unit, just fill array @@ -177,16 +178,12 @@ void SmartAI::GenerateWayPointArray(Movement::PointsArray* points) // xinef: first point in vector is unit real position points->clear(); points->push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); - uint32 wpCounter = mCurrentWPID; - auto itr = mWayPoints->find(wpCounter++); - do + // mCurrentWPID is 1-based + for (uint32 i = mCurrentWPID > 0 ? mCurrentWPID - 1 : 0; i < mWayPoints->Nodes.size(); ++i) { - WaypointData const& wp = (*itr).second; - points->push_back(G3D::Vector3(wp.x, wp.y, wp.z)); - - itr = mWayPoints->find(wpCounter++); + WaypointNode const& wp = mWayPoints->Nodes[i]; + points->push_back(G3D::Vector3(wp.X, wp.Y, wp.Z)); } - while (itr != mWayPoints->end()); } else { @@ -195,15 +192,15 @@ void SmartAI::GenerateWayPointArray(Movement::PointsArray* points) std::vector pVector; // xinef: first point in vector is unit real position pVector.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); - uint32 wpCounter = mCurrentWPID; - uint32 length = (mWayPoints->size() - mCurrentWPID) * size; + uint32 startIdx = mCurrentWPID > 0 ? mCurrentWPID - 1 : 0; + uint32 length = (uint32)((mWayPoints->Nodes.size() - startIdx) * size); uint32 cnt = 0; - for (auto itr = mWayPoints->find(wpCounter); itr != mWayPoints->end() && cnt++ <= length; ++itr) + for (uint32 i = startIdx; i < mWayPoints->Nodes.size() && cnt++ <= length; ++i) { - WaypointData const& wp = (*itr).second; - pVector.push_back(G3D::Vector3(wp.x, wp.y, wp.z)); + WaypointNode const& wp = mWayPoints->Nodes[i]; + pVector.push_back(G3D::Vector3(wp.X, wp.Y, wp.Z)); } if (pVector.size() > 2) // more than source + dest @@ -243,10 +240,10 @@ void SmartAI::StartPath(ForcedMovement forcedMovement, uint32 path, bool repeat, return; } - if (!mWayPoints || mWayPoints->empty()) + if (!mWayPoints || mWayPoints->Nodes.empty()) return; - if (WaypointData const* wp = GetNextWayPoint()) + if (WaypointNode const* wp = GetNextWayPoint()) { AddEscortState(SMART_ESCORT_ESCORTING); mCanRepeatPath = repeat; @@ -262,7 +259,7 @@ void SmartAI::StartPath(ForcedMovement forcedMovement, uint32 path, bool repeat, GenerateWayPointArray(&pathPoints); me->GetMotionMaster()->MoveSplinePath(&pathPoints, mForcedMovement); - GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_START, nullptr, wp->id, GetScript()->GetPathId()); + GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_START, nullptr, wp->Id, GetScript()->GetPathId()); } } @@ -321,10 +318,11 @@ void SmartAI::PausePath(uint32 delay, bool forced) me->StopMoving(); me->GetMotionMaster()->MoveIdle();//force stop - auto waypoint = mWayPoints->find(mCurrentWPID); - if (waypoint->second.orientation.has_value()) + if (mCurrentWPID > 0 && mCurrentWPID <= mWayPoints->Nodes.size()) { - me->SetFacingTo(*waypoint->second.orientation); + WaypointNode const& waypoint = mWayPoints->Nodes[mCurrentWPID - 1]; + if (waypoint.Orientation.has_value()) + me->SetFacingTo(*waypoint.Orientation); } } GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_PAUSED, nullptr, mCurrentWPID, GetScript()->GetPathId()); @@ -655,7 +653,7 @@ void SmartAI::MovepointReached(uint32 id) if (mLastWP) { - me->SetPosition(mLastWP->x, mLastWP->y, mLastWP->z, me->GetOrientation()); + me->SetPosition(mLastWP->X, mLastWP->Y, mLastWP->Z, me->GetOrientation()); me->SetHomePosition(me->GetPosition()); } @@ -1278,6 +1276,32 @@ void SmartAI::PathEndReached(uint32 /*pathId*/) me->LoadPath(0); } +void SmartAI::WaypointPathStarted(uint32 /*pathId*/) +{ +} + +void SmartAI::WaypointStarted(uint32 /*nodeId*/, uint32 /*pathId*/) +{ +} + +void SmartAI::WaypointReached(uint32 nodeId, uint32 pathId) +{ + if (!HasEscortState(SMART_ESCORT_ESCORTING)) + { + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, nodeId, pathId); + return; + } +} + +void SmartAI::WaypointPathEnded(uint32 nodeId, uint32 pathId) +{ + if (!HasEscortState(SMART_ESCORT_ESCORTING)) + { + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, nodeId, pathId); + return; + } +} + void SmartAI::DistancingEnded() { SetCurrentRangeMode(true, _pendingDistancing); diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index 34e6aa658..5449e4970 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -58,7 +58,7 @@ public: void StopPath(uint32 DespawnTime = 0, uint32 quest = 0, bool fail = false); void EndPath(bool fail = false); void ResumePath(); - WaypointData const* GetNextWayPoint(); + WaypointNode const* GetNextWayPoint(); void GenerateWayPointArray(Movement::PointsArray* points); bool HasEscortState(uint32 uiEscortState) { return (mEscortState & uiEscortState); } void AddEscortState(uint32 uiEscortState) { mEscortState |= uiEscortState; } @@ -206,6 +206,11 @@ public: void PathEndReached(uint32 pathId) override; + void WaypointPathStarted(uint32 pathId) override; + void WaypointStarted(uint32 nodeId, uint32 pathId) override; + void WaypointReached(uint32 nodeId, uint32 pathId) override; + void WaypointPathEnded(uint32 nodeId, uint32 pathId) override; + bool CanRespawn() override { return mcanSpawn; }; void SetCanRespawn(bool canSpawn) { mcanSpawn = canSpawn; } @@ -239,9 +244,9 @@ private: bool mWPReached; bool mOOCReached; uint32 mWPPauseTimer; - WaypointData const* mLastWP; + WaypointNode const* mLastWP; uint32 mEscortNPCFlags; - uint32 GetWPCount() { return mWayPoints ? mWayPoints->size() : 0; } + uint32 GetWPCount() { return mWayPoints ? mWayPoints->Nodes.size() : 0; } bool mCanRepeatPath; bool mEvadeDisabled; bool mCanAutoAttack; diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 82d9512f9..4024052b4 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -2533,14 +2533,12 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u break; } - if (!path || path->empty()) + if (!path || path->Nodes.empty()) continue; - auto itrWp = path->find(1); - if (itrWp != path->end()) { - WaypointData const& wpData = itrWp->second; - float distToThisPath = creature->GetExactDistSq(wpData.x, wpData.y, wpData.z); + WaypointNode const& wpData = path->Nodes[0]; + float distToThisPath = creature->GetExactDistSq(wpData.X, wpData.Y, wpData.Z); if (distToThisPath < distanceToClosest) { diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 3f0cbdef0..847856b35 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -48,11 +48,6 @@ void SmartWaypointMgr::LoadFromDB() { uint32 oldMSTime = getMSTime(); - for (auto itr : waypoint_map) - { - delete itr.second; - } - waypoint_map.clear(); WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_SMARTAI_WP); @@ -78,14 +73,15 @@ void SmartWaypointMgr::LoadFromDB() float x = fields[2].Get(); float y = fields[3].Get(); float z = fields[4].Get(); - Optional o; + std::optional o; if (!fields[5].IsNull()) o = fields[5].Get(); uint32 delay = fields[6].Get(); if (last_entry != entry) { - waypoint_map[entry] = new WaypointPath(); + waypoint_map[entry] = WaypointPath(); + waypoint_map[entry].Id = entry; last_id = 1; count++; } @@ -94,15 +90,15 @@ void SmartWaypointMgr::LoadFromDB() LOG_ERROR("sql.sql", "SmartWaypointMgr::LoadFromDB: Path entry {}, unexpected point id {}, expected {}.", entry, id, last_id); last_id++; - WaypointData data; - data.id = id; - data.x = x; - data.y = y; - data.z = z; - data.orientation = o; - data.delay = delay; - data.move_type = WAYPOINT_MOVE_TYPE_MAX; - (*waypoint_map[entry]).emplace(id, data); + WaypointNode node; + node.Id = id; + node.X = x; + node.Y = y; + node.Z = z; + node.Orientation = o; + node.Delay = delay; + node.MoveType = WAYPOINT_MOVE_TYPE_MAX; + waypoint_map[entry].Nodes.push_back(std::move(node)); last_entry = entry; total++; @@ -112,14 +108,6 @@ void SmartWaypointMgr::LoadFromDB() LOG_INFO("server.loading", " "); } -SmartWaypointMgr::~SmartWaypointMgr() -{ - for (auto itr : waypoint_map) - { - delete itr.second; - } -} - SmartAIMgr* SmartAIMgr::instance() { static SmartAIMgr instance; diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index e87e01e62..8163866e2 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -2064,21 +2064,22 @@ class SmartWaypointMgr { SmartWaypointMgr() {} public: - ~SmartWaypointMgr(); + ~SmartWaypointMgr() = default; static SmartWaypointMgr* instance(); void LoadFromDB(); - WaypointPath* GetPath(uint32 id) + WaypointPath const* GetPath(uint32 id) const { - if (waypoint_map.find(id) != waypoint_map.end()) - return waypoint_map[id]; - else return 0; + auto itr = waypoint_map.find(id); + if (itr != waypoint_map.end()) + return &itr->second; + return nullptr; } private: - std::unordered_map waypoint_map; + std::unordered_map waypoint_map; }; // all events for a single entry diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index d714343c9..b1ab32562 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -388,6 +388,33 @@ void Creature::SearchFormation() } } +bool Creature::IsFormationLeader() const +{ + if (!m_formation) + return false; + + return m_formation->GetLeader() == this; +} + +void Creature::SignalFormationMovement() +{ + if (!m_formation) + return; + + if (!m_formation->GetLeader() || m_formation->GetLeader() != this) + return; + + m_formation->LeaderStartedMoving(); +} + +bool Creature::IsFormationLeaderMoveAllowed() const +{ + if (!m_formation) + return true; + + return m_formation->CanLeaderStartMoving(); +} + void Creature::RemoveCorpse(bool setSpawnTime, bool skipVisibility) { if (getDeathState() != DeathState::Corpse) diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 2218ab8ec..f95946ab9 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -358,6 +358,14 @@ public: [[nodiscard]] uint32 GetCurrentWaypointID() const { return m_waypointID; } void UpdateWaypointID(uint32 wpID) { m_waypointID = wpID; } + // nodeId, pathId + std::pair GetCurrentWaypointInfo() const { return _currentWaypointNodeInfo; } + void UpdateCurrentWaypointInfo(uint32 nodeId, uint32 pathId) { _currentWaypointNodeInfo = { nodeId, pathId }; } + + bool IsFormationLeader() const; + void SignalFormationMovement(); + bool IsFormationLeaderMoveAllowed() const; + void SearchFormation(); [[nodiscard]] CreatureGroup const* GetFormation() const { return m_formation; } [[nodiscard]] CreatureGroup* GetFormation() { return m_formation; } @@ -519,6 +527,7 @@ private: // WaypointMovementGenerator variable uint32 m_waypointID; uint32 m_path_id; + std::pair _currentWaypointNodeInfo{0, 0}; // Formation variable CreatureGroup* m_formation; diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 7625a6b21..e9649225c 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -538,9 +538,9 @@ void MotionMaster::MovePath(uint32 path_id, ForcedMovement forcedMovement, PathS } Movement::PointsArray points; - for (auto& point : *path) + for (auto const& node : path->Nodes) { - points.push_back(G3D::Vector3(point.second.x, point.second.y, point.second.z)); + points.push_back(G3D::Vector3(node.X, node.Y, node.Z)); } // pass the new PointsArray* to the appropriate MoveSplinePath function @@ -918,7 +918,7 @@ void MotionMaster::MoveWaypoint(uint32 path_id, bool repeatable, PathSource path if (_owner->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE)) return; - Mutate(new WaypointMovementGenerator(path_id, pathSource, repeatable), MOTION_SLOT_IDLE); + Mutate(new WaypointMovementGenerator(path_id, repeatable, pathSource), MOTION_SLOT_IDLE); LOG_DEBUG("movement.motionmaster", "{} ({}) start moving over path(Id:{}, repeatable: {})", _owner->IsPlayer() ? "Player" : "Creature", _owner->GetGUID().ToString(), path_id, repeatable ? "YES" : "NO"); diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index 47a83f5ae..f1db91ac9 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -27,181 +27,283 @@ #include "Player.h" #include "Spell.h" #include "Transport.h" -#include "World.h" #include "SmartScriptMgr.h" +#include "World.h" -void WaypointMovementGenerator::LoadPath(Creature* creature) +inline G3D::Vector3 PositionToVector3(Position const& p) { return { p.GetPositionX(), p.GetPositionY(), p.GetPositionZ() }; } + +WaypointMovementGenerator::WaypointMovementGenerator(uint32 pathId, bool repeating, PathSource pathSource) : PathMovementBase((WaypointPath const*)nullptr), + _lastSplineId(0), _pathId(pathId), _waypointDelay(0), + _waypointReached(true), _recalculateSpeed(false), _repeating(repeating), _loadedFromDB(true), _stalled(false), _hasBeenStalled(false), _done(false), _pathSource(pathSource), + _smoothSplineLaunched(false), _lastPassedSplineIdx(0) { - switch (i_pathSource) - { - case PathSource::WAYPOINT_MGR: - { - if (!path_id) - path_id = creature->GetWaypointPath(); +} - i_path = sWaypointMgr->GetPath(path_id); - break; - } - case PathSource::SMART_WAYPOINT_MGR: +WaypointMovementGenerator::WaypointMovementGenerator(WaypointPath& path, bool repeating) : PathMovementBase((WaypointPath const*)nullptr), + _lastSplineId(0), _pathId(0), _waypointDelay(0), + _waypointReached(true), _recalculateSpeed(false), _repeating(repeating), _loadedFromDB(false), _stalled(false), _hasBeenStalled(false), _done(false), _pathSource(PathSource::WAYPOINT_MGR), + _smoothSplineLaunched(false), _lastPassedSplineIdx(0) +{ + i_path = &path; +} + +void WaypointMovementGenerator::DoInitialize(Creature* creature) +{ + _done = false; + + if (_loadedFromDB) + { + if (!_pathId) + _pathId = creature->GetWaypointPath(); + + switch (_pathSource) { - i_path = sSmartWaypointMgr->GetPath(path_id); - break; + default: + case PathSource::WAYPOINT_MGR: + i_path = sWaypointMgr->GetPath(_pathId); + break; + case PathSource::SMART_WAYPOINT_MGR: + i_path = sSmartWaypointMgr->GetPath(_pathId); + break; } } if (!i_path) { - // No movement found for entry - LOG_ERROR("sql.sql", "WaypointMovementGenerator::LoadPath: creature {} ({}) doesn't have waypoint path id: {}", - creature->GetName(), creature->GetGUID().ToString(), path_id); + LOG_ERROR("sql.sql", "WaypointMovementGenerator::DoInitialize: creature {} ({}) doesn't have waypoint path id: {}", + creature->GetName(), creature->GetGUID().ToString(), _pathId); return; } - i_currentNode = i_path->begin()->first; + // Determine our first waypoint from the creature's stored waypoint + if (CreatureData const* creatureData = creature->GetCreatureData()) + { + if (i_path->Nodes.size() > creatureData->currentwaypoint) + { + creature->UpdateCurrentWaypointInfo(creatureData->currentwaypoint, i_path->Id); + i_currentNode = creatureData->currentwaypoint; + } + } - StartMoveNow(creature); -} - -void WaypointMovementGenerator::DoInitialize(Creature* creature) -{ - LoadPath(creature); creature->AddUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE); + + // Inform AI + if (CreatureAI* AI = creature->AI()) + AI->WaypointPathStarted(i_path->Id); } void WaypointMovementGenerator::DoFinalize(Creature* creature) { creature->ClearUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE); + creature->SetWalk(false); } void WaypointMovementGenerator::DoReset(Creature* creature) { - if (stalled) + // We did not reach our last waypoint before reset, treat this scenario as resuming movement. + if (!_done && !_waypointReached) + _hasBeenStalled = true; + else if (_done) { - return; - } - creature->AddUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE); - StartMoveNow(creature); -} - -void WaypointMovementGenerator::OnArrived(Creature* creature) -{ - if (!i_path || i_path->empty()) - return; - if (m_isArrivalDone) - return; - - creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); - m_isArrivalDone = true; - - auto currentNodeItr = i_path->find(i_currentNode); - - if (currentNodeItr->second.event_id && urand(0, 99) < currentNodeItr->second.event_chance) - { - LOG_DEBUG("maps.script", "Creature movement start script {} at point {} for {}.", - currentNodeItr->second.event_id, i_currentNode, creature->GetGUID().ToString()); - creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); - creature->GetMap()->ScriptsStart(sWaypointScripts, currentNodeItr->second.event_id, creature, nullptr); - } - - // Inform script - MovementInform(creature); - creature->UpdateWaypointID(i_currentNode); - - if (currentNodeItr->second.delay) - { - creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); - Stop(currentNodeItr->second.delay); + // mimic IdleMovementGenerator + if (!creature->IsStopped()) + creature->StopMoving(); } } -bool WaypointMovementGenerator::StartMove(Creature* creature) +inline void UpdateHomePosition(Creature* creature, WaypointNode const& waypointNode) { - if (!i_path || i_path->empty()) - return false; - - // Xinef: Dont allow dead creatures to move - if (!creature->IsAlive()) - return false; - - if (Stopped()) - return true; + float x = waypointNode.X; + float y = waypointNode.Y; + float z = waypointNode.Z; + float o = creature->GetOrientation(); bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID(); - - if (m_isArrivalDone) + if (!transportPath) + creature->SetHomePosition(x, y, z, o); + else { + if (Transport* trans = (creature->GetTransport() ? creature->GetTransport()->ToMotionTransport() : nullptr)) { - auto currentNodeItr = i_path->find(i_currentNode); - float x = currentNodeItr->second.x; - float y = currentNodeItr->second.y; - float z = currentNodeItr->second.z; - float o = creature->GetOrientation(); + o -= trans->GetOrientation(); + creature->SetTransportHomePosition(x, y, z, o); + trans->CalculatePassengerPosition(x, y, z, &o); + creature->SetHomePosition(x, y, z, o); + } + } +} - if (!transportPath) - creature->SetHomePosition(x, y, z, o); - else +void WaypointMovementGenerator::ProcessWaypointArrival(Creature* creature, WaypointNode const& waypoint) +{ + if (_waypointReached) + return; + + if (waypoint.Delay > 0) + { + creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); + _waypointDelay = waypoint.Delay; + } + + // Check if the waypoint path has reached its end and may not repeat. Inform AI. + if ((i_currentNode == i_path->Nodes.size() - 1) && !_repeating && !_done) + { + _done = true; + creature->UpdateCurrentWaypointInfo(0, 0); + + if (CreatureAI* AI = creature->AI()) + { + AI->PathEndReached(i_path->Id); + AI->WaypointPathEnded(waypoint.Id, i_path->Id); + } + } + + UpdateHomePosition(creature, waypoint); + + if (waypoint.EventId && urand(0, 99) < waypoint.EventChance) + { + LOG_DEBUG("maps.script", "Creature movement start script {} at point {} for {}.", + waypoint.EventId, i_currentNode, creature->GetGUID().ToString()); + creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); + creature->GetMap()->ScriptsStart(sWaypointScripts, waypoint.EventId, creature, nullptr); + } + + creature->UpdateWaypointID(waypoint.Id); + creature->UpdateCurrentWaypointInfo(waypoint.Id, i_path->Id); + + // Inform AI + if (CreatureAI* AI = creature->AI()) + { + AI->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); + AI->WaypointReached(waypoint.Id, i_path->Id); + } + + if (Unit* owner = creature->GetCharmerOrOwner()) + { + if (UnitAI* AI = owner->GetAI()) + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); + } + else + { + if (TempSummon* tempSummon = creature->ToTempSummon()) + if (Unit* owner2 = tempSummon->GetSummonerUnit()) + if (UnitAI* AI = owner2->GetAI()) + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); + } + + // All hooks called and infos updated. Time to increment the waypoint node id + if (i_path && !i_path->Nodes.empty()) // ensure that the path has not been changed in one of the hooks. + i_currentNode = (i_currentNode + 1) % i_path->Nodes.size(); + + _waypointReached = true; +} + +void WaypointMovementGenerator::StartMove(Creature* creature, bool relaunch /*= false*/) +{ + // Formation checks. Do not launch a new spline when one of our formation members is currently in combat. + if (!relaunch) + { + if (!IsAllowedToMove(creature) || (creature->IsFormationLeader() && !creature->IsFormationLeaderMoveAllowed())) + { + _waypointDelay = 1000; + return; + } + } + + // Dont allow dead creatures to move + if (!creature->IsAlive()) + return; + + // Step two: node selection is done, build spline data + creature->AddUnitState(UNIT_STATE_ROAMING_MOVE); + WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode); + bool const useTransportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID(); + + Movement::MoveSplineInit init(creature); + //! If the creature is on transport, we assume waypoints set in DB are already transport offsets + if (useTransportPath) + init.DisableTransportPathTransformations(); + + if (waypoint.SmoothTransition && i_path->Nodes.size() > 2) + { + // Build a catmullrom spline segment, stopping at delay waypoints + init.Path().push_back(PositionToVector3(creature->GetPosition())); + + bool hasDelayInSegment = false; + uint32 segmentNodes = 0; + for (uint32 i = 0; i < i_path->Nodes.size(); ++i) + { + uint32 idx = (i_currentNode + i) % i_path->Nodes.size(); + WaypointNode const& node = i_path->Nodes.at(idx); + init.Path().push_back(G3D::Vector3(node.X, node.Y, node.Z)); + segmentNodes++; + + // Stop the segment at a waypoint with a delay + if (node.Delay > 0) { - if (Transport* trans = (creature->GetTransport() ? creature->GetTransport()->ToMotionTransport() : nullptr)) - { - o -= trans->GetOrientation(); - creature->SetTransportHomePosition(x, y, z, o); - trans->CalculatePassengerPosition(x, y, z, &o); - creature->SetHomePosition(x, y, z, o); - } - else - transportPath = false; - // else if (vehicle) - this should never happen, vehicle offsets are const + hasDelayInSegment = true; + break; } } - // Xinef: moved the upper IF here - uint32 lastPoint = i_path->rbegin()->first; - if ((i_currentNode == lastPoint) && !repeating) // If that's our last waypoint + // If no delays found and repeating, add wrap-around points for seamless loop + if (!hasDelayInSegment && _repeating) { - creature->AI()->PathEndReached(path_id); - creature->GetMotionMaster()->Initialize(); - return false; + for (uint32 i = 0; i < std::min(3, i_path->Nodes.size()); ++i) + { + uint32 idx = (i_currentNode + i) % i_path->Nodes.size(); + WaypointNode const& node = i_path->Nodes.at(idx); + init.Path().push_back(G3D::Vector3(node.X, node.Y, node.Z)); + } } - ++i_currentNode; - if (lastPoint < i_currentNode) - i_currentNode = i_path->begin()->first; + // Need at least 3 waypoints for a meaningful catmullrom spline + if (segmentNodes >= 3) + { + init.SetFirstPointId(i_currentNode); + init.SetSmooth(); + _smoothSplineLaunched = true; + _lastPassedSplineIdx = i_currentNode; + } + else + { + // Too few points for catmullrom, fall back to linear point-to-point + init.Path().clear(); + init.MoveTo(G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z)); + } } - - // xinef: do not initialize motion if we got stunned in movementinform - if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting()) + else if (!waypoint.SplinePoints.empty()) { - return true; + // We have spline points in waypoint_data_addon table + int32 splineIndex = 0; + + auto itr = waypoint.SplinePoints.begin(); + if (splineIndex) + std::advance(itr, splineIndex); + + init.Path().reserve(waypoint.SplinePoints.size() - splineIndex); + std::copy(itr, waypoint.SplinePoints.end(), std::back_inserter(init.Path())); + + // Add starting vertex and destination + init.Path().insert(init.Path().begin(), PositionToVector3(creature->GetPosition())); + init.Path().insert(init.Path().end(), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z)); } - - auto currentNodeItr = i_path->find(i_currentNode); - WaypointData const& node = currentNodeItr->second; - - m_isArrivalDone = false; - - creature->AddUnitState(UNIT_STATE_ROAMING_MOVE); - - Movement::Location formationDest(node.x, node.y, node.z, 0.0f); - Movement::MoveSplineInit init(creature); - - //! If creature is on transport, we assume waypoints set in DB are already transport offsets - if (transportPath) + else { - init.DisableTransportPathTransformations(); - if (TransportBase* trans = creature->GetDirectTransport()) - trans->CalculatePassengerPosition(formationDest.x, formationDest.y, formationDest.z, &formationDest.orientation); + // Smooth transition for short paths (<=2 nodes): use previous spline endpoint as start + if (waypoint.SmoothTransition && !creature->movespline->Finalized() && _lastSplineId == creature->movespline->GetId()) + { + init.MoveTo(creature->movespline->FinalDestination(), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z)); + if (!init.Path().empty()) + init.Path().insert(init.Path().begin(), PositionToVector3(creature->GetPosition())); + } + else + init.MoveTo(PositionToVector3(creature->GetPosition()), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z)); } - float z = node.z; - creature->UpdateAllowedPositionZ(node.x, node.y, z); - //! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call - //! but formationDest contains global coordinates - init.MoveTo(node.x, node.y, z, true, true); + if (waypoint.Orientation.has_value() && waypoint.Delay > 0) + init.SetFacing(*waypoint.Orientation); - if (node.orientation.has_value() && node.delay > 0) - init.SetFacing(*node.orientation); - - switch (node.move_type) + switch (waypoint.MoveType) { case WAYPOINT_MOVE_TYPE_LAND: init.SetAnimation(AnimTier::Ground); @@ -219,89 +321,203 @@ bool WaypointMovementGenerator::StartMove(Creature* creature) break; } + if (creature->CanFly()) + init.SetFly(); + + if (waypoint.Velocity > 0.f) + init.SetVelocity(waypoint.Velocity); + init.Launch(); - //Call for creature group update - if (creature->GetFormation() && creature->GetFormation()->GetLeader() == creature && creature->GetFormation()->CanLeaderStartMoving()) - creature->GetFormation()->LeaderStartedMoving(); + if (!creature->movespline->Finalized()) + _lastSplineId = creature->movespline->GetId(); - return true; + // Inform formation + creature->SignalFormationMovement(); + + // Inform AI + if (!relaunch) + if (CreatureAI* AI = creature->AI()) + AI->WaypointStarted(waypoint.Id, i_path->Id); + + _waypointReached = false; + _recalculateSpeed = false; + _hasBeenStalled = false; } bool WaypointMovementGenerator::DoUpdate(Creature* creature, uint32 diff) { - // Waypoint movement can be switched on/off - // This is quite handy for escort quests and other stuff - if (stalled) - { - Stop(1000); + if (!creature || !creature->IsAlive()) return true; - } - if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting()) + + if (_done || !i_path || i_path->Nodes.empty()) + return true; + + // Stop movement if paused, rooted, or casting + if (!IsAllowedToMove(creature) && !creature->movespline->Finalized()) { creature->StopMoving(); - Stop(1000); + _lastSplineId = 0; + _smoothSplineLaunched = false; + } + + // Set home position to current position. + if (!creature->movespline->Finalized()) + { + bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID(); + if (!transportPath) + creature->SetHomePosition(creature->GetPosition()); + } + + // Smooth spline: track waypoint passages without rebuilding + if (_smoothSplineLaunched && creature->movespline->GetId() == _lastSplineId) + { + int32 currentIdx = creature->movespline->currentPathIdx(); + + // Process passed waypoints + while (_lastPassedSplineIdx < currentIdx) + { + _lastPassedSplineIdx++; + WaypointNode const& passedWp = i_path->Nodes.at(i_currentNode); + + UpdateHomePosition(creature, passedWp); + creature->UpdateWaypointID(passedWp.Id); + creature->UpdateCurrentWaypointInfo(passedWp.Id, i_path->Id); + + if (passedWp.EventId && urand(0, 99) < passedWp.EventChance) + { + creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); + creature->GetMap()->ScriptsStart(sWaypointScripts, passedWp.EventId, creature, nullptr); + } + + if (CreatureAI* AI = creature->AI()) + { + AI->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); + AI->WaypointReached(passedWp.Id, i_path->Id); + } + + // Advance node + i_currentNode = (i_currentNode + 1) % i_path->Nodes.size(); + + // If this waypoint has a delay, stop the spline and pause + if (passedWp.Delay > 0) + { + creature->StopMoving(); + creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); + _waypointDelay = passedWp.Delay; + _waypointReached = true; + _smoothSplineLaunched = false; + if (passedWp.Orientation.has_value()) + creature->SetFacingTo(*passedWp.Orientation); + + return true; + } + } + + if (creature->movespline->Finalized()) + { + if (!_repeating) + { + // Path ended + _done = true; + _smoothSplineLaunched = false; + creature->UpdateCurrentWaypointInfo(0, 0); + if (CreatureAI* AI = creature->AI()) + { + AI->PathEndReached(i_path->Id); + AI->WaypointPathEnded(i_path->Nodes.at(i_currentNode).Id, i_path->Id); + } + } + else + { + // Repeating: rebuild spline + _smoothSplineLaunched = false; + StartMove(creature); + } + } + return true; } - // prevent a crash at empty waypoint path. - if (!i_path || i_path->empty()) - return false; + // Non-smooth: per-waypoint logic + WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode); + UpdateWaypointState(creature, waypoint); - // Xinef: Dont allow dead creatures to move - if (!creature->IsAlive()) - return false; + // Process movement preventing timers + if (_waypointDelay > 0) + { + _waypointDelay -= diff; + if (_waypointDelay > 0) + return true; + } - if (Stopped()) + if (_pauseTime.has_value()) { - if (CanMove(diff)) - return StartMove(creature); - } - else - { - if (creature->movespline->Finalized()) - { - OnArrived(creature); - return StartMove(creature); - } + *_pauseTime -= diff; + if (*_pauseTime > 0) + return true; + else + _pauseTime.reset(); } + + // Timers are ready, let's try to move + if (IsAllowedToMove(creature) && (_waypointReached || _recalculateSpeed || _hasBeenStalled)) + StartMove(creature, _recalculateSpeed || _hasBeenStalled); + return true; } -void WaypointMovementGenerator::MovementInform(Creature* creature) +void WaypointMovementGenerator::Pause(uint32 timer /*= 0*/) { - if (creature->AI()) - creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); - - if (Unit* owner = creature->GetCharmerOrOwner()) - { - if (UnitAI* AI = owner->GetAI()) - AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); - } - else - { - if (TempSummon* tempSummon = creature->ToTempSummon()) - if (Unit* owner = tempSummon->GetSummonerUnit()) - if (UnitAI* AI = owner->GetAI()) - AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); - } + _stalled = timer ? false : true; + _hasBeenStalled = !_waypointReached; + _pauseTime = timer; } -void WaypointMovementGenerator::Pause(uint32 timer) +void WaypointMovementGenerator::Resume(uint32 overrideTimer /*= 0*/) { - if (timer) - i_nextMoveTime.Reset(timer); - else - { - // No timer? Will be paused forever until ::Resume is called - stalled = true; - i_nextMoveTime.Reset(1); - } + _hasBeenStalled = !_waypointReached; + _stalled = false; + if (overrideTimer) + _pauseTime = overrideTimer; } -void WaypointMovementGenerator::Resume(uint32 /*overrideTimer/*/) +bool WaypointMovementGenerator::GetResetPosition(float& x, float& y, float& z) { - stalled = false; + // prevent a crash at empty waypoint path. + if (!i_path || i_path->Nodes.empty()) + return false; + + ASSERT(i_currentNode < i_path->Nodes.size(), "WaypointMovementGenerator::GetResetPos: tried to reference a node id ({}) which is not included in path ({})", i_currentNode, i_path->Id); + WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode); + + x = waypoint.X; + y = waypoint.Y; + z = waypoint.Z; + return true; +} + +bool WaypointMovementGenerator::IsAllowedToMove(Creature* creature) const +{ + if (_stalled || _done) + return false; + + if (_pauseTime.has_value()) + return false; + + if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting()) + return false; + + return true; +} + +void WaypointMovementGenerator::UpdateWaypointState(Creature* creature, WaypointNode const& waypointNode) +{ + if (creature->movespline->GetId() != _lastSplineId) + return; + + if (creature->movespline->Finalized()) + ProcessWaypointArrival(creature, waypointNode); } //----------------------------------------------------// diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h index 44194581e..12479820a 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h @@ -54,49 +54,43 @@ class WaypointMovementGenerator : public MovementGeneratorMedium< Crea public PathMovementBase { public: - WaypointMovementGenerator(uint32 _path_id = 0, PathSource pathSource = PathSource::WAYPOINT_MGR, bool _repeating = true, bool _stalled = false) - : PathMovementBase((WaypointPath const*)nullptr), i_nextMoveTime(0), m_isArrivalDone(false), path_id(_path_id), repeating(_repeating), stalled(_stalled), i_pathSource(pathSource) {} + explicit WaypointMovementGenerator(uint32 pathId = 0, bool repeating = true, PathSource pathSource = PathSource::WAYPOINT_MGR); + explicit WaypointMovementGenerator(WaypointPath& path, bool repeating = true); ~WaypointMovementGenerator() { i_path = nullptr; } + void DoInitialize(Creature*); void DoFinalize(Creature*); void DoReset(Creature*); bool DoUpdate(Creature*, uint32 diff); - void Pause(uint32 timer = 0); - void Resume(uint32 overrideTimer/* = 0*/); - void MovementInform(Creature*); + void unitSpeedChanged() override { _recalculateSpeed = true; } + void Pause(uint32 timer = 0) override; + void Resume(uint32 overrideTimer = 0) override; + bool GetResetPosition(float& x, float& y, float& z) override; - MovementGeneratorType GetMovementGeneratorType() { return WAYPOINT_MOTION_TYPE; } - - // now path movement implmementation - void LoadPath(Creature*); + MovementGeneratorType GetMovementGeneratorType() override { return WAYPOINT_MOTION_TYPE; } private: - void Stop(int32 time) { i_nextMoveTime.Reset(time);} + void ProcessWaypointArrival(Creature*, WaypointNode const&); + void StartMove(Creature*, bool relaunch = false); + bool IsAllowedToMove(Creature*) const; + void UpdateWaypointState(Creature*, WaypointNode const&); - bool Stopped() { return !i_nextMoveTime.Passed();} + uint32 _lastSplineId; + uint32 _pathId; + int32 _waypointDelay; + std::optional _pauseTime; + bool _waypointReached; - bool CanMove(int32 diff) - { - i_nextMoveTime.Update(diff); - return i_nextMoveTime.Passed(); - } - - void OnArrived(Creature*); - bool StartMove(Creature*); - - void StartMoveNow(Creature* creature) - { - i_nextMoveTime.Reset(0); - StartMove(creature); - } - - TimeTrackerSmall i_nextMoveTime; - bool m_isArrivalDone; - uint32 path_id; - bool repeating; - bool stalled; - PathSource i_pathSource; + bool _recalculateSpeed; + bool _repeating; + bool _loadedFromDB; + bool _stalled; + bool _hasBeenStalled; + bool _done; + PathSource _pathSource; + bool _smoothSplineLaunched; + int32 _lastPassedSplineIdx; }; /** FlightPathMovementGenerator generates movement of the player for the paths diff --git a/src/server/game/Movement/Spline/MoveSpline.h b/src/server/game/Movement/Spline/MoveSpline.h index 3b134d44e..07cfcb41f 100644 --- a/src/server/game/Movement/Spline/MoveSpline.h +++ b/src/server/game/Movement/Spline/MoveSpline.h @@ -122,6 +122,7 @@ namespace Movement [[nodiscard]] Vector3 FinalDestination() const { return Initialized() ? spline.getPoint(spline.last()) : Vector3(); } [[nodiscard]] Vector3 CurrentDestination() const { return Initialized() ? spline.getPoint(point_Idx + 1) : Vector3(); } [[nodiscard]] int32 currentPathIdx() const; + [[nodiscard]] int32 MaxPathIdx() const { return spline.last() - 1; } [[nodiscard]] bool HasAnimation() const { return splineflags.animation; } [[nodiscard]] uint8 GetAnimationType() const { return splineflags.animId; } diff --git a/src/server/game/Movement/Spline/MoveSplineInit.cpp b/src/server/game/Movement/Spline/MoveSplineInit.cpp index 4ae646e37..16b58a45d 100644 --- a/src/server/game/Movement/Spline/MoveSplineInit.cpp +++ b/src/server/game/Movement/Spline/MoveSplineInit.cpp @@ -199,6 +199,25 @@ namespace Movement args.flags.EnableFacingAngle(); } + void MoveSplineInit::MoveTo(Vector3 const& start, Vector3 const& dest, bool generatePath, bool forceDestination) + { + if (generatePath) + { + PathGenerator path(unit); + bool result = path.CalculatePath(start.x, start.y, start.z, dest.x, dest.y, dest.z, forceDestination); + if (result && !(path.GetPathType() & PATHFIND_NOPATH)) + { + MovebyPath(path.GetPath()); + return; + } + } + + args.path_Idx_offset = 0; + args.path.resize(2); + TransportPathTransform transform(unit, args.TransformForTransport); + args.path[1] = transform(dest); + } + void MoveSplineInit::MoveTo(const Vector3& dest, bool generatePath, bool forceDestination) { if (generatePath) diff --git a/src/server/game/Movement/Spline/MoveSplineInit.h b/src/server/game/Movement/Spline/MoveSplineInit.h index 5e26dad04..16120d9bb 100644 --- a/src/server/game/Movement/Spline/MoveSplineInit.h +++ b/src/server/game/Movement/Spline/MoveSplineInit.h @@ -99,6 +99,7 @@ namespace Movement /* Initializes simple A to B motion, A is current unit's position, B is destination */ + void MoveTo(Vector3 const& start, Vector3 const& destination, bool generatePath = true, bool forceDestination = false); void MoveTo(const Vector3& destination, bool generatePath = false, bool forceDestination = false); void MoveTo(float x, float y, float z, bool generatePath = false, bool forceDestination = false); diff --git a/src/server/game/Movement/Waypoints/WaypointDefines.h b/src/server/game/Movement/Waypoints/WaypointDefines.h new file mode 100644 index 000000000..6b55ab5af --- /dev/null +++ b/src/server/game/Movement/Waypoints/WaypointDefines.h @@ -0,0 +1,72 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef ACORE_WAYPOINTDEFINES_H +#define ACORE_WAYPOINTDEFINES_H + +#include "Define.h" +#include "G3D/Vector3.h" +#include +#include + +enum WaypointMoveType +{ + WAYPOINT_MOVE_TYPE_WALK, + WAYPOINT_MOVE_TYPE_RUN, + WAYPOINT_MOVE_TYPE_LAND, + WAYPOINT_MOVE_TYPE_TAKEOFF, + + WAYPOINT_MOVE_TYPE_MAX +}; + +struct WaypointNode +{ + WaypointNode() : Id(0), X(0.f), Y(0.f), Z(0.f), Velocity(0.f), Delay(0), EventId(0), MoveType(WAYPOINT_MOVE_TYPE_RUN), EventChance(0), SmoothTransition(false) { } + WaypointNode(uint32 id, float x, float y, float z, std::optional orientation = { }, float velocity = 0.f, uint32 delay = 0, bool smoothTransition = false) : + Id(id), X(x), Y(y), Z(z), Orientation(orientation), Velocity(velocity), Delay(delay), SmoothTransition(smoothTransition) + { + EventId = 0; + MoveType = WAYPOINT_MOVE_TYPE_WALK; + EventChance = 100; + } + + uint32 Id; + float X, Y, Z; + std::optional Orientation; + float Velocity; + uint32 Delay; + uint32 EventId; + uint32 MoveType; + uint8 EventChance; + bool SmoothTransition; + std::vector SplinePoints; +}; + +struct WaypointPath +{ + WaypointPath() : Id(0) { } + WaypointPath(uint32 _id, std::vector&& _nodes) + { + Id = _id; + Nodes = _nodes; + } + + std::vector Nodes; + uint32 Id; +}; + +#endif diff --git a/src/server/game/Movement/Waypoints/WaypointMgr.cpp b/src/server/game/Movement/Waypoints/WaypointMgr.cpp index 5d673b35a..0d0334d73 100644 --- a/src/server/game/Movement/Waypoints/WaypointMgr.cpp +++ b/src/server/game/Movement/Waypoints/WaypointMgr.cpp @@ -22,20 +22,6 @@ #include "QueryResult.h" #include "Timer.h" -WaypointMgr::WaypointMgr() -{ -} - -WaypointMgr::~WaypointMgr() -{ - for (WaypointPathContainer::iterator itr = _waypointStore.begin(); itr != _waypointStore.end(); ++itr) - { - itr->second.clear(); - } - - _waypointStore.clear(); -} - WaypointMgr* WaypointMgr::instance() { static WaypointMgr instance; @@ -46,8 +32,8 @@ void WaypointMgr::Load() { uint32 oldMSTime = getMSTime(); - // 0 1 2 3 4 5 6 7 8 9 - QueryResult result = WorldDatabase.Query("SELECT id, point, position_x, position_y, position_z, orientation, move_type, delay, action, action_chance FROM waypoint_data ORDER BY id, point"); + // 0 1 2 3 4 5 6 7 8 9 10 11 + QueryResult result = WorldDatabase.Query("SELECT id, point, position_x, position_y, position_z, orientation, velocity, delay, smoothTransition, move_type, action, action_chance FROM waypoint_data ORDER BY id, point"); if (!result) { @@ -61,66 +47,113 @@ void WaypointMgr::Load() do { Field* fields = result->Fetch(); - WaypointData data; - uint32 pathId = fields[0].Get(); - WaypointPath& path = _waypointStore[pathId]; - float x = fields[2].Get(); float y = fields[3].Get(); float z = fields[4].Get(); - std::optional o; + std::optional o; if (!fields[5].IsNull()) o = fields[5].Get(); + float velocity = fields[6].Get(); + Acore::NormalizeMapCoord(x); Acore::NormalizeMapCoord(y); - data.id = fields[1].Get(); - data.x = x; - data.y = y; - data.z = z; - data.orientation = o; - data.move_type = fields[6].Get(); + WaypointNode waypoint; + waypoint.Id = fields[1].Get(); + waypoint.X = x; + waypoint.Y = y; + waypoint.Z = z; + if (o.has_value()) + waypoint.Orientation = o; + waypoint.Velocity = velocity; + waypoint.Delay = fields[7].Get(); + waypoint.SmoothTransition = fields[8].Get(); + waypoint.MoveType = fields[9].Get(); - if (data.move_type >= WAYPOINT_MOVE_TYPE_MAX) + if (waypoint.MoveType >= WAYPOINT_MOVE_TYPE_MAX) { - //LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", wp->id); + LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", waypoint.Id); continue; } - data.delay = fields[7].Get(); - data.event_id = fields[8].Get(); - data.event_chance = fields[9].Get(); + waypoint.EventId = fields[10].Get(); + waypoint.EventChance = fields[11].Get(); - path.emplace(data.id, data); + WaypointPath& path = _waypointStore[pathId]; + path.Id = pathId; + path.Nodes.push_back(std::move(waypoint)); ++count; } while (result->NextRow()); - for (auto itr = _waypointStore.begin(); itr != _waypointStore.end(); ) - { - uint32 first = itr->second.begin()->first; - uint32 last = itr->second.rbegin()->first; - if (last - first + 1 != itr->second.size()) - { - LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has non-contiguous pointids, skipping", itr->first); - itr = _waypointStore.erase(itr); - } - else - ++itr; - } - LOG_INFO("server.loading", ">> Loaded {} waypoints in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } +void WaypointMgr::LoadWaypointAddons() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 3 4 5 + QueryResult result = WorldDatabase.Query("SELECT PathID, PointID, SplinePointIndex, PositionX, PositionY, PositionZ FROM waypoint_data_addon ORDER BY PathID, PointID, SplinePointIndex"); + + if (!result) + { + LOG_INFO("server.loading", ">> Loaded 0 waypoint addon data. DB table `waypoint_data_addon` is empty!"); + LOG_INFO("server.loading", " "); + return; + } + + uint32 count = 0; + + do + { + Field* fields = result->Fetch(); + uint32 pathId = fields[0].Get(); + + auto it = _waypointStore.find(pathId); + if (it == _waypointStore.end()) + { + LOG_ERROR("sql.sql", "Tried to load waypoint_data_addon data for PathID {} but there is no such path in waypoint_data. Ignoring.", pathId); + continue; + } + + WaypointPath& path = it->second; + uint32 pointId = fields[1].Get(); + + auto itr = std::find_if(path.Nodes.begin(), path.Nodes.end(), [pointId](WaypointNode const& node) + { + return node.Id == pointId; + }); + + if (itr == path.Nodes.end()) + { + LOG_ERROR("sql.sql", "Tried to load waypoint_data_addon data for PointID {} of PathID {} but there is no such point in waypoint_data. Ignoring.", pointId, pathId); + continue; + } + + float x = fields[3].Get(); + float y = fields[4].Get(); + float z = fields[5].Get(); + + Acore::NormalizeMapCoord(x); + Acore::NormalizeMapCoord(y); + + itr->SplinePoints.push_back(G3D::Vector3(x, y, z)); + + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} waypoint addon data in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); +} + void WaypointMgr::ReloadPath(uint32 id) { - WaypointPathContainer::iterator itr = _waypointStore.find(id); + auto itr = _waypointStore.find(id); if (itr != _waypointStore.end()) - { _waypointStore.erase(itr); - } WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID); @@ -131,13 +164,10 @@ void WaypointMgr::ReloadPath(uint32 id) if (!result) return; - WaypointPath& path = _waypointStore[id]; - + std::vector values; do { Field* fields = result->Fetch(); - WaypointData data; - float x = fields[1].Get(); float y = fields[2].Get(); float z = fields[3].Get(); @@ -145,26 +175,34 @@ void WaypointMgr::ReloadPath(uint32 id) if (!fields[4].IsNull()) o = fields[4].Get(); + float velocity = fields[5].Get(); + Acore::NormalizeMapCoord(x); Acore::NormalizeMapCoord(y); - data.id = fields[0].Get(); - data.x = x; - data.y = y; - data.z = z; - data.orientation = o; - data.move_type = fields[5].Get(); + WaypointNode waypoint; + waypoint.Id = fields[0].Get(); + waypoint.X = x; + waypoint.Y = y; + waypoint.Z = z; + if (o.has_value()) + waypoint.Orientation = o; + waypoint.Velocity = velocity; + waypoint.Delay = fields[6].Get(); + waypoint.SmoothTransition = fields[7].Get(); + waypoint.MoveType = fields[8].Get(); - if (data.move_type >= WAYPOINT_MOVE_TYPE_MAX) + if (waypoint.MoveType >= WAYPOINT_MOVE_TYPE_MAX) { - //LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", wp->id); + LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", waypoint.Id); continue; } - data.delay = fields[6].Get(); - data.event_id = fields[7].Get(); - data.event_chance = fields[8].Get(); + waypoint.EventId = fields[9].Get(); + waypoint.EventChance = fields[10].Get(); - path.emplace(data.id, data); + values.push_back(std::move(waypoint)); } while (result->NextRow()); + + _waypointStore[id] = WaypointPath(id, std::move(values)); } diff --git a/src/server/game/Movement/Waypoints/WaypointMgr.h b/src/server/game/Movement/Waypoints/WaypointMgr.h index f9c0d59df..d6bb3ec21 100644 --- a/src/server/game/Movement/Waypoints/WaypointMgr.h +++ b/src/server/game/Movement/Waypoints/WaypointMgr.h @@ -18,35 +18,8 @@ #ifndef ACORE_WAYPOINTMANAGER_H #define ACORE_WAYPOINTMANAGER_H -#include "Define.h" -#include +#include "WaypointDefines.h" #include -#include -#include - -enum WaypointMoveType -{ - WAYPOINT_MOVE_TYPE_WALK, - WAYPOINT_MOVE_TYPE_RUN, - WAYPOINT_MOVE_TYPE_LAND, - WAYPOINT_MOVE_TYPE_TAKEOFF, - - WAYPOINT_MOVE_TYPE_MAX -}; - -struct WaypointData -{ - uint32 id; - float x, y, z; - std::optional orientation; - uint32 delay; - uint32 event_id = 0; - uint32 move_type = 0; - uint8 event_chance = 0; -}; - -typedef std::map WaypointPath; -typedef std::unordered_map WaypointPathContainer; class WaypointMgr { @@ -59,10 +32,13 @@ public: // Loads all paths from database, should only run on startup void Load(); + // Loads additional path data for waypoints from database. Should only be called on startup. + void LoadWaypointAddons(); + // Returns the path from a given id WaypointPath const* GetPath(uint32 id) const { - WaypointPathContainer::const_iterator itr = _waypointStore.find(id); + auto itr = _waypointStore.find(id); if (itr != _waypointStore.end()) return &itr->second; @@ -70,10 +46,9 @@ public: } private: - WaypointMgr(); - ~WaypointMgr(); + WaypointMgr() { } - WaypointPathContainer _waypointStore; + std::unordered_map _waypointStore; }; #define sWaypointMgr WaypointMgr::instance() diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 2f87bbb00..ffaf405cb 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -779,6 +779,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Waypoints..."); sWaypointMgr->Load(); + LOG_INFO("server.loading", "Loading Waypoint Addons..."); + sWaypointMgr->LoadWaypointAddons(); + LOG_INFO("server.loading", "Loading SmartAI Waypoints..."); sSmartWaypointMgr->LoadFromDB(); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp index 3409fe6fa..55f33f152 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp @@ -213,6 +213,7 @@ struct npc_grimstone : public npc_escortAI } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -568,6 +569,7 @@ struct npc_rocknot : public npc_escortAI go->SetGoState((GOState)state); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp index 484a0ada7..910f95557 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp @@ -176,6 +176,7 @@ public: void JustEngagedWith(Unit* /*who*/) override { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/EasternKingdoms/zone_arathi_highlands.cpp b/src/server/scripts/EasternKingdoms/zone_arathi_highlands.cpp index 1979f0d7f..c4ea03272 100644 --- a/src/server/scripts/EasternKingdoms/zone_arathi_highlands.cpp +++ b/src/server/scripts/EasternKingdoms/zone_arathi_highlands.cpp @@ -52,6 +52,7 @@ public: { npc_professor_phizzlethorpeAI(Creature* creature) : npc_escortAI(creature) { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/EasternKingdoms/zone_ghostlands.cpp b/src/server/scripts/EasternKingdoms/zone_ghostlands.cpp index 13d92bc96..365d7fe6b 100644 --- a/src/server/scripts/EasternKingdoms/zone_ghostlands.cpp +++ b/src/server/scripts/EasternKingdoms/zone_ghostlands.cpp @@ -45,6 +45,7 @@ struct npc_ranger_lilatha : public npc_escortAI { npc_ranger_lilatha(Creature* creature) : npc_escortAI(creature) { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/EasternKingdoms/zone_hinterlands.cpp b/src/server/scripts/EasternKingdoms/zone_hinterlands.cpp index 8d08ca387..08a111e59 100644 --- a/src/server/scripts/EasternKingdoms/zone_hinterlands.cpp +++ b/src/server/scripts/EasternKingdoms/zone_hinterlands.cpp @@ -137,6 +137,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp b/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp index e6c8fbeae..4d3072519 100644 --- a/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp +++ b/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp @@ -68,6 +68,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/EasternKingdoms/zone_silverpine_forest.cpp b/src/server/scripts/EasternKingdoms/zone_silverpine_forest.cpp index dcf642d0e..1147fdf04 100644 --- a/src/server/scripts/EasternKingdoms/zone_silverpine_forest.cpp +++ b/src/server/scripts/EasternKingdoms/zone_silverpine_forest.cpp @@ -55,6 +55,7 @@ public: { npc_deathstalker_erlandAI(Creature* creature) : npc_escortAI(creature) { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/EasternKingdoms/zone_stormwind_city.cpp b/src/server/scripts/EasternKingdoms/zone_stormwind_city.cpp index 2cfb08110..d06fd85d8 100644 --- a/src/server/scripts/EasternKingdoms/zone_stormwind_city.cpp +++ b/src/server/scripts/EasternKingdoms/zone_stormwind_city.cpp @@ -88,6 +88,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -318,6 +319,7 @@ public: uiPhase = 0; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/EasternKingdoms/zone_undercity.cpp b/src/server/scripts/EasternKingdoms/zone_undercity.cpp index 747a452b3..eab33d378 100644 --- a/src/server/scripts/EasternKingdoms/zone_undercity.cpp +++ b/src/server/scripts/EasternKingdoms/zone_undercity.cpp @@ -1423,6 +1423,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -2820,6 +2821,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/EasternKingdoms/zone_westfall.cpp b/src/server/scripts/EasternKingdoms/zone_westfall.cpp index 7e2bbb70f..ab89105c8 100644 --- a/src/server/scripts/EasternKingdoms/zone_westfall.cpp +++ b/src/server/scripts/EasternKingdoms/zone_westfall.cpp @@ -81,6 +81,7 @@ public: textCounter = SAY_DS_DOWN_1; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/EasternKingdoms/zone_wetlands.cpp b/src/server/scripts/EasternKingdoms/zone_wetlands.cpp index 62bbb72f7..85749ea1c 100644 --- a/src/server/scripts/EasternKingdoms/zone_wetlands.cpp +++ b/src/server/scripts/EasternKingdoms/zone_wetlands.cpp @@ -55,6 +55,7 @@ public: IsFriendSummoned = false; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp index 7165323ce..2ad76cf20 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp @@ -527,6 +527,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 uiPointId) override { switch (uiPointId) diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp index fc54b6215..0030f23bc 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp @@ -303,6 +303,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -1014,6 +1015,7 @@ public: Start(false); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (waypointId == 7) diff --git a/src/server/scripts/Kalimdor/RuinsOfAhnQiraj/boss_rajaxx.cpp b/src/server/scripts/Kalimdor/RuinsOfAhnQiraj/boss_rajaxx.cpp index bcae88f33..8286e98e3 100644 --- a/src/server/scripts/Kalimdor/RuinsOfAhnQiraj/boss_rajaxx.cpp +++ b/src/server/scripts/Kalimdor/RuinsOfAhnQiraj/boss_rajaxx.cpp @@ -227,6 +227,7 @@ struct npc_general_andorov : public npc_escortAI events.ScheduleEvent(EVENT_STRIKE, 2s, 5s); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/Kalimdor/zone_ashenvale.cpp b/src/server/scripts/Kalimdor/zone_ashenvale.cpp index 5fee325ec..50b7dccef 100644 --- a/src/server/scripts/Kalimdor/zone_ashenvale.cpp +++ b/src/server/scripts/Kalimdor/zone_ashenvale.cpp @@ -126,6 +126,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (Player* player = GetPlayerForEscort()) diff --git a/src/server/scripts/Kalimdor/zone_azuremyst_isle.cpp b/src/server/scripts/Kalimdor/zone_azuremyst_isle.cpp index 2316a2e33..9779fb315 100644 --- a/src/server/scripts/Kalimdor/zone_azuremyst_isle.cpp +++ b/src/server/scripts/Kalimdor/zone_azuremyst_isle.cpp @@ -246,6 +246,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (Player* player = GetPlayerForEscort()) diff --git a/src/server/scripts/Kalimdor/zone_darkshore.cpp b/src/server/scripts/Kalimdor/zone_darkshore.cpp index c2a452f18..7b40eac34 100644 --- a/src/server/scripts/Kalimdor/zone_darkshore.cpp +++ b/src/server/scripts/Kalimdor/zone_darkshore.cpp @@ -330,6 +330,7 @@ public: // pSummoned->AI()->AttackStart(me); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (Player* player = GetPlayerForEscort()) diff --git a/src/server/scripts/Kalimdor/zone_desolace.cpp b/src/server/scripts/Kalimdor/zone_desolace.cpp index aea34b3c7..649b4adea 100644 --- a/src/server/scripts/Kalimdor/zone_desolace.cpp +++ b/src/server/scripts/Kalimdor/zone_desolace.cpp @@ -257,6 +257,7 @@ public: me->SetFaction(faction); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { RelocateSummons(); diff --git a/src/server/scripts/Kalimdor/zone_moonglade.cpp b/src/server/scripts/Kalimdor/zone_moonglade.cpp index d521c6693..bc1538d2c 100644 --- a/src/server/scripts/Kalimdor/zone_moonglade.cpp +++ b/src/server/scripts/Kalimdor/zone_moonglade.cpp @@ -553,6 +553,7 @@ public: else if (EventOnWait) EventTimer -= diff; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { CurrWP = waypointId; diff --git a/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp b/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp index 3392248da..2aa681190 100644 --- a/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp +++ b/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp @@ -102,6 +102,7 @@ public: { npc_kaya_flathoofAI(Creature* creature) : npc_escortAI(creature) {} + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/Kalimdor/zone_tanaris.cpp b/src/server/scripts/Kalimdor/zone_tanaris.cpp index 602d1ea43..4a995ca46 100644 --- a/src/server/scripts/Kalimdor/zone_tanaris.cpp +++ b/src/server/scripts/Kalimdor/zone_tanaris.cpp @@ -168,6 +168,7 @@ public: { npc_custodian_of_timeAI(Creature* creature) : npc_escortAI(creature) { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (Player* player = GetPlayerForEscort()) diff --git a/src/server/scripts/Kalimdor/zone_the_barrens.cpp b/src/server/scripts/Kalimdor/zone_the_barrens.cpp index fa4eabc52..8fe236fe5 100644 --- a/src/server/scripts/Kalimdor/zone_the_barrens.cpp +++ b/src/server/scripts/Kalimdor/zone_the_barrens.cpp @@ -74,6 +74,7 @@ public: void Reset() override { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); @@ -494,6 +495,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/Kalimdor/zone_thousand_needles.cpp b/src/server/scripts/Kalimdor/zone_thousand_needles.cpp index ac747b38b..9c60689c5 100644 --- a/src/server/scripts/Kalimdor/zone_thousand_needles.cpp +++ b/src/server/scripts/Kalimdor/zone_thousand_needles.cpp @@ -85,6 +85,7 @@ public: void Reset() override { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -170,6 +171,7 @@ public: void Reset() override { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/Kalimdor/zone_ungoro_crater.cpp b/src/server/scripts/Kalimdor/zone_ungoro_crater.cpp index 885ddac1f..ff4845c7e 100644 --- a/src/server/scripts/Kalimdor/zone_ungoro_crater.cpp +++ b/src/server/scripts/Kalimdor/zone_ungoro_crater.cpp @@ -69,6 +69,7 @@ public: uint32 DemoralizingShoutTimer; + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (Player* player = GetPlayerForEscort()) diff --git a/src/server/scripts/Kalimdor/zone_winterspring.cpp b/src/server/scripts/Kalimdor/zone_winterspring.cpp index 2130ce14e..4da2a9f6a 100644 --- a/src/server/scripts/Kalimdor/zone_winterspring.cpp +++ b/src/server/scripts/Kalimdor/zone_winterspring.cpp @@ -383,6 +383,7 @@ public: StartNextDialogueText(SAY_PRIESTESS_ALTAR_3); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 pointId) override { switch (pointId) diff --git a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp index fb6b32d03..faf314e6f 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp @@ -233,6 +233,7 @@ public: zarithrian->AI()->JustSummoned(me); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (waypointId == MAX_PATH_FLAMECALLER_WAYPOINTS) diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_argent_challenge.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_argent_challenge.cpp index 8d1bc1864..4f8d0fcea 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_argent_challenge.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_argent_challenge.cpp @@ -519,6 +519,7 @@ public: bCheck = false; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 uiPoint) override { if (uiPoint == 1) diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_black_knight.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_black_knight.cpp index 99d69be84..6b3ac0d9e 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_black_knight.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_black_knight.cpp @@ -340,6 +340,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 i) override { if (i == 12) diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_grand_champions.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_grand_champions.cpp index 374f1d2b5..d1cd70bd0 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_grand_champions.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_grand_champions.cpp @@ -592,6 +592,7 @@ public: void EnterEvadeMode(EvadeReason /*why*/) override {} + using CreatureAI::WaypointReached; void WaypointReached(uint32 i) override { if (!pInstance) diff --git a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp index 90e53cf4a..77f6c7069 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp @@ -791,6 +791,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp index cd8c36c9e..55ea322f0 100644 --- a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp +++ b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp @@ -267,6 +267,7 @@ struct boss_bjarngrim : public npc_escortAI m_uiStance = stance; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 Point) override { if (Point == 1) diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp index 7d5f74059..41f9cf1f0 100644 --- a/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp +++ b/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp @@ -312,6 +312,7 @@ public: TalkEvent = false; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 id) override; void InitializeEvent(); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp index 7b1728615..550beac7f 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp @@ -974,6 +974,7 @@ struct npc_mimirons_inferno : public npc_escortAI void AttackStart(Unit*) override { } void MoveInLineOfSight(Unit*) override { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 /*waypointId*/) override { } void DoAction(int32 param) override diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp index 4fc2897f6..a73aafa68 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp @@ -895,6 +895,7 @@ struct boss_thorim_lightning_orb : public npc_escortAI me->CastSpell(me, SPELL_LIGHTNING_DESTRUCTION, true); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 /*point*/) override { } @@ -960,6 +961,7 @@ struct boss_thorim_sif_blizzard : public npc_escortAI me->CastSpell(me, SPELL_BLIZZARD, true); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 /*point*/) override { } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp index f0f759e74..407793986 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp @@ -934,6 +934,7 @@ struct boss_yoggsaron_cloud : public npc_escortAI void MoveInLineOfSight(Unit* /*who*/) override {} void AttackStart(Unit* /*who*/) override {} + using CreatureAI::WaypointReached; void WaypointReached(uint32 /*point*/) override {} void Reset() override diff --git a/src/server/scripts/Northrend/VioletHold/violet_hold.cpp b/src/server/scripts/Northrend/VioletHold/violet_hold.cpp index 4d1bc0924..c805c43f0 100644 --- a/src/server/scripts/Northrend/VioletHold/violet_hold.cpp +++ b/src/server/scripts/Northrend/VioletHold/violet_hold.cpp @@ -312,6 +312,7 @@ struct violet_hold_trashAI : public npc_escortAI CreatureStartAttackDoor(); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 id) override { if (PLoc < 6) @@ -1021,6 +1022,7 @@ public: uint32 timer; uint8 count; + using CreatureAI::WaypointReached; void WaypointReached(uint32 uiWPointId) override { if (!pInstance) diff --git a/src/server/scripts/Northrend/zone_borean_tundra.cpp b/src/server/scripts/Northrend/zone_borean_tundra.cpp index af5491db7..a3cab28af 100644 --- a/src/server/scripts/Northrend/zone_borean_tundra.cpp +++ b/src/server/scripts/Northrend/zone_borean_tundra.cpp @@ -377,6 +377,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -752,6 +753,7 @@ public: player->FailQuest(QUEST_ESCAPING_THE_MIST); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); @@ -849,6 +851,7 @@ public: else Bonker_agro = 0; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/Northrend/zone_grizzly_hills.cpp b/src/server/scripts/Northrend/zone_grizzly_hills.cpp index bccd217ad..c83884871 100644 --- a/src/server/scripts/Northrend/zone_grizzly_hills.cpp +++ b/src/server/scripts/Northrend/zone_grizzly_hills.cpp @@ -75,6 +75,7 @@ public: summoned->AI()->AttackStart(me->GetVictim()); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/Northrend/zone_howling_fjord.cpp b/src/server/scripts/Northrend/zone_howling_fjord.cpp index 928a0a71e..731f022e9 100644 --- a/src/server/scripts/Northrend/zone_howling_fjord.cpp +++ b/src/server/scripts/Northrend/zone_howling_fjord.cpp @@ -126,6 +126,7 @@ public: DoMeleeAttackIfReady(); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); @@ -210,6 +211,7 @@ public: Start(false, summonerGUID); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { if (waypointId != 26) diff --git a/src/server/scripts/Northrend/zone_icecrown.cpp b/src/server/scripts/Northrend/zone_icecrown.cpp index 9afba1804..639991ba5 100644 --- a/src/server/scripts/Northrend/zone_icecrown.cpp +++ b/src/server/scripts/Northrend/zone_icecrown.cpp @@ -747,6 +747,7 @@ public: summons.Despawn(summon); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 pointId) override { switch (pointId) diff --git a/src/server/scripts/Northrend/zone_sholazar_basin.cpp b/src/server/scripts/Northrend/zone_sholazar_basin.cpp index 4af05b4b9..a9de0bf08 100644 --- a/src/server/scripts/Northrend/zone_sholazar_basin.cpp +++ b/src/server/scripts/Northrend/zone_sholazar_basin.cpp @@ -521,6 +521,7 @@ public: uint32 m_uiChatTimer; + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp index 7f3c5873a..b94dabc73 100644 --- a/src/server/scripts/Northrend/zone_storm_peaks.cpp +++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp @@ -73,6 +73,7 @@ struct npc_frosthound : public npc_escortAI return; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); @@ -568,6 +569,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 /*waypointId*/) override { } void JustDied(Unit* /*killer*/) override { } void OnCharmed(bool /*apply*/) override { } diff --git a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp index f281cae2c..140ef72fe 100644 --- a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp +++ b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp @@ -195,6 +195,7 @@ public: npc_escortAI::MoveInLineOfSight(who); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -320,6 +321,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/Outland/zone_nagrand.cpp b/src/server/scripts/Outland/zone_nagrand.cpp index 609391185..fbef4231c 100644 --- a/src/server/scripts/Outland/zone_nagrand.cpp +++ b/src/server/scripts/Outland/zone_nagrand.cpp @@ -114,6 +114,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) @@ -340,6 +341,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { switch (waypointId) diff --git a/src/server/scripts/Outland/zone_netherstorm.cpp b/src/server/scripts/Outland/zone_netherstorm.cpp index 5136e89dd..9c3cf495e 100644 --- a/src/server/scripts/Outland/zone_netherstorm.cpp +++ b/src/server/scripts/Outland/zone_netherstorm.cpp @@ -164,6 +164,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 i) override { Player* player = GetPlayerForEscort(); @@ -626,6 +627,7 @@ public: player->FailQuest(Q_ALMABTRIEB); } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); @@ -709,6 +711,7 @@ public: uiTakeTimer = 3000; } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/Outland/zone_shattrath_city.cpp b/src/server/scripts/Outland/zone_shattrath_city.cpp index 0cd938e3b..4447b2797 100644 --- a/src/server/scripts/Outland/zone_shattrath_city.cpp +++ b/src/server/scripts/Outland/zone_shattrath_city.cpp @@ -161,6 +161,7 @@ public: public: npc_kservantAI(Creature* creature) : npc_escortAI(creature) { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/Outland/zone_terokkar_forest.cpp b/src/server/scripts/Outland/zone_terokkar_forest.cpp index 63f623cd5..d58ff430b 100644 --- a/src/server/scripts/Outland/zone_terokkar_forest.cpp +++ b/src/server/scripts/Outland/zone_terokkar_forest.cpp @@ -374,6 +374,7 @@ public: { npc_isla_starmaneAI(Creature* creature) : npc_escortAI(creature) { } + using CreatureAI::WaypointReached; void WaypointReached(uint32 waypointId) override { Player* player = GetPlayerForEscort(); diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index ba6deb25d..3e7ebb34e 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -1356,6 +1356,7 @@ public: } } + using CreatureAI::WaypointReached; void WaypointReached(uint32 /*waypointId*/) override { } From 03ad0e2a9373040a18ee953eec744d49ca3b95fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Mar 2026 13:09:24 +0000 Subject: [PATCH 062/132] chore(DB): import pending files Referenced commit(s): 4201acddd5ec4da85403a4bdbd045bd2319f6562 --- .../2026_03_23_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_smooth_waypoint_movement.sql => db_world/2026_03_23_01.sql} (93%) diff --git a/data/sql/updates/pending_db_world/rev_smooth_waypoint_movement.sql b/data/sql/updates/db_world/2026_03_23_01.sql similarity index 93% rename from data/sql/updates/pending_db_world/rev_smooth_waypoint_movement.sql rename to data/sql/updates/db_world/2026_03_23_01.sql index 085777f2d..eb0d4e96c 100644 --- a/data/sql/updates/pending_db_world/rev_smooth_waypoint_movement.sql +++ b/data/sql/updates/db_world/2026_03_23_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_23_00 -> 2026_03_23_01 -- Add velocity and smoothTransition columns to waypoint_data ALTER TABLE `waypoint_data` ADD COLUMN `velocity` FLOAT NOT NULL DEFAULT 0 AFTER `orientation`, From c682c1d29abec6bd1912c6b7ff85c85aff60823e Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:10:38 -0300 Subject: [PATCH 063/132] fix(DB/Gossip): convert npc_slim gossip to database conditions (#25191) Co-authored-by: Claude Opus 4.6 (1M context) --- .../rev_1774213536815228000.sql | 22 +++++++++++ .../scripts/Outland/zone_terokkar_forest.cpp | 38 ------------------- 2 files changed, 22 insertions(+), 38 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774213536815228000.sql diff --git a/data/sql/updates/pending_db_world/rev_1774213536815228000.sql b/data/sql/updates/pending_db_world/rev_1774213536815228000.sql new file mode 100644 index 000000000..32639687f --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774213536815228000.sql @@ -0,0 +1,22 @@ +-- Convert npc_slim (entry 19679) from C++ script to database gossip + conditions + +-- Remove ScriptName from creature_template +UPDATE `creature_template` SET `ScriptName` = '' WHERE `entry` = 19679; + +-- Add gossip_menu entry for non-friendly text (9895) +DELETE FROM `gossip_menu` WHERE `MenuID` = 8021 AND `TextID` = 9895; +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES (8021, 9895); + +-- Add conditions for gossip_menu text display +-- Show text 9896 (vendor greeting) only if player is Friendly+ with Consortium (faction 933) +-- Friendly(16) + Honored(32) + Revered(64) + Exalted(128) = 240 +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 14 AND `SourceGroup` = 8021 AND `SourceEntry` IN (9895, 9896); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 8021, 9896, 0, 0, 5, 0, 933, 240, 0, 0, 0, 0, '', 'Gossip Menu 8021 - Show text 9896 if player is Friendly+ with Consortium'), +(14, 8021, 9895, 0, 0, 5, 0, 933, 240, 0, 1, 0, 0, '', 'Gossip Menu 8021 - Show text 9895 if player is not Friendly+ with Consortium'); + +-- Add condition for gossip_menu_option vendor option +-- Show vendor option only if player is Friendly+ with Consortium +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 15 AND `SourceGroup` = 8021 AND `SourceEntry` = 0; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8021, 0, 0, 0, 5, 0, 933, 240, 0, 0, 0, 0, '', 'Gossip Option 8021/0 - Show vendor option if player is Friendly+ with Consortium'); diff --git a/src/server/scripts/Outland/zone_terokkar_forest.cpp b/src/server/scripts/Outland/zone_terokkar_forest.cpp index d58ff430b..0d032ce14 100644 --- a/src/server/scripts/Outland/zone_terokkar_forest.cpp +++ b/src/server/scripts/Outland/zone_terokkar_forest.cpp @@ -613,43 +613,6 @@ public: } }; -/*###### -## npc_slim -######*/ - -enum Slim -{ - FACTION_CONSORTIUM = 933 -}; - -class npc_slim : public CreatureScript -{ -public: - npc_slim() : CreatureScript("npc_slim") { } - - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override - { - ClearGossipMenuFor(player); - if (action == GOSSIP_ACTION_TRADE) - player->GetSession()->SendListInventory(creature->GetGUID()); - - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - if (creature->IsVendor() && player->GetReputationRank(FACTION_CONSORTIUM) >= REP_FRIENDLY) - { - AddGossipItemFor(player, GOSSIP_ICON_VENDOR, GOSSIP_TEXT_BROWSE_GOODS, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_TRADE); - SendGossipMenuFor(player, 9896, creature->GetGUID()); - } - else - SendGossipMenuFor(player, 9895, creature->GetGUID()); - - return true; - } -}; - void AddSC_terokkar_forest() { RegisterSpellAndAuraScriptPair(spell_q10930_big_bone_worm, spell_q10930_big_bone_worm_aura); @@ -663,5 +626,4 @@ void AddSC_terokkar_forest() new npc_isla_starmane(); new go_skull_pile(); new go_ancient_skull_pile(); - new npc_slim(); } From cfeb6d85517aba1492d7ddacdfc73b9fa770b4af Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Mar 2026 19:11:51 +0000 Subject: [PATCH 064/132] chore(DB): import pending files Referenced commit(s): c682c1d29abec6bd1912c6b7ff85c85aff60823e --- .../rev_1774213536815228000.sql => db_world/2026_03_23_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774213536815228000.sql => db_world/2026_03_23_02.sql} (97%) diff --git a/data/sql/updates/pending_db_world/rev_1774213536815228000.sql b/data/sql/updates/db_world/2026_03_23_02.sql similarity index 97% rename from data/sql/updates/pending_db_world/rev_1774213536815228000.sql rename to data/sql/updates/db_world/2026_03_23_02.sql index 32639687f..d72285871 100644 --- a/data/sql/updates/pending_db_world/rev_1774213536815228000.sql +++ b/data/sql/updates/db_world/2026_03_23_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_23_01 -> 2026_03_23_02 -- Convert npc_slim (entry 19679) from C++ script to database gossip + conditions -- Remove ScriptName from creature_template From dc10485d8acd03cd7d0bbcedb02d96ed56fe6c57 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:45:55 -0300 Subject: [PATCH 065/132] refactor(Core/Scripts): move npc_maredis_firestar to DB gossip (#25192) Co-authored-by: Claude Opus 4.6 (1M context) --- .../rev_1774217677322848400.sql | 22 +++++ .../eastern_kingdoms_script_loader.cpp | 4 +- .../EasternKingdoms/zone_burning_steppes.cpp | 92 ------------------- 3 files changed, 24 insertions(+), 94 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774217677322848400.sql delete mode 100644 src/server/scripts/EasternKingdoms/zone_burning_steppes.cpp diff --git a/data/sql/updates/pending_db_world/rev_1774217677322848400.sql b/data/sql/updates/pending_db_world/rev_1774217677322848400.sql new file mode 100644 index 000000000..74e16fcd3 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774217677322848400.sql @@ -0,0 +1,22 @@ +-- Move npc_maredis_firestar (Mathredis Firestar, entry 9836) from C++ script to DB gossip + +-- Remove C++ ScriptName +UPDATE `creature_template` SET `ScriptName`='' WHERE `entry`=9836; + +-- Move gossip options from sub-menus (2299-2303) to the NPC's main menu (2298) with conditions +DELETE FROM `gossip_menu_option` WHERE `MenuID` IN (2298,2299,2300,2301,2302,2303); +INSERT INTO `gossip_menu_option` (`MenuID`,`OptionID`,`OptionIcon`,`OptionText`,`OptionBroadcastTextID`,`OptionType`,`OptionNpcFlag`,`ActionMenuID`,`ActionPoiID`,`BoxCoded`,`BoxMoney`,`BoxText`,`BoxBroadcastTextID`,`VerifiedBuild`) VALUES +(2298,0,0,'I present to you the Libram of Rumination.',5291,1,1,2299,0,0,0,'',0,0), +(2298,1,0,'I present to you the Libram of Constitution.',5416,1,1,2300,0,0,0,'',0,0), +(2298,2,0,'I present to you the Libram of Tenacity.',5417,1,1,2301,0,0,0,'',0,0), +(2298,3,0,'I present to you the Libram of Resilience.',5418,1,1,2302,0,0,0,'',0,0), +(2298,4,0,'I present to you the Libram of Voracity.',5419,1,1,2303,0,0,0,'',0,0); + +-- Add conditions: show each option only if player has the corresponding Libram +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=15 AND `SourceGroup`=2298; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`,`SourceGroup`,`SourceEntry`,`SourceId`,`ElseGroup`,`ConditionTypeOrReference`,`ConditionTarget`,`ConditionValue1`,`ConditionValue2`,`ConditionValue3`,`NegativeCondition`,`ErrorType`,`ErrorTextId`,`ScriptName`,`Comment`) VALUES +(15,2298,0,0,0,2,0,11732,1,0,0,0,0,'','Show gossip option if player has Libram of Rumination'), +(15,2298,1,0,0,2,0,11733,1,0,0,0,0,'','Show gossip option if player has Libram of Constitution'), +(15,2298,2,0,0,2,0,11734,1,0,0,0,0,'','Show gossip option if player has Libram of Tenacity'), +(15,2298,3,0,0,2,0,11736,1,0,0,0,0,'','Show gossip option if player has Libram of Resilience'), +(15,2298,4,0,0,2,0,11737,1,0,0,0,0,'','Show gossip option if player has Libram of Voracity'); diff --git a/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp b/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp index 1101b999b..9555f853e 100644 --- a/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp +++ b/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp @@ -162,7 +162,7 @@ void AddSC_undercity(); void AddSC_western_plaguelands(); void AddSC_westfall(); //void AddSC_wetlands(); -void AddSC_burning_steppes(); +//void AddSC_burning_steppes(); // The name of this function should match: // void Add${NameOfDirectory}Scripts() @@ -314,5 +314,5 @@ void AddEasternKingdomsScripts() AddSC_western_plaguelands(); AddSC_westfall(); //AddSC_wetlands(); - AddSC_burning_steppes(); + //AddSC_burning_steppes(); } diff --git a/src/server/scripts/EasternKingdoms/zone_burning_steppes.cpp b/src/server/scripts/EasternKingdoms/zone_burning_steppes.cpp deleted file mode 100644 index 308ac6608..000000000 --- a/src/server/scripts/EasternKingdoms/zone_burning_steppes.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "CreatureScript.h" -#include "Player.h" -#include "ScriptedCreature.h" - -/*###### -## NPC 9836: Maredis Firestar -######*/ - -enum MaredisFirestar -{ - ITEM_LIBRAM_OF_RUMINATION = 11732, - ITEM_LIBRAM_OF_CONSTITUTION = 11733, - ITEM_LIBRAM_OF_TENACITY = 11734, - ITEM_LIBRAM_OF_RESILIENCE = 11736, - ITEM_LIBRAM_OF_VORACITY = 11737, - GOSSIP_LIBRAM_OF_RUMINATION = 2299, - GOSSIP_LIBRAM_OF_CONSTITUTION = 2300, - GOSSIP_LIBRAM_OF_TENACITY = 2301, - GOSSIP_LIBRAM_OF_RESILIENCE = 2302, - GOSSIP_LIBRAM_OF_VORACITY = 2303 -}; - -class npc_maredis_firestar : public CreatureScript -{ -public: - npc_maredis_firestar() : CreatureScript("npc_maredis_firestar") {} - - struct npc_maredis_firestarAI : public CreatureAI - { - npc_maredis_firestarAI(Creature* creature) : CreatureAI(creature) {} - - void sGossipHello(Player* player) override - { - // If player has 2 different librams on him he will only see top most one. - // Count is default 1. In bank is default false. - if (player->HasItemCount(ITEM_LIBRAM_OF_RUMINATION)) - { - player->PrepareGossipMenu(me, GOSSIP_LIBRAM_OF_RUMINATION); - player->SendPreparedGossip(me); - } - else if (player->HasItemCount(ITEM_LIBRAM_OF_CONSTITUTION)) - { - player->PrepareGossipMenu(me, GOSSIP_LIBRAM_OF_CONSTITUTION); - player->SendPreparedGossip(me); - } - else if (player->HasItemCount(ITEM_LIBRAM_OF_TENACITY)) - { - player->PrepareGossipMenu(me, GOSSIP_LIBRAM_OF_TENACITY); - player->SendPreparedGossip(me); - } - else if (player->HasItemCount(ITEM_LIBRAM_OF_RESILIENCE)) - { - player->PrepareGossipMenu(me, GOSSIP_LIBRAM_OF_RESILIENCE); - player->SendPreparedGossip(me); - } - else if (player->HasItemCount(ITEM_LIBRAM_OF_VORACITY)) - { - player->PrepareGossipMenu(me, GOSSIP_LIBRAM_OF_VORACITY); - player->SendPreparedGossip(me); - } - } - - void sGossipSelect(Player* player, uint32 /*sender*/, uint32 /*action*/) override - { - // All gossip menus only have one option. Conditions are handled in db. - player->PrepareQuestMenu(me->GetGUID()); - player->SendPreparedQuest(me->GetGUID()); - } - }; -}; - -void AddSC_burning_steppes() -{ - new npc_maredis_firestar(); -} From f1ea0a57b4e446ae55f816ad040678e5ede02020 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Mar 2026 00:47:10 +0000 Subject: [PATCH 066/132] chore(DB): import pending files Referenced commit(s): dc10485d8acd03cd7d0bbcedb02d96ed56fe6c57 --- .../rev_1774217677322848400.sql => db_world/2026_03_24_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774217677322848400.sql => db_world/2026_03_24_00.sql} (97%) diff --git a/data/sql/updates/pending_db_world/rev_1774217677322848400.sql b/data/sql/updates/db_world/2026_03_24_00.sql similarity index 97% rename from data/sql/updates/pending_db_world/rev_1774217677322848400.sql rename to data/sql/updates/db_world/2026_03_24_00.sql index 74e16fcd3..6f0246d8c 100644 --- a/data/sql/updates/pending_db_world/rev_1774217677322848400.sql +++ b/data/sql/updates/db_world/2026_03_24_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_23_02 -> 2026_03_24_00 -- Move npc_maredis_firestar (Mathredis Firestar, entry 9836) from C++ script to DB gossip -- Remove C++ ScriptName From 066cd8bbd99c79a6979f03a026e579e28da1ff91 Mon Sep 17 00:00:00 2001 From: lightninjay Date: Mon, 23 Mar 2026 22:05:46 -0400 Subject: [PATCH 067/132] fix(DB/SAI): "Klaven's Tower" Defias Tower Patroller/Sentry Backstab/Ambush fix (#25200) --- .../DefiasTowerPatroller_smart_ai.sql | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 data/sql/updates/pending_db_world/DefiasTowerPatroller_smart_ai.sql diff --git a/data/sql/updates/pending_db_world/DefiasTowerPatroller_smart_ai.sql b/data/sql/updates/pending_db_world/DefiasTowerPatroller_smart_ai.sql new file mode 100644 index 000000000..fcf6343a6 --- /dev/null +++ b/data/sql/updates/pending_db_world/DefiasTowerPatroller_smart_ai.sql @@ -0,0 +1,56 @@ +-- Defias Tower Patroller smart ai +SET @ENTRY := 7052; +UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = @ENTRY; +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = @ENTRY); +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 +(@ENTRY, 0, 0, 0, 0, 0, 100, 0, 3100, 4700, 8600, 10100, 0, 0, 11, 5679, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - In Combat - Cast \'Torch Burn\''), +(@ENTRY, 0, 1, 0, 8, 0, 100, 0, 53, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (53)\' - Kill Self'), +(@ENTRY, 0, 2, 0, 8, 0, 100, 0, 2589, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (2589)\' - Kill Self'), +(@ENTRY, 0, 3, 0, 8, 0, 100, 0, 2590, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (2590)\' - Kill Self'), +(@ENTRY, 0, 4, 0, 8, 0, 100, 0, 2591, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (2591)\' - Kill Self'), +(@ENTRY, 0, 5, 0, 8, 0, 100, 0, 8721, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (8721)\' - Kill Self'), +(@ENTRY, 0, 6, 0, 8, 0, 100, 0, 11279, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (11279)\' - Kill Self'), +(@ENTRY, 0, 7, 0, 8, 0, 100, 0, 11280, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (11280)\' - Kill Self'), +(@ENTRY, 0, 8, 0, 8, 0, 100, 0, 11281, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (11281)\' - Kill Self'), +(@ENTRY, 0, 9, 0, 8, 0, 100, 0, 25300, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (25300)\' - Kill Self'), +(@ENTRY, 0, 10, 0, 8, 0, 100, 0, 26863, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (26863)\' - Kill Self'), +(@ENTRY, 0, 11, 0, 8, 0, 100, 0, 48656, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (48656)\' - Kill Self'), +(@ENTRY, 0, 12, 0, 8, 0, 100, 0, 48657, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Backstab (48657)\' - Kill Self'), +(@ENTRY, 0, 13, 0, 8, 0, 100, 0, 8676, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (8676)\' - Kill Self'), +(@ENTRY, 0, 14, 0, 8, 0, 100, 0, 8724, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (8724)\' - Kill Self'), +(@ENTRY, 0, 15, 0, 8, 0, 100, 0, 8725, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (8725)\' - Kill Self'), +(@ENTRY, 0, 16, 0, 8, 0, 100, 0, 11267, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (11267)\' - Kill Self'), +(@ENTRY, 0, 17, 0, 8, 0, 100, 0, 11268, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (11268)\' - Kill Self'), +(@ENTRY, 0, 18, 0, 8, 0, 100, 0, 11269, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (11269)\' - Kill Self'), +(@ENTRY, 0, 19, 0, 8, 0, 100, 0, 27441, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (27441)\' - Kill Self'), +(@ENTRY, 0, 20, 0, 8, 0, 100, 0, 48689, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Patroller - On Spellhit \'Ambush (48689)\' - Kill Self'); + +-- Defias Tower Sentry smart ai +SET @ENTRY := 7056; +UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = @ENTRY; +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = @ENTRY); +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 +(@ENTRY, 0, 0, 0, 1, 0, 100, 1, 1000, 1500, 0, 0, 0, 0, 11, 22766, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - Out of Combat - Cast \'Sneak\' (No Repeat)'), +(@ENTRY, 0, 1, 0, 8, 0, 100, 0, 53, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (53)\' - Kill Self'), +(@ENTRY, 0, 2, 0, 8, 0, 100, 0, 2589, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (2589)\' - Kill Self'), +(@ENTRY, 0, 3, 0, 8, 0, 100, 0, 2590, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (2590)\' - Kill Self'), +(@ENTRY, 0, 4, 0, 8, 0, 100, 0, 2591, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (2591)\' - Kill Self'), +(@ENTRY, 0, 5, 0, 8, 0, 100, 0, 8721, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (8721)\' - Kill Self'), +(@ENTRY, 0, 6, 0, 8, 0, 100, 0, 11279, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (11279)\' - Kill Self'), +(@ENTRY, 0, 7, 0, 8, 0, 100, 0, 11280, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (11280)\' - Kill Self'), +(@ENTRY, 0, 8, 0, 8, 0, 100, 0, 11281, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (11281)\' - Kill Self'), +(@ENTRY, 0, 9, 0, 8, 0, 100, 0, 25300, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (25300)\' - Kill Self'), +(@ENTRY, 0, 10, 0, 8, 0, 100, 0, 26863, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (26863)\' - Kill Self'), +(@ENTRY, 0, 11, 0, 8, 0, 100, 0, 48656, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (48656)\' - Kill Self'), +(@ENTRY, 0, 12, 0, 8, 0, 100, 0, 48657, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Backstab (48657)\' - Kill Self'), +(@ENTRY, 0, 13, 0, 8, 0, 100, 0, 8676, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (8676)\' - Kill Self'), +(@ENTRY, 0, 14, 0, 8, 0, 100, 0, 8724, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (8724)\' - Kill Self'), +(@ENTRY, 0, 15, 0, 8, 0, 100, 0, 8725, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (8725)\' - Kill Self'), +(@ENTRY, 0, 16, 0, 8, 0, 100, 0, 11267, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (11267)\' - Kill Self'), +(@ENTRY, 0, 17, 0, 8, 0, 100, 0, 11268, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (11268)\' - Kill Self'), +(@ENTRY, 0, 18, 0, 8, 0, 100, 0, 11269, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (11269)\' - Kill Self'), +(@ENTRY, 0, 19, 0, 8, 0, 100, 0, 27441, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (27441)\' - Kill Self'), +(@ENTRY, 0, 20, 0, 8, 0, 100, 0, 48689, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Defias Tower Sentry - On Spellhit \'Ambush (48689)\' - Kill Self'); + +-- Remove Conditions +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 22 AND (`SourceEntry` IN (7052, 7056)) AND `SourceId` = 0; From eaf97ce95495757dbed8bdb2661a4b7ec15885b3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Mar 2026 02:06:58 +0000 Subject: [PATCH 068/132] chore(DB): import pending files Referenced commit(s): 066cd8bbd99c79a6979f03a026e579e28da1ff91 --- .../2026_03_24_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/DefiasTowerPatroller_smart_ai.sql => db_world/2026_03_24_01.sql} (99%) diff --git a/data/sql/updates/pending_db_world/DefiasTowerPatroller_smart_ai.sql b/data/sql/updates/db_world/2026_03_24_01.sql similarity index 99% rename from data/sql/updates/pending_db_world/DefiasTowerPatroller_smart_ai.sql rename to data/sql/updates/db_world/2026_03_24_01.sql index fcf6343a6..5cc234b7e 100644 --- a/data/sql/updates/pending_db_world/DefiasTowerPatroller_smart_ai.sql +++ b/data/sql/updates/db_world/2026_03_24_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_24_00 -> 2026_03_24_01 -- Defias Tower Patroller smart ai SET @ENTRY := 7052; UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = @ENTRY; From 8ff031dbd736bed3fe0269127d9706bed780a1d7 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 24 Mar 2026 18:43:32 -0300 Subject: [PATCH 069/132] fix(Scripts/UP): rework Skadi the Ruthless encounter (#25195) Co-authored-by: Gustavo Co-authored-by: Claude Opus 4.6 (1M context) --- .../rev_1774220399272155500.sql | 198 ++++ .../game/Spells/SpellInfoCorrections.cpp | 6 - .../UtgardePinnacle/boss_skadi.cpp | 913 +++++++++++------- .../instance_utgarde_pinnacle.cpp | 10 + .../UtgardePinnacle/utgarde_pinnacle.h | 6 + 5 files changed, 761 insertions(+), 372 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774220399272155500.sql diff --git a/data/sql/updates/pending_db_world/rev_1774220399272155500.sql b/data/sql/updates/pending_db_world/rev_1774220399272155500.sql new file mode 100644 index 000000000..375e540e0 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774220399272155500.sql @@ -0,0 +1,198 @@ + +-- Ensure Grauf has RegenHealth disabled so harpoon damage persists +UPDATE `creature_template` SET `RegenHealth` = 0 WHERE `entry` IN (26893, 30775); + +-- Remove go_harpoon_canon script, use built-in GOOBER spell (48641) instead +UPDATE `gameobject_template` SET `ScriptName` = '' WHERE `entry` IN (192175, 192176, 192177); + +-- Register spell scripts +DELETE FROM `spell_script_names` WHERE `spell_id` IN (48642, 50255, 47574, 47594, 47579, 60020, 49308, 59275); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(48642, 'spell_skadi_launch_harpoon'), +(50255, 'spell_skadi_poisoned_spear'), +(47574, 'spell_freezing_cloud_area_left'), +(47594, 'spell_freezing_cloud_area_right'), +(47579, 'spell_freezing_cloud_damage'), +(60020, 'spell_freezing_cloud_damage'), +(59275, 'spell_summon_gauntlet_mobs_periodic'), +(49308, 'spell_skadi_reset_check'); + +-- Breath trigger SmartAI: on SpellHit 47563/47593 cast area spells 47574/47594 +UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = 28351; +DELETE FROM `smart_scripts` WHERE `entryorguid` = 28351 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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(28351, 0, 0, 0, 8, 0, 100, 0, 47563, 0, 0, 0, 11, 47574, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 'Breath Trigger - On SpellHit (Freezing Cloud Left) - Cast Freezing Cloud Left Area'), +(28351, 0, 1, 0, 8, 0, 100, 0, 47593, 0, 0, 0, 11, 47594, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 'Breath Trigger - On SpellHit (Freezing Cloud Right) - Cast Freezing Cloud Right Area'); + +-- Conditions for freezing cloud spells to target correct entries +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 13 AND `SourceEntry` IN (47593, 47594, 47563, 48642, 47547, 49308); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(13, 1, 47593, 0, 0, 31, 0, 3, 28351, 0, 0, 0, 0, '', 'Freezing Cloud Right - Target Breath Trigger'), +(13, 1, 47594, 0, 0, 31, 0, 3, 28351, 0, 0, 0, 0, '', 'Freezing Cloud Right Area - Target Breath Trigger'), +(13, 1, 47563, 0, 0, 31, 0, 3, 28351, 0, 0, 0, 0, '', 'Freezing Cloud Left - Target Breath Trigger'), +(13, 1, 48642, 0, 0, 31, 0, 3, 26893, 0, 0, 0, 0, '', 'Launch Harpoon - Target Grauf'), +(13, 1, 48642, 0, 1, 31, 0, 3, 22515, 0, 0, 0, 0, '', 'Launch Harpoon - Target World Trigger'), +(13, 1, 47547, 0, 0, 31, 0, 3, 28351, 0, 0, 0, 0, '', 'Gauntlet Effect - Target Breath Trigger'), +(13, 1, 49308, 0, 0, 31, 0, 3, 28351, 0, 0, 0, 0, '', 'Gauntlet Reset Check - Target Breath Trigger'), +(13, 2, 49308, 0, 0, 31, 0, 3, 26693, 0, 0, 0, 0, '', 'Gauntlet Reset Check - Target Skadi'); + +-- Update condition for harpoon launcher spell (48641) to target trigger NPC (TC) +UPDATE `conditions` SET `ConditionValue2` = 19871 WHERE `SourceTypeOrReferenceId` = 13 AND `SourceGroup` = 1 AND `SourceEntry` = 48641; + +-- Spawn World Trigger (Not Immune NPC) near harpoon launchers (from TC TDB335) +DELETE FROM `creature` WHERE `guid` BETWEEN 5300675 AND 5300678 AND `id1` = 19871; +INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `VerifiedBuild`, `CreateObject`, `Comment`) VALUES +(5300675, 19871, 0, 0, 575, 0, 0, 3, 1, 0, 389.929, -439.88, 83.0081, 2.79273, 300, 5, 0, 0, 0, 1, 0, 0, 0, '', NULL, 0, NULL), +(5300676, 19871, 0, 0, 575, 0, 0, 3, 1, 0, 490.516, -508.443, 107.042, 5.65487, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300677, 19871, 0, 0, 575, 0, 0, 3, 1, 0, 487.632, -517.329, 106.799, 5.55015, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300678, 19871, 0, 0, 575, 0, 0, 3, 1, 0, 480.202, -523.332, 107.042, 5.46288, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL); + +-- Skadi teleport destination (phase 2 transition) +DELETE FROM `spell_target_position` WHERE `ID` = 61790; +INSERT INTO `spell_target_position` (`ID`, `EffectIndex`, `MapID`, `PositionX`, `PositionY`, `PositionZ`, `Orientation`, `VerifiedBuild`) VALUES +(61790, 0, 575, 476.799, -511.167, 104.723, 3.14159, 0); + +-- Gauntlet area aura +DELETE FROM `spell_area` WHERE `spell` = 47546; +INSERT INTO `spell_area` (`spell`, `area`, `quest_start`, `quest_end`, `aura_spell`, `racemask`, `gender`, `autocast`, `quest_start_status`, `quest_end_status`) VALUES +(47546, 1196, 0, 0, 0, 0, 0, 1, 64, 11); + +-- Disable LoS for Gauntlet Reset Check +DELETE FROM `disables` WHERE `sourceType` = 0 AND `entry` = 49308; +INSERT INTO `disables` (`sourceType`, `entry`, `flags`, `params_0`, `params_1`, `comment`) VALUES +(0, 49308, 64, 0, 0, 'Disable LoS for spell Utgarde Pinnacle Reset Check'); + +-- Launch Harpoon ignore armor +DELETE FROM `spell_custom_attr` WHERE `spell_id` = 48642; +INSERT INTO `spell_custom_attr` (`spell_id`, `attributes`) VALUES +(48642, 32768); + +-- Missing spell difficulty entries +DELETE FROM `spelldifficulty_dbc` WHERE `ID` IN (50228, 50258, 49084, 49089, 49091); +INSERT INTO `spelldifficulty_dbc` (`ID`, `DifficultySpellID_1`, `DifficultySpellID_2`, `DifficultySpellID_3`, `DifficultySpellID_4`) VALUES +(49084, 49084, 59246, 0, 0), +(49089, 49089, 59247, 0, 0), +(49091, 49091, 59249, 0, 0), +(50228, 50228, 59322, 0, 0), +(50258, 50258, 59334, 0, 0); + +-- Spawn Freezing Cloud Breath Triggers along the Skadi gauntlet corridor (ported from TrinityCore TDB335) +DELETE FROM `creature` WHERE `guid` BETWEEN 5300600 AND 5300674 AND `id1` = 28351; +INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `VerifiedBuild`, `CreateObject`, `Comment`) VALUES +(5300600, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 483.221, -507.151, 104.806, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300601, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 474.404, -507.532, 104.807, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300602, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 473.898, -515.257, 104.806, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300603, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 477.645, -492.175, 104.82, 6.23082, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300604, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 477.021, -520.94, 104.806, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300605, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 425.011, -510.068, 104.832, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300606, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 430.051, -510.1, 104.944, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300607, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 434.669, -510.101, 104.983, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300608, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 440.11, -510.148, 104.97, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300609, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 445.131, -509.703, 104.831, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300610, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 450.289, -509.936, 104.705, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300611, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 455, -509.52, 104.757, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300612, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 460, -509.52, 104.757, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300613, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 465.341, -508.352, 104.438, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300614, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 470.032, -507.361, 104.807, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300615, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 489.557, -514.06, 105.487, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300616, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 482.133, -519.963, 104.806, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300617, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 325, -506.384, 104.419, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300618, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 330, -506.406, 104.434, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300619, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 335.192, -506.95, 105.056, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300620, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 339.973, -507.026, 104.669, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300621, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 344.931, -507.345, 104.592, 6.23082, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300622, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 349.864, -507.751, 104.655, 6.23082, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300623, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 354.289, -507.828, 104.759, 6.23082, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300624, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 359.405, -508.154, 104.871, 6.23082, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300625, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 364.746, -508.335, 104.896, 6.23082, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300626, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 370.505, -508.554, 104.898, 6.23082, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300627, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 374.827, -515.69, 104.877, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300628, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 379.7, -515.287, 104.841, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300629, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 384.809, -514.81, 104.812, 0.017453, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300630, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 390.011, -513.91, 104.811, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300631, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 394.974, -514.631, 104.808, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300632, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 399.738, -514.939, 104.809, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300633, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 404.576, -515.878, 104.827, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300634, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 409.497, -516.404, 105.045, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300635, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 415.076, -516.494, 105.342, 0.017453, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300636, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 420.109, -517.266, 105.089, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300637, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 488.181, -506.897, 104.806, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300638, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 477.551, -499.34, 104.81, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300639, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 485.686, -500.599, 104.806, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300640, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 471.163, -499.268, 104.81, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300641, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 478.264, -514.318, 104.806, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300642, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 483.638, -513.85, 104.806, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300643, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 471.796, -521.457, 104.806, 0.174533, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300644, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 424.786, -517.297, 105.033, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300645, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 429.779, -517.261, 105.039, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300646, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 434.894, -517.11, 105.029, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300647, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 439.402, -517.239, 105.008, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300648, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 444.91, -517.682, 104.947, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300649, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 450.614, -517.208, 104.903, 0.05236, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300650, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 455.604, -517.101, 104.832, 0.05236, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300651, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 460.245, -516.405, 104.679, 0.05236, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300652, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 465.499, -515.925, 104.59, 0.069813, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300653, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 470, -515.214, 105.112, 0.069813, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300654, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 375, -508.566, 104.878, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300655, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 380, -508.366, 104.876, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300656, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 385, -508.366, 104.876, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300657, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 390, -508.366, 104.876, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300658, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 395, -508.366, 104.876, 6.26573, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300659, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 400, -508.366, 104.876, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300660, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 405.287, -508.99, 105.087, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300661, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 409.909, -508.887, 105.106, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300662, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 415.074, -509.304, 105.271, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300663, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 420.093, -509.834, 104.897, 6.24828, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300664, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 478.792, -507.378, 104.806, 0.034907, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300665, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 324.531, -514.289, 104.761, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300666, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 329.63, -514.616, 104.712, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300667, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 334.909, -514.734, 104.623, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300668, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 339.554, -514.984, 104.61, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300669, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 344.693, -515.33, 104.573, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300670, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 349.831, -515.439, 104.556, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300671, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 354.77, -515.736, 104.57, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300672, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 359.338, -515.586, 104.638, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300673, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 364.401, -515.771, 104.757, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL), +(5300674, 28351, 0, 0, 575, 0, 0, 3, 1, 0, 369.849, -515.944, 104.886, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, '', NULL, 0, NULL); + +-- Grauf waypoint paths (ported from TrinityCore TDB335) +SET @PATH := 2689300; +DELETE FROM `waypoint_data` WHERE `id` IN (@PATH, @PATH+1, @PATH+2); +INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `delay`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES +-- Initial path (takeoff to breach) +(@PATH, 0, 310.2165, -510.5302, 120.5487, 0, 0, 2, 0, 100, 0), +(@PATH, 1, 300.8685, -520.3249, 133.3664, 0, 0, 2, 0, 100, 0), +(@PATH, 2, 298.3447, -529.4818, 137.2831, 0, 0, 2, 0, 100, 0), +(@PATH, 3, 309.0959, -540.3195, 134.9774, 0, 0, 2, 0, 100, 0), +(@PATH, 4, 323.6415, -547.9715, 130.3106, 0, 0, 2, 0, 100, 0), +(@PATH, 5, 357.078, -549.1597, 116.3105, 0, 0, 2, 0, 100, 0), +(@PATH, 6, 401.2573, -550.2768, 114.9216, 0, 0, 2, 0, 100, 0), +(@PATH, 7, 464.5062, -555.9442, 114.4494, 0, 0, 2, 0, 100, 0), +(@PATH, 8, 496.192, -556.9628, 114.8661, 0, 0, 2, 0, 100, 0), +(@PATH, 9, 523.2011, -548.9916, 114.8661, 0, 0, 2, 0, 100, 0), +-- Right path (breach to right end and back) +(@PATH+1, 0, 453.1242, -517.17, 120.0273, 0, 0, 2, 0, 100, 0), +(@PATH+1, 1, 388.3312, -514.3768, 121.1193, 0, 0, 2, 0, 100, 0), +(@PATH+1, 2, 340.2752, -512.0926, 122.3138, 0, 0, 2, 0, 100, 0), +(@PATH+1, 3, 313.0756, -509.138, 125.175, 0, 0, 2, 0, 100, 0), +(@PATH+1, 4, 296.6964, -522.6705, 133.7026, 0, 0, 2, 0, 100, 0), +(@PATH+1, 5, 301.2957, -549.4583, 137.4249, 0, 0, 2, 0, 100, 0), +(@PATH+1, 6, 335.0746, -552.1211, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+1, 7, 397.6237, -553.9203, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+1, 8, 459.6321, -558.0078, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+1, 9, 505.5458, -568.7815, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+1, 10, 518.099, -560.8499, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+1, 11, 520.4827, -541.5633, 119.8416, 0, 0, 2, 0, 100, 0), +-- Left path (breach to left end and back) +(@PATH+2, 0, 451.9824, -509.2508, 120.0273, 0, 0, 2, 0, 100, 0), +(@PATH+2, 1, 399.2607, -510.0719, 121.1193, 0, 0, 2, 0, 100, 0), +(@PATH+2, 2, 350.6099, -508.3901, 122.3138, 0, 0, 2, 0, 100, 0), +(@PATH+2, 3, 325.8217, -506.7769, 125.175, 0, 0, 2, 0, 100, 0), +(@PATH+2, 4, 301.2941, -516.6706, 133.7026, 0, 0, 2, 0, 100, 0), +(@PATH+2, 5, 301.2957, -549.4583, 137.4249, 0, 0, 2, 0, 100, 0), +(@PATH+2, 6, 335.0746, -552.1211, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+2, 7, 397.6237, -553.9203, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+2, 8, 459.6321, -558.0078, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+2, 9, 505.5458, -568.7815, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+2, 10, 518.099, -560.8499, 119.8416, 0, 0, 2, 0, 100, 0), +(@PATH+2, 11, 520.4827, -541.5633, 119.8416, 0, 0, 2, 0, 100, 0); diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index d19308e56..f5d1d83e2 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -1695,12 +1695,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[0].TargetB = SpellImplicitTargetInfo(); }); - // Flame Breath - ApplySpellFix({ 47592 }, [](SpellInfo* spellInfo) - { - spellInfo->Effects[EFFECT_0].Amplitude = 200; - }); - // Skarvald, Charge ApplySpellFix({ 43651 }, [](SpellInfo* spellInfo) { diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp index efa101f38..c983d83a0 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp @@ -17,91 +17,129 @@ #include "CombatAI.h" #include "CreatureScript.h" -#include "GameObjectScript.h" +#include "GridNotifiers.h" #include "Player.h" #include "ScriptedCreature.h" -#include "SpellInfo.h" +#include "SpellAuras.h" +#include "SpellScript.h" +#include "SpellScriptLoader.h" #include "Vehicle.h" #include "utgarde_pinnacle.h" -enum Misc +enum Spells +{ + // Skadi + SPELL_CRUSH = 50234, + SPELL_POISONED_SPEAR = 50255, + SPELL_POISONED_SPEAR_PERIODIC = 50258, + SPELL_WHIRLWIND = 50228, + SPELL_LAUNCH_HARPOON = 48642, + SPELL_SKADI_TELEPORT = 61790, + + // Grauf + SPELL_FREEZING_CLOUD = 47579, + SPELL_FREEZING_CLOUD_RIGHT_PERIODIC = 47592, + SPELL_FREEZING_CLOUD_LEFT_PERIODIC = 47590, + SPELL_FREEZING_CLOUD_RIGHT_AREA = 47594, + SPELL_FREEZING_CLOUD_LEFT_AREA = 47574, + + // Gauntlet + SPELL_SUMMON_GAUNTLET_MOBS_PERIODIC = 59275, + SPELL_SUMMON_YMIRJAR_WARRIOR_W = 48631, + SPELL_SUMMON_YMIRJAR_WARRIOR_E = 48632, + SPELL_SUMMON_YMIRJAR_HARPOONER_W = 48633, + SPELL_SUMMON_YMIRJAR_HARPOONER_E = 48634, + SPELL_SUMMON_YMIRJAR_WITCH_DOCTOR_W = 48635, + SPELL_SUMMON_YMIRJAR_WITCH_DOCTOR_E = 48636, + SPELL_GAUNTLET_RESET_CHECK = 49308, + SPELL_GAUNTLET_EFFECT = 47547, +}; + +enum Texts { - // TEXTS SAY_AGGRO = 0, SAY_KILL = 1, SAY_DEATH = 3, SAY_DRAKE_DEATH = 5, SAY_DRAKE_BREATH = 6, - - // EMOTES EMOTE_DEEP_BREATH = 0, - EMOTE_RANGE = 1, - - // SPELLS - SPELL_CRUSH = 50234, - SPELL_POISONED_SPEAR = 50255, - SPELL_WHIRLWIND = 50228, - - SPELL_FREEZING_CLOUD_VISUAL = 47592, - SPELL_FREEZING_CLOUD = 47579, - - SPELL_LAUNCH_HARPOON = 48642, - - // NPCS - NPC_YMIRJAR_WARRIOR = 26690, - NPC_YMIRJAR_WITCH_DOCTOR = 26691, - NPC_YMIRJAR_HARPOONER = 26692, - NPC_GRAUF = 26893, - NPC_BREATH_TRIGGER = 28351, - EQUIP_MACE = 17193, - - // ACTIONS - ACTION_START_EVENT = 1, - ACTION_REMOVE_SKADI = 2, - ACTION_PHASE2 = 3, - ACTION_MYGIRL_ACHIEVEMENT = 4, - - // ACHIEVEMENTS - ACHIEV_TIMED_LODI_DODI = 17726, + EMOTE_ON_RANGE = 1, }; +enum Actions +{ + ACTION_START_ENCOUNTER = 1, + ACTION_DRAKE_BREATH = 2, + ACTION_PHASE2 = 3, + ACTION_HARPOON_HIT = 4, +}; + +enum CombatPhase +{ + PHASE_FLYING = 0, + PHASE_GROUND +}; + +enum MiscData +{ + NPC_GRAUF = 26893, + NPC_TRIGGER_RESET = 23472, + NPC_WORLD_TRIGGER = 22515, + NPC_COMBAT_TRIGGER = 38667, + + ACHIEV_TIMED_LODI_DODI = 17726, + + FIRST_WAVE_MAX_WARRIORS = 10, + FIRST_WAVE_SIZE = 13, +}; + +Position const GraufLoc = { 341.741f, -516.955f, 104.670f, 3.12414f }; +Position const SpawnLoc = { 477.581f, -484.559f, 104.822f, 4.67748f }; + +Position const FirstWaveLocations[FIRST_WAVE_SIZE] = +{ + { 458.532f, -516.254f, 104.617f }, + { 429.424f, -517.562f, 104.894f }, + { 394.496f, -514.614f, 104.724f }, + { 362.286f, -515.877f, 104.754f }, + { 333.537f, -514.794f, 104.478f }, + { 457.611f, -508.836f, 104.401f }, + { 427.403f, -510.772f, 104.880f }, + { 392.511f, -507.943f, 104.743f }, + { 362.951f, -508.412f, 104.722f }, + { 333.536f, -506.081f, 104.426f }, + { 478.310f, -511.049f, 104.724f, 3.26377f }, + { 482.250f, -514.127f, 104.723f, 3.26377f }, + { 481.388f, -507.109f, 104.724f, 3.26377f }, +}; + +Position const SecondaryWavesInitialPoint = { 478.743f, -505.576f, 104.724f }; + +enum GraufPoints +{ + POINT_BREACH = 0, + POINT_LEFT = 1, + POINT_RIGHT = 2, +}; + +uint32 const PATH_INITIAL = 2689300; +uint32 const PATH_RIGHT = 2689301; +uint32 const PATH_LEFT = 2689302; + +float const BreachFacing = 2.670354f; + enum Events { - // SKADI - EVENT_SKADI_START = 1, - EVENT_SKADI_CRUSH = 2, - EVENT_SKADI_SPEAR = 3, - EVENT_SKADI_WHIRLWIND = 4, + // Skadi + EVENT_SKADI_CRUSH = 1, + EVENT_SKADI_SPEAR = 2, + EVENT_SKADI_WHIRLWIND = 3, + EVENT_SKADI_RESET_CHECK = 4, - // GRAUF + // Grauf EVENT_GRAUF_START = 10, - EVENT_GRAUF_MOVE = 11, - EVENT_GRAUF_SUMMON_HELPERS = 12, - EVENT_GRAUF_CHECK = 13, - EVENT_GRAUF_REMOVE_SKADI = 14, -}; - -static Position TrashPosition[] = -{ - {441.236f, -512.000f, 104.930f, 0.0f}, - {478.436f, -494.475f, 104.730f, 0.0f} -}; - -static Position SkadiPosition[] = -{ - {338.679f, -507.254f, 124.122f, 0.0f}, - {338.679f, -513.254f, 124.122f, 0.0f}, - {490.096f, -510.86f, 123.368f, 0.0f}, - {490.76f, -517.389f, 123.368f, 0.0f} -}; - -enum phase -{ - PHASE_NONE, - PHASE_START, - PHASE_FLIGHT, - PHASE_LAND, - PHASE_GROUND + EVENT_GRAUF_LEAVE_BREACH = 11, + EVENT_GRAUF_REMOVE_AURA = 13, }; class boss_skadi : public CreatureScript @@ -109,155 +147,237 @@ class boss_skadi : public CreatureScript public: boss_skadi() : CreatureScript("boss_skadi") { } - CreatureAI* GetAI(Creature* pCreature) const override + CreatureAI* GetAI(Creature* creature) const override { - return GetUtgardePinnacleAI(pCreature); + return GetUtgardePinnacleAI(creature); } struct boss_skadiAI : public ScriptedAI { - boss_skadiAI(Creature* pCreature) : ScriptedAI(pCreature), summons(me) + boss_skadiAI(Creature* creature) : ScriptedAI(creature), _summons(me) { - m_pInstance = pCreature->GetInstanceScript(); + _instance = creature->GetInstanceScript(); } - InstanceScript* m_pInstance; - EventMap events; - SummonList summons; - ObjectGuid GraufGUID; - bool SecondPhase, EventStarted; - void Reset() override { - events.Reset(); - summons.DespawnAll(); - if (Creature* cr = me->SummonCreature(NPC_GRAUF, 341.741f, -516.955f, 116.669f, 3.12414f)) - { - GraufGUID = cr->GetGUID(); - summons.Summon(cr); - } - SecondPhase = false; - EventStarted = false; + _events.Reset(); + _summons.DespawnAll(); + _phase = PHASE_GROUND; + _harpoonHit = 0; + _loveSkadi = 0; + _firstWaveSummoned = false; + _encounterStarted = false; + me->SetReactState(REACT_PASSIVE); me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(false); - if (m_pInstance) + if (_instance) { - m_pInstance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_LODI_DODI); - m_pInstance->SetData(DATA_SKADI_THE_RUTHLESS, NOT_STARTED); - m_pInstance->SetData(SKADI_IN_RANGE, 0); - m_pInstance->SetData(SKADI_HITS, 0); - m_pInstance->SetData(DATA_SKADI_ACHIEVEMENT, false); + _instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_LODI_DODI); + _instance->SetData(DATA_SKADI_THE_RUTHLESS, NOT_STARTED); + _instance->SetData(DATA_SKADI_ACHIEVEMENT, false); } + + if (!_instance || !_instance->GetCreature(DATA_GRAUF)) + if (Creature* grauf = me->SummonCreature(NPC_GRAUF, GraufLoc)) + _summons.Summon(grauf); } - Creature* GetGrauf() { return ObjectAccessor::GetCreature(*me, GraufGUID); } + void JustSummoned(Creature* summon) override + { + switch (summon->GetEntry()) + { + case NPC_YMIRJAR_WARRIOR: + case NPC_YMIRJAR_WITCH_DOCTOR: + case NPC_YMIRJAR_HARPOONER: + if (_firstWaveSummoned) + summon->GetMotionMaster()->MovePoint(1, SecondaryWavesInitialPoint); + break; + default: + break; + } + _summons.Summon(summon); + } + + void SpawnFirstWave() + { + for (uint8 i = 0; i < FIRST_WAVE_MAX_WARRIORS; ++i) + if (Creature* summon = me->SummonCreature(NPC_YMIRJAR_WARRIOR, SpawnLoc, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000)) + summon->GetMotionMaster()->MovePoint(0, FirstWaveLocations[i]); + + if (Creature* crea = me->SummonCreature(NPC_YMIRJAR_WITCH_DOCTOR, SpawnLoc, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000)) + crea->GetMotionMaster()->MovePoint(0, FirstWaveLocations[10]); + if (Creature* crea = me->SummonCreature(NPC_YMIRJAR_HARPOONER, SpawnLoc, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000)) + crea->GetMotionMaster()->MovePoint(0, FirstWaveLocations[11]); + if (Creature* crea = me->SummonCreature(NPC_YMIRJAR_HARPOONER, SpawnLoc, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000)) + crea->GetMotionMaster()->MovePoint(0, FirstWaveLocations[12]); + + _firstWaveSummoned = true; + } + + void JustEngagedWith(Unit* /*who*/) override + { + DoAction(ACTION_START_ENCOUNTER); + } void DoAction(int32 param) override { - if (param == ACTION_START_EVENT) + switch (param) { - if (!EventStarted) - { - EventStarted = true; + case ACTION_START_ENCOUNTER: + if (_encounterStarted) + return; + + _encounterStarted = true; + _phase = PHASE_FLYING; Talk(SAY_AGGRO); - if (m_pInstance) + + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->setActive(true); + + SpawnFirstWave(); + + if (_instance) { if (IsHeroic()) - m_pInstance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_LODI_DODI); - - m_pInstance->SetData(DATA_SKADI_THE_RUTHLESS, IN_PROGRESS); + _instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_LODI_DODI); + _instance->SetData(DATA_SKADI_THE_RUTHLESS, IN_PROGRESS); } - me->SetControlled(true, UNIT_STATE_ROOT); - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - events.RescheduleEvent(EVENT_SKADI_START, 2s); - } - } - else if (param == ACTION_PHASE2) - { - SecondPhase = true; - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - events.ScheduleEvent(EVENT_SKADI_CRUSH, 8s); - events.ScheduleEvent(EVENT_SKADI_SPEAR, 10s); - events.ScheduleEvent(EVENT_SKADI_WHIRLWIND, 15s); + if (Creature* summonTrigger = me->SummonCreature(NPC_WORLD_TRIGGER, SpawnLoc)) + summonTrigger->CastSpell(summonTrigger, SPELL_SUMMON_GAUNTLET_MOBS_PERIODIC, true); - if (me->GetVictim()) - me->ResumeChasingVictim(); - else - me->SetInCombatWithZone(); + if (Creature* combatTrigger = me->SummonCreature(NPC_COMBAT_TRIGGER, SpawnLoc)) + combatTrigger->AI()->DoZoneInCombat(); + + _events.ScheduleEvent(EVENT_GRAUF_START, 2s); + _events.ScheduleEvent(EVENT_SKADI_RESET_CHECK, 6s); + break; + case ACTION_DRAKE_BREATH: + if (_loveSkadi == 1) + ++_loveSkadi; + Talk(SAY_DRAKE_BREATH); + break; + case ACTION_PHASE2: + Talk(SAY_DRAKE_DEATH); + DoCastSelf(SPELL_SKADI_TELEPORT); + _summons.DespawnEntry(NPC_WORLD_TRIGGER); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(false); + me->SetReactState(REACT_AGGRESSIVE); + _phase = PHASE_GROUND; + + _events.ScheduleEvent(EVENT_SKADI_CRUSH, 8s); + _events.ScheduleEvent(EVENT_SKADI_SPEAR, 11s); + _events.ScheduleEvent(EVENT_SKADI_WHIRLWIND, 23s); + break; + case ACTION_HARPOON_HIT: + ++_harpoonHit; + if (_harpoonHit == 1) + _loveSkadi = 1; + break; } } + uint32 GetData(uint32 id) const override + { + if (id == DATA_LOVE_TO_SKADI) + return _loveSkadi; + return 0; + } + void UpdateAI(uint32 diff) override { - if (!UpdateVictim() && SecondPhase) - return; + _events.Update(diff); - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) + if (_phase == PHASE_FLYING) { - case EVENT_SKADI_START: + switch (_events.ExecuteEvent()) + { + case EVENT_GRAUF_START: { - me->SetControlled(false, UNIT_STATE_ROOT); - if (Creature* cr = GetGrauf()) + if (Creature* grauf = _instance->GetCreature(DATA_GRAUF)) { - me->EnterVehicleUnattackable(cr, 0); - cr->AI()->DoAction(ACTION_START_EVENT); + me->EnterVehicleUnattackable(grauf, 0); + grauf->AI()->DoAction(ACTION_START_ENCOUNTER); } else EnterEvadeMode(); - break; } + case EVENT_SKADI_RESET_CHECK: + { + if (Creature* resetTrigger = me->FindNearestCreature(NPC_TRIGGER_RESET, 200.0f)) + resetTrigger->CastSpell(resetTrigger, SPELL_GAUNTLET_RESET_CHECK, true); + _events.Repeat(6s); + break; + } + } + return; + } + + if (!UpdateVictim()) + return; + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (_events.ExecuteEvent()) + { case EVENT_SKADI_CRUSH: - { - me->CastSpell(me->GetVictim(), SPELL_CRUSH, false); - events.Repeat(8s); - break; - } + DoCastVictim(SPELL_CRUSH); + _events.Repeat(8s); + break; case EVENT_SKADI_SPEAR: - { - if (Unit* tgt = SelectTarget(SelectTargetMethod::Random, 0)) - me->CastSpell(tgt, SPELL_POISONED_SPEAR, false); - - events.Repeat(10s); - break; - } + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) + DoCast(target, SPELL_POISONED_SPEAR); + _events.Repeat(10s); + break; case EVENT_SKADI_WHIRLWIND: - { - me->CastSpell(me, SPELL_WHIRLWIND, false); - events.Repeat(15s, 20s); - events.DelayEvents(10s); - break; - } + DoCast(me, SPELL_WHIRLWIND); + _events.Repeat(15s, 20s); + _events.DelayEvents(10s); + break; } DoMeleeAttackIfReady(); } - void JustDied(Unit* /*pKiller*/) override + void JustDied(Unit* /*killer*/) override { - summons.DespawnAll(); + _summons.DespawnAll(); Talk(SAY_DEATH); - if (m_pInstance) + if (_instance) { - m_pInstance->SetData(DATA_SKADI_THE_RUTHLESS, DONE); - m_pInstance->HandleGameObject(m_pInstance->GetGuidData(SKADI_DOOR), true); + _instance->SetData(DATA_SKADI_THE_RUTHLESS, DONE); + _instance->HandleGameObject(_instance->GetGuidData(SKADI_DOOR), true); } } - void KilledUnit(Unit* /*pVictim*/) override + void KilledUnit(Unit* who) override { - if (urand(0, 1)) - return; - - Talk(SAY_KILL); + if (who->IsPlayer()) + Talk(SAY_KILL); } + + void EnterEvadeMode(EvadeReason /*why*/ = EVADE_REASON_OTHER) override + { + _summons.DespawnAll(); + ScriptedAI::EnterEvadeMode(); + } + + private: + InstanceScript* _instance; + EventMap _events; + SummonList _summons; + CombatPhase _phase; + uint8 _harpoonHit; + uint8 _loveSkadi; + bool _firstWaveSummoned; + bool _encounterStarted; }; }; @@ -266,263 +386,324 @@ class boss_skadi_grauf : public CreatureScript public: boss_skadi_grauf() : CreatureScript("boss_skadi_grauf") { } - CreatureAI* GetAI(Creature* pCreature) const override + CreatureAI* GetAI(Creature* creature) const override { - return GetUtgardePinnacleAI(pCreature); + return GetUtgardePinnacleAI(creature); } struct boss_skadi_graufAI : public VehicleAI { - boss_skadi_graufAI(Creature* pCreature) : VehicleAI(pCreature), summons(me) + boss_skadi_graufAI(Creature* creature) : VehicleAI(creature), _summons(me) { - m_pInstance = pCreature->GetInstanceScript(); + _instance = creature->GetInstanceScript(); } - InstanceScript* m_pInstance; - EventMap events; - SummonList summons; - uint8 currentPos; - uint8 AchievementHitCount; - void Reset() override { - events.Reset(); - summons.DespawnAll(); - currentPos = 0; - AchievementHitCount = 0; - me->RemoveAllAuras(); + _events.Reset(); + _summons.DespawnAll(); + me->SetReactState(REACT_PASSIVE); + me->SetSpeedRate(MOVE_RUN, 2.5f); } void DoAction(int32 param) override { - if (param == ACTION_START_EVENT) + if (param == ACTION_START_ENCOUNTER) { - events.RescheduleEvent(EVENT_GRAUF_CHECK, 5s); - events.RescheduleEvent(EVENT_GRAUF_START, 2s); - } - else if (param == ACTION_REMOVE_SKADI) - { - if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0)) - if (Creature* skadi = passenger->ToCreature()) - skadi->AI()->Talk(SAY_DRAKE_DEATH); - me->GetMotionMaster()->MovePoint(10, 480.0f, -513.0f, 108.0f); - events.ScheduleEvent(EVENT_GRAUF_REMOVE_SKADI, 2s); - } - else if (param == ACTION_MYGIRL_ACHIEVEMENT) - { - AchievementHitCount++; - if (AchievementHitCount >= 3 && m_pInstance) - m_pInstance->SetData(DATA_SKADI_ACHIEVEMENT, true); + me->setActive(true); + me->SetCanFly(true); + me->SetDisableGravity(true); + me->GetMotionMaster()->Clear(true); + me->SetAnimTier(AnimTier::Fly); + me->GetMotionMaster()->MovePath(PATH_INITIAL, FORCED_MOVEMENT_RUN); } } - void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override + void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override { - if (spellInfo->Id == 47593) // SPELL_FREEZING_CLOUD_VISUAL trigger - target->CastSpell(target, SPELL_FREEZING_CLOUD, true); + if (spellInfo->Id == SPELL_LAUNCH_HARPOON) + if (Creature* skadi = _instance->GetCreature(DATA_SKADI_THE_RUTHLESS)) + skadi->AI()->DoAction(ACTION_HARPOON_HIT); } - void SpawnFlameTriggers(uint8 point) + void MovementInform(uint32 type, uint32 /*id*/) override { - for(uint8 j = 0; j < 50; ++j) + if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized()) { - if (point == 1) - me->SummonCreature(NPC_BREATH_TRIGGER, 480.0f - (j * 3), -518.0f + (j / 16.0f), 105.0f, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 30000); - else - me->SummonCreature(NPC_BREATH_TRIGGER, 480.0f - (j * 3), -510.0f + (j / 16.0f), 105.0f, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 30000); - } - // and out of loop, cover the small room - if (point == 0) - { - Creature* cr; - if ((cr = me->SummonCreature(NPC_BREATH_TRIGGER, 483, -484.9f, 105, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 30000))) - cr->CastSpell(cr, SPELL_FREEZING_CLOUD, true); - if ((cr = me->SummonCreature(NPC_BREATH_TRIGGER, 471.0f, -484.7f, 105, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 30000))) - cr->CastSpell(cr, SPELL_FREEZING_CLOUD, true); - - for (uint8 j = 0; j < 7; j++) - if ((cr = me->SummonCreature(NPC_BREATH_TRIGGER, 477.0f, -507.0f + (j * 3), 105.0f, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 30000))) - cr->CastSpell(cr, SPELL_FREEZING_CLOUD, true); - } - } - - void MovementInform(uint32 /*uiType*/, uint32 Id) override - { - switch (Id) - { - case 0: - case 1: - me->RemoveAurasDueToSpell(SPELL_FREEZING_CLOUD_VISUAL); - me->SetFacingTo(M_PI * 2); - break; - case 2: - case 3: - if (m_pInstance) - m_pInstance->SetData(SKADI_IN_RANGE, 1); - Talk(EMOTE_RANGE); - me->SetFacingTo(M_PI); - break; - } - } - - uint8 SelectNextPos(uint8 Position) - { - switch (Position) - { - case 0: - case 1: - return 2 + urand(0, 1); - default: - if (me->GetPositionY() < -515.0f) - return 1; - else - return 0; - } - } - - void JustEngagedWith(Unit*) override - { - me->SetInCombatWithZone(); - } - - void RemoveSkadi(bool withEvade) - { - if (Unit* skadi = me->GetVehicleKit()->GetPassenger(0)) - { - summons.DespawnAll(); - skadi->ExitVehicle(); - if (withEvade) - { - skadi->ToCreature()->AI()->EnterEvadeMode(); - skadi->UpdatePosition(343.02f, -507.325f, 104.567f, M_PI, true); - } - else - skadi->ToCreature()->AI()->DoAction(ACTION_PHASE2); - - skadi->StopMovingOnCurrentPos(); - } - } - - void CheckPlayers() - { - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr) - { - if (itr->GetSource()->GetPositionY() > -490.0f || itr->GetSource()->IsGameMaster() || !itr->GetSource()->IsAlive()) - continue; - - return; - } - - RemoveSkadi(true); - } - - void SpawnHelpers(uint8 Spot) - { - if (Creature* Harpooner = me->SummonCreature(NPC_YMIRJAR_HARPOONER, TrashPosition[Spot].GetPositionX() + rand() % 5, TrashPosition[Spot].GetPositionY() + rand() % 5, TrashPosition[Spot].GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 30000)) - { - Harpooner->SetInCombatWithZone(); - summons.Summon(Harpooner); - } - if (Creature* Second = me->SummonCreature((urand(0, 1) ? NPC_YMIRJAR_WARRIOR : NPC_YMIRJAR_WITCH_DOCTOR), TrashPosition[Spot].GetPositionX() + rand() % 5, TrashPosition[Spot].GetPositionY() + rand() % 5, TrashPosition[Spot].GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 30000)) - { - Second->SetInCombatWithZone(); - summons.Summon(Second); + me->SetFacingTo(BreachFacing); + Talk(EMOTE_ON_RANGE); + _events.ScheduleEvent(EVENT_GRAUF_LEAVE_BREACH, 10s); } } void UpdateAI(uint32 diff) override { - events.Update(diff); - switch (events.ExecuteEvent()) + _events.Update(diff); + switch (_events.ExecuteEvent()) { - case EVENT_GRAUF_CHECK: - { - CheckPlayers(); - events.Repeat(2s); - break; - } - case EVENT_GRAUF_START: - { - me->GetMotionMaster()->Clear(true); - me->GetMotionMaster()->MoveTakeoff(10, SkadiPosition[0].GetPositionX(), SkadiPosition[0].GetPositionY(), SkadiPosition[0].GetPositionZ(), 3.0f); + case EVENT_GRAUF_LEAVE_BREACH: + _lastSide = RAND(POINT_LEFT, POINT_RIGHT); + me->GetMotionMaster()->MovePath(_lastSide == POINT_LEFT ? PATH_LEFT : PATH_RIGHT, FORCED_MOVEMENT_RUN); - SpawnHelpers(0); - SpawnHelpers(0); - events.ScheduleEvent(EVENT_GRAUF_MOVE, 5s); - events.ScheduleEvent(EVENT_GRAUF_SUMMON_HELPERS, 20s); - break; - } - case EVENT_GRAUF_MOVE: - { - AchievementHitCount = 0; - uint8 targetPoint = SelectNextPos(currentPos); - me->GetMotionMaster()->MovePoint(targetPoint, SkadiPosition[targetPoint].GetPositionX(), SkadiPosition[targetPoint].GetPositionY(), SkadiPosition[targetPoint].GetPositionZ()); - if (targetPoint <= 1) - { - Talk(EMOTE_DEEP_BREATH); - SpawnFlameTriggers(targetPoint); - me->CastSpell(me, SPELL_FREEZING_CLOUD_VISUAL, false); - } + if (_lastSide == POINT_LEFT) + DoCast(me, SPELL_FREEZING_CLOUD_LEFT_PERIODIC); + else + DoCast(me, SPELL_FREEZING_CLOUD_RIGHT_PERIODIC); - if (m_pInstance) - m_pInstance->SetData(SKADI_IN_RANGE, 0); + if (Creature* skadi = _instance->GetCreature(DATA_SKADI_THE_RUTHLESS)) + skadi->AI()->DoAction(ACTION_DRAKE_BREATH); - currentPos = targetPoint; - events.Repeat(25s); - break; - } - case EVENT_GRAUF_SUMMON_HELPERS: - { - SpawnHelpers(1); - events.Repeat(15s); - break; - } - case EVENT_GRAUF_REMOVE_SKADI: - { - RemoveSkadi(false); - me->DespawnOrUnsummon(); - break; - } + _events.ScheduleEvent(EVENT_GRAUF_REMOVE_AURA, 10s); + break; + case EVENT_GRAUF_REMOVE_AURA: + me->RemoveAurasDueToSpell(SPELL_FREEZING_CLOUD_LEFT_PERIODIC); + me->RemoveAurasDueToSpell(SPELL_FREEZING_CLOUD_RIGHT_PERIODIC); + break; } } + + void JustDied(Unit* /*killer*/) override + { + if (Creature* skadi = _instance->GetCreature(DATA_SKADI_THE_RUTHLESS)) + { + skadi->ExitVehicle(); + skadi->AI()->DoAction(ACTION_PHASE2); + + if (skadi->AI()->GetData(DATA_LOVE_TO_SKADI) == 1) + _instance->SetData(DATA_SKADI_ACHIEVEMENT, true); + } + me->DespawnOrUnsummon(6s); + } + + private: + InstanceScript* _instance; + EventMap _events; + SummonList _summons; + uint8 _lastSide = POINT_LEFT; }; }; -class go_harpoon_canon : public GameObjectScript +// 48642 - Launch Harpoon +class spell_skadi_launch_harpoon : public SpellScript { -public: - go_harpoon_canon() : GameObjectScript("go_harpoon_canon") { } + PrepareSpellScript(spell_skadi_launch_harpoon); - bool OnGossipHello(Player* pPlayer, GameObject* go) override + void FilterTargets(std::list& targets) { - InstanceScript* m_pInstance = go->GetInstanceScript(); - if (m_pInstance && m_pInstance->GetData(DATA_SKADI_THE_RUTHLESS) == IN_PROGRESS) - if (m_pInstance->GetData(SKADI_IN_RANGE) == 1) - { - uint8 count = m_pInstance->GetData(SKADI_HITS) + 1; - m_pInstance->SetData(SKADI_HITS, count); + if (targets.size() >= 2) + targets.remove_if([](WorldObject* obj) { return obj->GetEntry() != NPC_GRAUF; }); + } - if (Creature* grauf = ObjectAccessor::GetCreature(*pPlayer, m_pInstance->GetGuidData(DATA_GRAUF))) - { - if (count >= 3) - { - m_pInstance->SetData(SKADI_IN_RANGE, 0); - grauf->AI()->DoAction(ACTION_REMOVE_SKADI); - } + void HandleDamageCalc() + { + if (Unit* target = GetHitUnit()) + SetHitDamage(target->CountPctFromMaxHealth(35)); + } - grauf->AI()->DoAction(ACTION_MYGIRL_ACHIEVEMENT); - } - go->CastSpell((Unit*)nullptr, SPELL_LAUNCH_HARPOON); - } + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_skadi_launch_harpoon::FilterTargets, EFFECT_0, TARGET_UNIT_CONE_ENTRY); + OnHit += SpellHitFn(spell_skadi_launch_harpoon::HandleDamageCalc); + } +}; +// 50255 - Poisoned Spear +class spell_skadi_poisoned_spear : public SpellScript +{ + PrepareSpellScript(spell_skadi_poisoned_spear); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_POISONED_SPEAR_PERIODIC }); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetHitUnit()->CastSpell(GetHitUnit(), SPELL_POISONED_SPEAR_PERIODIC, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_skadi_poisoned_spear::HandleScript, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); + } +}; + +// 47594 - Freezing Cloud (right side) +class spell_freezing_cloud_area_right : public SpellScript +{ + PrepareSpellScript(spell_freezing_cloud_area_right); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_FREEZING_CLOUD }); + } + + void FilterTargets(std::list& targets) + { + targets.remove_if([](WorldObject* obj) { return obj->GetPositionY() > -511.0f; }); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetHitUnit()->CastSpell(GetHitUnit(), SPELL_FREEZING_CLOUD, true); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_freezing_cloud_area_right::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + OnEffectHitTarget += SpellEffectFn(spell_freezing_cloud_area_right::HandleScript, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } +}; + +// 47574 - Freezing Cloud (left side) +class spell_freezing_cloud_area_left : public SpellScript +{ + PrepareSpellScript(spell_freezing_cloud_area_left); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_FREEZING_CLOUD }); + } + + void FilterTargets(std::list& targets) + { + targets.remove_if([](WorldObject* obj) { return obj->GetPositionY() < -511.0f; }); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetHitUnit()->CastSpell(GetHitUnit(), SPELL_FREEZING_CLOUD, true); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_freezing_cloud_area_left::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + OnEffectHitTarget += SpellEffectFn(spell_freezing_cloud_area_left::HandleScript, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } +}; + +// 47579, 60020 - Freezing Cloud (damage aura) +class spell_freezing_cloud_damage : public AuraScript +{ + PrepareAuraScript(spell_freezing_cloud_damage); + + bool CanBeAppliedOn(Unit* target) + { + if (Aura* aur = target->GetAura(GetId())) + if (aur->GetOwner() != GetOwner()) + return false; return true; } + + void Register() override + { + DoCheckAreaTarget += AuraCheckAreaTargetFn(spell_freezing_cloud_damage::CanBeAppliedOn); + } +}; + +// 59275 - Summon Gauntlet Mobs Periodic +class spell_summon_gauntlet_mobs_periodic : public AuraScript +{ + PrepareAuraScript(spell_summon_gauntlet_mobs_periodic); + + void CastTheNextTwoSpells() + { + for (uint8 i = 0; i < 2; ++i) + { + uint32 spellId = _summonSpells.front(); + GetTarget()->CastSpell((Unit*)nullptr, spellId, true); + _summonSpells.push_back(spellId); + _summonSpells.pop_front(); + } + } + + void PushBackTheNextTwoSpells() + { + for (uint8 j = 0; j < 2; ++j) + { + _summonSpells.push_back(_summonSpells.front()); + _summonSpells.pop_front(); + } + } + + void OnPeriodic(AuraEffect const* /*aurEff*/) + { + if (RAND(0, 1)) + { + CastTheNextTwoSpells(); + PushBackTheNextTwoSpells(); + } + else + { + PushBackTheNextTwoSpells(); + CastTheNextTwoSpells(); + } + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_summon_gauntlet_mobs_periodic::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + +private: + std::deque _summonSpells = + { + SPELL_SUMMON_YMIRJAR_WARRIOR_E, + SPELL_SUMMON_YMIRJAR_HARPOONER_W, + SPELL_SUMMON_YMIRJAR_WARRIOR_W, + SPELL_SUMMON_YMIRJAR_HARPOONER_E, + SPELL_SUMMON_YMIRJAR_WARRIOR_W, + SPELL_SUMMON_YMIRJAR_WITCH_DOCTOR_E, + SPELL_SUMMON_YMIRJAR_WARRIOR_E, + SPELL_SUMMON_YMIRJAR_WITCH_DOCTOR_W + }; +}; + +// 49308 - Utgarde Pinnacle Gauntlet Reset Check +class spell_skadi_reset_check : public SpellScript +{ + PrepareSpellScript(spell_skadi_reset_check); + + void CountTargets(std::list& targets) + { + targets.remove_if(Acore::UnitAuraCheck(false, SPELL_GAUNTLET_EFFECT)); + _targetCount = targets.size(); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + if (_targetCount) + return; + + Creature* target = GetHitCreature(); + if (!target) + return; + + if (InstanceScript* instance = target->GetInstanceScript()) + if (instance->GetData(DATA_SKADI_THE_RUTHLESS) == IN_PROGRESS) + if (Creature* skadi = instance->GetCreature(DATA_SKADI_THE_RUTHLESS)) + skadi->AI()->EnterEvadeMode(); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_skadi_reset_check::CountTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + OnEffectHitTarget += SpellEffectFn(spell_skadi_reset_check::HandleDummy, EFFECT_1, SPELL_EFFECT_DUMMY); + } + +private: + uint32 _targetCount = 0; }; void AddSC_boss_skadi() { new boss_skadi(); new boss_skadi_grauf(); - new go_harpoon_canon(); + RegisterSpellScript(spell_skadi_launch_harpoon); + RegisterSpellScript(spell_skadi_poisoned_spear); + RegisterSpellScript(spell_freezing_cloud_area_left); + RegisterSpellScript(spell_freezing_cloud_area_right); + RegisterSpellScript(spell_freezing_cloud_damage); + RegisterSpellScript(spell_summon_gauntlet_mobs_periodic); + RegisterSpellScript(spell_skadi_reset_check); } diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/instance_utgarde_pinnacle.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/instance_utgarde_pinnacle.cpp index cddc964ae..c332fccbd 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/instance_utgarde_pinnacle.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/instance_utgarde_pinnacle.cpp @@ -23,9 +23,18 @@ ObjectData const creatureData[] = { { NPC_SKADI_THE_RUTHLESS, DATA_SKADI_THE_RUTHLESS }, + { NPC_GARUF, DATA_GRAUF }, { 0, 0 } }; +ObjectData const summonData[] = +{ + { NPC_YMIRJAR_WARRIOR, DATA_SKADI_THE_RUTHLESS }, + { NPC_YMIRJAR_WITCH_DOCTOR, DATA_SKADI_THE_RUTHLESS }, + { NPC_YMIRJAR_HARPOONER, DATA_SKADI_THE_RUTHLESS }, + { 0, 0 } +}; + class instance_utgarde_pinnacle : public InstanceMapScript { public: @@ -65,6 +74,7 @@ public: { SetHeaders(DataHeader); LoadObjectData(creatureData, nullptr); + LoadSummonData(summonData); SkadiHits = 0; SkadiInRange = 0; diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/utgarde_pinnacle.h b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/utgarde_pinnacle.h index f05387598..cb67ff7e3 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/utgarde_pinnacle.h +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/utgarde_pinnacle.h @@ -49,6 +49,7 @@ enum Data DATA_SVALA_ACHIEVEMENT = 50, DATA_SKADI_ACHIEVEMENT = 51, DATA_YMIRON_ACHIEVEMENT = 52, + DATA_LOVE_TO_SKADI = 53, }; enum Objects @@ -71,6 +72,9 @@ enum Objects NPC_MASSIVE_JORMUNGAR = 26685, NPC_FEROCIOUS_RHINO = 26686, NPC_GARUF = 26893, + NPC_YMIRJAR_WARRIOR = 26690, + NPC_YMIRJAR_WITCH_DOCTOR = 26691, + NPC_YMIRJAR_HARPOONER = 26692, }; template @@ -79,4 +83,6 @@ inline AI* GetUtgardePinnacleAI(T* obj) return GetInstanceAI(obj, UtgardePinnacleScriptName); } +#define RegisterUtgardePinnacleCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetUtgardePinnacleAI) + #endif From 72cf7b311bc55f4b317bf891ea404de2d6a65c64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Mar 2026 21:44:43 +0000 Subject: [PATCH 070/132] chore(DB): import pending files Referenced commit(s): 8ff031dbd736bed3fe0269127d9706bed780a1d7 --- .../rev_1774220399272155500.sql => db_world/2026_03_24_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774220399272155500.sql => db_world/2026_03_24_02.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1774220399272155500.sql b/data/sql/updates/db_world/2026_03_24_02.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774220399272155500.sql rename to data/sql/updates/db_world/2026_03_24_02.sql index 375e540e0..ff8d469f4 100644 --- a/data/sql/updates/pending_db_world/rev_1774220399272155500.sql +++ b/data/sql/updates/db_world/2026_03_24_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_24_01 -> 2026_03_24_02 -- Ensure Grauf has RegenHealth disabled so harpoon damage persists UPDATE `creature_template` SET `RegenHealth` = 0 WHERE `entry` IN (26893, 30775); From bbccf17e5ee800a4ffcb264f02f2682590407c99 Mon Sep 17 00:00:00 2001 From: Ryan Turner <16946913+TheSCREWEDSoftware@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:49:24 +0000 Subject: [PATCH 071/132] fix(DB/Pet Names) - Adjustments to Death Knights Ghoul names (#25201) Co-authored-by: FlyingArowana --- .../updates/pending_db_world/rev_1774352396882701300.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774352396882701300.sql diff --git a/data/sql/updates/pending_db_world/rev_1774352396882701300.sql b/data/sql/updates/pending_db_world/rev_1774352396882701300.sql new file mode 100644 index 000000000..f2b65c517 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774352396882701300.sql @@ -0,0 +1,6 @@ +-- https://eu.forums.blizzard.com/en/wow/t/unnerf-unholy-dk-pet-names-plz/113162 +-- Removes extra 'Stone' from First names for DK Ghouls +DELETE FROM `pet_name_generation` WHERE `id`=261 AND `word` = 'Stone' AND `entry` = 26125 AND `half` = 0; + +-- Updates last name 'rawler' to 'crawler' for DK Ghouls +UPDATE `pet_name_generation` SET `word` = 'crawler' WHERE `id` = 308 AND `word` = 'rawler' AND `entry` = 26125 AND `half` = 1; From ce1fa96a17ff323778b55d0ae8bb43b2cab31d3b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Mar 2026 21:50:45 +0000 Subject: [PATCH 072/132] chore(DB): import pending files Referenced commit(s): bbccf17e5ee800a4ffcb264f02f2682590407c99 --- .../rev_1774352396882701300.sql => db_world/2026_03_24_03.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774352396882701300.sql => db_world/2026_03_24_03.sql} (90%) diff --git a/data/sql/updates/pending_db_world/rev_1774352396882701300.sql b/data/sql/updates/db_world/2026_03_24_03.sql similarity index 90% rename from data/sql/updates/pending_db_world/rev_1774352396882701300.sql rename to data/sql/updates/db_world/2026_03_24_03.sql index f2b65c517..d49004ab0 100644 --- a/data/sql/updates/pending_db_world/rev_1774352396882701300.sql +++ b/data/sql/updates/db_world/2026_03_24_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_24_02 -> 2026_03_24_03 -- https://eu.forums.blizzard.com/en/wow/t/unnerf-unholy-dk-pet-names-plz/113162 -- Removes extra 'Stone' from First names for DK Ghouls DELETE FROM `pet_name_generation` WHERE `id`=261 AND `word` = 'Stone' AND `entry` = 26125 AND `half` = 0; From 9d49639da18ea6dd438b5ab5e274ffefa15fdc2c Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:12:26 +0100 Subject: [PATCH 073/132] fix(DB/SAI): Solve High Priest Talet-Kha AI issues. (#25205) --- .../pending_db_world/rev_1774386152.sql | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774386152.sql diff --git a/data/sql/updates/pending_db_world/rev_1774386152.sql b/data/sql/updates/pending_db_world/rev_1774386152.sql new file mode 100644 index 000000000..f3de93dd2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774386152.sql @@ -0,0 +1,47 @@ + +-- Set High Priest Talet-Kha SAI. +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 26073; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 26073); +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 +(26073, 0, 0, 1, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Set Event Phase 0'), +(26073, 0, 1, 2, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 18, 33555200, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Set Flags Immune To Players & Immune To NPC\'s & Not Selectable'), +(26073, 0, 2, 3, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 10, 85118, 25422, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Respawn Closest Creature \'Mystical Webbing\''), +(26073, 0, 3, 4, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 10, 85098, 25422, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Respawn Closest Creature \'Mystical Webbing\''), +(26073, 0, 4, 5, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 23, 0, 0, 0, 0, 0, 10, 85175, 23033, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Do Action ID 23'), +(26073, 0, 5, 6, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 23, 0, 0, 0, 0, 0, 10, 85176, 23033, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Do Action ID 23'), +(26073, 0, 6, 7, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 23, 0, 0, 0, 0, 0, 10, 85098, 25422, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Do Action ID 23'), +(26073, 0, 7, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 23, 0, 0, 0, 0, 0, 10, 85118, 25422, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Reset - Do Action ID 23'), +(26073, 0, 8, 0, 72, 0, 100, 1, 25, 0, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Action 25 Done - Increment Phase (No Repeat)'), +(26073, 0, 9, 0, 72, 0, 100, 1, 26, 0, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Action 26 Done - Increment Phase (No Repeat)'), +(26073, 0, 10, 0, 66, 0, 100, 1, 2, 0, 0, 0, 0, 0, 80, 2607300, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - On Event Phase 2 Set - Run Script (No Repeat)'), +(26073, 0, 11, 0, 2, 0, 100, 0, 1, 45, 1000, 3000, 0, 0, 11, 11640, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - Between 1-45% Health - Cast \'Renew\''), +(26073, 0, 12, 0, 0, 0, 100, 0, 4000, 6000, 5000, 8000, 0, 0, 11, 15587, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - In Combat - Cast \'Mind Blast\''); + +-- Set Action List +DELETE FROM `smart_scripts` WHERE (`source_type` = 9 AND `entryorguid` = 2607300); +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 +(2607300, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - Actionlist - Remove FlagStandstate Sit Down'), +(2607300, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - Actionlist - Set Fly Off'), +(2607300, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 3788.44, 3418.25, 85.0562, 0, 'High Priest Talet-Kha - Actionlist - Move To Position'), +(2607300, 9, 3, 0, 0, 0, 100, 0, 1000, 1000, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1.1672, 'High Priest Talet-Kha - Actionlist - Set Orientation 1.1672'), +(2607300, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - Actionlist - Say Line 0'), +(2607300, 9, 5, 0, 0, 0, 100, 0, 4000, 4000, 0, 0, 0, 0, 11, 45492, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - Actionlist - Cast \'Shadow Nova\''), +(2607300, 9, 6, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 19, 33555200, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - Actionlist - Remove Flags Immune To Players & Immune To NPC\'s & Not Selectable'), +(2607300, 9, 7, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 21, 40, 0, 0, 0, 0, 0, 0, 0, 'High Priest Talet-Kha - Actionlist - Start Attacking'); + +-- Set Invisible Stalker & Mystical Webbing Guid SAIs. +DELETE FROM `smart_scripts` WHERE (`source_type` = 0) AND (`entryorguid` IN (-85175, -85176, -85098, -85118)); +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 +(-85176, 0, 0, 0, 72, 0, 100, 0, 23, 0, 0, 0, 0, 0, 11, 45497, 2, 0, 0, 0, 0, 11, 26073, 40, 0, 0, 0, 0, 0, 0, 'Invisible Stalker (Floating) - On Action 23 Done - Cast \'Web Beam\''), +(-85176, 0, 1, 0, 72, 0, 100, 0, 24, 0, 0, 0, 0, 0, 92, 0, 45497, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Invisible Stalker (Floating) - On Action 24 Done - Interrupt Spell \'Web Beam\''), +(-85175, 0, 0, 0, 72, 0, 100, 0, 23, 0, 0, 0, 0, 0, 11, 45497, 2, 0, 0, 0, 0, 11, 26073, 40, 0, 0, 0, 0, 0, 0, 'Invisible Stalker (Floating) - On Action 23 Done - Cast \'Web Beam\''), +(-85175, 0, 1, 0, 72, 0, 100, 0, 24, 0, 0, 0, 0, 0, 92, 0, 45497, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Invisible Stalker (Floating) - On Action 24 Done - Interrupt Spell \'Web Beam\''), +(-85098, 0, 0, 1, 72, 0, 100, 0, 23, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Action 23 Done - Set Reactstate Passive'), +(-85098, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 45497, 2, 0, 0, 0, 0, 11, 26073, 40, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Action 23 Done - Cast \'Web Beam\''), +(-85098, 0, 2, 3, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 24, 0, 0, 0, 0, 0, 10, 85176, 23033, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Just Died - Do Action ID 24'), +(-85098, 0, 3, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 26, 0, 0, 0, 0, 0, 10, 85240, 26073, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Just Died - Do Action ID 26'), +(-85118, 0, 0, 1, 72, 0, 100, 0, 23, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Action 23 Done - Set Reactstate Passive'), +(-85118, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 45497, 2, 0, 0, 0, 0, 11, 26073, 40, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Action 23 Done - Cast \'Web Beam\''), +(-85118, 0, 2, 3, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 24, 0, 0, 0, 0, 0, 10, 85175, 23033, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Just Died - Do Action ID 24'), +(-85118, 0, 3, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 25, 0, 0, 0, 0, 0, 10, 85240, 26073, 0, 0, 0, 0, 0, 0, 'Mystical Webbing - On Just Died - Do Action ID 25'); From 6ee7b5e3aeb43073802c01e689b7988ccf61a955 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:13:26 -0300 Subject: [PATCH 074/132] feat(Scripts/Commands): Add .debug loot command (#25164) Co-authored-by: Claude Opus 4.6 --- .../rev_1774115806392066400.sql | 18 ++ src/server/game/Miscellaneous/Language.h | 15 +- src/server/scripts/Commands/cs_debug.cpp | 234 ++++++++++++++++++ 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774115806392066400.sql diff --git a/data/sql/updates/pending_db_world/rev_1774115806392066400.sql b/data/sql/updates/pending_db_world/rev_1774115806392066400.sql new file mode 100644 index 000000000..10008e5ad --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774115806392066400.sql @@ -0,0 +1,18 @@ +-- +DELETE FROM `command` WHERE `name` = 'debug loot'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('debug loot', 2, 'Syntax: .debug loot [count]\nSimulates loot generation for the given loot type and ID, outputting the results to chat without creating items.\nOptional count (1-100) repeats the simulation and shows aggregated drop rates.\nValid types: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player'); + +DELETE FROM `acore_string` WHERE `entry` IN (30099, 30100, 30101, 30102, 30103, 30104, 30105, 30106, 30107, 30108, 30109); +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(30099, 'Simulating loot for {} "{}" (ID: {})', '루팅 시뮬레이션: {} "{}" (ID: {})', 'Simulation du butin pour {} "{}" (ID: {})', 'Beutesimulation für {} "{}" (ID: {})', '模拟 {} "{}" 的战利品 (ID: {})', '模擬 {} "{}" 的戰利品 (ID: {})', 'Simulando botín para {} "{}" (ID: {})', 'Simulando botín para {} "{}" (ID: {})', 'Симуляция добычи для {} "{}" (ID: {})'), +(30100, ' [{}] x{} - {} ({}, RandProp: {}, RandSuffix: {})', ' [{}] x{} - {} ({}, 랜덤속성: {}, 랜덤접미사: {})', ' [{}] x{} - {} ({}, PropAléa: {}, SuffAléa: {})', ' [{}] x{} - {} ({}, ZufEig: {}, ZufSuf: {})', ' [{}] x{} - {} ({}, 随机属性: {}, 随机后缀: {})', ' [{}] x{} - {} ({}, 隨機屬性: {}, 隨機後綴: {})', ' [{}] x{} - {} ({}, PropAleat: {}, SufAleat: {})', ' [{}] x{} - {} ({}, PropAleat: {}, SufAleat: {})', ' [{}] x{} - {} ({}, СлСвойство: {}, СлСуффикс: {})'), +(30101, ' [{}] x{} - {} ({}) [Quest]', ' [{}] x{} - {} ({}) [퀘스트]', ' [{}] x{} - {} ({}) [Quête]', ' [{}] x{} - {} ({}) [Quest]', ' [{}] x{} - {} ({}) [任务]', ' [{}] x{} - {} ({}) [任務]', ' [{}] x{} - {} ({}) [Misión]', ' [{}] x{} - {} ({}) [Misión]', ' [{}] x{} - {} ({}) [Задание]'), +(30102, 'Gold: {} copper ({}g {}s {}c)', '골드: {} 코퍼 ({}금 {}은 {}동)', 'Or: {} cuivre ({}po {}pa {}pc)', 'Gold: {} Kupfer ({}G {}S {}K)', '金币: {} 铜 ({}金 {}银 {}铜)', '金幣: {} 銅 ({}金 {}銀 {}銅)', 'Oro: {} cobre ({}o {}p {}c)', 'Oro: {} cobre ({}o {}p {}c)', 'Золото: {} медь ({}зол {}сер {}мед)'), +(30103, 'No loot generated.', '생성된 전리품이 없습니다.', 'Aucun butin généré.', 'Keine Beute generiert.', '未生成战利品。', '未生成戰利品。', 'No se generó botín.', 'No se generó botín.', 'Добыча не сгенерирована.'), +(30104, 'Invalid loot type "{}". Valid: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', '잘못된 전리품 유형 "{}". 유효: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Type de butin invalide "{}". Valides: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Ungültiger Beutetyp "{}". Gültig: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', '无效的战利品类型 "{}"。有效: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', '無效的戰利品類型 "{}"。有效: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Tipo de botín inválido "{}". Válidos: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Tipo de botín inválido "{}". Válidos: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Неверный тип добычи "{}". Допустимые: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player'), +(30105, 'No loot template found for {} ID {}.', '{}의 전리품 템플릿을 찾을 수 없습니다 (ID: {}).', 'Aucun modèle de butin trouvé pour {} ID {}.', 'Keine Beutevorlage gefunden für {} ID {}.', '未找到 {} ID {} 的战利品模板。', '未找到 {} ID {} 的戰利品模板。', 'No se encontró plantilla de botín para {} ID {}.', 'No se encontró plantilla de botín para {} ID {}.', 'Шаблон добычи не найден для {} ID {}.'), +(30106, 'Simulating loot for {} "{}" (ID: {}) - {} iterations', '루팅 시뮬레이션: {} "{}" (ID: {}) - {} 반복', 'Simulation du butin pour {} "{}" (ID: {}) - {} itérations', 'Beutesimulation für {} "{}" (ID: {}) - {} Durchläufe', '模拟 {} "{}" 的战利品 (ID: {}) - {} 次迭代', '模擬 {} "{}" 的戰利品 (ID: {}) - {} 次迭代', 'Simulando botín para {} "{}" (ID: {}) - {} iteraciones', 'Simulando botín para {} "{}" (ID: {}) - {} iteraciones', 'Симуляция добычи для {} "{}" (ID: {}) - {} итераций'), +(30107, ' [{}] x{} - {} ({}) - dropped {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - 드롭 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - obtenu {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - gefallen {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - выпало {}/{} ({}.{}%)'), +(30108, ' [{}] x{} - {} ({}) [Quest] - dropped {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [퀘스트] - 드롭 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Quête] - obtenu {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Quest] - gefallen {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [任务] - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [任務] - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Misión] - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Misión] - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Задание] - выпало {}/{} ({}.{}%)'), +(30109, 'Gold avg: {} copper ({}g {}s {}c) over {} iterations', '골드 평균: {} 코퍼 ({}금 {}은 {}동) {} 반복', 'Or moy: {} cuivre ({}po {}pa {}pc) sur {} itérations', 'Gold Durchschn.: {} Kupfer ({}G {}S {}K) über {} Durchläufe', '金币均值: {} 铜 ({}金 {}银 {}铜) 共 {} 次迭代', '金幣均值: {} 銅 ({}金 {}銀 {}銅) 共 {} 次迭代', 'Oro prom: {} cobre ({}o {}p {}c) en {} iteraciones', 'Oro prom: {} cobre ({}o {}p {}c) en {} iteraciones', 'Золото сред.: {} медь ({}зол {}сер {}мед) за {} итераций'); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 4482dcb7b..4ee2eb3e7 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1391,6 +1391,19 @@ enum AcoreStrings LANG_DEBUG_LFG_ON = 30096, LANG_DEBUG_LFG_OFF = 30097, - LANG_DEBUG_LFG_CONF = 30098 + LANG_DEBUG_LFG_CONF = 30098, + + // debug loot command + LANG_DEBUG_LOOT_HEADER = 30099, + LANG_DEBUG_LOOT_ITEM = 30100, + LANG_DEBUG_LOOT_ITEM_QUEST = 30101, + LANG_DEBUG_LOOT_GOLD = 30102, + LANG_DEBUG_LOOT_EMPTY = 30103, + LANG_DEBUG_LOOT_INVALID_TYPE = 30104, + LANG_DEBUG_LOOT_NO_TEMPLATE = 30105, + LANG_DEBUG_LOOT_HEADER_MULTI = 30106, + LANG_DEBUG_LOOT_ITEM_MULTI = 30107, + LANG_DEBUG_LOOT_ITEM_QUEST_MULTI = 30108, + LANG_DEBUG_LOOT_GOLD_MULTI = 30109 }; #endif diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 156c0373a..f4531e9ed 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -22,9 +22,11 @@ #include "Chat.h" #include "CommandScript.h" #include "GridNotifiersImpl.h" +#include "ItemTemplate.h" #include "LFGMgr.h" #include "Language.h" #include "Log.h" +#include "LootMgr.h" #include "M2Stores.h" #include "MapMgr.h" #include "ObjectAccessor.h" @@ -33,7 +35,9 @@ #include "ScriptMgr.h" #include "Transport.h" #include "Warden.h" +#include #include +#include #include using namespace Acore::ChatCommands; @@ -96,6 +100,7 @@ public: { "itemexpire", HandleDebugItemExpireCommand, SEC_ADMINISTRATOR, Console::No }, { "areatriggers", HandleDebugAreaTriggersCommand, SEC_ADMINISTRATOR, Console::No }, { "lfg", HandleDebugDungeonFinderCommand, SEC_ADMINISTRATOR, Console::Yes}, + { "loot", HandleDebugLootCommand, SEC_GAMEMASTER, Console::Yes}, { "los", HandleDebugLoSCommand, SEC_ADMINISTRATOR, Console::No }, { "moveflags", HandleDebugMoveflagsCommand, SEC_ADMINISTRATOR, Console::No }, { "unitstate", HandleDebugUnitStateCommand, SEC_ADMINISTRATOR, Console::No }, @@ -1607,6 +1612,235 @@ public: handler->PSendSysMessage("Player count in zone {} ({}): {}.", zoneId, (zoneEntry ? zoneEntry->area_name[LOCALE_enUS] : ""), player->GetMap()->GetPlayerCountInZone(zoneId)); return true; } + + static std::string GetLootSourceName(std::string const& type, uint32 lootId) + { + if (type == "creature" || type == "skinning" || type == "pickpocketing") + { + if (CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(lootId)) + return ct->Name; + } + else if (type == "gameobject") + { + if (GameObjectTemplate const* gt = sObjectMgr->GetGameObjectTemplate(lootId)) + return gt->name; + } + else if (type == "item" || type == "disenchant" || type == "prospecting" || type == "milling") + { + if (ItemTemplate const* it = sObjectMgr->GetItemTemplate(lootId)) + return it->Name1; + } + else if (type == "fishing") + { + if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(lootId)) + return area->area_name[LOCALE_enUS]; + } + + return ""; + } + + static char const* GetItemQualityName(uint32 quality) + { + static char const* const qualityNames[MAX_ITEM_QUALITY] = + { + "Poor", "Normal", "Uncommon", "Rare", + "Epic", "Legendary", "Artifact", "Heirloom" + }; + + if (quality < MAX_ITEM_QUALITY) + return qualityNames[quality]; + return "Unknown"; + } + + static void GenerateLoot(Loot& loot, LootTemplate const* tab, + LootStore const& store, Player* player, std::string const& type, uint32 lootId) + { + loot.clear(); + loot.items.reserve(MAX_NR_LOOT_ITEMS); + loot.quest_items.reserve(MAX_NR_QUEST_ITEMS); + tab->Process(loot, store, LOOT_MODE_DEFAULT, player); + + if (type == "creature") + { + if (CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(lootId)) + loot.generateMoneyLoot(ct->mingold, ct->maxgold); + } + else if (type == "gameobject") + { + if (GameObjectTemplateAddon const* addon = sObjectMgr->GetGameObjectTemplateAddon(lootId)) + loot.generateMoneyLoot(addon->mingold, addon->maxgold); + } + else if (type == "item") + { + if (ItemTemplate const* it = sObjectMgr->GetItemTemplate(lootId)) + loot.generateMoneyLoot(it->MinMoneyLoot, it->MaxMoneyLoot); + } + } + + static bool HandleDebugLootCommand(ChatHandler* handler, std::string type, uint32 lootId, Optional count) + { + static std::unordered_map const lootStoreMap = + { + { "creature", &LootTemplates_Creature }, + { "gameobject", &LootTemplates_Gameobject }, + { "fishing", &LootTemplates_Fishing }, + { "item", &LootTemplates_Item }, + { "pickpocketing", &LootTemplates_Pickpocketing }, + { "skinning", &LootTemplates_Skinning }, + { "disenchant", &LootTemplates_Disenchant }, + { "prospecting", &LootTemplates_Prospecting }, + { "milling", &LootTemplates_Milling }, + { "spell", &LootTemplates_Spell }, + { "reference", &LootTemplates_Reference }, + { "mail", &LootTemplates_Mail }, + { "player", &LootTemplates_Player } + }; + + // Lowercase the type for case-insensitive matching + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + + auto itr = lootStoreMap.find(type); + if (itr == lootStoreMap.end()) + { + handler->SendErrorMessage(LANG_DEBUG_LOOT_INVALID_TYPE, type); + return false; + } + + LootStore const* store = itr->second; + + LootTemplate const* tab = store->GetLootFor(lootId); + if (!tab) + { + handler->SendErrorMessage(LANG_DEBUG_LOOT_NO_TEMPLATE, type, lootId); + return false; + } + + uint32 iterations = std::min(count.value_or(1), uint32(100)); + if (iterations == 0) + iterations = 1; + + Player* player = handler->GetPlayer(); + std::string sourceName = GetLootSourceName(type, lootId); + + // Single iteration - original behavior + if (iterations == 1) + { + Loot loot; + GenerateLoot(loot, tab, *store, player, type, lootId); + + handler->PSendSysMessage(LANG_DEBUG_LOOT_HEADER, type, sourceName, lootId); + + if (loot.items.empty() && loot.quest_items.empty() && loot.gold == 0) + { + handler->PSendSysMessage(LANG_DEBUG_LOOT_EMPTY); + return true; + } + + for (LootItem const& li : loot.items) + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(li.itemid); + std::string name = proto ? proto->Name1 : "Unknown"; + char const* qualityName = GetItemQualityName(proto ? proto->Quality : 0); + handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM, + li.itemid, uint32(li.count), name, qualityName, + li.randomPropertyId, li.randomSuffix); + } + + for (LootItem const& li : loot.quest_items) + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(li.itemid); + std::string name = proto ? proto->Name1 : "Unknown"; + char const* qualityName = GetItemQualityName(proto ? proto->Quality : 0); + handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM_QUEST, + li.itemid, uint32(li.count), name, qualityName); + } + + if (loot.gold > 0) + { + uint32 gold = loot.gold / 10000; + uint32 silver = (loot.gold % 10000) / 100; + uint32 copper = loot.gold % 100; + handler->PSendSysMessage(LANG_DEBUG_LOOT_GOLD, + loot.gold, gold, silver, copper); + } + + return true; + } + + // Multi iteration - aggregate results + struct ItemStats + { + uint32 totalCount = 0; + uint32 timesDropped = 0; + bool questItem = false; + }; + + std::map itemStats; + uint64 totalGold = 0; + + for (uint32 i = 0; i < iterations; ++i) + { + Loot loot; + GenerateLoot(loot, tab, *store, player, type, lootId); + + std::set seenThisRun; + + for (LootItem const& li : loot.items) + { + itemStats[li.itemid].totalCount += li.count; + if (seenThisRun.insert(li.itemid).second) + itemStats[li.itemid].timesDropped++; + } + + for (LootItem const& li : loot.quest_items) + { + itemStats[li.itemid].totalCount += li.count; + itemStats[li.itemid].questItem = true; + if (seenThisRun.insert(li.itemid).second) + itemStats[li.itemid].timesDropped++; + } + + totalGold += loot.gold; + } + + handler->PSendSysMessage(LANG_DEBUG_LOOT_HEADER_MULTI, + type, sourceName, lootId, iterations); + + if (itemStats.empty() && totalGold == 0) + { + handler->PSendSysMessage(LANG_DEBUG_LOOT_EMPTY); + return true; + } + + for (auto const& [itemId, stats] : itemStats) + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); + std::string name = proto ? proto->Name1 : "Unknown"; + char const* qualityName = GetItemQualityName(proto ? proto->Quality : 0); + uint32 dropPct = (stats.timesDropped * 10000) / iterations; + + if (stats.questItem) + handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM_QUEST_MULTI, + itemId, stats.totalCount, name, qualityName, + stats.timesDropped, iterations, dropPct / 100, dropPct % 100); + else + handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM_MULTI, + itemId, stats.totalCount, name, qualityName, + stats.timesDropped, iterations, dropPct / 100, dropPct % 100); + } + + if (totalGold > 0) + { + uint32 avgGold = static_cast(totalGold / iterations); + uint32 gold = avgGold / 10000; + uint32 silver = (avgGold % 10000) / 100; + uint32 copper = avgGold % 100; + handler->PSendSysMessage(LANG_DEBUG_LOOT_GOLD_MULTI, + avgGold, gold, silver, copper, iterations); + } + + return true; + } }; void AddSC_debug_commandscript() From 1b64c8c0bcb5e0653cd27a477049261b4c33371c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Mar 2026 22:35:48 +0000 Subject: [PATCH 075/132] chore(DB): import pending files Referenced commit(s): ce1fa96a17ff323778b55d0ae8bb43b2cab31d3b --- .../rev_1774115806392066400.sql => db_world/2026_03_24_04.sql} | 1 + .../rev_1774386152.sql => db_world/2026_03_24_05.sql} | 1 + 2 files changed, 2 insertions(+) rename data/sql/updates/{pending_db_world/rev_1774115806392066400.sql => db_world/2026_03_24_04.sql} (99%) rename data/sql/updates/{pending_db_world/rev_1774386152.sql => db_world/2026_03_24_05.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1774115806392066400.sql b/data/sql/updates/db_world/2026_03_24_04.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774115806392066400.sql rename to data/sql/updates/db_world/2026_03_24_04.sql index 10008e5ad..3316cfc4a 100644 --- a/data/sql/updates/pending_db_world/rev_1774115806392066400.sql +++ b/data/sql/updates/db_world/2026_03_24_04.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_24_03 -> 2026_03_24_04 -- DELETE FROM `command` WHERE `name` = 'debug loot'; INSERT INTO `command` (`name`, `security`, `help`) VALUES diff --git a/data/sql/updates/pending_db_world/rev_1774386152.sql b/data/sql/updates/db_world/2026_03_24_05.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774386152.sql rename to data/sql/updates/db_world/2026_03_24_05.sql index f3de93dd2..4809cba70 100644 --- a/data/sql/updates/pending_db_world/rev_1774386152.sql +++ b/data/sql/updates/db_world/2026_03_24_05.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_24_04 -> 2026_03_24_05 -- Set High Priest Talet-Kha SAI. UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 26073; From 5c9c7341c2303e250c3c55e29919ebde535494c7 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:46:09 -0700 Subject: [PATCH 076/132] chore(Core/OutdoorPvP): Add outdoor pvp min/max getters (#25203) --- src/server/game/OutdoorPvP/OutdoorPvP.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.h b/src/server/game/OutdoorPvP/OutdoorPvP.h index 25a82959b..774244a14 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvP.h +++ b/src/server/game/OutdoorPvP/OutdoorPvP.h @@ -137,6 +137,8 @@ public: { return _value; } + [[nodiscard]] float GetMaxValue() const { return _maxValue; } + [[nodiscard]] float GetMinValue() const { return _minValue; } protected: bool AddObject(uint32 type, uint32 entry, uint32 map, float x, float y, float z, float o, From ff385e4a6c069c17b1697572323f301c37225b6b Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:04:08 -0300 Subject: [PATCH 077/132] feat(Scripts/Commands): add debug factionchange command (#25156) Co-authored-by: Claude Opus 4.6 --- .../rev_1774097862453121000.sql | 24 ++++ src/server/game/Miscellaneous/Language.h | 20 ++- src/server/scripts/Commands/cs_debug.cpp | 124 ++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774097862453121000.sql diff --git a/data/sql/updates/pending_db_world/rev_1774097862453121000.sql b/data/sql/updates/pending_db_world/rev_1774097862453121000.sql new file mode 100644 index 000000000..979663d73 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774097862453121000.sql @@ -0,0 +1,24 @@ +-- Add acore_string entries (30110-30125) and command table entry for .debug factionchange + +DELETE FROM `acore_string` WHERE `entry` BETWEEN 30110 AND 30125; +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(30110, '=== Faction/Race Change Debug for {} (GUID: {}) ===', '=== {} 진영/종족 변경 디버그 (GUID: {}) ===', '=== Débogage changement de faction/race pour {} (GUID : {}) ===', '=== Fraktions-/Rassenwechsel-Debug für {} (GUID: {}) ===', '=== 阵营/种族变更调试:{} (GUID: {}) ===', '=== 陣營/種族變更調試:{} (GUID: {}) ===', '=== Depuración de cambio de facción/raza para {} (GUID: {}) ===', '=== Depuración de cambio de facción/raza para {} (GUID: {}) ===', '=== Отладка смены фракции/расы для {} (GUID: {}) ==='), +(30111, ' Faction change flag: SET [OK]', ' 진영 변경 플래그: 설정됨 [OK]', ' Flag changement de faction : ACTIVÉ [OK]', ' Fraktionswechsel-Flag: GESETZT [OK]', ' 阵营变更标记:已设置 [OK]', ' 陣營變更標記:已設置 [OK]', ' Flag de cambio de facción: ACTIVO [OK]', ' Flag de cambio de facción: ACTIVO [OK]', ' Флаг смены фракции: УСТАНОВЛЕН [OK]'), +(30112, ' Race change flag: SET [OK]', ' 종족 변경 플래그: 설정됨 [OK]', ' Flag changement de race : ACTIVÉ [OK]', ' Rassenwechsel-Flag: GESETZT [OK]', ' 种族变更标记:已设置 [OK]', ' 種族變更標記:已設置 [OK]', ' Flag de cambio de raza: ACTIVO [OK]', ' Flag de cambio de raza: ACTIVO [OK]', ' Флаг смены расы: УСТАНОВЛЕН [OK]'), +(30113, ' Change flag: NEITHER faction nor race change flag is set [FAIL]', ' 변경 플래그: 진영 및 종족 변경 플래그가 설정되지 않음 [실패]', ' Flag de changement : NI faction NI race n''est activé [ÉCHEC]', ' Wechsel-Flag: WEDER Fraktions- noch Rassenwechsel-Flag gesetzt [FEHLER]', ' 变更标记:阵营和种族变更标记均未设置 [失败]', ' 變更標記:陣營和種族變更標記均未設置 [失敗]', ' Flag de cambio: NI facción NI raza están activos [FALLO]', ' Flag de cambio: NI facción NI raza están activos [FALLO]', ' Флаг смены: НЕ установлен ни флаг фракции, ни расы [ОШИБКА]'), +(30114, ' Guild: member of \"{}\" - must leave before faction change [FAIL]', ' 길드: \"{}\"의 멤버 - 진영 변경 전 탈퇴 필요 [실패]', ' Guilde : membre de \"{}\" - doit quitter avant le changement de faction [ÉCHEC]', ' Gilde: Mitglied von \"{}\" - muss vor dem Fraktionswechsel austreten [FEHLER]', ' 公会:是\"{}\"的成员 - 必须在阵营变更前退出 [失败]', ' 公會:是\"{}\"的成員 - 必須在陣營變更前退出 [失敗]', ' Hermandad: miembro de \"{}\" - debe abandonar antes de cambiar de facción [FALLO]', ' Hermandad: miembro de \"{}\" - debe abandonar antes de cambiar de facción [FALLO]', ' Гильдия: состоит в \"{}\" - необходимо покинуть перед сменой фракции [ОШИБКА]'), +(30115, ' Guild: not in a guild or cross-faction guilds enabled [OK]', ' 길드: 길드 미가입 또는 교차 진영 길드 허용됨 [OK]', ' Guilde : pas dans une guilde ou guildes cross-faction activées [OK]', ' Gilde: nicht in einer Gilde oder fraktionsübergreifende Gilden aktiviert [OK]', ' 公会:未加入公会或已启用跨阵营公会 [OK]', ' 公會:未加入公會或已啟用跨陣營公會 [OK]', ' Hermandad: sin hermandad o hermandades cross-facción habilitadas [OK]', ' Hermandad: sin hermandad o hermandades cross-facción habilitadas [OK]', ' Гильдия: не в гильдии или межфракционные гильдии включены [OK]'), +(30116, ' Arena captain: is captain of an arena team - must transfer or disband [FAIL]', ' 아레나 대장: 아레나 팀의 대장 - 양도 또는 해체 필요 [실패]', ' Capitaine d''arène : est capitaine d''une équipe d''arène - doit transférer ou dissoudre [ÉCHEC]', ' Arena-Kapitän: ist Kapitän eines Arena-Teams - muss übergeben oder auflösen [FEHLER]', ' 竞技场队长:是竞技场队伍的队长 - 必须转让或解散 [失败]', ' 競技場隊長:是競技場隊伍的隊長 - 必須轉讓或解散 [失敗]', ' Capitán de arena: es capitán de un equipo de arena - debe transferir o disolver [FALLO]', ' Capitán de arena: es capitán de un equipo de arena - debe transferir o disolver [FALLO]', ' Капитан арены: является капитаном команды арены - необходимо передать или распустить [ОШИБКА]'), +(30117, ' Arena captain: not a captain [OK]', ' 아레나 대장: 대장 아님 [OK]', ' Capitaine d''arène : n''est pas capitaine [OK]', ' Arena-Kapitän: kein Kapitän [OK]', ' 竞技场队长:不是队长 [OK]', ' 競技場隊長:不是隊長 [OK]', ' Capitán de arena: no es capitán [OK]', ' Capitán de arena: no es capitán [OK]', ' Капитан арены: не является капитаном [OK]'), +(30118, ' Mail: {} message(s) in mailbox - must clear mail [FAIL]', ' 우편: 우편함에 {}개의 메시지 - 우편 정리 필요 [실패]', ' Courrier : {} message(s) dans la boîte - doit vider le courrier [ÉCHEC]', ' Post: {} Nachricht(en) im Postfach - Post muss gelöscht werden [FEHLER]', ' 邮件:邮箱中有{}封邮件 - 必须清空邮箱 [失败]', ' 郵件:信箱中有{}封郵件 - 必須清空信箱 [失敗]', ' Correo: {} mensaje(s) en el buzón - debe vaciar el correo [FALLO]', ' Correo: {} mensaje(s) en el buzón - debe vaciar el correo [FALLO]', ' Почта: {} сообщений в почтовом ящике - необходимо очистить почту [ОШИБКА]'), +(30119, ' Mail: mailbox is empty [OK]', ' 우편: 우편함 비어 있음 [OK]', ' Courrier : boîte aux lettres vide [OK]', ' Post: Postfach ist leer [OK]', ' 邮件:邮箱为空 [OK]', ' 郵件:信箱為空 [OK]', ' Correo: buzón vacío [OK]', ' Correo: buzón vacío [OK]', ' Почта: почтовый ящик пуст [OK]'), +(30120, ' Auctions: has active auctions or bids - must clear auctions [FAIL]', ' 경매: 활성 경매 또는 입찰 있음 - 경매 정리 필요 [실패]', ' Enchères : a des enchères ou offres actives - doit supprimer les enchères [ÉCHEC]', ' Auktionen: hat aktive Auktionen oder Gebote - Auktionen müssen gelöscht werden [FEHLER]', ' 拍卖:有活跃的拍卖或出价 - 必须清除拍卖 [失败]', ' 拍賣:有活躍的拍賣或出價 - 必須清除拍賣 [失敗]', ' Subastas: tiene subastas o pujas activas - debe eliminar las subastas [FALLO]', ' Subastas: tiene subastas o pujas activas - debe eliminar las subastas [FALLO]', ' Аукционы: есть активные аукционы или ставки - необходимо очистить аукционы [ОШИБКА]'), +(30121, ' Auctions: no active auctions or bids [OK]', ' 경매: 활성 경매 또는 입찰 없음 [OK]', ' Enchères : aucune enchère ni offre active [OK]', ' Auktionen: keine aktiven Auktionen oder Gebote [OK]', ' 拍卖:没有活跃的拍卖或出价 [OK]', ' 拍賣:沒有活躍的拍賣或出價 [OK]', ' Subastas: sin subastas ni pujas activas [OK]', ' Subastas: sin subastas ni pujas activas [OK]', ' Аукционы: нет активных аукционов или ставок [OK]'), +(30122, ' Gold: {} copper, exceeds limit of {} copper [FAIL]', ' 골드: {} 코퍼, 제한 {} 코퍼 초과 [실패]', ' Or : {} cuivres, dépasse la limite de {} cuivres [ÉCHEC]', ' Gold: {} Kupfer, überschreitet Limit von {} Kupfer [FEHLER]', ' 金币:{}铜币,超过限额{}铜币 [失败]', ' 金幣:{}銅幣,超過限額{}銅幣 [失敗]', ' Oro: {} cobres, excede el límite de {} cobres [FALLO]', ' Oro: {} cobres, excede el límite de {} cobres [FALLO]', ' Золото: {} медных, превышает лимит в {} медных [ОШИБКА]'), +(30123, ' Gold: {} copper (limit: {} copper) [OK]', ' 골드: {} 코퍼 (제한: {} 코퍼) [OK]', ' Or : {} cuivres (limite : {} cuivres) [OK]', ' Gold: {} Kupfer (Limit: {} Kupfer) [OK]', ' 金币:{}铜币(限额:{}铜币)[OK]', ' 金幣:{}銅幣(限額:{}銅幣)[OK]', ' Oro: {} cobres (límite: {} cobres) [OK]', ' Oro: {} cobres (límite: {} cobres) [OK]', ' Золото: {} медных (лимит: {} медных) [OK]'), +(30124, ' Gold: {} copper (no limit configured) [OK]', ' 골드: {} 코퍼 (제한 없음) [OK]', ' Or : {} cuivres (aucune limite configurée) [OK]', ' Gold: {} Kupfer (kein Limit konfiguriert) [OK]', ' 金币:{}铜币(未配置限额)[OK]', ' 金幣:{}銅幣(未配置限額)[OK]', ' Oro: {} cobres (sin límite configurado) [OK]', ' Oro: {} cobres (sin límite configurado) [OK]', ' Золото: {} медных (лимит не настроен) [OK]'), +(30125, ' Guild/Arena/Mail/Auction checks: N/A (faction change not requested, only applies to faction change)', ' 길드/아레나/우편/경매 검사: 해당 없음 (진영 변경이 요청되지 않음, 진영 변경에만 적용)', ' Vérifications guilde/arène/courrier/enchères : N/A (changement de faction non demandé, applicable uniquement au changement de faction)', ' Gilden-/Arena-/Post-/Auktionsprüfungen: Nicht zutreffend (kein Fraktionswechsel angefordert, gilt nur für Fraktionswechsel)', ' 公会/竞技场/邮件/拍卖检查:不适用(未请求阵营变更,仅适用于阵营变更)', ' 公會/競技場/郵件/拍賣檢查:不適用(未請求陣營變更,僅適用於陣營變更)', ' Comprobaciones de hermandad/arena/correo/subastas: N/A (no se solicitó cambio de facción, solo aplica a cambio de facción)', ' Comprobaciones de hermandad/arena/correo/subastas: N/A (no se solicitó cambio de facción, solo aplica a cambio de facción)', ' Проверки гильдии/арены/почты/аукционов: Н/Д (смена фракции не запрошена, применимо только к смене фракции)'); + +DELETE FROM `command` WHERE `name` = 'debug factionchange'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('debug factionchange', 3, 'Syntax: .debug factionchange [$playerName]\nChecks all faction/race change requirements for the target player and reports pass/fail for each condition (AT_LOGIN flags, guild, arena captain, mail, auctions, gold limit).'); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 4ee2eb3e7..61977af9e 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1404,6 +1404,24 @@ enum AcoreStrings LANG_DEBUG_LOOT_HEADER_MULTI = 30106, LANG_DEBUG_LOOT_ITEM_MULTI = 30107, LANG_DEBUG_LOOT_ITEM_QUEST_MULTI = 30108, - LANG_DEBUG_LOOT_GOLD_MULTI = 30109 + LANG_DEBUG_LOOT_GOLD_MULTI = 30109, + + // debug factionchange command (30110-30125) + LANG_DEBUG_FACTIONCHANGE_HEADER = 30110, + LANG_DEBUG_FACTIONCHANGE_FLAG_FACTION = 30111, + LANG_DEBUG_FACTIONCHANGE_FLAG_RACE = 30112, + LANG_DEBUG_FACTIONCHANGE_FLAG_NONE = 30113, + LANG_DEBUG_FACTIONCHANGE_GUILD_FAIL = 30114, + LANG_DEBUG_FACTIONCHANGE_GUILD_OK = 30115, + LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_FAIL = 30116, + LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_OK = 30117, + LANG_DEBUG_FACTIONCHANGE_MAIL_FAIL = 30118, + LANG_DEBUG_FACTIONCHANGE_MAIL_OK = 30119, + LANG_DEBUG_FACTIONCHANGE_AUCTION_FAIL = 30120, + LANG_DEBUG_FACTIONCHANGE_AUCTION_OK = 30121, + LANG_DEBUG_FACTIONCHANGE_GOLD_FAIL = 30122, + LANG_DEBUG_FACTIONCHANGE_GOLD_OK = 30123, + LANG_DEBUG_FACTIONCHANGE_GOLD_NOLIMIT = 30124, + LANG_DEBUG_FACTIONCHANGE_NA = 30125 }; #endif diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index f4531e9ed..cda1b6244 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -15,13 +15,18 @@ * with this program. If not, see . */ +#include "ArenaTeamMgr.h" +#include "AuctionHouseMgr.h" #include "Bag.h" #include "BattlegroundMgr.h" #include "CellImpl.h" #include "Channel.h" +#include "CharacterCache.h" +#include "DatabaseEnv.h" #include "Chat.h" #include "CommandScript.h" #include "GridNotifiersImpl.h" +#include "GuildMgr.h" #include "ItemTemplate.h" #include "LFGMgr.h" #include "Language.h" @@ -32,6 +37,7 @@ #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "PoolMgr.h" +#include "RaceMgr.h" #include "ScriptMgr.h" #include "Transport.h" #include "Warden.h" @@ -109,6 +115,7 @@ public: { "mapdata", HandleDebugMapDataCommand, SEC_ADMINISTRATOR, Console::No }, { "boundary", HandleDebugBoundaryCommand, SEC_ADMINISTRATOR, Console::No }, { "visibilitydata", HandleDebugVisibilityDataCommand, SEC_ADMINISTRATOR, Console::No }, + { "factionchange", HandleDebugFactionChangeCommand, SEC_ADMINISTRATOR, Console::Yes}, { "zonestats", HandleDebugZoneStatsCommand, SEC_MODERATOR, Console::Yes} }; static ChatCommandTable commandTable = @@ -1841,6 +1848,123 @@ public: return true; } + + static bool HandleDebugFactionChangeCommand(ChatHandler* handler, Optional playerTarget) + { + if (!playerTarget) + playerTarget = PlayerIdentifier::FromTarget(handler); + + if (!playerTarget) + { + handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND); + return false; + } + + ObjectGuid guid = playerTarget->GetGUID(); + std::string name = playerTarget->GetName(); + + CharacterCacheEntry const* playerData = sCharacterCache->GetCharacterCacheByGuid(guid); + if (!playerData) + { + handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND); + return false; + } + + // Query at_login flags and money from the database (synchronous) + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_AT_LOGIN_TITLES_MONEY); + stmt->SetData(0, guid.GetCounter()); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + if (!result) + { + handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND); + return false; + } + + Field* fields = result->Fetch(); + uint32 atLoginFlags = fields[0].Get(); + uint32 money = fields[2].Get(); + + // Header + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_HEADER, name, guid.GetCounter()); + + // AT_LOGIN flags + bool hasFactionFlag = (atLoginFlags & AT_LOGIN_CHANGE_FACTION) != 0; + bool hasRaceFlag = (atLoginFlags & AT_LOGIN_CHANGE_RACE) != 0; + + if (hasFactionFlag) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_FLAG_FACTION); + if (hasRaceFlag) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_FLAG_RACE); + if (!hasFactionFlag && !hasRaceFlag) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_FLAG_NONE); + + // Faction-change-only checks (guild, arena captain, mail, auctions) + if (hasFactionFlag) + { + // Guild check + if (playerData->GuildId && !sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD)) + { + std::string guildName = sGuildMgr->GetGuildNameById(playerData->GuildId); + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GUILD_FAIL, guildName); + } + else + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GUILD_OK); + + // Arena team captain check + if (sArenaTeamMgr->GetArenaTeamByCaptain(guid)) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_FAIL); + else + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_OK); + + // Mail check + if (playerData->MailCount) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_MAIL_FAIL, playerData->MailCount); + else + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_MAIL_OK); + + // Auction check - mirrors CharacterHandler.cpp logic + // AH faction IDs: 0 = neutral, 12 = alliance, 29 = horde + bool hasAuctions = false; + for (uint8 i = 0; i < 2; ++i) + { + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap( + i == 0 ? 0 : (((1 << (playerData->Race - 1)) & sRaceMgr->GetAllianceRaceMask()) ? 12 : 29)); + + for (auto const& [auID, Aentry] : auctionHouse->GetAuctions()) + { + if (Aentry && (Aentry->owner == guid || Aentry->bidder == guid)) + { + hasAuctions = true; + break; + } + } + + if (hasAuctions) + break; + } + + if (hasAuctions) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_AUCTION_FAIL); + else + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_AUCTION_OK); + } + else + { + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_NA); + } + + // Gold limit check + uint32 maxMoney = sWorld->getIntConfig(CONFIG_CHANGE_FACTION_MAX_MONEY); + if (maxMoney && money > maxMoney) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GOLD_FAIL, money, maxMoney); + else if (maxMoney) + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GOLD_OK, money, maxMoney); + else + handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GOLD_NOLIMIT, money); + + return true; + } }; void AddSC_debug_commandscript() From 1303ab11d0e2798cc09c9287db963345f4b3d169 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Mar 2026 00:06:35 +0000 Subject: [PATCH 078/132] chore(DB): import pending files Referenced commit(s): ff385e4a6c069c17b1697572323f301c37225b6b --- .../rev_1774097862453121000.sql => db_world/2026_03_25_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774097862453121000.sql => db_world/2026_03_25_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1774097862453121000.sql b/data/sql/updates/db_world/2026_03_25_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774097862453121000.sql rename to data/sql/updates/db_world/2026_03_25_00.sql index 979663d73..893b7c5cf 100644 --- a/data/sql/updates/pending_db_world/rev_1774097862453121000.sql +++ b/data/sql/updates/db_world/2026_03_25_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_24_05 -> 2026_03_25_00 -- Add acore_string entries (30110-30125) and command table entry for .debug factionchange DELETE FROM `acore_string` WHERE `entry` BETWEEN 30110 AND 30125; From 01c0ed3d61f12a73026e2e95b4c7ed6639cfd7a9 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 24 Mar 2026 22:18:14 -0300 Subject: [PATCH 079/132] feat(Scripts/Commands): add commands for autobroadcast management (#25154) Co-authored-by: Claude Opus 4.6 Co-authored-by: Ryan Turner <16946913+TheSCREWEDSoftware@users.noreply.github.com> --- .../rev_1774073616034639439.sql | 21 ++ .../Database/Implementation/LoginDatabase.cpp | 7 + .../Database/Implementation/LoginDatabase.h | 7 + src/server/game/Miscellaneous/Language.h | 14 +- .../scripts/Commands/cs_autobroadcast.cpp | 208 ++++++++++++++++++ .../scripts/Commands/cs_script_loader.cpp | 2 + 6 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774073616034639439.sql create mode 100644 src/server/scripts/Commands/cs_autobroadcast.cpp diff --git a/data/sql/updates/pending_db_world/rev_1774073616034639439.sql b/data/sql/updates/pending_db_world/rev_1774073616034639439.sql new file mode 100644 index 000000000..4a51df829 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774073616034639439.sql @@ -0,0 +1,21 @@ +-- Add autobroadcast commands +DELETE FROM `command` WHERE `name` IN ('autobroadcast', 'autobroadcast list', 'autobroadcast add', 'autobroadcast locale', 'autobroadcast remove'); +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('autobroadcast', 2, 'Syntax: .autobroadcast $subcommand\nType .autobroadcast to see a list of subcommands or .help autobroadcast $subcommand to see info on subcommands.'), +('autobroadcast list', 2, 'Syntax: .autobroadcast list\nList all autobroadcast entries.'), +('autobroadcast add', 3, 'Syntax: .autobroadcast add $weight $text\nAdd a new autobroadcast entry with the given weight and text.'), +('autobroadcast locale', 3, 'Syntax: .autobroadcast locale $id $locale $text\nAdd or replace a localized text for the autobroadcast entry with the given ID.'), +('autobroadcast remove', 3, 'Syntax: .autobroadcast remove $id\nRemove the autobroadcast entry with the given ID and its locale entries.'); + +-- Add acore_string entries for autobroadcast commands +DELETE FROM `acore_string` WHERE `entry` IN (5126, 5127, 5128, 5129, 5130, 5131, 5132, 5133, 5134); +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(5126, 'Autobroadcast entries:', '자동 방송 항목:', 'Entrées d''annonce automatique :', 'Autobroadcast-Einträge:', '自动广播条目:', '自動廣播條目:', 'Entradas de transmisión automática:', 'Entradas de transmisión automática:', 'Записи автоматической рассылки:'), +(5127, ' ID: {} | Weight: {} | Text: {}', ' ID: {} | 가중치: {} | 텍스트: {}', ' ID : {} | Poids : {} | Texte : {}', ' ID: {} | Gewicht: {} | Text: {}', ' ID:{} | 权重:{} | 文本:{}', ' ID:{} | 權重:{} | 文本:{}', ' ID: {} | Peso: {} | Texto: {}', ' ID: {} | Peso: {} | Texto: {}', ' ID: {} | Вес: {} | Текст: {}'), +(5128, 'No autobroadcast entries found.', '자동 방송 항목을 찾을 수 없습니다.', 'Aucune entrée d''annonce automatique trouvée.', 'Keine Autobroadcast-Einträge gefunden.', '未找到自动广播条目。', '未找到自動廣播條目。', 'No se encontraron entradas de transmisión automática.', 'No se encontraron entradas de transmisión automática.', 'Записи автоматической рассылки не найдены.'), +(5129, 'Autobroadcast entry added with ID {}.', 'ID {}로 자동 방송 항목이 추가되었습니다.', 'Entrée d''annonce automatique ajoutée avec l''ID {}.', 'Autobroadcast-Eintrag mit ID {} hinzugefügt.', '已添加ID为{}的自动广播条目。', '已新增ID為{}的自動廣播條目。', 'Entrada de transmisión automática añadida con ID {}.', 'Entrada de transmisión automática añadida con ID {}.', 'Добавлена запись автоматической рассылки с ID {}.'), +(5130, 'Autobroadcast entry #{} removed.', '자동 방송 항목 #{}이(가) 제거되었습니다.', 'Entrée d''annonce automatique #{} supprimée.', 'Autobroadcast-Eintrag #{} entfernt.', '自动广播条目 #{} 已移除。', '自動廣播條目 #{} 已移除。', 'Entrada de transmisión automática #{} eliminada.', 'Entrada de transmisión automática #{} eliminada.', 'Запись автоматической рассылки #{} удалена.'), +(5131, 'Autobroadcast entry #{} not found.', '자동 방송 항목 #{}을(를) 찾을 수 없습니다.', 'Entrée d''annonce automatique #{} introuvable.', 'Autobroadcast-Eintrag #{} nicht gefunden.', '未找到自动广播条目 #{}。', '未找到自動廣播條目 #{}。', 'Entrada de transmisión automática #{} no encontrada.', 'Entrada de transmisión automática #{} no encontrada.', 'Запись автоматической рассылки #{} не найдена.'), +(5132, 'Autobroadcast locale ''{}'' added for entry #{}.', '자동 방송 항목 #{}에 로캘 ''{}''이(가) 추가되었습니다.', 'Localisation ''{}'' ajoutée pour l''entrée d''annonce automatique #{}.', 'Autobroadcast-Lokalisierung ''{}'' für Eintrag #{} hinzugefügt.', '已为自动广播条目 #{} 添加语言区域 ''{}''。', '已為自動廣播條目 #{} 新增語言區域 ''{}''。', 'Localización ''{}'' añadida para la entrada de transmisión automática #{}.', 'Localización ''{}'' añadida para la entrada de transmisión automática #{}.', 'Локализация ''{}'' добавлена для записи автоматической рассылки #{}.'), +(5133, ' Locale: {} | Text: {}', ' 로캘: {} | 텍스트: {}', ' Langue : {} | Texte : {}', ' Sprache: {} | Text: {}', ' 语言区域:{} | 文本:{}', ' 語言區域:{} | 文本:{}', ' Localización: {} | Texto: {}', ' Localización: {} | Texto: {}', ' Локализация: {} | Текст: {}'), +(5134, 'Invalid locale. Valid locales: enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.', '잘못된 로캘입니다. 유효한 로캘: enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.', 'Langue invalide. Langues valides : enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.', 'Ungültige Sprache. Gültige Sprachen: enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.', '无效的语言区域。有效的语言区域:enUS、koKR、frFR、deDE、zhCN、zhTW、esES、esMX、ruRU。', '無效的語言區域。有效的語言區域:enUS、koKR、frFR、deDE、zhCN、zhTW、esES、esMX、ruRU。', 'Localización inválida. Localizaciones válidas: enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.', 'Localización inválida. Localizaciones válidas: enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.', 'Недопустимая локализация. Допустимые локализации: enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.'); diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 298ae09d5..256664672 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -118,6 +118,13 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_DEL_ACCOUNT, "DELETE FROM account WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_AUTOBROADCAST_LOCALIZED, "SELECT id, locale, text FROM autobroadcast_locale WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH); + PrepareStatement(LOGIN_INS_AUTOBROADCAST, "INSERT INTO autobroadcast (realmid, weight, text) VALUES (?, ?, ?)", CONNECTION_SYNCH); + PrepareStatement(LOGIN_DEL_AUTOBROADCAST, "DELETE FROM autobroadcast WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH); + PrepareStatement(LOGIN_INS_AUTOBROADCAST_LOCALE, "REPLACE INTO autobroadcast_locale (realmid, id, locale, text) VALUES (?, ?, ?, ?)", CONNECTION_SYNCH); + PrepareStatement(LOGIN_DEL_AUTOBROADCAST_LOCALE, "DELETE FROM autobroadcast_locale WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_AUTOBROADCAST_BY_ID, "SELECT 1 FROM autobroadcast WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_AUTOBROADCAST_LOCALE_BY_ID, "SELECT locale, text FROM autobroadcast_locale WHERE (realmid = ? OR realmid = -1) AND id = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_AUTOBROADCAST_MAX_ID, "SELECT MAX(id) FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_MOTD, "SELECT text FROM motd WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_MOTD_LOCALE, "SELECT locale, text FROM motd_localized WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH); PrepareStatement(LOGIN_REP_MOTD, "REPLACE INTO motd (realmid, text) VALUES (?, ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 563cdf3eb..6b6debb81 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -100,6 +100,13 @@ enum LoginDatabaseStatements : uint32 LOGIN_DEL_ACCOUNT, LOGIN_SEL_AUTOBROADCAST, LOGIN_SEL_AUTOBROADCAST_LOCALIZED, + LOGIN_INS_AUTOBROADCAST, + LOGIN_DEL_AUTOBROADCAST, + LOGIN_INS_AUTOBROADCAST_LOCALE, + LOGIN_DEL_AUTOBROADCAST_LOCALE, + LOGIN_SEL_AUTOBROADCAST_BY_ID, + LOGIN_SEL_AUTOBROADCAST_LOCALE_BY_ID, + LOGIN_SEL_AUTOBROADCAST_MAX_ID, LOGIN_SEL_MOTD, LOGIN_SEL_MOTD_LOCALE, LOGIN_REP_MOTD, diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 61977af9e..23847a5f3 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1203,7 +1203,19 @@ enum AcoreStrings LANG_BF_QUEUE_PLAYER_QUEUE = 5123, LANG_BF_QUEUE_PLAYER_INVITED = 5124, LANG_BF_QUEUE_PLAYER_WAR = 5125, - // Room for more strings 5126-9999 + + // Autobroadcast commands + LANG_AUTOBROADCAST_LIST_HEADER = 5126, + LANG_AUTOBROADCAST_LIST_ENTRY = 5127, + LANG_AUTOBROADCAST_LIST_EMPTY = 5128, + LANG_AUTOBROADCAST_ADD_SUCCESS = 5129, + LANG_AUTOBROADCAST_REMOVE_SUCCESS = 5130, + LANG_AUTOBROADCAST_NOT_FOUND = 5131, + LANG_AUTOBROADCAST_LOCALE_SUCCESS = 5132, + LANG_AUTOBROADCAST_LOCALE_ENTRY = 5133, + LANG_AUTOBROADCAST_INVALID_LOCALE = 5134, + + // Room for more strings 5135-9999 // Level requirement notifications LANG_SAY_REQ = 6604, diff --git a/src/server/scripts/Commands/cs_autobroadcast.cpp b/src/server/scripts/Commands/cs_autobroadcast.cpp new file mode 100644 index 000000000..27b5a8c20 --- /dev/null +++ b/src/server/scripts/Commands/cs_autobroadcast.cpp @@ -0,0 +1,208 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "AutobroadcastMgr.h" +#include "Chat.h" +#include "CommandScript.h" +#include "Config.h" +#include "Language.h" + +using namespace Acore::ChatCommands; + +class autobroadcast_commandscript : public CommandScript +{ +public: + autobroadcast_commandscript() : CommandScript("autobroadcast_commandscript") { } + + ChatCommandTable GetCommands() const override + { + static ChatCommandTable autobroadcastCommandTable = + { + { "list", HandleAutobroadcastListCommand, SEC_GAMEMASTER, Console::Yes }, + { "add", HandleAutobroadcastAddCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "locale", HandleAutobroadcastLocaleCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "remove", HandleAutobroadcastRemoveCommand, SEC_ADMINISTRATOR, Console::Yes } + }; + + static ChatCommandTable commandTable = + { + { "autobroadcast", autobroadcastCommandTable } + }; + + return commandTable; + } + + static bool HandleAutobroadcastListCommand(ChatHandler* handler) + { + uint32 realmId = sConfigMgr->GetOption("RealmID", 0); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_AUTOBROADCAST); + stmt->SetData(0, realmId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + if (!result) + { + handler->SendSysMessage(LANG_AUTOBROADCAST_LIST_EMPTY); + return true; + } + + // Prefetch all locales and group by id + LoginDatabasePreparedStatement* localeStmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_AUTOBROADCAST_LOCALIZED); + localeStmt->SetData(0, realmId); + PreparedQueryResult localeResult = LoginDatabase.Query(localeStmt); + + std::unordered_map>> localeMap; + if (localeResult) + { + do + { + Field* localeFields = localeResult->Fetch(); + uint32 localeId = localeFields[0].Get(); + std::string locale = localeFields[1].Get(); + std::string localeText = localeFields[2].Get(); + localeMap[localeId].emplace_back(std::move(locale), std::move(localeText)); + } while (localeResult->NextRow()); + } + + handler->SendSysMessage(LANG_AUTOBROADCAST_LIST_HEADER); + + do + { + Field* fields = result->Fetch(); + uint32 id = fields[0].Get(); + uint8 weight = fields[1].Get(); + std::string text = fields[2].Get(); + + handler->PSendSysMessage(LANG_AUTOBROADCAST_LIST_ENTRY, id, weight, text); + + auto itr = localeMap.find(id); + if (itr != localeMap.end()) + for (auto const& [locale, localeText] : itr->second) + handler->PSendSysMessage(LANG_AUTOBROADCAST_LOCALE_ENTRY, locale, localeText); + } while (result->NextRow()); + + return true; + } + + static bool HandleAutobroadcastAddCommand(ChatHandler* handler, uint8 weight, Tail text) + { + if (text.empty()) + return false; + + uint32 realmId = sConfigMgr->GetOption("RealmID", 0); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_AUTOBROADCAST); + stmt->SetData(0, realmId); + stmt->SetData(1, weight); + stmt->SetData(2, std::string(text)); + LoginDatabase.DirectExecute(stmt); + + // Retrieve the newly inserted ID + stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_AUTOBROADCAST_MAX_ID); + stmt->SetData(0, realmId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + uint32 newId = 0; + if (result) + newId = result->Fetch()[0].Get(); + + sAutobroadcastMgr->LoadAutobroadcasts(); + sAutobroadcastMgr->LoadAutobroadcastsLocalized(); + + handler->PSendSysMessage(LANG_AUTOBROADCAST_ADD_SUCCESS, newId); + return true; + } + + static bool HandleAutobroadcastLocaleCommand(ChatHandler* handler, uint32 id, std::string locale, Tail text) + { + if (text.empty()) + return false; + + if (!IsLocaleValid(locale)) + { + handler->SendErrorMessage(LANG_AUTOBROADCAST_INVALID_LOCALE); + return true; + } + + uint32 realmId = sConfigMgr->GetOption("RealmID", 0); + + // Verify the autobroadcast entry exists + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_AUTOBROADCAST_BY_ID); + stmt->SetData(0, id); + stmt->SetData(1, realmId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + if (!result) + { + handler->SendErrorMessage(LANG_AUTOBROADCAST_NOT_FOUND, id); + return true; + } + + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_AUTOBROADCAST_LOCALE); + stmt->SetData(0, realmId); + stmt->SetData(1, id); + stmt->SetData(2, locale); + stmt->SetData(3, std::string(text)); + LoginDatabase.DirectExecute(stmt); + + sAutobroadcastMgr->LoadAutobroadcasts(); + sAutobroadcastMgr->LoadAutobroadcastsLocalized(); + + handler->PSendSysMessage(LANG_AUTOBROADCAST_LOCALE_SUCCESS, locale, id); + return true; + } + + static bool HandleAutobroadcastRemoveCommand(ChatHandler* handler, uint32 id) + { + uint32 realmId = sConfigMgr->GetOption("RealmID", 0); + + // Verify the autobroadcast entry exists + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_AUTOBROADCAST_BY_ID); + stmt->SetData(0, id); + stmt->SetData(1, realmId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + if (!result) + { + handler->SendErrorMessage(LANG_AUTOBROADCAST_NOT_FOUND, id); + return true; + } + + // Delete the autobroadcast entry + stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_AUTOBROADCAST); + stmt->SetData(0, id); + stmt->SetData(1, realmId); + LoginDatabase.DirectExecute(stmt); + + // Delete associated locale entries + stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_AUTOBROADCAST_LOCALE); + stmt->SetData(0, id); + stmt->SetData(1, realmId); + LoginDatabase.DirectExecute(stmt); + + sAutobroadcastMgr->LoadAutobroadcasts(); + sAutobroadcastMgr->LoadAutobroadcastsLocalized(); + + handler->PSendSysMessage(LANG_AUTOBROADCAST_REMOVE_SUCCESS, id); + return true; + } +}; + +void AddSC_autobroadcast_commandscript() +{ + new autobroadcast_commandscript(); +} diff --git a/src/server/scripts/Commands/cs_script_loader.cpp b/src/server/scripts/Commands/cs_script_loader.cpp index 68bd683f7..8b867a0fa 100644 --- a/src/server/scripts/Commands/cs_script_loader.cpp +++ b/src/server/scripts/Commands/cs_script_loader.cpp @@ -19,6 +19,7 @@ void AddSC_account_commandscript(); void AddSC_achievement_commandscript(); void AddSC_arena_commandscript(); +void AddSC_autobroadcast_commandscript(); void AddSC_bag_commandscript(); void AddSC_ban_commandscript(); void AddSC_bf_commandscript(); @@ -73,6 +74,7 @@ void AddCommandsScripts() AddSC_account_commandscript(); AddSC_achievement_commandscript(); AddSC_arena_commandscript(); + AddSC_autobroadcast_commandscript(); AddSC_bag_commandscript(); AddSC_ban_commandscript(); AddSC_bf_commandscript(); From 45c98fcd2c59dab4ca5c8f56e2cfea5bd02b9df5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Mar 2026 01:30:56 +0000 Subject: [PATCH 080/132] chore(DB): import pending files Referenced commit(s): 01c0ed3d61f12a73026e2e95b4c7ed6639cfd7a9 --- .../rev_1774073616034639439.sql => db_world/2026_03_25_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774073616034639439.sql => db_world/2026_03_25_01.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1774073616034639439.sql b/data/sql/updates/db_world/2026_03_25_01.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774073616034639439.sql rename to data/sql/updates/db_world/2026_03_25_01.sql index 4a51df829..f5314b3b0 100644 --- a/data/sql/updates/pending_db_world/rev_1774073616034639439.sql +++ b/data/sql/updates/db_world/2026_03_25_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_25_00 -> 2026_03_25_01 -- Add autobroadcast commands DELETE FROM `command` WHERE `name` IN ('autobroadcast', 'autobroadcast list', 'autobroadcast add', 'autobroadcast locale', 'autobroadcast remove'); INSERT INTO `command` (`name`, `security`, `help`) VALUES From 8d218a7558dc4ec50ed18e1163d672ad79d2bb12 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:25:34 -0300 Subject: [PATCH 081/132] chore(Core): add .claude directory to .gitignore (#25210) Co-authored-by: Claude Opus 4.6 (1M context) --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 548213a63..dff3ed7be 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,11 @@ CMakeLists.txt.user *.BASE.* *.LOCAL.* +# +# AI tools +# +.claude + # # IDE & other software # From c607c9cfe1a6aec2f3ee866de64e2d822a487883 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:41:23 -0300 Subject: [PATCH 082/132] fix(Core/Movement): DAMAGE_FALL_TO_VOID bypasses all immunities (#25211) Co-authored-by: Claude Opus 4.6 --- src/server/game/Entities/Player/Player.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index fb3af1eb4..a5d5a0c13 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -769,7 +769,9 @@ bool Player::IsImmuneToEnvironmentalDamage() uint32 Player::EnvironmentalDamage(EnviromentalDamage type, uint32 damage) { - if (IsImmuneToEnvironmentalDamage()) + // DAMAGE_FALL_TO_VOID bypasses all immunities (e.g. Divine Shield) to prevent + // players from being stuck infinitely falling below the map + if (type != DAMAGE_FALL_TO_VOID && IsImmuneToEnvironmentalDamage()) return 0; // Absorb, resist some environmental damage type From ee02b9acfc33d79835ae50a6624c1c6fa3f87207 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 00:18:16 -0500 Subject: [PATCH 083/132] chore(Core/Movement): Add missing co-author attribution for #25106 (#25209) - Adds missing `Co-Authored-By` credit for **ccrs** whose TrinityCore commit (`97585597f0`, `Core/Movement: waypoint movement (#20121)`) was the base for #25106. Co-Authored-By: ccrs Co-authored-by: blinkysc Co-authored-by: ccrs From 973954c7955169314ad45e43583fccf139e06e6f Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 25 Mar 2026 03:30:13 -0300 Subject: [PATCH 084/132] fix(Scripts/Gundrak): Fix Drakkari Colossus not activating when Living Mojo is attacked (#25214) Co-authored-by: Claude Opus 4.6 --- .../Northrend/Gundrak/boss_drakkari_colossus.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp b/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp index 7cb576a4a..2b7fd9805 100644 --- a/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp +++ b/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp @@ -363,7 +363,7 @@ public: ScriptedAI::MoveInLineOfSight(who); } - void AttackStart(Unit* who) override + void JustEngagedWith(Unit* who) override { if (me->ToTempSummon()) { @@ -373,6 +373,14 @@ public: return; } + ScriptedAI::JustEngagedWith(who); + } + + void AttackStart(Unit* who) override + { + if (me->ToTempSummon()) + return; + ScriptedAI::AttackStart(who); } From 2e80e9a3518b995759ee6646085c165d341c40d3 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 25 Mar 2026 07:01:46 -0300 Subject: [PATCH 085/132] fix(DB/Creature): repopulate CreatureImmunitiesId after broken migration (#25216) Co-authored-by: Claude Opus 4.6 --- .../rev_1774432077228950500.sql | 819 ++++++++++++++++++ 1 file changed, 819 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774432077228950500.sql diff --git a/data/sql/updates/pending_db_world/rev_1774432077228950500.sql b/data/sql/updates/pending_db_world/rev_1774432077228950500.sql new file mode 100644 index 000000000..0bc9a96d5 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774432077228950500.sql @@ -0,0 +1,819 @@ +-- +-- Fix: Repopulate CreatureImmunitiesId for all creatures that had mechanic/school immune masks +-- The original migration (2026_03_22_03) ran the UPDATE before creature_immunities rows existed, +-- resulting in all creatures getting CreatureImmunitiesId=0 +-- + +DELETE FROM `creature_immunities` WHERE `ID` IN (1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(1972, 0, 0, 2, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2'), +(1973, 0, 0, 4, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4'), +(1974, 0, 0, 16, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16'), +(1975, 0, 0, 18, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 18'), +(1976, 0, 0, 34, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 34'), +(1977, 0, 0, 40, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 40'), +(1978, 0, 0, 42, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 42'), +(1979, 0, 0, 64, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 64'), +(1980, 0, 0, 512, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 512'), +(1981, 0, 0, 514, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 514'), +(1982, 0, 0, 1024, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1024'), +(1983, 0, 0, 1056, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1056'), +(1984, 0, 0, 1058, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1058'), +(1985, 0, 0, 2048, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2048'), +(1986, 0, 0, 2050, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2050'), +(1987, 0, 0, 2052, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2052'), +(1988, 0, 0, 2080, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2080'), +(1989, 0, 0, 2082, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2082'), +(1990, 0, 0, 2178, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2178'), +(1991, 0, 0, 2192, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2192'), +(1992, 0, 0, 2208, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2208'), +(1993, 0, 0, 2210, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2210'), +(1994, 0, 0, 2562, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2562'), +(1995, 0, 0, 3074, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 3074'), +(1996, 0, 0, 4098, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4098'), +(1997, 0, 0, 4114, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4114'), +(1998, 0, 0, 4128, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4128'), +(1999, 0, 0, 4608, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4608'), +(2000, 0, 0, 4624, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4624'), +(2001, 0, 0, 6178, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 6178'), +(2002, 0, 0, 6810, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 6810'), +(2003, 0, 0, 8192, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 8192'), +(2004, 0, 0, 8224, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 8224'), +(2005, 0, 0, 12584, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 12584'), +(2006, 0, 0, 14374, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 14374'), +(2007, 0, 0, 14982, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 14982'), +(2008, 0, 0, 16384, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16384'), +(2009, 0, 0, 20516, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 20516'), +(2010, 0, 0, 21114, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 21114'), +(2011, 0, 0, 32768, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 32768'), +(2012, 0, 0, 65278, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 65278'), +(2013, 0, 0, 131072, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 131072'), +(2014, 0, 0, 131074, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 131074'), +(2015, 0, 0, 131106, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 131106'), +(2016, 0, 0, 132128, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 132128'), +(2017, 0, 0, 133122, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 133122'), +(2018, 0, 0, 133248, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 133248'), +(2019, 0, 0, 133250, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 133250'), +(2020, 0, 0, 139264, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 139264'), +(2021, 0, 0, 139298, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 139298'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(2022, 0, 0, 141440, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 141440'), +(2023, 0, 0, 143360, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 143360'), +(2024, 0, 0, 143396, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 143396'), +(2025, 0, 0, 262144, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 262144'), +(2026, 0, 0, 262146, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 262146'), +(2027, 0, 0, 262172, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 262172'), +(2028, 0, 0, 263222, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 263222'), +(2029, 0, 0, 265792, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 265792'), +(2030, 0, 0, 274592, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 274592'), +(2031, 0, 0, 278534, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 278534'), +(2032, 0, 0, 404998, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 404998'), +(2033, 0, 0, 407714, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 407714'), +(2034, 0, 0, 16777216, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16777216'), +(2035, 0, 0, 16777248, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16777248'), +(2036, 0, 0, 16777250, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16777250'), +(2037, 0, 0, 16777264, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16777264'), +(2038, 0, 0, 16777312, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16777312'), +(2039, 0, 0, 16777376, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16777376'), +(2040, 0, 0, 16779424, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16779424'), +(2041, 0, 0, 16779428, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16779428'), +(2042, 0, 0, 16779488, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16779488'), +(2043, 0, 0, 16781344, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16781344'), +(2044, 0, 0, 16781984, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16781984'), +(2045, 0, 0, 16783392, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16783392'), +(2046, 0, 0, 16788644, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16788644'), +(2047, 0, 0, 16791714, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16791714'), +(2048, 0, 0, 16794664, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16794664'), +(2049, 0, 0, 16794668, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16794668'), +(2050, 0, 0, 16794676, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16794676'), +(2051, 0, 0, 16827436, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16827436'), +(2052, 0, 0, 16932526, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 16932526'), +(2053, 0, 0, 17039394, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17039394'), +(2054, 0, 0, 17039520, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17039520'), +(2055, 0, 0, 17041570, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17041570'), +(2056, 0, 0, 17045536, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17045536'), +(2057, 0, 0, 17171490, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17171490'), +(2058, 0, 0, 17171494, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17171494'), +(2059, 0, 0, 17204258, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17204258'), +(2060, 0, 0, 17825824, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17825824'), +(2061, 0, 0, 17825826, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17825826'), +(2062, 0, 0, 17826336, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 17826336'), +(2063, 0, 0, 25198706, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 25198706'), +(2064, 0, 0, 25574434, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 25574434'), +(2065, 0, 0, 26216098, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 26216098'), +(2066, 0, 0, 67272418, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 67272418'), +(2067, 0, 0, 75497986, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 75497986'), +(2068, 0, 0, 75502082, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 75502082'), +(2069, 0, 0, 75765794, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 75765794'), +(2070, 0, 0, 84032160, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 84032160'), +(2071, 0, 0, 84180644, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 84180644'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(2072, 0, 0, 84180654, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 84180654'), +(2073, 0, 0, 92569262, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 92569262'), +(2074, 0, 0, 93617828, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 93617828'), +(2075, 0, 0, 134217728, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 134217728'), +(2076, 0, 0, 134219776, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 134219776'), +(2077, 0, 0, 134221834, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 134221834'), +(2078, 0, 0, 134368350, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 134368350'), +(2079, 0, 0, 134487696, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 134487696'), +(2080, 0, 0, 134487698, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 134487698'), +(2081, 0, 0, 134610954, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 134610954'), +(2082, 0, 0, 142884558, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 142884558'), +(2083, 0, 0, 151259878, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 151259878'), +(2084, 0, 0, 151262366, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 151262366'), +(2085, 0, 0, 151262398, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 151262398'), +(2086, 0, 0, 151273214, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 151273214'), +(2087, 0, 0, 151314166, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 151314166'), +(2088, 0, 0, 151401654, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 151401654'), +(2089, 0, 0, 152469182, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 152469182'), +(2090, 0, 0, 159516926, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 159516926'), +(2091, 0, 0, 160853686, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 160853686'), +(2092, 0, 0, 160857782, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 160857782'), +(2093, 0, 0, 160857790, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 160857790'), +(2094, 0, 0, 160857854, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 160857854'), +(2095, 0, 0, 161361662, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 161361662'), +(2096, 0, 0, 201719818, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 201719818'), +(2097, 0, 0, 226915902, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 226915902'), +(2098, 0, 0, 227962558, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 227962558'), +(2099, 0, 0, 688815860, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 688815860'), +(2100, 0, 0, 1002405630, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1002405630'), +(2101, 0, 0, 1073741824, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1073741824'), +(2102, 0, 0, 1073741826, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1073741826'), +(2103, 0, 0, 1073741892, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1073741892'), +(2104, 0, 0, 1073758240, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1073758240'), +(2105, 0, 0, 1073873920, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1073873920'), +(2106, 0, 0, 1073877158, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1073877158'), +(2107, 0, 0, 1073886882, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1073886882'), +(2108, 0, 0, 1074009602, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1074009602'), +(2109, 0, 0, 1074167486, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1074167486'), +(2110, 0, 0, 1075202048, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1075202048'), +(2111, 0, 0, 1082677838, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1082677838'), +(2112, 0, 0, 1082686030, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1082686030'), +(2113, 0, 0, 1082686046, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1082686046'), +(2114, 0, 0, 1090534560, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090534560'), +(2115, 0, 0, 1090679030, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090679030'), +(2116, 0, 0, 1090681014, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090681014'), +(2117, 0, 0, 1090681270, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090681270'), +(2118, 0, 0, 1090936558, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090936558'), +(2119, 0, 0, 1090936574, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090936574'), +(2120, 0, 0, 1090940076, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090940076'), +(2121, 0, 0, 1090940670, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090940670'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(2122, 0, 0, 1090944742, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090944742'), +(2123, 0, 0, 1090944766, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1090944766'), +(2124, 0, 0, 1099040290, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1099040290'), +(2125, 0, 0, 1099043874, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1099043874'), +(2126, 0, 0, 1099060258, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1099060258'), +(2127, 0, 0, 1100219550, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100219550'), +(2128, 0, 0, 1100248830, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100248830'), +(2129, 0, 0, 1100350654, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100350654'), +(2130, 0, 0, 1100359230, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100359230'), +(2131, 0, 0, 1100366910, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100366910'), +(2132, 0, 0, 1100375718, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100375718'), +(2133, 0, 0, 1100379710, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100379710'), +(2134, 0, 0, 1100379774, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100379774'), +(2135, 0, 0, 1100904190, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100904190'), +(2136, 0, 0, 1100906238, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1100906238'), +(2137, 0, 0, 1102477054, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1102477054'), +(2138, 0, 0, 1102872318, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1102872318'), +(2139, 0, 0, 1141118466, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1141118466'), +(2140, 0, 0, 1158053548, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1158053548'), +(2141, 0, 0, 1158053550, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1158053550'), +(2142, 0, 0, 1158053630, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1158053630'), +(2143, 0, 0, 1167484670, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1167484670'), +(2144, 0, 0, 1167488694, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1167488694'), +(2145, 0, 0, 1207983274, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1207983274'), +(2146, 0, 0, 1208099366, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1208099366'), +(2147, 0, 0, 1208110334, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1208110334'), +(2148, 0, 0, 1208117942, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1208117942'), +(2149, 0, 0, 1208118454, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1208118454'), +(2150, 0, 0, 1208123134, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1208123134'), +(2151, 0, 0, 1208245410, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1208245410'), +(2152, 0, 0, 1209171606, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1209171606'), +(2153, 0, 0, 1217817766, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1217817766'), +(2154, 0, 0, 1224758964, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1224758964'), +(2155, 0, 0, 1224758966, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1224758966'), +(2156, 0, 0, 1224764576, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1224764576'), +(2157, 0, 0, 1224891646, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1224891646'), +(2158, 0, 0, 1224892158, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1224892158'), +(2159, 0, 0, 1224899750, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1224899750'), +(2160, 0, 0, 1224899830, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1224899830'), +(2161, 0, 0, 1225030910, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225030910'), +(2162, 0, 0, 1225031358, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225031358'), +(2163, 0, 0, 1225064188, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225064188'), +(2164, 0, 0, 1225162494, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225162494'), +(2165, 0, 0, 1225293566, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225293566'), +(2166, 0, 0, 1225424638, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225424638'), +(2167, 0, 0, 1225588478, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225588478'), +(2168, 0, 0, 1225686782, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1225686782'), +(2169, 0, 0, 1226206902, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1226206902'), +(2170, 0, 0, 1226209946, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1226209946'), +(2171, 0, 0, 1226209974, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1226209974'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(2172, 0, 0, 1226210998, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1226210998'), +(2173, 0, 0, 1226211006, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1226211006'), +(2174, 0, 0, 1226211070, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1226211070'), +(2175, 0, 0, 1230420214, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1230420214'), +(2176, 0, 0, 1233288886, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1233288886'), +(2177, 0, 0, 1233419518, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1233419518'), +(2178, 0, 0, 1233551102, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1233551102'), +(2179, 0, 0, 1233944318, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1233944318'), +(2180, 0, 0, 1234075390, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234075390'), +(2181, 0, 0, 1234568766, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234568766'), +(2182, 0, 0, 1234583806, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234583806'), +(2183, 0, 0, 1234595510, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234595510'), +(2184, 0, 0, 1234595518, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234595518'), +(2185, 0, 0, 1234595582, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234595582'), +(2186, 0, 0, 1234597430, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234597430'), +(2187, 0, 0, 1234599094, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599094'), +(2188, 0, 0, 1234599158, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599158'), +(2189, 0, 0, 1234599606, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599606'), +(2190, 0, 0, 1234599610, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599610'), +(2191, 0, 0, 1234599614, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599614'), +(2192, 0, 0, 1234599654, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599654'), +(2193, 0, 0, 1234599670, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599670'), +(2194, 0, 0, 1234599678, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234599678'), +(2195, 0, 0, 1234632374, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234632374'), +(2196, 0, 0, 1234992894, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1234992894'), +(2197, 0, 0, 1235119870, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1235119870'), +(2198, 0, 0, 1235123964, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1235123964'), +(2199, 0, 0, 1235123966, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1235123966'), +(2200, 0, 0, 1237221118, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1237221118'), +(2201, 0, 0, 1275494326, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1275494326'), +(2202, 0, 0, 1291996862, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1291996862'), +(2203, 0, 0, 1292000954, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292000954'), +(2204, 0, 0, 1292000958, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292000958'), +(2205, 0, 0, 1292009150, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292009150'), +(2206, 0, 0, 1292033724, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292033724'), +(2207, 0, 0, 1292033726, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292033726'), +(2208, 0, 0, 1292270270, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292270270'), +(2209, 0, 0, 1292271166, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292271166'), +(2210, 0, 0, 1292271294, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292271294'), +(2211, 0, 0, 1292271358, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1292271358'), +(2212, 0, 0, 1293188798, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293188798'), +(2213, 0, 0, 1293311670, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293311670'), +(2214, 0, 0, 1293315754, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293315754'), +(2215, 0, 0, 1293315774, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293315774'), +(2216, 0, 0, 1293317684, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293317684'), +(2217, 0, 0, 1293318838, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293318838'), +(2218, 0, 0, 1293319862, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293319862'), +(2219, 0, 0, 1293319870, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293319870'), +(2220, 0, 0, 1293319926, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293319926'), +(2221, 0, 0, 1293876926, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1293876926'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(2222, 0, 0, 1300692542, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1300692542'), +(2223, 0, 0, 1300692670, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1300692670'), +(2224, 0, 0, 1301053166, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301053166'), +(2225, 0, 0, 1301184254, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301184254'), +(2226, 0, 0, 1301446398, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301446398'), +(2227, 0, 0, 1301677626, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301677626'), +(2228, 0, 0, 1301677630, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301677630'), +(2229, 0, 0, 1301702270, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301702270'), +(2230, 0, 0, 1301703934, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301703934'), +(2231, 0, 0, 1301704382, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301704382'), +(2232, 0, 0, 1301704446, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301704446'), +(2233, 0, 0, 1301706494, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301706494'), +(2234, 0, 0, 1301707958, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301707958'), +(2235, 0, 0, 1301708022, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708022'), +(2236, 0, 0, 1301708462, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708462'), +(2237, 0, 0, 1301708470, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708470'), +(2238, 0, 0, 1301708478, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708478'), +(2239, 0, 0, 1301708526, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708526'), +(2240, 0, 0, 1301708534, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708534'), +(2241, 0, 0, 1301708538, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708538'), +(2242, 0, 0, 1301708540, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708540'), +(2243, 0, 0, 1301708542, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301708542'), +(2244, 0, 0, 1301741246, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301741246'), +(2245, 0, 0, 1301741310, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1301741310'), +(2246, 0, 0, 1302232766, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1302232766'), +(2247, 0, 0, 1302232830, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1302232830'), +(2248, 0, 0, 1303871230, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1303871230'), +(2249, 0, 0, 1304395518, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1304395518'), +(2250, 0, 0, 1304428286, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1304428286'), +(2251, 0, 0, 1305378558, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1305378558'), +(2252, 0, 0, 1335787254, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1335787254'), +(2253, 0, 0, 1539276542, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1539276542'), +(2254, 0, 0, 1770663678, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1770663678'), +(2255, 0, 0, 1770946302, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1770946302'), +(2256, 0, 0, 1804534846, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 1804534846'), +(2257, 0, 0, 2013265920, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2013265920'), +(2258, 0, 0, 2013528064, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2013528064'), +(2259, 0, 0, 2013528100, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2013528100'), +(2260, 0, 0, 2013660192, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2013660192'), +(2261, 0, 0, 2022966272, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2022966272'), +(2262, 0, 0, 2030453792, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2030453792'), +(2263, 0, 0, 2031386302, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031386302'), +(2264, 0, 0, 2031502368, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031502368'), +(2265, 0, 0, 2031502372, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031502372'), +(2266, 0, 0, 2031512736, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031512736'), +(2267, 0, 0, 2031516704, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031516704'), +(2268, 0, 0, 2031516708, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031516708'), +(2269, 0, 0, 2031516836, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031516836'), +(2270, 0, 0, 2031517372, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2031517372'), +(2271, 0, 0, 2038842400, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2038842400'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(2272, 0, 0, 2038842402, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2038842402'), +(2273, 0, 0, 2039890978, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039890978'), +(2274, 0, 0, 2039890982, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039890982'), +(2275, 0, 0, 2039901750, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039901750'), +(2276, 0, 0, 2039905318, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039905318'), +(2277, 0, 0, 2039905444, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039905444'), +(2278, 0, 0, 2039905446, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039905446'), +(2279, 0, 0, 2039905454, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039905454'), +(2280, 0, 0, 2039905956, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039905956'), +(2281, 0, 0, 2039905974, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039905974'), +(2282, 0, 0, 2039906038, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039906038'), +(2283, 0, 0, 2039906046, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2039906046'), +(2284, 0, 0, 2041872062, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2041872062'), +(2285, 0, 0, 2042588340, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2042588340'), +(2286, 0, 0, 2042588342, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2042588342'), +(2287, 0, 0, 2042592436, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2042592436'), +(2288, 0, 0, 2042592438, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2042592438'), +(2289, 0, 0, 2044099622, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2044099622'), +(2290, 0, 0, 2076147382, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2076147382'), +(2291, 0, 0, 2107014838, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2107014838'), +(2292, 0, 0, 2143256254, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2143256254'), +(2293, 0, 0, 2143289022, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2143289022'), +(2294, 0, 0, 2147483646, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 2147483646'), +(2295, 0, 0, 3116367612, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 3116367612'), +(2296, 0, 0, 3386277110, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 3386277110'), +(2297, 0, 0, 4294934270, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4294934270'), +(2298, 0, 0, 4294967038, '', '', 0, 0, 'SchoolMask 0, MechanicsMask 4294967038'), +(2299, 1, 0, 16777248, '', '', 0, 0, 'SchoolMask 1, MechanicsMask 16777248'), +(2300, 1, 0, 1301708478, '', '', 0, 0, 'SchoolMask 1, MechanicsMask 1301708478'), +(2301, 4, 0, 0, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 0'), +(2302, 4, 0, 32, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 32'), +(2303, 4, 0, 17416, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 17416'), +(2304, 4, 0, 17420, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 17420'), +(2305, 4, 0, 17432, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 17432'), +(2306, 4, 0, 16784544, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 16784544'), +(2307, 4, 0, 1226210998, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1226210998'), +(2308, 4, 0, 1226211006, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1226211006'), +(2309, 4, 0, 1234599606, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1234599606'), +(2310, 4, 0, 1292000954, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1292000954'), +(2311, 4, 0, 1292271294, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1292271294'), +(2312, 4, 0, 1293188798, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1293188798'), +(2313, 4, 0, 1301704446, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1301704446'), +(2314, 4, 0, 1301708478, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1301708478'), +(2315, 4, 0, 1301708542, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 1301708542'), +(2316, 4, 0, 2039905974, '', '', 0, 0, 'SchoolMask 4, MechanicsMask 2039905974'), +(2317, 8, 0, 0, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 0'), +(2318, 8, 0, 16, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 16'), +(2319, 8, 0, 1090674362, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1090674362'), +(2320, 8, 0, 1217817766, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1217817766'), +(2321, 8, 0, 1234599606, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1234599606'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347,2348,2349,2350,2351,2352,2353,2354,2355); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(2322, 8, 0, 1291998778, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1291998778'), +(2323, 8, 0, 1292000954, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1292000954'), +(2324, 8, 0, 1292000958, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1292000958'), +(2325, 8, 0, 1292271358, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1292271358'), +(2326, 8, 0, 1293188798, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1293188798'), +(2327, 8, 0, 1293319870, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1293319870'), +(2328, 8, 0, 1300397758, '', '', 0, 0, 'SchoolMask 8, MechanicsMask 1300397758'), +(2329, 16, 0, 0, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 0'), +(2330, 16, 0, 16, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 16'), +(2331, 16, 0, 20516, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 20516'), +(2332, 16, 0, 16777248, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 16777248'), +(2333, 16, 0, 1090674362, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1090674362'), +(2334, 16, 0, 1217817766, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1217817766'), +(2335, 16, 0, 1226210998, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1226210998'), +(2336, 16, 0, 1234599606, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1234599606'), +(2337, 16, 0, 1292000954, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1292000954'), +(2338, 16, 0, 1293188798, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1293188798'), +(2339, 16, 0, 1301708538, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1301708538'), +(2340, 16, 0, 1301708542, '', '', 0, 0, 'SchoolMask 16, MechanicsMask 1301708542'), +(2341, 32, 0, 0, '', '', 0, 0, 'SchoolMask 32, MechanicsMask 0'), +(2342, 64, 0, 0, '', '', 0, 0, 'SchoolMask 64, MechanicsMask 0'), +(2343, 64, 0, 32, '', '', 0, 0, 'SchoolMask 64, MechanicsMask 32'), +(2344, 64, 0, 6688, '', '', 0, 0, 'SchoolMask 64, MechanicsMask 6688'), +(2345, 64, 0, 10368, '', '', 0, 0, 'SchoolMask 64, MechanicsMask 10368'), +(2346, 64, 0, 1234599094, '', '', 0, 0, 'SchoolMask 64, MechanicsMask 1234599094'), +(2347, 64, 0, 1235123966, '', '', 0, 0, 'SchoolMask 64, MechanicsMask 1235123966'), +(2348, 96, 0, 0, '', '', 0, 0, 'SchoolMask 96, MechanicsMask 0'), +(2349, 116, 0, 0, '', '', 0, 0, 'SchoolMask 116, MechanicsMask 0'), +(2350, 124, 0, 17171490, '', '', 0, 0, 'SchoolMask 124, MechanicsMask 17171490'), +(2351, 124, 0, 1234599614, '', '', 0, 0, 'SchoolMask 124, MechanicsMask 1234599614'), +(2352, 126, 0, 0, '', '', 0, 0, 'SchoolMask 126, MechanicsMask 0'), +(2353, 126, 0, 1292271286, '', '', 0, 0, 'SchoolMask 126, MechanicsMask 1292271286'), +(2354, 127, 0, 16777248, '', '', 0, 0, 'SchoolMask 127, MechanicsMask 16777248'), +(2355, 127, 0, 1301741246, '', '', 0, 0, 'SchoolMask 127, MechanicsMask 1301741246'); + +-- Update creature_template.CreatureImmunitiesId +UPDATE `creature_template` SET `CreatureImmunitiesId`=1537 WHERE `entry` IN (17543,18404); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1664 WHERE `entry` IN (4468); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1794 WHERE `entry` IN (3253,36551,37564); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1964 WHERE `entry` IN (20047); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1971 WHERE `entry` IN (20038,25367,25368,25371,25507); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1972 WHERE `entry` IN (352,523,752,931,1387,1571,1572,1573,2299,2409,2432,2835,2851,2858,2859,2861,2941,2995,3305,3310,3615,3725,3728,4312,4314,4317,4321,6026,6726,7823,7824,8018,8020,8609,8610,10378,11139,11830,11899,11900,11901,12596,12616,12617,12740,13177,15178,16218,16461,16593,16699,16704,17083,17229,17398,17420,17429,17461,17491,17622,17695,18048,18049,18050,18052,18053,18054,18057,18058,18603,18609,18610,18612,18615,18619,18631,18632,18635,18636,18830,19885,19887,19889,19903,20033,20034,20255,20256,20257,20258,20259,20260,20261,20578,20579,20580,20581,20582,20586,20587,20590,20594,20621,20624,20626,20639,20640,20641,20642,20644,20645,20661,20693,20695,20699,20701,20923,21230,21522,21524,21527,21528,21532,21545,21548,21563,21564,21565,21570,21571,21573,21575,21576,21585,21587,21613,21857,21865,21873,22876,23168,24994,25509,25591,25593,25595,25599,25851,26799,26800,26801,26802,26803,26805,27947,27949,29503,29626,30460,30478,30485,30495,30496,30497,30498,30508,30509,32922,32923,32924,32925,33235,33236,33259,33287,33355,33527,33579,33620,33622,33624,33626,33627,33629,33662,33669,33672,33696,33701,33737,33741,33754,33755,33757,33758,33774,33775,33816,33818,33819); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1972 WHERE `entry` IN (33820,33822,33823,33824,33827,33828,33829,33830,33831,33832,33956,33957,34054,34113,34144,34145,34198,34199,34236,34237,34255,34256,34257,34441,34442,34443,34444,34445,34447,34448,34449,34450,34451,34453,34454,34455,34456,34458,34459,34460,34461,34463,34465,34466,34467,34468,34469,34470,34471,34472,34473,34474,34475,35465,35610,35662,35663,35664,35665,35666,35667,35668,35669,35670,35671,35672,35673,35674,35675,35676,35680,35681,35682,35683,35684,35685,35686,35687,35688,35689,35690,35691,35692,35693,35694,35695,35696,35697,35699,35700,35701,35702,35703,35704,35705,35706,35707,35708,35709,35710,35711,35712,35713,35714,35715,35716,35718,35719,35720,35721,35722,35723,35724,35725,35726,35728,35729,35730,35731,35732,35733,35734,35735,35736,35737,35738,35739,35740,35741,35742,35743,35744,35745,35746,35747,35748,35749,35774,35775,35776,36070,36301,36302,36303,36473,36474,36475); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1973 WHERE `entry` IN (12737); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1974 WHERE `entry` IN (13996,14462,25752,25753,25758,25792); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1975 WHERE `entry` IN (24553,24554,24555,24556,24557,24558,24559,24561,24656,25541,25549,25550,25553,25555,25556,25574,25578,25579); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1976 WHERE `entry` IN (8717,21549); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1977 WHERE `entry` IN (30839); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1978 WHERE `entry` IN (20869,21586); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1979 WHERE `entry` IN (2673,2674,4952,12426,16844,16968,17578,18215,19139,21157,21849,22038,22466,22482,24792,30527,31143,31144,31146,32541,32542,32543,32545,32546,32547,32666,32667,33229,33243,33272,37127,38126); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1980 WHERE `entry` IN (20897,23398); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1981 WHERE `entry` IN (21597,22879,22882,22945,23018,23049,23147,23172,23223,23235,23236,23237,24815,25566); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1982 WHERE `entry` IN (5277,5280,5283,8319,16098,17669,20574); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1983 WHERE `entry` IN (22299,22300); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1984 WHERE `entry` IN (3678); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1985 WHERE `entry` IN (23584); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1986 WHERE `entry` IN (19005,22874,22885); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1987 WHERE `entry` IN (18707); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1988 WHERE `entry` IN (18533,20772); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1989 WHERE `entry` IN (21166); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1990 WHERE `entry` IN (18044,22939); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1991 WHERE `entry` IN (8908); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1992 WHERE `entry` IN (18258); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1993 WHERE `entry` IN (18046); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1994 WHERE `entry` IN (22957,22962); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1995 WHERE `entry` IN (18420,21574); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1996 WHERE `entry` IN (20052); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1997 WHERE `entry` IN (21225); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1998 WHERE `entry` IN (24549); +UPDATE `creature_template` SET `CreatureImmunitiesId`=1999 WHERE `entry` IN (34702,34703,35569,35570,35571,35572,35617,36082,36083,36084,36085,36086,36087,36088,36089,36090,36091); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2000 WHERE `entry` IN (20048); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2001 WHERE `entry` IN (20883,21615); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2002 WHERE `entry` IN (28163,28168); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2003 WHERE `entry` IN (12800,12801,12802); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2004 WHERE `entry` IN (24047); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2005 WHERE `entry` IN (18423); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2006 WHERE `entry` IN (12458); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2007 WHERE `entry` IN (22875); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2008 WHERE `entry` IN (7397); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2009 WHERE `entry` IN (13601); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2010 WHERE `entry` IN (30829,30830,30831); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2011 WHERE `entry` IN (2667,2671,7076,7077,7309,8376,8816,9657,15104,15108,15429,15631,15698,15706,15904,15910,15964,15973,16511); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2012 WHERE `entry` IN (26688); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2013 WHERE `entry` IN (16700,17732,19632,20174,24179,29273); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2014 WHERE `entry` IN (14261,14262,14263,14264,14265,20588,20589); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2015 WHERE `entry` IN (17816,17817,19884,19892); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2016 WHERE `entry` IN (19513,19598,19865,21560,21561,21562); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2017 WHERE `entry` IN (17465,20583,22955); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2018 WHERE `entry` IN (32361,32377,32409,32417,32422,32429,32447,32475,32481,32485); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2019 WHERE `entry` IN (22965); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2020 WHERE `entry` IN (24180); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2021 WHERE `entry` IN (24065); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2022 WHERE `entry` IN (21515); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2023 WHERE `entry` IN (24530); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2024 WHERE `entry` IN (18322,20696); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2025 WHERE `entry` IN (15146,25363); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2026 WHERE `entry` IN (17818,17864,18848,20656,20881,20900,21619,21621,25592,25948); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2027 WHERE `entry` IN (7728); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2028 WHERE `entry` IN (25798,25799); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2029 WHERE `entry` IN (26928,26929,26930,30511,30512,30513); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2030 WHERE `entry` IN (18182); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2031 WHERE `entry` IN (32500); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2032 WHERE `entry` IN (17731,20173); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2033 WHERE `entry` IN (19191); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2034 WHERE `entry` IN (19261); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (3,36,114,115,200,201,204,210,302,314,315,392,412,480,503,511,522,570,572,573,604,624,625,626,725,846,882,948,1159,1270,1488,1489,1491,1501,1502,1525,1526,1527,1528,1529,1530,1532,1533,1654,1656,1663,1666,1674,1675,1696,1716,1717,1720,1753,1772,1773,1790,1791,1792,1793,1794,1795,1796,1798,1805,1847,1849,1850,1852,1866,1868,1917,1918,1919,1921,1939,1940,1941,1942,1943,1944,1948,1971,1974,2045,2056,2217,2218,2219,2220,2221,2222,2227,2278,2288,2325,2433,2475,2479,2520,2531,2535,2536,2537,2623,2676,2678,2748,2763,2922,3094,3301,3538,3558,3565,3617,3654,3669,3670,3671,3673,3674,3799,3802,3803,3869,3870,3876,3914,3940,3941,3942,3983,4073,4074,4251,4252,4260,4308,4473,4474,4475,4476,4550,4566,4606,4610,4612,4854,4872,4945,4946,4958,5097,5202,5263,5267,5270,5271,5400,5621,5624,5626,5627,5674,5685,5686,5687,5712,5713,5714,5715,5716,5717,5723,5775,5854,5912,5954,6036,6092,6106,6107,6108,6225,6226,6227,6230,6231,6232,6233,6234,6346,6386,6426,6427,6486,6493,6669,6906); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (6907,6908,6910,7023,7039,7050,7067,7068,7069,7070,7071,7072,7075,7166,7206,7209,7270,7273,7274,7276,7286,7291,7310,7327,7328,7329,7332,7333,7334,7340,7347,7348,7349,7351,7352,7353,7354,7355,7356,7357,7358,7374,7375,7503,7504,7527,7624,7784,7791,7806,7807,7849,7895,7897,8035,8447,8500,8501,8530,8531,8532,8534,8535,8536,8537,8543,8544,8545,8552,8555,8556,8557,8558,8559,8567,8580,8585,8615,8663,8856,9437,9438,9439,9441,9442,9443,9476,9623,10080,10081,10082,10117,10381,10382,10383,10395,10397,10402,10403,10405,10406,10407,10408,10409,10411,10412,10413,10414,10416,10417,10463,10464,10479,10480,10481,10483,10484,10492,10493,10494,10495,10497,10498,10499,10500,10580,10666,10684,10698,10699,10778,10801,10810,10818,10819,10820,10821,10825,10836,10926,10927,10936,10937,10939,10943,10944,10945,10946,10949,10950,10951,10953,10954,11027,11030,11075,11082,11151,11153,11154,11155,11179,11195,11199,11216,11217,11263,11277,11278,11279,11280,11281,11282,11283,11286,11287,11290,11291,11292,11316,11412,11414,11415,11446,11468,11469,11470,11471,11472,11473,11474,11475,11477,11517,11518,11519,11520,11551,11580,11598); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (11628,11684,11875,11936,12238,12239,12240,12241,12242,12243,12248,12250,12261,12262,12263,12363,12364,12365,12367,12368,12379,12380,12385,12473,12865,13146,13378,13416,13619,13738,13739,13740,13741,13742,14224,14308,14337,14358,14364,14484,14485,14494,14511,14512,14513,14514,14518,14519,14520,14521,14551,14552,14553,14554,14562,14563,14605,14683,14685,14686,14687,14688,14691,14692,14694,14695,14696,14697,14698,14699,14700,14701,14702,14703,14704,14705,14706,14707,14708,14709,14710,14711,14712,14713,14714,14825,14826,14986,15079,15081,15117,15121,15163,15195,15349,15364,15368,15374,15376,15377,15547,15548,15551,15654,15655,15656,15657,15658,15665,15720,15958,16025,16026,16039,16040,16059,16066,16067,16093,16099,16103,16104,16119,16120,16125,16138,16141,16145,16146,16148,16153,16154,16159,16163,16169,16184,16211,16245,16246,16247,16248,16249,16250,16298,16300,16301,16302,16303,16305,16306,16307,16308,16309,16311,16312,16313,16314,16316,16319,16320,16321,16322,16323,16324,16325,16326,16327,16328,16329,16342,16357,16382,16383,16388,16389,16390,16393,16394,16402,16403,16406,16407,16408,16409,16410,16411,16412,16415,16424,16425,16426,16432,16437,16439,16451,16459,16460,16468,16470,16471); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (16481,16482,16525,16526,16805,16806,16811,16812,16813,16814,16815,16861,16904,16905,16906,16976,16977,16978,17007,17063,17064,17067,17086,17261,17415,17466,17503,17588,17589,17592,17612,17660,17672,17674,17714,17878,17895,17897,17898,17902,17903,17905,17906,18043,18287,18319,18320,18327,18355,18367,18441,18460,18478,18498,18499,18500,18501,18506,18521,18524,18556,18557,18558,18559,18588,18643,18689,18700,18797,18872,18873,19416,19417,19460,19463,19464,19488,19489,19543,19544,19545,19546,19579,19580,19682,19698,19719,19736,19749,19751,19825,19826,19827,19846,19863,19864,19872,19873,19874,19875,19876,19878,19879,19881,19937,20117,20137,20298,20303,20305,20310,20311,20312,20313,20315,20316,20317,20319,20320,20321,20322,20323,20409,20410,20480,20483,20495,20496,20512,20662,20691,20697,20698,20934,21049,21058,21065,21093,21163,21184,21198,21199,21200,21201,21202,21324,21354,21384,21385,21386,21430,21446,21449,21450,21452,21628,21636,21638,21651,21763,21778,21779,21784,21787,21788,21795,21797,21801,21815,21866,21867,21869,21870,21941,22025,22041,22045,22062,22138,22226,22235,22369,22441,22452,22454,22701,22706,23066,23067,23068,23103,23109,23111,23126,23161,23193,23204,23370,23371,23389); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (23408,23503,23509,23545,23554,23555,23561,23562,23563,23575,23643,23644,23645,23786,23861,23898,23904,23935,23970,23992,23993,24013,24019,24027,24029,24041,24073,24074,24084,24118,24153,24168,24177,24207,24246,24247,24248,24258,24262,24266,24267,24272,24327,24344,24440,24446,24447,24485,24546,24562,24563,24564,24566,24621,24622,24623,24624,24625,24626,24627,24693,24695,24712,24789,24790,24796,24808,24814,24871,24872,24874,24875,24876,24877,24880,25027,25028,25041,25224,25227,25228,25268,25294,25296,25330,25331,25332,25333,25350,25351,25352,25359,25365,25366,25375,25377,25382,25383,25386,25387,25393,25396,25445,25451,25452,25453,25462,25463,25465,25469,25542,25546,25559,25582,25600,25611,25619,25622,25625,25629,25650,25652,25655,25660,25668,25678,25682,25684,25981,26073,26076,26103,26115,26125,26126,26165,26195,26202,26203,26224,26225,26250,26252,26286,26287,26292,26294,26317,26318,26320,26336,26343,26344,26402,26404,26413,26455,26457,26458,26461,26475,26490,26491,26492,26501,26509,26515,26517,26518,26526,26536,26555,26570,26573,26605,26606,26607,26608,26621,26623,26624,26635,26636,26637,26638,26658,26669,26670,26676,26690,26691,26692,26694,26696,26702,26703,26769,26770,26771,26811); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (26812,26824,26830,26840,26841,26858,26871,26891,26946,26948,26966,26967,27018,27059,27105,27122,27152,27153,27165,27219,27220,27224,27225,27226,27229,27240,27241,27268,27270,27272,27282,27283,27284,27285,27286,27287,27288,27290,27335,27360,27362,27363,27370,27383,27400,27401,27410,27465,27505,27510,27513,27531,27533,27534,27551,27552,27556,27561,27604,27605,27611,27614,27616,27618,27620,27623,27624,27630,27631,27680,27683,27685,27686,27691,27693,27694,27696,27712,27729,27733,27734,27736,27737,27766,27767,27768,27769,27770,27771,27772,27773,27774,27775,27776,27777,27778,27779,27780,27781,27782,27797,27799,27800,27807,27808,27809,27821,27822,27823,27824,27825,27829,27835,27836,27848,27854,27871,27874,27941,27957,28005,28006,28007,28014,28018,28022,28023,28026,28101,28103,28104,28108,28144,28158,28159,28162,28170,28194,28199,28200,28201,28207,28208,28211,28212,28218,28220,28232,28242,28243,28246,28249,28255,28257,28258,28268,28270,28278,28349,28350,28356,28357,28364,28365,28369,28377,28383,28395,28405,28406,28412,28414,28419,28420,28421,28422,28423,28424,28425,28426,28427,28428,28429,28430,28431,28432,28433,28434,28435,28436,28437,28438,28444,28445,28446,28447,28448,28449,28450,28471); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (28472,28474,28475,28476,28487,28488,28498,28499,28500,28503,28506,28510,28512,28519,28528,28534,28564,28565,28570,28589,28590,28599,28603,28615,28628,28641,28642,28647,28651,28653,28654,28657,28658,28659,28666,28669,28670,28711,28732,28733,28734,28735,28736,28745,28747,28748,28749,28750,28752,28754,28756,28759,28760,28768,28769,28782,28788,28793,28802,28803,28805,28843,28864,28866,28867,28868,28869,28870,28871,28872,28875,28878,28879,28889,28890,28897,28901,28903,28905,28906,28907,28908,28909,28910,28911,28913,28914,28915,28919,28924,28925,28930,28931,28933,28934,28937,28943,28948,28998,29030,29031,29047,29051,29053,29062,29063,29064,29096,29097,29098,29101,29106,29107,29108,29109,29110,29111,29112,29113,29115,29117,29118,29119,29123,29128,29129,29133,29136,29153,29172,29173,29183,29185,29186,29187,29188,29189,29190,29191,29193,29194,29195,29196,29197,29198,29199,29200,29201,29202,29204,29205,29206,29207,29208,29209,29210,29212,29213,29214,29216,29217,29219,29221,29227,29230,29231,29232,29238,29239,29245,29246,29256,29263,29280,29286,29335,29340,29347,29349,29353,29354,29355,29356,29359,29362,29363,29371,29388,29394,29400,29414,29449,29450,29451,29453,29468,29480,29488,29501,29517); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (29519,29520,29522,29565,29566,29567,29570,29574,29576,29587,29609,29629,29632,29633,29634,29635,29646,29648,29654,29656,29697,29699,29704,29719,29720,29722,29738,29769,29770,29821,29823,29824,29825,29828,29831,29833,29835,29837,29840,29842,29851,29852,29856,29858,29859,29860,29863,29872,29882,29887,29890,29891,29892,29893,29894,29895,29897,29898,29899,29900,29901,29934,29935,29943,29974,29985,29986,29987,29988,29989,29990,30015,30016,30018,30027,30028,30029,30030,30031,30032,30033,30034,30047,30048,30049,30071,30074,30075,30083,30085,30087,30097,30135,30144,30150,30202,30203,30204,30205,30216,30230,30232,30235,30250,30264,30276,30277,30278,30283,30284,30285,30286,30287,30288,30303,30306,30307,30309,30310,30311,30333,30335,30338,30378,30389,30403,30404,30406,30409,30424,30432,30443,30471,30482,30486,30501,30523,30541,30542,30543,30544,30545,30546,30575,30593,30594,30597,30609,30629,30670,30687,30689,30696,30697,30698,30701,30728,30746,30778,30791,30806,30816,30817,30818,30819,30821,30822,30823,30836,30843,30851,30863,30864,30865,30894,30895,30920,30921,30922,30944,30945,30946,30947,30949,30951,30952,30953,30954,30955,30956,30957,30958,30960,30984,30985,30986,30987,30988,30989,30992); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (30993,31012,31014,31015,31029,31037,31039,31040,31042,31043,31048,31078,31082,31083,31084,31087,31088,31089,31090,31094,31095,31096,31097,31098,31099,31100,31104,31107,31110,31123,31135,31137,31139,31140,31141,31142,31147,31150,31152,31154,31155,31157,31159,31160,31161,31163,31178,31179,31183,31184,31187,31188,31191,31192,31193,31194,31195,31196,31198,31199,31200,31205,31207,31208,31220,31221,31222,31223,31224,31225,31226,31227,31231,31237,31251,31255,31263,31266,31268,31271,31274,31278,31283,31301,31318,31320,31321,31322,31323,31324,31325,31326,31327,31341,31342,31346,31347,31351,31352,31354,31355,31357,31361,31363,31396,31398,31411,31413,31438,31442,31443,31447,31449,31450,31451,31457,31460,31466,31468,31530,31531,31532,31554,31555,31556,31565,31583,31585,31586,31587,31588,31589,31590,31591,31593,31594,31595,31596,31597,31598,31599,31600,31601,31602,31603,31604,31605,31606,31607,31608,31609,31613,31614,31635,31647,31671,31681,31692,31702,31718,31721,31754,31775,31779,31780,31783,31787,31812,31813,31815,31843,31844,31847,31853,31900,32031,32037,32149,32160,32161,32163,32164,32175,32176,32178,32179,32181,32182,32183,32184,32188,32255,32257,32267,32272,32278,32280,32283,32284,32288); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (32292,32299,32300,32309,32323,32326,32350,32390,32391,32404,32408,32410,32423,32446,32467,32479,32480,32482,32483,32490,32492,32497,32499,32502,32503,32505,32507,32508,32511,32556,32593,32609,32767,32769,32770,32771,32772,32786,32787,33016,33017,33429,33438,33441,33499,33513,33519,33550,33663,33664,33667,33668,33687,33704,33798,34127,34154,34238,34801,35169,35255,35444,35461,35474,35493,35547,35557,35559,35560,35652,35653,35654,35763,36065,36066,36128,36173,36478,36503,36504,36522,36564,36595,36666,36677,36724,36725,36726,36792,36794,36796,36807,36824,36829,36830,36840,36841,36842,36844,36875,36877,36879,36880,36881,36886,36893,36896,36907,36916,36940,36941,37007,37011,37012,37022,37023,37038,37069,37120,37128,37228,37229,37232,37351,37357,37491,37493,37494,37495,37501,37502,37530,37532,37535,37538,37539,37541,37542,37544,37545,37546,37549,37550,37551,37563,37565,37569,37571,37586,37595,37612,37622,37635,37636,37637,37638,37639,37640,37642,37644,37655,37656,37657,37658,37662,37663,37664,37665,37666,37678,37711,37728,37729,37730,37731,37755,37845,37846,37857,37881,37893,37906,37945,37976,38004,38031,38057,38058,38059,38061,38062,38063,38073,38074,38077,38098,38099,38100,38101,38102); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2035 WHERE `entry` IN (38104,38105,38108,38134,38137,38151,38172,38173,38175,38176,38177,38184,38185,38186,38193,38197,38198,38216,38222,38248,38249,38260,38308,38309,38349,38350,38351,38352,38362,38367,38386,38388,38391,38392,38410,38419,38429,38430,38445,38446,38479,38480,38481,38487,38524,38525,38544,38545,38563,38564,38567,38568,38572,38610,38763,38857,38883,39048,39231,39232,39233,39234,39296,39639,40274); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2036 WHERE `entry` IN (25597,37695,39309,39310,39311); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2037 WHERE `entry` IN (8836); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2038 WHERE `entry` IN (7915,16111); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2039 WHERE `entry` IN (14682); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2040 WHERE `entry` IN (18259,18648); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2041 WHERE `entry` IN (20682); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2042 WHERE `entry` IN (20783,20784,20785,20786,20788,20789,20790); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2043 WHERE `entry` IN (14684,14690); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2044 WHERE `entry` IN (14693); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2045 WHERE `entry` IN (17725,17871,20188,20190); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2046 WHERE `entry` IN (22848); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2047 WHERE `entry` IN (11447,11497,11498); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2048 WHERE `entry` IN (1157,1158,1160,1534,1655,1800,1801,1802,1804,1946,1983,2044,2176,2177,2178,2638,3667,3801,4472,6116,6117,6133,7073,7074,7370,7523,7524,7864,8538,8539,8540,8541,8542,10387,10388,10389,10940,10947,10948,11064,11078,11141,11285,11288,11289,11296,11560,11620,11621,11686,11687,11873,12178,12179,12199,12377,12378,14564,16423); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2049 WHERE `entry` IN (1531,2184,3863,3872,3873,3875,3877,6118,10358,10384,10385,10938,10996,16143,16379); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2050 WHERE `entry` IN (18856); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2051 WHERE `entry` IN (6021,9299,10988); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2052 WHERE `entry` IN (15554); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2053 WHERE `entry` IN (22338); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2054 WHERE `entry` IN (23269); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2055 WHERE `entry` IN (19354); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2056 WHERE `entry` IN (17724,20185,20465,21943); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2057 WHERE `entry` IN (48,202,203,531,623,785,787,1110,1520,1522,1523,1657,1658,1783,1784,1785,1787,1788,1789,1865,1869,1870,1871,1890,1916,1973,2454,6388,6412,7343,7344,7346,7786,8477,8523,8524,8525,8526,8527,8528,8529,8884,10390,10391,10482,10816,10952,11076,11077,11156,11197,11200,11258,11476,11547,12208,12341,12342,12343,12344,14486,14489,14558,16299,16422,16438); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2058 WHERE `entry` IN (771,2283,2946,7341,7342,7345,10393,10394,10478,10486,10487,10488,10489,10491,10826,16380); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2059 WHERE `entry` IN (11561); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2060 WHERE `entry` IN (22953); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2061 WHERE `entry` IN (5711); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2062 WHERE `entry` IN (23401); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2063 WHERE `entry` IN (28443); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2064 WHERE `entry` IN (19307,20265); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2065 WHERE `entry` IN (17958); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2066 WHERE `entry` IN (12557); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2067 WHERE `entry` IN (22853); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2068 WHERE `entry` IN (22869,23339); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2069 WHERE `entry` IN (20875,21604); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2070 WHERE `entry` IN (21102); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2071 WHERE `entry` IN (21181,21251); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2072 WHERE `entry` IN (16504); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2073 WHERE `entry` IN (20866,20898,21598,21614,22954); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2074 WHERE `entry` IN (16414,16473); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2075 WHERE `entry` IN (35644,36558); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2076 WHERE `entry` IN (33060,33062,33067,33109,33167,34045); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2077 WHERE `entry` IN (19797); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2078 WHERE `entry` IN (17454); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2079 WHERE `entry` IN (17940,19891,21127); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2080 WHERE `entry` IN (21843); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2081 WHERE `entry` IN (21270,21274); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2082 WHERE `entry` IN (20873,21605); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2083 WHERE `entry` IN (20175); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2084 WHERE `entry` IN (17726,17735,20191,20193); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2085 WHERE `entry` IN (17727,20192); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2086 WHERE `entry` IN (20908,20909,21616,21617); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2087 WHERE `entry` IN (22849); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2088 WHERE `entry` IN (14349); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2089 WHERE `entry` IN (644); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2090 WHERE `entry` IN (15334,15725,15726,15728,15802); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2091 WHERE `entry` IN (18497,20299); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2092 WHERE `entry` IN (21694,21914,24068,25372,31655); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2093 WHERE `entry` IN (18796,20652); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2094 WHERE `entry` IN (17907); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2095 WHERE `entry` IN (20043,20044); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2096 WHERE `entry` IN (21268,21269,21271,21272,21273); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2097 WHERE `entry` IN (14987); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2098 WHERE `entry` IN (15630,15984); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2099 WHERE `entry` IN (25334,26523,27881,27894,28094,28312,28319,28366,28781,32627,32629,32795,32796,34775,34776,34777,34778,34793,34802,34922,34924,34929,34935,34944,35069,35273,35403,35405,35410,35413,35415,35417,35419,35421,35427,35429,35431,35433,35436,35819,36355,36356,36357,36358); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2100 WHERE `entry` IN (20992); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2101 WHERE `entry` IN (16507); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2102 WHERE `entry` IN (20593); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2103 WHERE `entry` IN (16857); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2104 WHERE `entry` IN (18554); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2105 WHERE `entry` IN (23586); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2106 WHERE `entry` IN (19886); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2107 WHERE `entry` IN (34657,34701,34705); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2108 WHERE `entry` IN (22845,23374); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2109 WHERE `entry` IN (12339); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2110 WHERE `entry` IN (10814); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2111 WHERE `entry` IN (23402); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2112 WHERE `entry` IN (23403); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2113 WHERE `entry` IN (23400); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2114 WHERE `entry` IN (6584); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2115 WHERE `entry` IN (17827,20165); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2116 WHERE `entry` IN (22930); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2117 WHERE `entry` IN (17671,20584,20993); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2118 WHERE `entry` IN (23196); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2119 WHERE `entry` IN (22880,22956,22964,23222,23239,23337,23399); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2120 WHERE `entry` IN (22199); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2121 WHERE `entry` IN (18680,22877,22884); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2122 WHERE `entry` IN (22960); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2123 WHERE `entry` IN (22844,22847,22873,22878,22963,23028,23030); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2124 WHERE `entry` IN (34605,34650,35658,35659); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2125 WHERE `entry` IN (36561,36571,40681,40682,40683,40684); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2126 WHERE `entry` IN (40417,40418,40419,40420,40421,40422,40423,40424); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2127 WHERE `entry` IN (37890,37949,38393,38394,38625,38626,38628,38629); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2128 WHERE `entry` IN (18692); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2129 WHERE `entry` IN (38009,38010,38135,38136,38395,38396,38397,38398,38630,38631,38632,38633,38634,38635,39000,39001); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2130 WHERE `entry` IN (38508,38596,38597,38598); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2131 WHERE `entry` IN (38472,38485); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2132 WHERE `entry` IN (34607,34648,35655,35656); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2133 WHERE `entry` IN (37863,38171,38727,38737); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2134 WHERE `entry` IN (36791,37868,37886,37934,38166,38167,38169,38170,38721,38722,38723,38724,38725,38733,38734,38735); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2135 WHERE `entry` IN (20032); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2136 WHERE `entry` IN (20040); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2137 WHERE `entry` IN (20035); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2138 WHERE `entry` IN (20050); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2139 WHERE `entry` IN (22846); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2140 WHERE `entry` IN (20216,20600,22910); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2141 WHERE `entry` IN (16545,22911); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2142 WHERE `entry` IN (23330); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2143 WHERE `entry` IN (36609,37799,39120,39121,39122,39284,39285,39286); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2144 WHERE `entry` IN (33346,33886); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2145 WHERE `entry` IN (22075); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2146 WHERE `entry` IN (20692); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2147 WHERE `entry` IN (18836); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2148 WHERE `entry` IN (11347); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2149 WHERE `entry` IN (11348); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2150 WHERE `entry` IN (17256); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2151 WHERE `entry` IN (22074); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2152 WHERE `entry` IN (14101); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2153 WHERE `entry` IN (639,642,643,645,646,647,1763,3586,3886,3887,3927,4274,4275,4278,4279,4420,4421,4422,4424,4428,4829,4830,4831,4832,4842,6168,6228,6229,6235,6243,6488,6489,6490,7361,7800,10439,11356,12203,12225,12236,12258,12902,13021,13596); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2154 WHERE `entry` IN (24683,24684,24686,24687,24688,24689,24690,24762,25565,25567,25568,25570,25572,25575,25576,25577); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2155 WHERE `entry` IN (24685,25569); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2156 WHERE `entry` IN (12803); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2157 WHERE `entry` IN (19389,21350); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2158 WHERE `entry` IN (18829); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2159 WHERE `entry` IN (8923,9217,9218,9219,9718,10263,10809,11467); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2160 WHERE `entry` IN (30385,31474); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2161 WHERE `entry` IN (17723,20164); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2162 WHERE `entry` IN (11663); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2163 WHERE `entry` IN (21346,21612); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2164 WHERE `entry` IN (23061,23261,23281,23282); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2165 WHERE `entry` IN (18731,20636); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2166 WHERE `entry` IN (22076); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2167 WHERE `entry` IN (17990,20189); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2168 WHERE `entry` IN (17796,20630); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2169 WHERE `entry` IN (8929,9025,9502,10076,14321,14323,14326); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2170 WHERE `entry` IN (12422); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2171 WHERE `entry` IN (10376,10596); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2172 WHERE `entry` IN (8983,9018,9019,9024,9027,9028,9029,9030,9031,9032,9033,9034,9035,9036,9037,9038,9039,9040,9041,9042,9056,9156,9196,9236,9237,9319,9537,9543,9568,9736,9938,10220,10268,10339,10363,10429,10430,10503,10509,10584,10812,11486,11487,11488,11489,11490,11492,11496,11501,12459,12461,12463,12464,14322,14324,14325,14327,14354,14502,14506,15229,15230,15233,15235,15236,15240,15246,15249,15250,15252,15262,15264,15277,15311,15312,16042,16080,16097); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2173 WHERE `entry` IN (11662,12460,12467); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2174 WHERE `entry` IN (17862,20521); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2175 WHERE `entry` IN (24238); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2176 WHERE `entry` IN (1840); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2177 WHERE `entry` IN (20901,20902,21610,21611); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2178 WHERE `entry` IN (21218,21221,21298,21299,21301,21339,21508); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2179 WHERE `entry` IN (20885,20886,21466,21467,21590,21599,21600,21624); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2180 WHERE `entry` IN (17826,17882,18105,20168,20183,20184); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2181 WHERE `entry` IN (33836,34218); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2182 WHERE `entry` IN (30245,30249,31750,31751); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2183 WHERE `entry` IN (33202,33398); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2184 WHERE `entry` IN (15516,32857,33694); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2185 WHERE `entry` IN (36272,36296,36565); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2186 WHERE `entry` IN (32916,33400); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2187 WHERE `entry` IN (12899); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2188 WHERE `entry` IN (16472); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2189 WHERE `entry` IN (929,1284,1748,2425,3974,3975,3976,3977,4542,4543,6487,7228,9095,10156,10162,10432,10433,10435,10436,10437,10438,10502,10504,10505,10506,10507,10508,10558,10808,10811,10813,10901,10942,10997,11032,11142,11261,11380,11439,11622,11946,11947,11948,11949,11978,11982,12129,12756,12804,13256,13419,14352,14507,14509,14510,14515,14517,14831,14834,14861,14988,15082,15083,15084,15085,15114,15192,15204,15205,15299,15302,15348,15362,15369,15370,15378,15379,15380,15381,15382,15410,15411,15412,15413,15427,15481,15491,15509,15510,15511,15514,15543,15544,15571,15608,15625,15628,15633,15667,15688,15776,15963,16077,16101,16102,16118,16128,16440,16524,16775,16776,16777,16778,16816,17008,17009,17010,17011,17012,17026,17076,17233,17354,17380,17381,17500,17521,17533,17534,17651,17772,17797,17798,17852,17874,17941,17942,17948,17949,17991,18040,18041,18060,18063,18075,18076,18141,18254,18338,18343,18344,18371,18373,18472,18601,18621,18624,18667,18728,18780,18932,19214,19556,19597,19604,19647,19893,19894,19895,20266,20268,20306,20318,20481,20629,20633,20637,20690,21003,21538,21768,21823,21845,21932,22409,22522,22605,22606,22627,22629,22641,22644,22996,23035,23054,23230,23426,23467,23499,23508); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2189 WHERE `entry` IN (23578,23759,23812,23863,23877,23878,23879,23880,23899,23912,23913,23914,23944,24143,24144,24239,24505,31055,31818,31819,31820,31821,31822,32918,32919,33399,33401,37243,37244,37283,37444); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2190 WHERE `entry` IN (15552); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2191 WHERE `entry` IN (11382,12017,15263,23576,23577); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2192 WHERE `entry` IN (37226); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2193 WHERE `entry` IN (2784,3057,3516,4949,4968,7937,7999,10181,10540,20888,21224,21227,22825,22826,22827,22828,22920); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2194 WHERE `entry` IN (16028,16800,16802,17468,19622,20906,21606,21964,22951,23419,23953,23954,23980,24200,24201,26529,26530,26532,26533,27389,27390,28684,29249,29268,29278,29305,29311,29315,29324,29373,29417,29446,29447,29448,29611,29615,29701,29718,29940,29955,29991,30057,30061,30452,30530,30549,30600,30601,30602,30603,30748,31125,31211,31212,31215,31217,31464,31507,31534,31612,31656,31657,31673,31674,31679,31680,31722,32273,32295,32313,32353,32368,32865,33147,33271,33350,33388,33449,33453,33515,33524,33846,33850,33851,33852,33985,33986,33988,33989,33993,33994,34015,34016,34057,34115,34152,34175,34496,34497,34780,35013,35143,35216,35268,35269,35347,35348,35349,35350,35351,35352,35359,35360,36064,36067,36476,36477,36494,36502,36612,36658,36723,36855,36938,37613,37627,37629,37677,37720,37813,37957,37958,37959,38106,38112,38113,38296,38297,38402,38433,38462,38482,38483,38582,38583,38599,38603); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2195 WHERE `entry` IN (15193); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2196 WHERE `entry` IN (20905,20910,20911,21588,21589,21618,21838); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2197 WHERE `entry` IN (24560,25560); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2198 WHERE `entry` IN (20896,21702); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2199 WHERE `entry` IN (11980,16807,16808,16809,17711,17734,18481,20039,20045,20046,20049,20187,20568,20596,20597,20864,20867,20868,20879,20880,21174,21303,21304,21591,21592,21593,21594,21595,21596,21608,21623,22330,22346,23397,24723,24744,25562,25573); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2200 WHERE `entry` IN (20036,20037,24664,24857); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2201 WHERE `entry` IN (25772); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2202 WHERE `entry` IN (4814); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2203 WHERE `entry` IN (329,2359,7031,7032,8324,14331); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2204 WHERE `entry` IN (16193); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2205 WHERE `entry` IN (11665,11672,12076,12099,12100,12101); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2206 WHERE `entry` IN (14401); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2207 WHERE `entry` IN (16336,16338); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2208 WHERE `entry` IN (15141); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2209 WHERE `entry` IN (11669); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2210 WHERE `entry` IN (11658,11659,11661,11671,11673,11988,12057,12098,12118,12259,12264,14081,14465,14466,14751,14752); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2211 WHERE `entry` IN (24722,25552); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2212 WHERE `entry` IN (14464); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2213 WHERE `entry` IN (10264); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2214 WHERE `entry` IN (15748,15751,15754,15758,15807,15810); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2215 WHERE `entry` IN (11664,15747,15753,15757,15812); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2216 WHERE `entry` IN (10516); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2217 WHERE `entry` IN (12465); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2218 WHERE `entry` IN (9499,12468,15247); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2219 WHERE `entry` IN (1842,5709,5710,5719,5720,5721,5722,7267,7271,7272,7275,7604,7795,7796,7797,8127,8443,11878,12018,15743,15744,15750,15806,15813,15814,15815,15816,15817,15818); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2220 WHERE `entry` IN (10184); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2221 WHERE `entry` IN (15286,15288,15290); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2222 WHERE `entry` IN (14662,14663,14664,14666); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2223 WHERE `entry` IN (14503); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2224 WHERE `entry` IN (22855); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2225 WHERE `entry` IN (17770,20169); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2226 WHERE `entry` IN (15689); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2227 WHERE `entry` IN (30084); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2228 WHERE `entry` IN (32187); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2229 WHERE `entry` IN (22056); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2230 WHERE `entry` IN (25588); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2231 WHERE `entry` IN (37698,39299,39300,39301); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2232 WHERE `entry` IN (22952,34815,35262); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2233 WHERE `entry` IN (21362); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2234 WHERE `entry` IN (33203,33376); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2235 WHERE `entry` IN (34147,34148); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2236 WHERE `entry` IN (17767,17808,17842,17888); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2237 WHERE `entry` IN (1853,12397,12435,15517,15589,15690,15727,17225,17535,17968,18341,18473,18708,18732,20267,20653,20657,20706,23574); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2238 WHERE `entry` IN (10340,10538,14020,15206,15207,15208,15220,15467,15740,15741,15742,18168,33052,33116); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2239 WHERE `entry` IN (25741); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2240 WHERE `entry` IN (15550,16152,18832,18834,18835,23682,23709,29306,29310,31368,31465,34796,35438); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2241 WHERE `entry` IN (27386,30178,30756,31448); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2242 WHERE `entry` IN (32877,33155); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2243 WHERE `entry` IN (10404,15687,15691,16151,16457,17096,17257,17281,17306,17308,17455,17536,17537,17848,17879,17880,17881,17975,17976,17977,17978,17980,18051,18055,18069,18096,18398,18399,18400,18401,18402,18432,18433,18434,18436,18805,18831,18945,19044,19218,19219,19220,19221,19516,19710,19781,19782,19783,20060,20062,20063,20064,20243,20531,20535,20737,20738,20745,21212,21213,21214,21215,21216,21217,21364,21369,21525,21526,21533,21536,21537,21551,21558,21559,21581,21582,21697,21698,21712,21875,21920,21958,21965,21966,22055,22119,22120,22140,22167,22841,22856,22871,22887,22898,22917,22947,22948,22949,22950,22997,23089,23191,23197,23375,23418,23420,23421,23469,23965,24850,24882,24891,24892,25038,25165,25166,25315,25502,25708,25840,26630,26631,26632,26668,26683,26684,26685,26686,26687,26693,26723,26731,26763,26794,26796,26798,26861,26893,26918,27447,27483,27654,27655,27656,27975,27977,27978,28132,28171,28546,28586,28587,28729,28730,28731,28859,28860,28921,28922,28923,29120,29266,29281,29304,29307,29308,29309,29312,29313,29314,29316,29573,29932,30114,30176,30397,30398,30449,30451,30510,30522,30529,30532,30540,30770,30772,30774,30775,30788,30790,30803,30807,30809,30810,31134,31311,31349,31350,31360,31362); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2243 WHERE `entry` IN (31365,31367,31370,31381,31384,31386,31441,31456,31469,31473,31506,31508,31509,31510,31511,31512,31520,31533,31535,31536,31537,31538,31558,31559,31560,31561,31592,31610,31611,31615,31616,31617,31672,31734,32845,32846,32867,32871,32872,32873,32874,32875,32876,32878,32882,32883,32885,32886,32904,32906,32907,32908,32913,32914,32915,32927,32930,32933,32934,32955,33070,33089,33113,33118,33121,33148,33149,33150,33151,33152,33153,33154,33156,33157,33158,33159,33161,33162,33163,33186,33190,33191,33228,33288,33293,33329,33344,33360,33385,33391,33392,33393,33432,33433,33567,33651,33670,33692,33693,33699,33700,33715,33716,33717,33718,33719,33720,33722,33723,33724,33756,33772,33773,33885,33888,33890,33909,33910,33911,33943,33954,33955,33959,33966,33967,33983,33984,33990,33995,34003,34004,34014,34035,34069,34071,34097,34106,34108,34109,34133,34134,34139,34141,34164,34165,34166,34171,34185,34197,34215,34221,34222,34226,34564,34566,34660,34797,34799,34800,34813,34825,34826,34928,34942,35028,35029,35030,35031,35032,35033,35034,35036,35037,35038,35039,35040,35041,35042,35043,35044,35045,35046,35047,35048,35049,35050,35051,35052,35119,35144,35263,35264,35265,35266,35267,35270,35271,35272,35278,35279); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2243 WHERE `entry` IN (35280,35311,35439,35440,35441,35442,35443,35447,35448,35449,35451,35490,35511,35512,35513,35514,35515,35516,35517,35518,35519,35520,35521,35522,35523,35524,35525,35527,35528,35529,35530,35531,35532,35533,35534,35535,35536,35537,35538,35539,35540,35541,35542,35543,35544,35615,35616,36135,36497,36498,36538,36597,36626,36627,36633,36678,36701,36789,36838,36839,36853,36897,36899,36939,36948,36950,36954,36957,36960,36961,36968,36969,36970,36971,36978,36982,37025,37098,37116,37117,37126,37182,37184,37215,37217,37227,37230,37488,37504,37505,37506,37531,37533,37534,37540,37562,37697,37833,37955,37970,37972,37973,38064,38103,38110,38123,38128,38129,38138,38139,38156,38157,38174,38219,38220,38256,38257,38258,38261,38262,38265,38266,38267,38369,38390,38399,38400,38401,38403,38404,38405,38406,38407,38408,38418,38431,38434,38435,38436,38444,38454,38490,38494,38549,38550,38585,38586,38602,38604,38637,38638,38639,38640,38675,38676,38677,38678,38679,38680,38681,38682,38683,38684,38685,38686,38687,38688,38689,38690,38691,38692,38693,38694,38699,38700,38701,38702,38758,38759,38760,38761,38769,38770,38771,38772,38775,38776,38777,38784,38785,39166,39167,39168,39190,39287,39288,39289,39302,39303,39304); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2243 WHERE `entry` IN (39305,39306,39307,39746,39747,39751,39805,39814,39815,39823,39863,39864,39899,39920,39922,39944,39945,40142,40143,40144,40145); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2244 WHERE `entry` IN (14862); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2245 WHERE `entry` IN (32926,32938,33352,33353,36980,38320,38321,38322); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2246 WHERE `entry` IN (15261); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2247 WHERE `entry` IN (20041,23394); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2248 WHERE `entry` IN (18503,20309); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2249 WHERE `entry` IN (36619,38233,38459,38460,38711,38712,38970,38971,38972,38973,38974,38975); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2250 WHERE `entry` IN (38028); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2251 WHERE `entry` IN (20870,20912,21601,21626); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2252 WHERE `entry` IN (17839,20744,21104,21140,21148,22170,22171,22172); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2253 WHERE `entry` IN (22128,22129); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2254 WHERE `entry` IN (23597); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2255 WHERE `entry` IN (24374); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2256 WHERE `entry` IN (2675,4277,8937); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2257 WHERE `entry` IN (16127); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2258 WHERE `entry` IN (16124); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2259 WHERE `entry` IN (16149); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2260 WHERE `entry` IN (16156,16157,16158); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2261 WHERE `entry` IN (11657,11677,12096,12097,13078,13079,13086,13088,13137,13138,13139,13143,13144,13145,13147,13152,13153,13154,13176,13179,13180,13181,13216,13218,13236,13257,13296,13297,13298,13299,13300,13318,13319,13320,13356,13357,13437,13438,13439,13441,13442,13447,13577,13597,13598,13616,13617,13797,13798); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2262 WHERE `entry` IN (16244,16360,16427,16983); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2263 WHERE `entry` IN (16150); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2264 WHERE `entry` IN (16022,16981); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2265 WHERE `entry` IN (16027,16164,16449); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2266 WHERE `entry` IN (16167); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2267 WHERE `entry` IN (16020); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2268 WHERE `entry` IN (16024); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2269 WHERE `entry` IN (16375); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2270 WHERE `entry` IN (16290); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2271 WHERE `entry` IN (16447); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2272 WHERE `entry` IN (16982,16984); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2273 WHERE `entry` IN (16036,16056,16057,16236,16297,16698,17055); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2274 WHERE `entry` IN (16037); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2275 WHERE `entry` IN (15318,15319,15320,15323,15324,15325,15336); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2276 WHERE `entry` IN (16034); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2277 WHERE `entry` IN (16428,16429); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2278 WHERE `entry` IN (16021,16165,16368,16448,16452); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2279 WHERE `entry` IN (16194,16215,16216); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2280 WHERE `entry` IN (16017,16018,16029); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2281 WHERE `entry` IN (11352,12159,14750,14762,14763,14764,14765,14766,14767,14768,14769,14770,14771,14772,14773,14774,14775,14776,14777,14943,14944,14945,14946,14947,14948,15327,15335,15338,15339,15340,15341,15343,15355,15385,15386,15388,15389,15390,15391,15392,15555,16062); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2282 WHERE `entry` IN (15928,15929,15930,15931,15932,15936,15952,15953,15954,15956,15989,15990,16011,16060,16061,16063,16064,16065); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2283 WHERE `entry` IN (16441); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2284 WHERE `entry` IN (16126); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2285 WHERE `entry` IN (15974,15975,16505,29274); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2286 WHERE `entry` IN (15980,15981,16506); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2287 WHERE `entry` IN (16573); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2288 WHERE `entry` IN (15976,15978,15979,16142,16453); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2289 WHERE `entry` IN (16243); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2290 WHERE `entry` IN (16387); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2291 WHERE `entry` IN (10440); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2292 WHERE `entry` IN (12119,15620,16168,16446); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2293 WHERE `entry` IN (13116,13117,14302,15047,15224,15922); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2294 WHERE `entry` IN (22250); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2295 WHERE `entry` IN (24175); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2296 WHERE `entry` IN (12900); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2297 WHERE `entry` IN (32357,32358,32471,32495,32501); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2298 WHERE `entry` IN (32386,32398,32400,32438); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2299 WHERE `entry` IN (11284); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2300 WHERE `entry` IN (15276); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2301 WHERE `entry` IN (2091,2108,2726,4339,4676,6073,7135,7136,7137,8608,8616,14668,15209,15438,17267,19203,19973,20703,21706,22323,25001,25417,28584,30416,30847,30983,31453); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2302 WHERE `entry` IN (12143); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2303 WHERE `entry` IN (2760,3417,4036,4037,4038,5893,5896,6521,7266,7738,9178,9878,9879); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2304 WHERE `entry` IN (2745,8281,8909,8910,8911,9026,9376); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2305 WHERE `entry` IN (14460); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2306 WHERE `entry` IN (2447); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2307 WHERE `entry` IN (9016,9017,9816); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2308 WHERE `entry` IN (11981,11983,14601); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2309 WHERE `entry` IN (7846,8680,15203); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2310 WHERE `entry` IN (575,5850,5852,6520,17003); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2311 WHERE `entry` IN (11502,11667,11668,12056,12265); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2312 WHERE `entry` IN (14461); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2313 WHERE `entry` IN (19551); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2314 WHERE `entry` IN (11583,13020); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2315 WHERE `entry` IN (19514); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2316 WHERE `entry` IN (11666); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2317 WHERE `entry` IN (832,2258,2592,2736,2752,2762,4034,4035,4499,4526,5898,5902,6239,8667,9377,9397,11576,11577,11578,11744,11745,11777,11778,11779,11783,11784,14399,14400,14478,15352,16043,17085,17154,17156,17157,17158,17159,17160,18062,18181,21707,22036,23029,26407,28411,28547,28585,28825,30418,30965,30970,30979,31452); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2318 WHERE `entry` IN (14455); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2319 WHERE `entry` IN (5855); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2320 WHERE `entry` IN (12237); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2321 WHERE `entry` IN (14435); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2322 WHERE `entry` IN (92); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2323 WHERE `entry` IN (2735,2791); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2324 WHERE `entry` IN (11321); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2325 WHERE `entry` IN (30258,31463); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2326 WHERE `entry` IN (14454); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2327 WHERE `entry` IN (12201); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2328 WHERE `entry` IN (14887,14888,14889,14890); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2329 WHERE `entry` IN (2761,2776,2794,3851,3861,3950,4978,5462,5894,5895,5897,6047,6220,6748,7132,7428,7429,8519,8520,8521,8522,8837,9453,10198,10642,11256,11862,12759,13278,13279,13322,13456,13696,13736,14269,14350,15211,16570,17153,17155,17167,17207,17358,19204,20704,21428,21728,22035,22309,24228,24601,25226,25514,25715,25755,25756,25757,26116,26178,26204,26214,26215,26216,26316,26340,26341,26342,30419,30846,31454,37994); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2330 WHERE `entry` IN (14458); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2331 WHERE `entry` IN (13282); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2332 WHERE `entry` IN (12876); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2333 WHERE `entry` IN (3917); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2334 WHERE `entry` IN (7079); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2335 WHERE `entry` IN (13280); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2336 WHERE `entry` IN (15305); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2337 WHERE `entry` IN (510,691,5461,10756,10757,10955); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2338 WHERE `entry` IN (14457); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2339 WHERE `entry` IN (22009); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2340 WHERE `entry` IN (25740,25865,26338,26339); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2341 WHERE `entry` IN (703,1043,1364,19206,20705); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2342 WHERE `entry` IN (6492,6550,11480,11483,11484,14397,15527,16530,16854,18864,18865,18866,18867,19205,20516,20702,22310,26370,30848); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2343 WHERE `entry` IN (10202); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2344 WHERE `entry` IN (10662); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2345 WHERE `entry` IN (10663); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2346 WHERE `entry` IN (10664); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2347 WHERE `entry` IN (6109); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2348 WHERE `entry` IN (21032); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2349 WHERE `entry` IN (7734,7735); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2350 WHERE `entry` IN (10485); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2351 WHERE `entry` IN (15275); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2352 WHERE `entry` IN (16491); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2353 WHERE `entry` IN (12457); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2354 WHERE `entry` IN (28912); +UPDATE `creature_template` SET `CreatureImmunitiesId`=2355 WHERE `entry` IN (8317); From e4da9f98d1d5bfa7a5d8994a9fc4309f8792c030 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Mar 2026 10:02:58 +0000 Subject: [PATCH 086/132] chore(DB): import pending files Referenced commit(s): 2e80e9a3518b995759ee6646085c165d341c40d3 --- .../rev_1774432077228950500.sql => db_world/2026_03_25_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774432077228950500.sql => db_world/2026_03_25_02.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1774432077228950500.sql b/data/sql/updates/db_world/2026_03_25_02.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774432077228950500.sql rename to data/sql/updates/db_world/2026_03_25_02.sql index 0bc9a96d5..4c96ce780 100644 --- a/data/sql/updates/pending_db_world/rev_1774432077228950500.sql +++ b/data/sql/updates/db_world/2026_03_25_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_25_01 -> 2026_03_25_02 -- -- Fix: Repopulate CreatureImmunitiesId for all creatures that had mechanic/school immune masks -- The original migration (2026_03_22_03) ran the UPDATE before creature_immunities rows existed, From 231abbfab58f40acc9de5008ea7c709bbd5b26a9 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 25 Mar 2026 07:44:51 -0300 Subject: [PATCH 087/132] fix(Scripts/Pet): prevent recursive EnterEvadeMode in snake trap AI (#25217) Co-authored-by: Claude Opus 4.6 --- src/server/scripts/Pet/pet_hunter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/scripts/Pet/pet_hunter.cpp b/src/server/scripts/Pet/pet_hunter.cpp index 239ae9660..b325c2bc3 100644 --- a/src/server/scripts/Pet/pet_hunter.cpp +++ b/src/server/scripts/Pet/pet_hunter.cpp @@ -68,14 +68,13 @@ struct npc_pet_hunter_snake_trap : public ScriptedAI void EnterEvadeMode(EvadeReason /*why*/) override { // _EnterEvadeMode(); + me->AddUnitState(UNIT_STATE_EVADE); me->GetThreatMgr().ClearAllThreat(); me->CombatStop(true); me->LoadCreaturesAddon(true); me->SetLootRecipient(nullptr); me->ResetPlayerDamageReq(); me->ClearLastLeashExtensionTimePtr(); - - me->AddUnitState(UNIT_STATE_EVADE); me->GetMotionMaster()->MoveTargetedHome(); Reset(); From cf6ba26703bd50b2b471e74f0cada1ce2776b9eb Mon Sep 17 00:00:00 2001 From: sogladev Date: Wed, 25 Mar 2026 12:40:45 +0100 Subject: [PATCH 088/132] fix(DB/Creature): seed `creature_immunities` from `creature_template` (#25218) --- .../rev_1774435393371251635.sql | 879 ++++++++++++++++++ 1 file changed, 879 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774435393371251635.sql diff --git a/data/sql/updates/pending_db_world/rev_1774435393371251635.sql b/data/sql/updates/pending_db_world/rev_1774435393371251635.sql new file mode 100644 index 000000000..cdf509955 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774435393371251635.sql @@ -0,0 +1,879 @@ +-- +-- ============================================================ +-- creature_immunities seed +-- ============================================================ +DELETE FROM `creature_immunities` WHERE `ID` < 0; +DELETE FROM `creature_immunities` WHERE `ID` >= 1972; +INSERT INTO `creature_immunities` + (`ID`,`SchoolMask`,`DispelTypeMask`,`MechanicsMask`, + `Effects`,`Auras`,`ImmuneAoE`,`ImmuneChain`,`Comment`) +VALUES +(-1,0,0,0,'114','11',0,0,'flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-2,0,0,0,'','',1,0,'flags=AVOID_AOE'), +(-3,0,0,0,'98,144','',0,0,'flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-4,4,0,0,'','',0,0,'school=0x4(FIRE)'), +(-5,8,0,0,'','',0,0,'school=0x8(NATURE)'), +(-6,16,0,0,'','',0,0,'school=0x10(FROST)'), +(-7,32,0,0,'','',0,0,'school=0x20(SHADOW)'), +(-8,64,0,0,'','',0,0,'school=0x40(ARCANE)'), +(-9,96,0,0,'','',0,0,'school=0x60(SHADOW|ARCANE)'), +(-10,116,0,0,'','',0,0,'school=0x74(FIRE|FROST|SHADOW|ARCANE)'), +(-11,126,0,0,'','',0,0,'school=0x7E(HOLY|FIRE|NATURE|FROST|SHADOW|ARCANE)'), +(-12,0,0,2,'','',0,0,'mech=0x1(CHARM)'), +(-13,0,0,2,'114','11',0,0,'mech=0x1(CHARM), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-14,0,0,4,'','',0,0,'mech=0x2(DISORIENTED)'), +(-15,0,0,16,'','',0,0,'mech=0x8(DISTRACT)'), +(-16,8,0,16,'','',0,0,'school=0x8(NATURE), mech=0x8(DISTRACT)'), +(-17,16,0,16,'','',0,0,'school=0x10(FROST), mech=0x8(DISTRACT)'), +(-18,0,0,18,'','',0,0,'mech=0x9(CHARM|DISTRACT)'), +(-19,0,0,18,'114','11',0,0,'mech=0x9(CHARM|DISTRACT), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-20,0,0,32,'','',0,0,'mech=0x10(FEAR)'), +(-21,4,0,32,'','',0,0,'school=0x4(FIRE), mech=0x10(FEAR)'), +(-22,64,0,32,'','',0,0,'school=0x40(ARCANE), mech=0x10(FEAR)'), +(-23,0,0,34,'','',0,0,'mech=0x11(CHARM|FEAR)'), +(-24,0,0,40,'','',0,0,'mech=0x14(DISARM|FEAR)'), +(-25,0,0,42,'114','11',0,0,'mech=0x15(CHARM|DISARM|FEAR), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-26,0,0,64,'','',0,0,'mech=0x20(GRIP)'), +(-27,0,0,64,'98,144','',0,0,'mech=0x20(GRIP), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-28,0,0,512,'','',0,0,'mech=0x100(SILENCE)'), +(-29,0,0,514,'','',0,0,'mech=0x101(CHARM|SILENCE)'), +(-30,0,0,514,'114','11',0,0,'mech=0x101(CHARM|SILENCE), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-31,0,0,1024,'','',0,0,'mech=0x200(SLEEP)'), +(-32,0,0,1056,'','',0,0,'mech=0x210(FEAR|SLEEP)'), +(-33,0,0,1058,'','',0,0,'mech=0x211(CHARM|FEAR|SLEEP)'), +(-34,0,0,2048,'','',0,0,'mech=0x400(SNARE)'), +(-35,0,0,2050,'','',0,0,'mech=0x401(CHARM|SNARE)'), +(-36,0,0,2052,'','',0,0,'mech=0x402(DISORIENTED|SNARE)'), +(-37,0,0,2080,'','',0,0,'mech=0x410(FEAR|SNARE)'), +(-38,0,0,2082,'','',0,0,'mech=0x411(CHARM|FEAR|SNARE)'), +(-39,0,0,2176,'','',0,0,'mech=0x440(ROOT|SNARE)'), +(-40,0,0,2178,'','',0,0,'mech=0x441(CHARM|ROOT|SNARE)'), +(-41,0,0,2192,'','',0,0,'mech=0x448(DISTRACT|ROOT|SNARE)'), +(-42,0,0,2208,'','',0,0,'mech=0x450(FEAR|ROOT|SNARE)'), +(-43,0,0,2210,'','',0,0,'mech=0x451(CHARM|FEAR|ROOT|SNARE)'), +(-44,0,0,2562,'','',0,0,'mech=0x501(CHARM|SILENCE|SNARE)'), +(-45,0,0,3074,'','',0,0,'mech=0x601(CHARM|SLEEP|SNARE)'), +(-46,0,0,4096,'','',0,0,'mech=0x800(STUN)'), +(-47,0,0,4098,'','',0,0,'mech=0x801(CHARM|STUN)'), +(-48,0,0,4114,'','',0,0,'mech=0x809(CHARM|DISTRACT|STUN)'), +(-49,0,0,4128,'114','11',0,0,'mech=0x810(FEAR|STUN), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-50,0,0,4608,'98,144','',0,0,'mech=0x900(SILENCE|STUN), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-51,0,0,4624,'','',0,0,'mech=0x908(DISTRACT|SILENCE|STUN)'), +(-52,0,0,6178,'114','11',0,0,'mech=0xC11(CHARM|FEAR|SNARE|STUN), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-53,64,0,6688,'','',0,0,'school=0x40(ARCANE), mech=0xD10(FEAR|SILENCE|SNARE|STUN)'), +(-54,0,0,6810,'','',0,0,'mech=0xD4D(CHARM|DISARM|DISTRACT|ROOT|SILENCE|SNARE|STUN)'), +(-55,0,0,8192,'','',0,0,'mech=0x1000(FREEZE)'), +(-56,0,0,8224,'','',0,0,'mech=0x1010(FEAR|FREEZE)'), +(-57,64,0,10368,'','',0,0,'school=0x40(ARCANE), mech=0x1440(ROOT|SNARE|FREEZE)'), +(-58,0,0,12584,'','',0,0,'mech=0x1894(DISARM|FEAR|SLOW_ATTACK|STUN|FREEZE)'), +(-59,0,0,14374,'','',0,0,'mech=0x1C13(CHARM|DISORIENTED|FEAR|SNARE|STUN|FREEZE)'), +(-60,0,0,14982,'','',0,0,'mech=0x1D43(CHARM|DISORIENTED|ROOT|SILENCE|SNARE|STUN|FREEZE)'), +(-61,0,0,16384,'','',0,0,'mech=0x2000(KNOCKOUT)'), +(-62,4,0,17416,'','',0,0,'school=0x4(FIRE), mech=0x2204(DISARM|SLEEP|KNOCKOUT)'), +(-63,4,0,17420,'','',0,0,'school=0x4(FIRE), mech=0x2206(DISORIENTED|DISARM|SLEEP|KNOCKOUT)'), +(-64,4,0,17432,'','',0,0,'school=0x4(FIRE), mech=0x220C(DISARM|DISTRACT|SLEEP|KNOCKOUT)'), +(-65,0,0,20516,'','',0,0,'mech=0x2812(DISORIENTED|FEAR|STUN|KNOCKOUT)'), +(-66,16,0,20516,'','',0,0,'school=0x10(FROST), mech=0x2812(DISORIENTED|FEAR|STUN|KNOCKOUT)'), +(-67,0,0,21114,'','',0,0,'mech=0x293D(CHARM|DISARM|DISTRACT|FEAR|GRIP|SILENCE|STUN|KNOCKOUT)'), +(-68,0,0,32768,'','',0,0,'mech=0x4000(BLEED)'), +(-69,0,0,65278,'','',0,0,'mech=0x7F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED)'), +(-70,0,0,131072,'','',0,0,'mech=0x10000(POLYMORPH)'), +(-71,0,0,131074,'','',0,0,'mech=0x10001(CHARM|POLYMORPH)'), +(-72,0,0,131106,'','',0,0,'mech=0x10011(CHARM|FEAR|POLYMORPH)'), +(-73,0,0,132128,'','',0,0,'mech=0x10210(FEAR|SLEEP|POLYMORPH)'), +(-74,0,0,133122,'','',0,0,'mech=0x10401(CHARM|SNARE|POLYMORPH)'), +(-75,0,0,133248,'','',0,0,'mech=0x10440(ROOT|SNARE|POLYMORPH)'), +(-76,0,0,133250,'','',0,0,'mech=0x10441(CHARM|ROOT|SNARE|POLYMORPH)'), +(-77,0,0,139264,'','',0,0,'mech=0x11000(FREEZE|POLYMORPH)'), +(-78,0,0,139298,'','',0,0,'mech=0x11011(CHARM|FEAR|FREEZE|POLYMORPH)'), +(-79,0,0,141440,'','',0,0,'mech=0x11440(ROOT|SNARE|FREEZE|POLYMORPH)'), +(-80,0,0,143360,'','',0,0,'mech=0x11800(STUN|FREEZE|POLYMORPH)'), +(-81,0,0,143396,'','',0,0,'mech=0x11812(DISORIENTED|FEAR|STUN|FREEZE|POLYMORPH)'), +(-82,0,0,262144,'','',0,0,'mech=0x20000(BANISH)'), +(-83,0,0,262146,'','',0,0,'mech=0x20001(CHARM|BANISH)'), +(-84,0,0,262146,'114','11',0,0,'mech=0x20001(CHARM|BANISH), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-85,0,0,262172,'','',0,0,'mech=0x2000E(DISORIENTED|DISARM|DISTRACT|BANISH)'), +(-86,0,0,263222,'','',0,0,'mech=0x2021B(CHARM|DISORIENTED|DISTRACT|FEAR|SLEEP|BANISH)'), +(-87,0,0,265792,'98,144','',0,0,'mech=0x20720(GRIP|SILENCE|SLEEP|SNARE|BANISH), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-88,0,0,274592,'','',0,0,'mech=0x21850(FEAR|ROOT|STUN|FREEZE|BANISH)'), +(-89,0,0,278534,'','',0,0,'mech=0x22003(CHARM|DISORIENTED|KNOCKOUT|BANISH)'), +(-90,0,0,404998,'','',0,0,'mech=0x31703(CHARM|DISORIENTED|SILENCE|SLEEP|SNARE|FREEZE|POLYMORPH|BANISH)'), +(-91,0,0,407714,'','',0,0,'mech=0x31C51(CHARM|FEAR|ROOT|SNARE|STUN|FREEZE|POLYMORPH|BANISH)'), +(-92,0,0,16777216,'','',0,0,'mech=0x800000(HORROR)'), +(-93,0,0,16777248,'','',0,0,'mech=0x800010(FEAR|HORROR)'), +(-94,0,0,16777248,'114','11',0,0,'mech=0x800010(FEAR|HORROR), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-95,0,0,16777248,'','',1,0,'mech=0x800010(FEAR|HORROR), flags=AVOID_AOE'), +(-96,0,0,16777248,'98,144','',0,0,'mech=0x800010(FEAR|HORROR), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-97,1,0,16777248,'','',0,0,'school=0x1(NORMAL), mech=0x800010(FEAR|HORROR)'), +(-98,16,0,16777248,'','',0,0,'school=0x10(FROST), mech=0x800010(FEAR|HORROR)'), +(-99,127,0,16777248,'','',0,0,'school=0x7F(NORMAL|HOLY|FIRE|NATURE|FROST|SHADOW|ARCANE), mech=0x800010(FEAR|HORROR)'), +(-100,0,0,16777250,'','',0,0,'mech=0x800011(CHARM|FEAR|HORROR)'), +(-101,0,0,16777264,'','',0,0,'mech=0x800018(DISTRACT|FEAR|HORROR)'), +(-102,0,0,16777312,'','',0,0,'mech=0x800030(FEAR|GRIP|HORROR)'), +(-103,0,0,16777376,'','',0,0,'mech=0x800050(FEAR|ROOT|HORROR)'), +(-104,0,0,16779424,'','',0,0,'mech=0x800450(FEAR|ROOT|SNARE|HORROR)'), +(-105,0,0,16779428,'','',0,0,'mech=0x800452(DISORIENTED|FEAR|ROOT|SNARE|HORROR)'), +(-106,0,0,16779488,'','',0,0,'mech=0x800470(FEAR|GRIP|ROOT|SNARE|HORROR)'), +(-107,0,0,16781344,'','',0,0,'mech=0x800810(FEAR|STUN|HORROR)'), +(-108,0,0,16781984,'','',0,0,'mech=0x800950(FEAR|ROOT|SILENCE|STUN|HORROR)'), +(-109,0,0,16783392,'','',0,0,'mech=0x800C10(FEAR|SNARE|STUN|HORROR)'), +(-110,4,0,16784544,'','',0,0,'school=0x4(FIRE), mech=0x800E50(FEAR|ROOT|SLEEP|SNARE|STUN|HORROR)'), +(-111,0,0,16788644,'','',0,0,'mech=0x801652(DISORIENTED|FEAR|ROOT|SLEEP|SNARE|FREEZE|HORROR)'), +(-112,0,0,16791714,'','',0,0,'mech=0x801C51(CHARM|FEAR|ROOT|SNARE|STUN|FREEZE|HORROR)'), +(-113,0,0,16794664,'','',0,0,'mech=0x802214(DISARM|FEAR|SLEEP|KNOCKOUT|HORROR)'), +(-114,0,0,16794668,'','',0,0,'mech=0x802216(DISORIENTED|DISARM|FEAR|SLEEP|KNOCKOUT|HORROR)'), +(-115,0,0,16794676,'','',0,0,'mech=0x80221A(DISORIENTED|DISTRACT|FEAR|SLEEP|KNOCKOUT|HORROR)'), +(-116,0,0,16827436,'','',0,0,'mech=0x806216(DISORIENTED|DISARM|FEAR|SLEEP|KNOCKOUT|BLEED|HORROR)'), +(-117,0,0,16932526,'','',0,0,'mech=0x812F57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR)'), +(-118,0,0,17039394,'','',0,0,'mech=0x820011(CHARM|FEAR|BANISH|HORROR)'), +(-119,0,0,17039520,'','',0,0,'mech=0x820050(FEAR|ROOT|BANISH|HORROR)'), +(-120,0,0,17041570,'','',0,0,'mech=0x820451(CHARM|FEAR|ROOT|SNARE|BANISH|HORROR)'), +(-121,0,0,17045536,'','',0,0,'mech=0x820C10(FEAR|SNARE|STUN|BANISH|HORROR)'), +(-122,0,0,17171490,'','',0,0,'mech=0x830211(CHARM|FEAR|SLEEP|POLYMORPH|BANISH|HORROR)'), +(-123,124,0,17171490,'','',0,0,'school=0x7C(FIRE|NATURE|FROST|SHADOW|ARCANE), mech=0x830211(CHARM|FEAR|SLEEP|POLYMORPH|BANISH|HORROR)'), +(-124,0,0,17171494,'','',0,0,'mech=0x830213(CHARM|DISORIENTED|FEAR|SLEEP|POLYMORPH|BANISH|HORROR)'), +(-125,0,0,17204258,'','',0,0,'mech=0x834211(CHARM|FEAR|SLEEP|BLEED|POLYMORPH|BANISH|HORROR)'), +(-126,0,0,17825824,'','',0,0,'mech=0x880010(FEAR|SHACKLE|HORROR)'), +(-127,0,0,17825826,'','',0,0,'mech=0x880011(CHARM|FEAR|SHACKLE|HORROR)'), +(-128,0,0,17826336,'','',0,0,'mech=0x880110(FEAR|SILENCE|SHACKLE|HORROR)'), +(-129,0,0,25198706,'','',0,0,'mech=0xC04039(CHARM|DISTRACT|FEAR|GRIP|BLEED|TURN|HORROR)'), +(-130,0,0,25574434,'','',0,0,'mech=0xC31E11(CHARM|FEAR|SLEEP|SNARE|STUN|FREEZE|POLYMORPH|BANISH|TURN|HORROR)'), +(-131,0,0,26216098,'','',0,0,'mech=0xC80351(CHARM|FEAR|ROOT|SILENCE|SLEEP|SHACKLE|TURN|HORROR)'), +(-132,0,0,67108864,'','',0,0,'mech=0x2000000(INTERRUPT)'), +(-133,0,0,67109376,'','',0,0,'mech=0x2000100(SILENCE|INTERRUPT)'), +(-134,0,0,67272418,'','',0,0,'mech=0x2013F71(CHARM|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|INTERRUPT)'), +(-135,0,0,75497986,'','',0,0,'mech=0x2400101(CHARM|SILENCE|TURN|INTERRUPT)'), +(-136,0,0,75502082,'','',0,0,'mech=0x2400901(CHARM|SILENCE|STUN|TURN|INTERRUPT)'), +(-137,0,0,75765794,'','',0,0,'mech=0x2420C11(CHARM|FEAR|SNARE|STUN|BANISH|TURN|INTERRUPT)'), +(-138,0,0,84032160,'98,144','',0,0,'mech=0x2811D50(FEAR|ROOT|SILENCE|SNARE|STUN|FREEZE|POLYMORPH|HORROR|INTERRUPT), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-139,0,0,84180644,'','',0,0,'mech=0x2823F52(DISORIENTED|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|HORROR|INTERRUPT)'), +(-140,0,0,84180654,'','',0,0,'mech=0x2823F57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|HORROR|INTERRUPT)'), +(-141,0,0,92569262,'','',0,0,'mech=0x2C23F57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|TURN|HORROR|INTERRUPT)'), +(-142,0,0,92569262,'114','11',0,0,'mech=0x2C23F57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|TURN|HORROR|INTERRUPT), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-143,0,0,92569262,'98,144','',0,0,'mech=0x2C23F57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|TURN|HORROR|INTERRUPT), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-144,0,0,93617828,'114','11',0,0,'mech=0x2CA3F52(DISORIENTED|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|TURN|HORROR|INTERRUPT), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-145,0,0,134217728,'','',0,0,'mech=0x4000000(DAZE)'), +(-146,0,0,134219776,'','',0,0,'mech=0x4000400(SNARE|DAZE)'), +(-147,0,0,134221834,'','',0,0,'mech=0x4000805(CHARM|DISARM|STUN|DAZE)'), +(-148,0,0,134368350,'','',0,0,'mech=0x401262F(CHARM|DISORIENTED|DISARM|DISTRACT|GRIP|SLEEP|SNARE|KNOCKOUT|POLYMORPH|DAZE)'), +(-149,0,0,134487696,'','',0,0,'mech=0x4020F48(DISTRACT|ROOT|SILENCE|SLEEP|SNARE|STUN|BANISH|DAZE)'), +(-150,0,0,134487698,'','',0,0,'mech=0x4020F49(CHARM|DISTRACT|ROOT|SILENCE|SLEEP|SNARE|STUN|BANISH|DAZE)'), +(-151,0,0,134610954,'98,144','',0,0,'mech=0x4030005(CHARM|DISARM|POLYMORPH|BANISH|DAZE), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-152,0,0,142884558,'','',0,0,'mech=0x4421F67(CHARM|DISORIENTED|DISARM|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|BANISH|TURN|DAZE)'), +(-153,0,0,151259878,'','',0,0,'mech=0x4820573(CHARM|DISORIENTED|FEAR|GRIP|ROOT|SILENCE|SNARE|BANISH|HORROR|DAZE)'), +(-154,0,0,151262366,'','',0,0,'mech=0x4820A4F(CHARM|DISORIENTED|DISARM|DISTRACT|ROOT|SLEEP|STUN|BANISH|HORROR|DAZE)'), +(-155,0,0,151262398,'','',0,0,'mech=0x4820A5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SLEEP|STUN|BANISH|HORROR|DAZE)'), +(-156,0,0,151273214,'','',0,0,'mech=0x4821F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|BANISH|HORROR|DAZE)'), +(-157,0,0,151314166,'','',0,0,'mech=0x4826F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|BLEED|BANISH|HORROR|DAZE)'), +(-158,0,0,151401654,'','',0,0,'mech=0x4831A5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|STUN|FREEZE|POLYMORPH|BANISH|HORROR|DAZE)'), +(-159,0,0,152469182,'','',0,0,'mech=0x48B3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE)'), +(-160,0,0,159516926,'','',0,0,'mech=0x4C1047F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SNARE|POLYMORPH|TURN|HORROR|DAZE)'), +(-161,0,0,160853686,'','',0,0,'mech=0x4CB375B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE)'), +(-162,0,0,160857782,'','',0,0,'mech=0x4CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE)'), +(-163,0,0,160857790,'','',0,0,'mech=0x4CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE)'), +(-164,0,0,160857854,'','',0,0,'mech=0x4CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE)'), +(-165,0,0,161361662,'','',0,0,'mech=0x4CF177F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|FREEZE|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|DAZE)'), +(-166,0,0,201719818,'98,144','',0,0,'mech=0x6030005(CHARM|DISARM|POLYMORPH|BANISH|INTERRUPT|DAZE), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-167,0,0,226915902,'','',0,0,'mech=0x6C33B1F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|TURN|HORROR|INTERRUPT|DAZE)'), +(-168,0,0,227962558,'','',0,0,'mech=0x6CB375F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE)'), +(-169,0,0,688815860,'','',0,0,'mech=0x14873F7A(DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|HORROR|DAZE|IMMUNE_SHIELD)'), +(-170,0,0,1002405630,'','',0,0,'mech=0x1DDFBF7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|INVULNERABILITY|DAZE|DISCOVERY|IMMUNE_SHIELD)'), +(-171,0,0,1073741824,'','',0,0,'mech=0x20000000(SAPPED)'), +(-172,0,0,1073741826,'','',0,0,'mech=0x20000001(CHARM|SAPPED)'), +(-173,0,0,1073741892,'98,144','',0,0,'mech=0x20000022(DISORIENTED|GRIP|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-174,0,0,1073758240,'','',0,0,'mech=0x20002010(FEAR|KNOCKOUT|SAPPED)'), +(-175,0,0,1073873920,'','',0,0,'mech=0x20010200(SLEEP|POLYMORPH|SAPPED)'), +(-176,0,0,1073877158,'','',0,0,'mech=0x20010853(CHARM|DISORIENTED|FEAR|ROOT|STUN|POLYMORPH|SAPPED)'), +(-177,0,0,1073886882,'98,144','',0,0,'mech=0x20011B51(CHARM|FEAR|ROOT|SILENCE|SLEEP|STUN|FREEZE|POLYMORPH|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-178,0,0,1074009602,'','',0,0,'mech=0x20020B01(CHARM|SILENCE|SLEEP|STUN|BANISH|SAPPED)'), +(-179,0,0,1074167486,'','',0,0,'mech=0x20033F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SAPPED)'), +(-180,0,0,1075202048,'','',0,0,'mech=0x200B2400(SNARE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|SAPPED)'), +(-181,0,0,1082677838,'','',0,0,'mech=0x20442D27(CHARM|DISORIENTED|DISARM|GRIP|SILENCE|SNARE|STUN|KNOCKOUT|SHIELD|TURN|SAPPED)'), +(-182,0,0,1082686030,'','',0,0,'mech=0x20443D27(CHARM|DISORIENTED|DISARM|GRIP|SILENCE|SNARE|STUN|FREEZE|KNOCKOUT|SHIELD|TURN|SAPPED)'), +(-183,0,0,1082686046,'','',0,0,'mech=0x20443D2F(CHARM|DISORIENTED|DISARM|DISTRACT|GRIP|SILENCE|SNARE|STUN|FREEZE|KNOCKOUT|SHIELD|TURN|SAPPED)'), +(-184,0,0,1090534560,'','',0,0,'mech=0x20801E50(FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|HORROR|SAPPED)'), +(-185,8,0,1090674362,'','',0,0,'school=0x8(NATURE), mech=0x20812F5D(CHARM|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|SAPPED)'), +(-186,16,0,1090674362,'','',0,0,'school=0x10(FROST), mech=0x20812F5D(CHARM|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|SAPPED)'), +(-187,0,0,1090679030,'','',0,0,'mech=0x2081387B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|STUN|FREEZE|KNOCKOUT|POLYMORPH|HORROR|SAPPED)'), +(-188,0,0,1090681014,'','',0,0,'mech=0x20813C5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|HORROR|SAPPED)'), +(-189,0,0,1090681270,'','',0,0,'mech=0x20813CDB(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLOW_ATTACK|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|HORROR|SAPPED)'), +(-190,0,0,1090936558,'','',0,0,'mech=0x20832F77(CHARM|DISORIENTED|DISARM|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|BANISH|HORROR|SAPPED)'), +(-191,0,0,1090936574,'','',0,0,'mech=0x20832F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|BANISH|HORROR|SAPPED)'), +(-192,0,0,1090940076,'','',0,0,'mech=0x20833656(DISORIENTED|DISARM|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|SAPPED)'), +(-193,0,0,1090940670,'','',0,0,'mech=0x2083377F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|SAPPED)'), +(-194,0,0,1090944742,'114','11',0,0,'mech=0x20833F73(CHARM|DISORIENTED|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-195,0,0,1090944766,'','',0,0,'mech=0x20833F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|SAPPED)'), +(-196,0,0,1090944766,'114','11',0,0,'mech=0x20833F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-197,0,0,1099040290,'','',0,0,'mech=0x20C10311(CHARM|FEAR|SILENCE|SLEEP|POLYMORPH|TURN|HORROR|SAPPED)'), +(-198,0,0,1099043874,'','',0,0,'mech=0x20C10A11(CHARM|FEAR|SLEEP|STUN|POLYMORPH|TURN|HORROR|SAPPED)'), +(-199,0,0,1099043874,'114','11',0,0,'mech=0x20C10A11(CHARM|FEAR|SLEEP|STUN|POLYMORPH|TURN|HORROR|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-200,0,0,1099043874,'98,144','',0,0,'mech=0x20C10A11(CHARM|FEAR|SLEEP|STUN|POLYMORPH|TURN|HORROR|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-201,0,0,1099060258,'','',0,0,'mech=0x20C12A11(CHARM|FEAR|SLEEP|STUN|KNOCKOUT|POLYMORPH|TURN|HORROR|SAPPED)'), +(-202,0,0,1100219550,'','',0,0,'mech=0x20CA024F(CHARM|DISORIENTED|DISARM|DISTRACT|ROOT|SLEEP|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-203,0,0,1100248830,'','',0,0,'mech=0x20CA3B7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-204,0,0,1100350654,'','',0,0,'mech=0x20CB025F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SLEEP|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-205,0,0,1100359230,'','',0,0,'mech=0x20CB131F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|FREEZE|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-206,0,0,1100366910,'','',0,0,'mech=0x20CB221F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-207,0,0,1100375718,'','',0,0,'mech=0x20CB3353(CHARM|DISORIENTED|FEAR|ROOT|SILENCE|SLEEP|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-208,0,0,1100379710,'','',0,0,'mech=0x20CB3B1F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-209,0,0,1100379774,'','',0,0,'mech=0x20CB3B3F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|SAPPED)'), +(-210,0,0,1100904190,'','',0,0,'mech=0x20CF3B7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|SAPPED)'), +(-211,0,0,1100906238,'98,144','',0,0,'mech=0x20CF3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-212,0,0,1102477054,'','',0,0,'mech=0x20DB3B7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|MOUNT|TURN|HORROR|SAPPED)'), +(-213,0,0,1102872318,'','',0,0,'mech=0x20DE3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|SAPPED)'), +(-214,0,0,1141118466,'','',0,0,'mech=0x22020B01(CHARM|SILENCE|SLEEP|STUN|BANISH|INTERRUPT|SAPPED)'), +(-215,0,0,1158053548,'','',0,0,'mech=0x22833F56(DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|SAPPED)'), +(-216,0,0,1158053550,'','',0,0,'mech=0x22833F57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|SAPPED)'), +(-217,0,0,1158053630,'114','11',0,0,'mech=0x22833F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-218,0,0,1167484670,'','',0,0,'mech=0x22CB337F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|SAPPED)'), +(-219,0,0,1167484670,'98,144','',0,0,'mech=0x22CB337F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-220,0,0,1167488694,'','',0,0,'mech=0x22CB3B5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|SAPPED)'), +(-221,0,0,1207983274,'','',0,0,'mech=0x24002E55(CHARM|DISARM|FEAR|ROOT|SLEEP|SNARE|STUN|KNOCKOUT|DAZE|SAPPED)'), +(-222,0,0,1208099366,'','',0,0,'mech=0x24011113(CHARM|DISORIENTED|FEAR|SILENCE|FREEZE|POLYMORPH|DAZE|SAPPED)'), +(-223,0,0,1208110334,'98,144','',0,0,'mech=0x2401267F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|KNOCKOUT|POLYMORPH|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-224,0,0,1208117942,'','',0,0,'mech=0x2401355B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SNARE|FREEZE|KNOCKOUT|POLYMORPH|DAZE|SAPPED)'), +(-225,0,0,1208118454,'','',0,0,'mech=0x2401365B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|DAZE|SAPPED)'), +(-226,0,0,1208123134,'','',0,0,'mech=0x24013F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|DAZE|SAPPED)'), +(-227,0,0,1208245410,'','',0,0,'mech=0x24022E51(CHARM|FEAR|ROOT|SLEEP|SNARE|STUN|KNOCKOUT|BANISH|DAZE|SAPPED)'), +(-228,0,0,1209171606,'','',0,0,'mech=0x24093F4B(CHARM|DISORIENTED|DISTRACT|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|SHACKLE|DAZE|SAPPED)'), +(-229,0,0,1217817766,'','',0,0,'mech=0x244B3653(CHARM|DISORIENTED|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|DAZE|SAPPED)'), +(-230,0,0,1217817766,'114','11',0,0,'mech=0x244B3653(CHARM|DISORIENTED|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-231,8,0,1217817766,'','',0,0,'school=0x8(NATURE), mech=0x244B3653(CHARM|DISORIENTED|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|DAZE|SAPPED)'), +(-232,16,0,1217817766,'','',0,0,'school=0x10(FROST), mech=0x244B3653(CHARM|DISORIENTED|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|DAZE|SAPPED)'), +(-233,0,0,1224758964,'','',0,0,'mech=0x24802B5A(DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|STUN|KNOCKOUT|HORROR|DAZE|SAPPED)'), +(-234,0,0,1224758966,'','',0,0,'mech=0x24802B5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|STUN|KNOCKOUT|HORROR|DAZE|SAPPED)'), +(-235,0,0,1224764576,'','',0,0,'mech=0x24803650(FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|HORROR|DAZE|SAPPED)'), +(-236,0,0,1224891646,'98,144','',0,0,'mech=0x24812E7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-237,0,0,1224892158,'','',0,0,'mech=0x24812F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|DAZE|SAPPED)'), +(-238,0,0,1224899750,'','',0,0,'mech=0x24813E53(CHARM|DISORIENTED|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|HORROR|DAZE|SAPPED)'), +(-239,0,0,1224899830,'','',0,0,'mech=0x24813E7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|HORROR|DAZE|SAPPED)'), +(-240,0,0,1225030910,'','',0,0,'mech=0x24823E7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|HORROR|DAZE|SAPPED)'), +(-241,0,0,1225031358,'','',0,0,'mech=0x24823F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|HORROR|DAZE|SAPPED)'), +(-242,0,0,1225064188,'','',0,0,'mech=0x24827F7E(DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|BANISH|HORROR|DAZE|SAPPED)'), +(-243,0,0,1225162494,'','',0,0,'mech=0x24833F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|DAZE|SAPPED)'), +(-244,0,0,1225293566,'','',0,0,'mech=0x24843F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|SHIELD|HORROR|DAZE|SAPPED)'), +(-245,0,0,1225424638,'','',0,0,'mech=0x24853F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|SHIELD|HORROR|DAZE|SAPPED)'), +(-246,0,0,1225588478,'','',0,0,'mech=0x24867F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|BANISH|SHIELD|HORROR|DAZE|SAPPED)'), +(-247,0,0,1225686782,'','',0,0,'mech=0x24873F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|HORROR|DAZE|SAPPED)'), +(-248,0,0,1226206902,'','',0,0,'mech=0x248B375B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-249,0,0,1226209946,'','',0,0,'mech=0x248B3D4D(CHARM|DISARM|DISTRACT|ROOT|SILENCE|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-250,0,0,1226209974,'','',0,0,'mech=0x248B3D5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-251,0,0,1226210998,'','',0,0,'mech=0x248B3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-252,4,0,1226210998,'','',0,0,'school=0x4(FIRE), mech=0x248B3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-253,16,0,1226210998,'','',0,0,'school=0x10(FROST), mech=0x248B3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-254,0,0,1226211006,'','',0,0,'mech=0x248B3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-255,4,0,1226211006,'','',0,0,'school=0x4(FIRE), mech=0x248B3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED)'), +(-256,0,0,1226211070,'98,144','',0,0,'mech=0x248B3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-257,0,0,1230420214,'','',0,0,'mech=0x24AB5C7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SNARE|STUN|FREEZE|BLEED|POLYMORPH|BANISH|SHACKLE|INFECTED|HORROR|DAZE|SAPPED)'), +(-258,0,0,1233288886,'','',0,0,'mech=0x24C13F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|TURN|HORROR|DAZE|SAPPED)'), +(-259,0,0,1233419518,'','',0,0,'mech=0x24C23E7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|TURN|HORROR|DAZE|SAPPED)'), +(-260,0,0,1233551102,'','',0,0,'mech=0x24C33F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|TURN|HORROR|DAZE|SAPPED)'), +(-261,0,0,1233944318,'','',0,0,'mech=0x24C63F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHIELD|TURN|HORROR|DAZE|SAPPED)'), +(-262,0,0,1234075390,'','',0,0,'mech=0x24C73F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|TURN|HORROR|DAZE|SAPPED)'), +(-263,0,0,1234568766,'','',0,0,'mech=0x24CB031F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-264,0,0,1234583806,'98,144','',0,0,'mech=0x24CB207F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-265,0,0,1234595510,'','',0,0,'mech=0x24CB375B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-266,0,0,1234595518,'','',0,0,'mech=0x24CB375F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-267,0,0,1234595518,'98,144','',0,0,'mech=0x24CB375F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-268,0,0,1234595582,'','',0,0,'mech=0x24CB377F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-269,0,0,1234597430,'','',0,0,'mech=0x24CB3B1B(CHARM|DISORIENTED|DISTRACT|FEAR|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-270,0,0,1234599094,'','',0,0,'mech=0x24CB3E5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-271,64,0,1234599094,'','',0,0,'school=0x40(ARCANE), mech=0x24CB3E5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-272,0,0,1234599158,'','',0,0,'mech=0x24CB3E7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-273,0,0,1234599606,'','',0,0,'mech=0x24CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-274,0,0,1234599606,'114','11',0,0,'mech=0x24CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-275,0,0,1234599606,'98,144','',0,0,'mech=0x24CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-276,4,0,1234599606,'','',0,0,'school=0x4(FIRE), mech=0x24CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-277,8,0,1234599606,'','',0,0,'school=0x8(NATURE), mech=0x24CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-278,16,0,1234599606,'','',0,0,'school=0x10(FROST), mech=0x24CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-279,0,0,1234599610,'','',0,0,'mech=0x24CB3F5D(CHARM|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-280,0,0,1234599614,'','',0,0,'mech=0x24CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-281,0,0,1234599614,'114','11',0,0,'mech=0x24CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-282,124,0,1234599614,'114','11',0,0,'school=0x7C(FIRE|NATURE|FROST|SHADOW|ARCANE), mech=0x24CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-283,0,0,1234599654,'','',0,0,'mech=0x24CB3F73(CHARM|DISORIENTED|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-284,0,0,1234599670,'','',0,0,'mech=0x24CB3F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-285,0,0,1234599678,'','',0,0,'mech=0x24CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-286,0,0,1234599678,'98,144','',0,0,'mech=0x24CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-287,0,0,1234599678,'98,114,144','11',0,0,'mech=0x24CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=NO_TAUNT|IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),114(ATTACK_ME),144(KNOCK_BACK_DEST), auras=11(BIND)'), +(-288,0,0,1234632374,'','',0,0,'mech=0x24CB7F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-289,0,0,1234992894,'','',0,0,'mech=0x24CE3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHIELD|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-290,0,0,1235119870,'114','11',0,0,'mech=0x24CF377F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-291,0,0,1235123964,'','',0,0,'mech=0x24CF3F7E(DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-292,0,0,1235123966,'','',0,0,'mech=0x24CF3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-293,0,0,1235123966,'114','11',0,0,'mech=0x24CF3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-294,64,0,1235123966,'','',0,0,'school=0x40(ARCANE), mech=0x24CF3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|DAZE|SAPPED)'), +(-295,0,0,1237221118,'','',0,0,'mech=0x24DF3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|DAZE|SAPPED)'), +(-296,0,0,1275494326,'','',0,0,'mech=0x26033FDB(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLOW_ATTACK|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|INTERRUPT|DAZE|SAPPED)'), +(-297,0,0,1291996862,'','',0,0,'mech=0x2681275F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-298,8,0,1291998778,'','',0,0,'school=0x8(NATURE), mech=0x26812B1D(CHARM|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|STUN|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-299,0,0,1292000954,'','',0,0,'mech=0x26812F5D(CHARM|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-300,4,0,1292000954,'','',0,0,'school=0x4(FIRE), mech=0x26812F5D(CHARM|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-301,8,0,1292000954,'','',0,0,'school=0x8(NATURE), mech=0x26812F5D(CHARM|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-302,16,0,1292000954,'','',0,0,'school=0x10(FROST), mech=0x26812F5D(CHARM|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-303,0,0,1292000958,'','',0,0,'mech=0x26812F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-304,8,0,1292000958,'','',0,0,'school=0x8(NATURE), mech=0x26812F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-305,0,0,1292009150,'','',0,0,'mech=0x26813F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-306,0,0,1292033724,'','',0,0,'mech=0x26816F5E(DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|BLEED|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-307,0,0,1292033726,'','',0,0,'mech=0x26816F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|BLEED|POLYMORPH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-308,0,0,1292270270,'','',0,0,'mech=0x26833D5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-309,0,0,1292271166,'','',0,0,'mech=0x26833F1F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-310,126,0,1292271286,'','',0,0,'school=0x7E(HOLY|FIRE|NATURE|FROST|SHADOW|ARCANE), mech=0x26833F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-311,0,0,1292271294,'','',0,0,'mech=0x26833F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-312,4,0,1292271294,'','',0,0,'school=0x4(FIRE), mech=0x26833F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-313,0,0,1292271358,'','',0,0,'mech=0x26833F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-314,8,0,1292271358,'','',0,0,'school=0x8(NATURE), mech=0x26833F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-315,0,0,1293188798,'','',0,0,'mech=0x268A3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-316,4,0,1293188798,'','',0,0,'school=0x4(FIRE), mech=0x268A3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-317,8,0,1293188798,'','',0,0,'school=0x8(NATURE), mech=0x268A3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-318,16,0,1293188798,'','',0,0,'school=0x10(FROST), mech=0x268A3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-319,0,0,1293311670,'','',0,0,'mech=0x268B2F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-320,0,0,1293315754,'','',0,0,'mech=0x268B3755(CHARM|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-321,0,0,1293315774,'','',0,0,'mech=0x268B375F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-322,0,0,1293317684,'','',0,0,'mech=0x268B3B1A(DISORIENTED|DISTRACT|FEAR|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-323,0,0,1293318838,'','',0,0,'mech=0x268B3D5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-324,0,0,1293319862,'','',0,0,'mech=0x268B3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-325,0,0,1293319870,'','',0,0,'mech=0x268B3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-326,8,0,1293319870,'','',0,0,'school=0x8(NATURE), mech=0x268B3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-327,0,0,1293319926,'98,144','',0,0,'mech=0x268B3F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-328,0,0,1293876926,'','',0,0,'mech=0x268F7F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|POLYMORPH|BANISH|SHIELD|SHACKLE|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-329,8,0,1300397758,'','',0,0,'school=0x8(NATURE), mech=0x26C13F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-330,0,0,1300692542,'114','11',1,0,'mech=0x26C37F1F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|POLYMORPH|BANISH|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT|AVOID_AOE, effects=114(ATTACK_ME), auras=11(BIND)'), +(-331,0,0,1300692670,'','',0,0,'mech=0x26C37F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|POLYMORPH|BANISH|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-332,0,0,1301053166,'114','11',0,0,'mech=0x26C63F77(CHARM|DISORIENTED|DISARM|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHIELD|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-333,0,0,1301184254,'','',0,0,'mech=0x26C73F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-334,0,0,1301446398,'114','11',0,0,'mech=0x26C93F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-335,0,0,1301677626,'98,144','',0,0,'mech=0x26CB031D(CHARM|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-336,0,0,1301677630,'98,144','',0,0,'mech=0x26CB031F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SILENCE|SLEEP|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-337,0,0,1301702270,'','',0,0,'mech=0x26CB333F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|SILENCE|SLEEP|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-338,0,0,1301703934,'','',0,0,'mech=0x26CB367F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-339,0,0,1301704382,'98,144','',0,0,'mech=0x26CB375F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-340,0,0,1301704446,'98,144','',0,0,'mech=0x26CB377F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-341,4,0,1301704446,'','',0,0,'school=0x4(FIRE), mech=0x26CB377F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-342,0,0,1301706494,'98,114,144','11',0,0,'mech=0x26CB3B7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT|IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),114(ATTACK_ME),144(KNOCK_BACK_DEST), auras=11(BIND)'), +(-343,0,0,1301707958,'','',0,0,'mech=0x26CB3E5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-344,0,0,1301708022,'','',0,0,'mech=0x26CB3E7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-345,0,0,1301708462,'','',0,0,'mech=0x26CB3F57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-346,0,0,1301708470,'','',0,0,'mech=0x26CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-347,0,0,1301708470,'114','11',0,0,'mech=0x26CB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-348,0,0,1301708478,'','',0,0,'mech=0x26CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-349,0,0,1301708478,'114','11',0,0,'mech=0x26CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-350,0,0,1301708478,'98,144','',0,0,'mech=0x26CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-351,1,0,1301708478,'114','11',0,0,'school=0x1(NORMAL), mech=0x26CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-352,4,0,1301708478,'114','11',0,0,'school=0x4(FIRE), mech=0x26CB3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-353,0,0,1301708526,'98,144','',0,0,'mech=0x26CB3F77(CHARM|DISORIENTED|DISARM|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-354,0,0,1301708534,'','',0,0,'mech=0x26CB3F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-355,0,0,1301708534,'98,144','',0,0,'mech=0x26CB3F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-356,0,0,1301708534,'98,114,144','11',0,0,'mech=0x26CB3F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT|IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),114(ATTACK_ME),144(KNOCK_BACK_DEST), auras=11(BIND)'), +(-357,0,0,1301708538,'98,144','',0,0,'mech=0x26CB3F7D(CHARM|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-358,16,0,1301708538,'','',0,0,'school=0x10(FROST), mech=0x26CB3F7D(CHARM|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-359,0,0,1301708540,'98,144','',0,0,'mech=0x26CB3F7E(DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-360,0,0,1301708542,'','',0,0,'mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-361,0,0,1301708542,'98,144','',0,0,'mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-362,0,0,1301708542,'98,114,144','11',0,0,'mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=NO_TAUNT|IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),114(ATTACK_ME),144(KNOCK_BACK_DEST), auras=11(BIND)'), +(-363,0,0,1301708542,'98,144','',1,0,'mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=AVOID_AOE|IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-364,4,0,1301708542,'98,144','',0,0,'school=0x4(FIRE), mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-365,16,0,1301708542,'','',0,0,'school=0x10(FROST), mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-366,16,0,1301708542,'98,144','',0,0,'school=0x10(FROST), mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-367,0,0,1301741246,'','',0,0,'mech=0x26CB7F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-368,127,0,1301741246,'','',0,0,'school=0x7F(NORMAL|HOLY|FIRE|NATURE|FROST|SHADOW|ARCANE), mech=0x26CB7F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-369,0,0,1301741310,'98,144','',0,0,'mech=0x26CB7F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-370,0,0,1302232766,'','',1,0,'mech=0x26CF3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=AVOID_AOE'), +(-371,0,0,1302232830,'','',0,0,'mech=0x26CF3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-372,0,0,1302232830,'98,144','',0,0,'mech=0x26CF3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-373,0,0,1303871230,'','',0,0,'mech=0x26DBBF7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHACKLE|MOUNT|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-374,0,0,1304395518,'','',0,0,'mech=0x26DFBF7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-375,0,0,1304428286,'','',0,0,'mech=0x26DFFF7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-376,0,0,1305378558,'','',0,0,'mech=0x26E73F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|INFECTED|TURN|HORROR|INTERRUPT|DAZE|SAPPED)'), +(-377,0,0,1335787254,'','',0,0,'mech=0x27CF3F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|SHACKLE|TURN|HORROR|INVULNERABILITY|INTERRUPT|DAZE|SAPPED)'), +(-378,0,0,1539276542,'','',0,0,'mech=0x2DDFBF7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|INVULNERABILITY|DAZE|DISCOVERY|SAPPED)'), +(-379,0,0,1770663678,'','',0,0,'mech=0x34C5177F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|FREEZE|POLYMORPH|SHIELD|TURN|HORROR|DAZE|IMMUNE_SHIELD|SAPPED)'), +(-380,0,0,1770946302,'114','11',0,0,'mech=0x34C73F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHIELD|TURN|HORROR|DAZE|IMMUNE_SHIELD|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-381,0,0,1804534846,'','',0,0,'mech=0x35C7821F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|SLEEP|BANDAGE|POLYMORPH|BANISH|SHIELD|TURN|HORROR|INVULNERABILITY|DAZE|IMMUNE_SHIELD|SAPPED)'), +(-382,0,0,2013265920,'','',0,0,'mech=0x3C000000(DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-383,0,0,2013528064,'','',0,0,'mech=0x3C020000(BANISH|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-384,0,0,2013528100,'','',0,0,'mech=0x3C020012(DISORIENTED|FEAR|BANISH|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-385,0,0,2013660192,'','',0,0,'mech=0x3C030210(FEAR|SLEEP|POLYMORPH|BANISH|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-386,0,0,2022966272,'','',0,0,'mech=0x3C4A0200(SLEEP|BANISH|SHACKLE|TURN|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-387,0,0,2030453792,'','',0,0,'mech=0x3C832210(FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-388,0,0,2031386302,'','',0,0,'mech=0x3C8A3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-389,0,0,2031502368,'','',0,0,'mech=0x3C8B2210(FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-390,0,0,2031502372,'','',0,0,'mech=0x3C8B2212(DISORIENTED|FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-391,0,0,2031512736,'','',0,0,'mech=0x3C8B3650(FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-392,0,0,2031516704,'','',0,0,'mech=0x3C8B3E10(FEAR|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-393,0,0,2031516708,'','',0,0,'mech=0x3C8B3E12(DISORIENTED|FEAR|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-394,0,0,2031516836,'','',0,0,'mech=0x3C8B3E52(DISORIENTED|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-395,0,0,2031517372,'','',0,0,'mech=0x3C8B3F5E(DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-396,0,0,2038842400,'','',0,0,'mech=0x3CC32210(FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-397,0,0,2038842402,'','',0,0,'mech=0x3CC32211(CHARM|FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-398,0,0,2039890978,'','',0,0,'mech=0x3CCB2211(CHARM|FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-399,0,0,2039890982,'','',0,0,'mech=0x3CCB2213(CHARM|DISORIENTED|FEAR|SLEEP|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-400,0,0,2039901750,'','',0,0,'mech=0x3CCB371B(CHARM|DISORIENTED|DISTRACT|FEAR|SILENCE|SLEEP|SNARE|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-401,0,0,2039905318,'','',0,0,'mech=0x3CCB3E13(CHARM|DISORIENTED|FEAR|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-402,0,0,2039905444,'','',0,0,'mech=0x3CCB3E52(DISORIENTED|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-403,0,0,2039905446,'','',0,0,'mech=0x3CCB3E53(CHARM|DISORIENTED|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-404,0,0,2039905454,'','',0,0,'mech=0x3CCB3E57(CHARM|DISORIENTED|DISARM|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-405,0,0,2039905956,'','',0,0,'mech=0x3CCB3F52(DISORIENTED|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-406,0,0,2039905974,'','',0,0,'mech=0x3CCB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-407,0,0,2039905974,'98,144','',0,0,'mech=0x3CCB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-408,0,0,2039905974,'114','11',0,0,'mech=0x3CCB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED), flags=NO_TAUNT, effects=114(ATTACK_ME), auras=11(BIND)'), +(-409,4,0,2039905974,'','',0,0,'school=0x4(FIRE), mech=0x3CCB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-410,0,0,2039906038,'98,144','',0,0,'mech=0x3CCB3F7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),144(KNOCK_BACK_DEST)'), +(-411,0,0,2039906046,'','',0,0,'mech=0x3CCB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-412,0,0,2041872062,'','',0,0,'mech=0x3CDA3F5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANISH|SHACKLE|MOUNT|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-413,0,0,2042588340,'','',0,0,'mech=0x3CDFB65A(DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-414,0,0,2042588342,'','',0,0,'mech=0x3CDFB65B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-415,0,0,2042592436,'','',0,0,'mech=0x3CDFBE5A(DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-416,0,0,2042592438,'','',0,0,'mech=0x3CDFBE5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-417,0,0,2044099622,'','',0,0,'mech=0x3CEB3E13(CHARM|DISORIENTED|FEAR|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|INFECTED|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-418,0,0,2076147382,'','',0,0,'mech=0x3DDFBF5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|INVULNERABILITY|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-419,0,0,2107014838,'','',0,0,'mech=0x3ECB3F5B(CHARM|DISORIENTED|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|INTERRUPT|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-420,0,0,2143256254,'','',0,0,'mech=0x3FDFBF5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|INVULNERABILITY|INTERRUPT|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-421,0,0,2143289022,'','',0,0,'mech=0x3FDFFF5F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|INVULNERABILITY|INTERRUPT|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-422,0,0,2147483646,'','',0,0,'mech=0x3FFFFFFF(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SLOW_ATTACK|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|INFECTED|TURN|HORROR|INVULNERABILITY|INTERRUPT|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED)'), +(-423,0,0,3116367612,'','',0,0,'mech=0x5CDFFF7E(DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|TURN|HORROR|DAZE|DISCOVERY|IMMUNE_SHIELD|ENRAGED)'), +(-424,0,0,3386277110,'','',0,0,'mech=0x64EB3E7B(CHARM|DISORIENTED|DISTRACT|FEAR|GRIP|ROOT|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|INFECTED|TURN|HORROR|DAZE|SAPPED|ENRAGED)'), +(-425,0,0,4294934270,'','',0,0,'mech=0x7FFFBF7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|INFECTED|TURN|HORROR|INVULNERABILITY|INTERRUPT|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED|ENRAGED)'), +(-426,0,0,4294967038,'','',0,0,'mech=0x7FFFFF7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|BLEED|BANDAGE|POLYMORPH|BANISH|SHIELD|SHACKLE|MOUNT|INFECTED|TURN|HORROR|INVULNERABILITY|INTERRUPT|DAZE|DISCOVERY|IMMUNE_SHIELD|SAPPED|ENRAGED)'); + +-- ============================================================ +-- Clear existing CreatureImmunitiesId references +-- ============================================================ +UPDATE `creature_template` SET `CreatureImmunitiesId` = 0; + +-- ============================================================ +-- Update creature_template to reference the new IDs by entry +-- ============================================================ +UPDATE `creature_template` SET `CreatureImmunitiesId` = -1 WHERE `entry` IN (14667,17680,22379,23498); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -2 WHERE `entry` IN (18703,20700,24745,25328); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -3 WHERE `entry` IN (18696,20132,21380,23285,37132,37133,37134,37950,38125,38130,38131,38132,38133,38153,38752); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -4 WHERE `entry` IN (2091,2108,2726,4339,4676,6073,7135,7136,7137,8608,8616,14668,15209,15438,17267,19203,19973,20703,21706,22323,25001,25417,28584,30416,30847,30983,31453); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -5 WHERE `entry` IN (832,2258,2592,2736,2752,2762,4034,4035,4499,4526,5898,5902,6239,8667,9377,9397,11576,11577,11578,11744,11745,11777,11778,11779,11783,11784,14399,14400,14478,15352,16043,17085,17154,17156,17157,17158,17159,17160,18062,18181,21707,22036,23029,26407,28411,28547,28585,28825,30418,30965,30970,30979,31452); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -6 WHERE `entry` IN (2761,2776,2794,3851,3861,3950,4978,5462,5894,5895,5897,6047,6220,6748,7132,7428,7429,8519,8520,8521,8522,8837,9453,10198,10642,11256,11862,12759,13278,13279,13322,13456,13696,13736,14269,14350,15211,16570,17153,17155,17167,17207,17358,19204,20704,21428,21728,22035,22309,24228,24601,25226,25514,25715,25755,25756,25757,26116,26178,26204,26214,26215,26216,26316,26340,26341,26342,30419,30846,31454,37994); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -7 WHERE `entry` IN (703,1043,1364,19206,20705); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -8 WHERE `entry` IN (6492,6550,11480,11483,11484,14397,15527,16530,16854,18864,18865,18866,18867,19205,20516,20702,22310,26370,30848); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -9 WHERE `entry` IN (21032); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -10 WHERE `entry` IN (7734,7735); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -11 WHERE `entry` IN (16491); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -12 WHERE `entry` IN (352,523,752,931,1387,1571,1572,1573,2299,2409,2432,2835,2851,2858,2859,2861,2941,2995,3305,3310,3615,3725,3728,4312,4314,4317,4321,6026,6726,7823,7824,8018,8020,8609,8610,10378,11139,11830,11899,11900,11901,12596,12616,12617,12740,13177,15178,16218,16461,16593,16699,16704,17083,17229,17398,17420,17429,17461,17491,17622,17695,18048,18049,18050,18052,18053,18054,18057,18058,18603,18609,18610,18612,18615,18619,18631,18632,18635,18636,18830,19885,19887,19889,19903,20033,20034,20255,20256,20257,20258,20259,20260,20261,20578,20579,20580,20581,20582,20586,20587,20590,20594,20621,20624,20626,20639,20640,20641,20642,20644,20645,20661,20693,20695,20699,20701,20923,21230,21522,21524,21527,21528,21532,21545,21548,21563,21564,21565,21570,21571,21573,21575,21576,21585,21587,21613,21857,21865,21873,22876,23168,24994,25509,25591,25593,25595,25599,25851,26799,26800,26801,26802,26803,26805,27947,27949,29503,29626,30460,30478,30485,30495,30496,30497,30498,30508,30509,32922,32923,32924,32925,33235,33236,33259,33287,33355,33527,33579,33620,33622,33624,33626,33627,33629,33662,33669,33672,33696,33701,33737,33741,33754,33755,33757,33758,33774,33775,33816,33818,33819,33820,33822,33823,33824,33827,33828,33829,33830,33831,33832,33956,33957,34054,34113,34144,34145,34198,34199,34236,34237,34255,34256,34257,34441,34442,34444,34445,34447,34448,34449,34450,34451,34453,34454,34455,34456,34458,34459,34460,34461,34463,34465,34466,34467,34468,34469,34470,34471,34472,34473,34474,34475,35465,35610,35662,35665,35668,35671,35674,35680,35683,35686,35689,35692,35695,35699,35702,35705,35708,35711,35714,35718,35721,35724,35728,35731,35734,35737,35740,35743,35746,35774,36070,36301,36473); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -13 WHERE `entry` IN (34443,35663,35664,35666,35667,35669,35670,35672,35673,35675,35676,35681,35682,35684,35685,35687,35688,35690,35691,35693,35694,35696,35697,35700,35701,35703,35704,35706,35707,35709,35710,35712,35713,35715,35716,35719,35720,35722,35723,35725,35726,35729,35730,35732,35733,35735,35736,35738,35739,35741,35742,35744,35745,35747,35748,35749,35775,35776,36302,36303,36474,36475); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -14 WHERE `entry` IN (12737); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -15 WHERE `entry` IN (13996,14462,25752,25753,25758,25792); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -16 WHERE `entry` IN (14455); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -17 WHERE `entry` IN (14458); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -18 WHERE `entry` IN (24656,25553); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -19 WHERE `entry` IN (24553,24554,24555,24556,24557,24558,24559,24561,25541,25549,25550,25555,25556,25574,25578,25579); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -20 WHERE `entry` IN (17543,18404); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -21 WHERE `entry` IN (12143); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -22 WHERE `entry` IN (10202); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -23 WHERE `entry` IN (8717,21549); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -24 WHERE `entry` IN (30839); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -25 WHERE `entry` IN (20869,21586); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -26 WHERE `entry` IN (2673,2674,4952,12426,17578,18215,19139,21157,24792,30527,31143,31144,31146,32541,32542,32543,32545,32546,32547,32666,32667,33229,33243,33272); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -27 WHERE `entry` IN (16844,16968,21849,22038,22466,22482,37127,38126); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -28 WHERE `entry` IN (20897,23398); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -29 WHERE `entry` IN (21597,22879,22882,22945,23018,23049,23147,23223,23235,23236,23237,24815,25566); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -30 WHERE `entry` IN (23172); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -31 WHERE `entry` IN (5277,5280,5283,8319,16098,17669,20574); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -32 WHERE `entry` IN (22299,22300); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -33 WHERE `entry` IN (3678); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -34 WHERE `entry` IN (23584); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -35 WHERE `entry` IN (19005,22874,22885); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -36 WHERE `entry` IN (18707); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -37 WHERE `entry` IN (18533,20772); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -38 WHERE `entry` IN (21166); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -39 WHERE `entry` IN (4468); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -40 WHERE `entry` IN (18044,22939); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -41 WHERE `entry` IN (8908); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -42 WHERE `entry` IN (18258); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -43 WHERE `entry` IN (18046); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -44 WHERE `entry` IN (22957,22962); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -45 WHERE `entry` IN (18420,21574); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -46 WHERE `entry` IN (20038,25367,25368,25371,25507); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -47 WHERE `entry` IN (20052); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -48 WHERE `entry` IN (21225); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -49 WHERE `entry` IN (24549); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -50 WHERE `entry` IN (34702,34703,35569,35570,35571,35572,35617,36082,36083,36084,36085,36086,36087,36088,36089,36090,36091); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -51 WHERE `entry` IN (20048); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -52 WHERE `entry` IN (20883,21615); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -53 WHERE `entry` IN (10662); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -54 WHERE `entry` IN (28163,28168); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -55 WHERE `entry` IN (12800,12801,12802); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -56 WHERE `entry` IN (24047); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -57 WHERE `entry` IN (10663); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -58 WHERE `entry` IN (18423); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -59 WHERE `entry` IN (12458); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -60 WHERE `entry` IN (22875); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -61 WHERE `entry` IN (7397); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -62 WHERE `entry` IN (2760,3417,4036,4037,4038,5893,5896,6521,7266,7738,9178,9878,9879); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -63 WHERE `entry` IN (2745,8281,8909,8910,8911,9026,9376); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -64 WHERE `entry` IN (14460); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -65 WHERE `entry` IN (13601); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -66 WHERE `entry` IN (13282); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -67 WHERE `entry` IN (30829,30830,30831); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -68 WHERE `entry` IN (2667,2671,7076,7077,7309,8376,8816,9657,15104,15108,15429,15631,15698,15706,15904,15910,15964,15973,16511); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -69 WHERE `entry` IN (26688); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -70 WHERE `entry` IN (16700,17732,19632,20174,24179,29273); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -71 WHERE `entry` IN (14261,14262,14263,14264,14265,20588,20589); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -72 WHERE `entry` IN (17816,17817,19884,19892); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -73 WHERE `entry` IN (19513,19598,19865,21560,21561,21562); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -74 WHERE `entry` IN (17465,20583,22955); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -75 WHERE `entry` IN (32361,32377,32409,32417,32422,32429,32447,32475,32481,32485); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -76 WHERE `entry` IN (22965); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -77 WHERE `entry` IN (24180); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -78 WHERE `entry` IN (24065); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -79 WHERE `entry` IN (21515); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -80 WHERE `entry` IN (24530); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -81 WHERE `entry` IN (18322,20696); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -82 WHERE `entry` IN (15146,25363); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -83 WHERE `entry` IN (17864,18848,20656,20881,20900,21619,21621,25592,25948); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -84 WHERE `entry` IN (17818); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -85 WHERE `entry` IN (7728); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -86 WHERE `entry` IN (25798,25799); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -87 WHERE `entry` IN (26928,26929,26930,30511,30512,30513); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -88 WHERE `entry` IN (18182); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -89 WHERE `entry` IN (32500); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -90 WHERE `entry` IN (17731,20173); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -91 WHERE `entry` IN (19191); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -92 WHERE `entry` IN (19261); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -93 WHERE `entry` IN (3,36,114,115,200,201,204,210,302,314,315,392,412,480,503,511,522,570,572,573,604,624,625,626,725,846,882,948,1159,1270,1488,1489,1491,1501,1502,1525,1526,1527,1528,1529,1530,1532,1533,1654,1656,1663,1666,1674,1675,1696,1716,1717,1720,1753,1772,1773,1790,1791,1792,1793,1794,1795,1796,1798,1805,1847,1849,1850,1852,1866,1868,1917,1918,1919,1921,1939,1940,1941,1942,1943,1944,1948,1971,1974,2045,2056,2217,2218,2219,2220,2221,2222,2227,2278,2288,2325,2433,2475,2479,2520,2531,2535,2536,2537,2623,2676,2678,2748,2763,2922,3094,3301,3538,3558,3565,3617,3654,3669,3670,3671,3673,3674,3799,3802,3803,3869,3870,3876,3914,3940,3941,3942,3983,4073,4074,4251,4252,4260,4308,4473,4474,4475,4476,4550,4566,4606,4610,4612,4854,4872,4945,4946,4958,5097,5202,5263,5267,5270,5271,5400,5621,5624,5626,5627,5674,5685,5686,5687,5712,5713,5714,5715,5716,5717,5723,5775,5854,5912,5954,6036,6092,6106,6107,6108,6225,6226,6227,6230,6231,6232,6233,6234,6346,6386,6426,6427,6486,6493,6669,6906,6907,6908,6910,7023,7039,7050,7067,7068,7069,7070,7071,7072,7075,7166,7206,7209,7270,7273,7274,7276,7286,7291,7310,7327,7328,7329,7332,7333,7334,7340,7347,7348,7349,7351,7352,7353,7354,7355,7356,7357,7358,7374,7375,7503,7504,7527,7624,7784,7791,7806,7807,7849,7895,7897,8035,8447,8500,8501,8530,8531,8532,8534,8535,8536,8537,8543,8544,8545,8552,8555,8556,8557,8558,8559,8567,8580,8585,8615,8663,8856,9437,9438,9439,9441,9442,9443,9476,9623,10080,10081,10082,10117,10381,10382,10383,10395,10397,10402,10403,10405,10406,10407,10408,10409,10411,10412,10413,10414,10416,10417,10463,10464,10479,10480,10481,10483,10484,10492,10493,10494,10495,10497,10498,10499,10500,10580,10666,10684,10698,10699,10778,10801,10810,10818,10819,10820,10821,10825,10836,10926,10927,10936,10937,10939,10943,10944,10945,10946,10949,10950,10951,10953,10954,11027,11030,11075,11082,11151,11153,11154,11155,11179,11195,11199,11216,11217,11263,11277,11278,11279,11280,11281,11282,11283,11286,11287,11290,11291,11292,11316,11412,11414,11415,11468,11469,11470,11471,11472,11473,11474,11475,11477,11517,11518,11519,11520,11551,11580,11598,11628,11684,11875,11936,12238,12239,12240,12241,12242,12243,12248,12250,12261,12262,12263,12363,12364,12365,12367,12368,12379,12380,12385,12473,12865,13146,13378,13416,13619,13738,13739,13740,13741,13742,14224,14308,14337,14358,14364,14484,14485,14494,14511,14512,14513,14514,14518,14519,14520,14521,14551,14552,14553,14554,14562,14563,14605,14683,14685,14686,14687,14688,14691,14692,14694,14695,14696,14697,14698,14699,14700,14701,14702,14703,14704,14705,14706,14707,14708,14709,14710,14711,14712,14713,14714,14825,14826,15079,15081,15117,15121,15195,15349,15364,15368,15374,15376,15377,15547,15548,15551,15654,15655,15656,15657,15658,15665,15720,15958,16025,16026,16039,16040,16059,16066,16067,16093,16099,16103,16104,16119,16120,16125,16138,16141,16145,16146,16148,16153,16154,16159,16163,16169,16184,16211,16245,16246,16247,16248,16249,16250,16298,16300,16301,16302,16303,16305,16306,16307,16308,16309,16311,16312,16313,16314,16316,16319,16320,16321,16322,16323,16324,16325,16326,16327,16328,16329,16342,16357,16382,16383,16388,16389,16390,16393,16394,16402,16403,16406,16407,16408,16409,16410,16411,16412,16415,16424,16425,16426,16432,16437,16439,16451,16459,16460,16468,16470,16471,16481,16482,16525,16526,16805,16806,16811,16812,16813,16814,16815,16861,16904,16905,16906,16976,16977,16978,17007,17063,17064,17067,17086,17261,17415,17466,17503,17588,17589,17592,17612,17660,17672,17674,17714,17878,17895,17897,17898,17902,17903,17905,17906,18043,18287,18319,18320,18327,18355,18367,18441,18460,18478,18498,18499,18500,18501,18506,18521,18524,18556,18557,18558,18559,18588,18643,18689,18700,18797,18872,18873,19416,19417,19460,19463,19464,19488,19489,19543,19544,19545,19546,19579,19580,19682,19698,19719,19736,19749,19751,19825,19826,19827,19846,19863,19864,19872,19873,19874,19875,19876,19878,19879,19881,19937,20117,20137,20298,20303,20305,20310,20311,20312,20313,20315,20316,20317,20319,20320,20321,20322,20323,20409,20410,20480,20483,20495,20496,20512,20662,20691,20697,20698,20934,21049,21058,21065,21093,21163,21184,21198,21199,21200,21201,21202,21324,21354,21384,21385,21386,21430,21446,21449,21450,21452,21628,21636,21638,21651,21763,21778,21779,21784,21787,21788,21795,21797,21801,21815,21866,21867,21869,21870,21941,22025,22041,22045,22062,22138,22226,22235,22369,22441,22452,22454,22701,22706,23066,23067,23068,23103,23109,23111,23126,23161,23193,23204,23370,23371,23389,23408,23503,23509,23545,23554,23555,23561,23562,23563,23575,23643,23644,23645,23786,23861,23898,23904,23935,23970,23992,23993,24013,24019,24027,24029,24041,24073,24074,24084,24118,24153,24168,24177,24207,24246,24247,24248,24258,24262,24266,24267,24272,24327,24344,24440,24446,24447,24485,24546,24562,24563,24564,24566,24621,24622,24623,24624,24625,24626,24627,24693,24695,24712,24789,24790,24796,24808,24814,24871,24872,24874,24875,24876,24877,24880,25027,25028,25041,25224,25227,25228,25268,25294,25296,25330,25331,25332,25333,25350,25351,25352,25359,25365,25366,25375,25377,25382,25383,25386,25387,25393,25396,25445,25451,25452,25453,25462,25463,25465,25469,25542,25546,25559,25582,25600,25611,25619,25622,25625,25629,25650,25652,25655,25660,25668,25678,25682,25684,25981,26073,26076,26103,26115,26125,26126,26165,26195,26202,26203,26224,26225,26250,26252,26286,26287,26292,26294,26317,26318,26320,26336,26343,26344,26402,26404,26413,26455,26457,26458,26461,26475,26490,26491,26492,26501,26509,26515,26517,26518,26526,26536,26555,26570,26573,26605,26606,26607,26608,26621,26623,26624,26635,26636,26637,26638,26658,26669,26670,26676,26690,26691,26692,26694,26696,26702,26703,26769,26770,26771,26811,26812,26824,26830); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -93 WHERE `entry` IN (26840,26841,26858,26871,26891,26946,26948,26966,26967,27018,27059,27105,27122,27152,27153,27165,27219,27220,27224,27225,27226,27229,27240,27241,27268,27270,27272,27282,27283,27284,27285,27286,27287,27288,27290,27335,27360,27362,27363,27370,27383,27400,27401,27410,27465,27505,27510,27513,27531,27533,27534,27551,27552,27556,27561,27604,27605,27611,27614,27616,27618,27620,27623,27624,27630,27631,27680,27683,27685,27686,27691,27693,27694,27696,27712,27729,27733,27734,27736,27737,27766,27767,27768,27769,27770,27771,27772,27773,27774,27775,27776,27777,27778,27779,27780,27781,27782,27797,27799,27800,27807,27808,27809,27821,27822,27823,27824,27825,27829,27835,27836,27848,27854,27871,27874,27941,27957,28005,28006,28007,28014,28018,28022,28023,28026,28101,28103,28104,28108,28144,28158,28159,28162,28170,28194,28199,28200,28201,28207,28208,28211,28212,28218,28220,28232,28242,28243,28246,28249,28255,28257,28258,28268,28270,28278,28349,28350,28356,28357,28364,28365,28369,28377,28383,28395,28405,28406,28412,28414,28419,28420,28421,28422,28423,28424,28425,28426,28427,28428,28429,28430,28431,28432,28433,28434,28435,28436,28437,28438,28444,28445,28446,28447,28448,28449,28450,28471,28472,28474,28475,28476,28487,28488,28498,28499,28500,28503,28506,28510,28512,28519,28528,28534,28564,28565,28570,28589,28590,28599,28603,28615,28628,28641,28642,28647,28651,28653,28654,28657,28658,28659,28666,28669,28670,28711,28732,28733,28734,28735,28736,28745,28747,28748,28749,28750,28752,28754,28756,28759,28760,28768,28769,28782,28788,28793,28802,28803,28805,28843,28864,28866,28867,28868,28869,28870,28871,28872,28875,28878,28879,28889,28890,28897,28901,28903,28905,28906,28907,28908,28909,28910,28911,28913,28914,28915,28919,28924,28925,28930,28931,28933,28934,28937,28943,28948,28998,29030,29031,29047,29051,29053,29062,29063,29064,29096,29097,29098,29101,29106,29107,29108,29109,29110,29111,29112,29113,29115,29117,29118,29119,29123,29128,29129,29133,29136,29153,29172,29173,29183,29185,29186,29187,29188,29189,29190,29191,29193,29194,29195,29196,29197,29198,29199,29200,29201,29202,29204,29205,29206,29207,29208,29209,29210,29212,29213,29214,29216,29217,29219,29221,29227,29230,29231,29232,29238,29239,29245,29246,29256,29263,29280,29286,29335,29340,29347,29349,29353,29354,29355,29356,29359,29362,29363,29371,29388,29394,29400,29414,29449,29450,29451,29453,29468,29480,29488,29501,29517,29519,29520,29522,29565,29566,29567,29570,29574,29576,29587,29609,29629,29632,29633,29634,29635,29646,29648,29654,29656,29697,29699,29704,29719,29720,29722,29738,29769,29770,29821,29823,29824,29825,29828,29831,29833,29835,29837,29840,29842,29851,29852,29856,29858,29859,29860,29863,29872,29882,29887,29890,29891,29892,29893,29894,29895,29897,29898,29899,29900,29901,29934,29935,29943,29974,29985,29986,29987,29988,29989,29990,30015,30016,30018,30027,30028,30029,30030,30031,30032,30033,30034,30047,30048,30049,30071,30074,30075,30083,30085,30087,30097,30135,30144,30150,30202,30203,30204,30205,30216,30230,30232,30235,30250,30264,30276,30277,30278,30283,30284,30285,30286,30287,30288,30303,30306,30307,30309,30310,30311,30333,30335,30338,30378,30389,30403,30404,30406,30409,30424,30432,30443,30471,30482,30486,30501,30523,30541,30542,30543,30544,30545,30546,30575,30593,30594,30597,30609,30629,30670,30687,30689,30696,30697,30698,30701,30728,30746,30778,30791,30806,30816,30817,30818,30819,30821,30822,30823,30836,30843,30851,30863,30864,30865,30894,30895,30920,30921,30922,30944,30945,30946,30947,30949,30951,30952,30953,30954,30955,30956,30957,30958,30960,30984,30985,30986,30987,30988,30989,30992,30993,31012,31014,31015,31029,31037,31039,31040,31042,31043,31048,31078,31082,31083,31084,31087,31088,31089,31090,31094,31095,31096,31097,31098,31099,31100,31104,31107,31110,31123,31135,31137,31139,31140,31141,31142,31147,31150,31152,31154,31155,31157,31159,31160,31161,31163,31178,31179,31183,31184,31187,31188,31191,31192,31193,31194,31195,31196,31198,31199,31200,31205,31207,31208,31220,31221,31222,31223,31224,31225,31226,31227,31231,31237,31251,31255,31263,31266,31268,31271,31274,31278,31283,31301,31318,31320,31321,31322,31323,31324,31325,31326,31327,31341,31342,31346,31347,31351,31352,31354,31355,31357,31361,31363,31396,31398,31411,31413,31438,31442,31443,31447,31449,31450,31451,31457,31460,31466,31468,31530,31531,31532,31554,31555,31556,31565,31583,31585,31586,31587,31588,31589,31590,31591,31593,31594,31595,31596,31597,31598,31599,31600,31601,31602,31603,31604,31605,31606,31607,31608,31609,31613,31614,31635,31647,31671,31681,31692,31702,31718,31721,31754,31775,31779,31780,31783,31787,31812,31813,31815,31843,31844,31847,31853,31900,32031,32037,32149,32160,32161,32163,32164,32175,32176,32178,32179,32181,32182,32183,32184,32188,32255,32257,32267,32272,32278,32280,32283,32284,32288,32292,32299,32300,32309,32323,32326,32350,32390,32391,32404,32408,32410,32423,32446,32467,32479,32480,32482,32483,32490,32492,32497,32499,32502,32503,32505,32507,32508,32511,32556,32593,32609,32767,32769,32770,32771,32772,32786,32787,33016,33017,33429,33438,33441,33499,33513,33519,33550,33663,33664,33667,33668,33687,33704,33798,34127,34154,34238,34801,35169,35255,35444,35461,35474,35493,35547,35557,35559,35560,35652,35653,35654,35763,36065,36066,36128,36173,36478,36503,36504,36522,36564,36595,36666,36677,36724,36725,36726,36792,36794,36796,36807,36829,36830,36840,36841,36842,36844,36875,36877,36879,36880,36881,36886,36893,36896,36907,36916,36940,36941,37011,37012,37022,37023,37038,37069,37120,37128,37228,37229,37232,37351,37357,37491,37493,37494,37495,37501,37502,37530,37532,37535,37538,37539,37541,37542,37544,37545,37546,37549,37550,37551,37563,37565,37569,37571,37586,37595,37612,37622,37635,37636,37637,37638,37639,37640,37642,37644,37655,37656,37657,37658,37662,37663,37664,37665,37666,37678,37711,37728,37729,37730,37731,37755,37845,37846,37857,37881,37893,37906,37945,37976,38004,38057,38058,38059,38061,38062,38063,38073,38074,38077,38098,38099,38100,38101,38102,38104,38105,38108,38134,38137,38151); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -93 WHERE `entry` IN (38172,38173,38175,38176,38177,38184,38185,38186,38193,38197,38198,38216,38222,38249,38260,38308,38309,38349,38350,38351,38352,38362,38367,38386,38388,38391,38392,38410,38419,38429,38430,38445,38446,38479,38480,38481,38487,38524,38525,38544,38545,38563,38564,38567,38568,38572,38610,38763,38857,38883,39048,39231,39232,39233,39234,39639,40274); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -94 WHERE `entry` IN (36824,37007,38031,39296); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -95 WHERE `entry` IN (14986,15163); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -96 WHERE `entry` IN (11446,38248); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -97 WHERE `entry` IN (11284); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -98 WHERE `entry` IN (12876); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -99 WHERE `entry` IN (28912); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -100 WHERE `entry` IN (25597,37695,39309,39310,39311); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -101 WHERE `entry` IN (8836); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -102 WHERE `entry` IN (7915,16111); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -103 WHERE `entry` IN (14682); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -104 WHERE `entry` IN (18259,18648); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -105 WHERE `entry` IN (20682); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -106 WHERE `entry` IN (20783,20784,20785,20786,20788,20789,20790); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -107 WHERE `entry` IN (14684,14690); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -108 WHERE `entry` IN (14693); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -109 WHERE `entry` IN (17725,17871,20188,20190); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -110 WHERE `entry` IN (2447); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -111 WHERE `entry` IN (22848); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -112 WHERE `entry` IN (11447,11497,11498); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -113 WHERE `entry` IN (1157,1158,1160,1534,1655,1800,1801,1802,1804,1946,1983,2044,2176,2177,2178,2638,3667,3801,4472,6116,6117,6133,7073,7074,7370,7523,7524,7864,8538,8539,8540,8541,8542,10387,10388,10389,10940,10947,10948,11064,11078,11141,11285,11288,11289,11296,11560,11620,11621,11686,11687,11873,12178,12179,12199,12377,12378,14564,16423); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -114 WHERE `entry` IN (1531,2184,3863,3872,3873,3875,3877,6118,10358,10384,10385,10938,10996,16143,16379); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -115 WHERE `entry` IN (18856); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -116 WHERE `entry` IN (6021,9299,10988); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -117 WHERE `entry` IN (15554); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -118 WHERE `entry` IN (22338); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -119 WHERE `entry` IN (23269); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -120 WHERE `entry` IN (19354); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -121 WHERE `entry` IN (17724,20185,20465,21943); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -122 WHERE `entry` IN (48,202,203,531,623,785,787,1110,1520,1522,1523,1657,1658,1783,1784,1785,1787,1788,1789,1865,1869,1870,1871,1890,1916,1973,2454,6388,6412,7343,7344,7346,7786,8477,8523,8524,8525,8526,8527,8528,8529,8884,10390,10391,10482,10816,10952,11076,11077,11156,11197,11200,11258,11476,11547,12208,12341,12342,12343,12344,14486,14489,14558,16299,16422,16438); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -123 WHERE `entry` IN (10485); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -124 WHERE `entry` IN (771,2283,2946,7341,7342,7345,10393,10394,10478,10486,10487,10488,10489,10491,10826,16380); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -125 WHERE `entry` IN (11561); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -126 WHERE `entry` IN (22953); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -127 WHERE `entry` IN (5711); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -128 WHERE `entry` IN (23401); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -129 WHERE `entry` IN (28443); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -130 WHERE `entry` IN (19307,20265); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -131 WHERE `entry` IN (17958); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -132 WHERE `entry` IN (3253,36551,37564); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -133 WHERE `entry` IN (20047); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -134 WHERE `entry` IN (12557); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -135 WHERE `entry` IN (22853); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -136 WHERE `entry` IN (22869,23339); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -137 WHERE `entry` IN (20875,21604); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -138 WHERE `entry` IN (21102); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -139 WHERE `entry` IN (21181,21251); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -140 WHERE `entry` IN (16504); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -141 WHERE `entry` IN (20866,21614); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -142 WHERE `entry` IN (22954); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -143 WHERE `entry` IN (20898,21598); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -144 WHERE `entry` IN (16414,16473); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -145 WHERE `entry` IN (35644,36558); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -146 WHERE `entry` IN (33060,33062,33067,33109,33167,34045); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -147 WHERE `entry` IN (19797); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -148 WHERE `entry` IN (17454); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -149 WHERE `entry` IN (17940,19891,21127); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -150 WHERE `entry` IN (21843); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -151 WHERE `entry` IN (21270,21274); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -152 WHERE `entry` IN (20873,21605); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -153 WHERE `entry` IN (20175); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -154 WHERE `entry` IN (17726,17735,20191,20193); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -155 WHERE `entry` IN (17727,20192); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -156 WHERE `entry` IN (20908,20909,21616,21617); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -157 WHERE `entry` IN (22849); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -158 WHERE `entry` IN (14349); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -159 WHERE `entry` IN (644); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -160 WHERE `entry` IN (15334,15725,15726,15728,15802); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -161 WHERE `entry` IN (18497,20299); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -162 WHERE `entry` IN (21694,21914,24068,25372,31655); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -163 WHERE `entry` IN (18796,20652); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -164 WHERE `entry` IN (17907); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -165 WHERE `entry` IN (20043,20044); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -166 WHERE `entry` IN (21268,21269,21271,21272,21273); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -167 WHERE `entry` IN (14987); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -168 WHERE `entry` IN (15630,15984); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -169 WHERE `entry` IN (25334,26523,27881,27894,28094,28312,28319,28366,28781,32627,32629,32795,32796,34775,34776,34777,34778,34793,34802,34922,34924,34929,34935,34944,35069,35273,35403,35405,35410,35413,35415,35417,35419,35421,35427,35429,35431,35433,35436,35819,36355,36356,36357,36358); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -170 WHERE `entry` IN (20992); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -171 WHERE `entry` IN (16507); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -172 WHERE `entry` IN (20593); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -173 WHERE `entry` IN (16857); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -174 WHERE `entry` IN (18554); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -175 WHERE `entry` IN (23586); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -176 WHERE `entry` IN (19886); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -177 WHERE `entry` IN (34657,34701,34705); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -178 WHERE `entry` IN (22845,23374); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -179 WHERE `entry` IN (12339); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -180 WHERE `entry` IN (10814); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -181 WHERE `entry` IN (23402); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -182 WHERE `entry` IN (23403); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -183 WHERE `entry` IN (23400); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -184 WHERE `entry` IN (6584); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -185 WHERE `entry` IN (5855); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -186 WHERE `entry` IN (3917); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -187 WHERE `entry` IN (17827,20165); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -188 WHERE `entry` IN (22930); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -189 WHERE `entry` IN (17671,20584,20993); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -190 WHERE `entry` IN (23196); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -191 WHERE `entry` IN (22880,22956,22964,23222,23239,23337,23399); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -192 WHERE `entry` IN (22199); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -193 WHERE `entry` IN (18680,22877,22884); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -194 WHERE `entry` IN (22960); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -195 WHERE `entry` IN (22844,22847,22873,22963); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -196 WHERE `entry` IN (22878,23028,23030); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -197 WHERE `entry` IN (34605,34650,35658,35659); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -198 WHERE `entry` IN (40683,40684); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -199 WHERE `entry` IN (40681,40682); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -200 WHERE `entry` IN (36561,36571); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -201 WHERE `entry` IN (40417,40418,40419,40420,40421,40422,40423,40424); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -202 WHERE `entry` IN (37890,37949,38393,38394,38625,38626,38628,38629); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -203 WHERE `entry` IN (18692); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -204 WHERE `entry` IN (38009,38010,38135,38136,38395,38396,38397,38398,38630,38631,38632,38633,38634,38635,39000,39001); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -205 WHERE `entry` IN (38508,38596,38597,38598); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -206 WHERE `entry` IN (38472,38485); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -207 WHERE `entry` IN (34607,34648,35655,35656); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -208 WHERE `entry` IN (37863,38171,38727,38737); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -209 WHERE `entry` IN (36791,37868,37886,37934,38166,38167,38169,38170,38721,38722,38723,38724,38725,38733,38734,38735); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -210 WHERE `entry` IN (20032); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -211 WHERE `entry` IN (20040); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -212 WHERE `entry` IN (20035); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -213 WHERE `entry` IN (20050); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -214 WHERE `entry` IN (22846); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -215 WHERE `entry` IN (20216,20600,22910); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -216 WHERE `entry` IN (16545,22911); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -217 WHERE `entry` IN (23330); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -218 WHERE `entry` IN (37799,39284,39285,39286); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -219 WHERE `entry` IN (36609,39120,39121,39122); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -220 WHERE `entry` IN (33346,33886); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -221 WHERE `entry` IN (22075); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -222 WHERE `entry` IN (20692); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -223 WHERE `entry` IN (18836); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -224 WHERE `entry` IN (11347); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -225 WHERE `entry` IN (11348); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -226 WHERE `entry` IN (17256); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -227 WHERE `entry` IN (22074); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -228 WHERE `entry` IN (14101); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -229 WHERE `entry` IN (639,642,643,645,646,647,1763,3586,3886,3887,3927,4274,4275,4278,4279,4420,4421,4422,4424,4428,4829,4830,4831,4832,4842,6168,6228,6229,6235,6243,6488,6489,6490,7361,10439,11356,12203,12225,12236,12258,12902,13021,13596); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -230 WHERE `entry` IN (7800); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -231 WHERE `entry` IN (12237); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -232 WHERE `entry` IN (7079); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -233 WHERE `entry` IN (24683,24684,24686,24687,24688,24689,24690,24762,25565,25567,25568,25570,25572,25575,25576,25577); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -234 WHERE `entry` IN (24685,25569); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -235 WHERE `entry` IN (12803); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -236 WHERE `entry` IN (19389,21350); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -237 WHERE `entry` IN (18829); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -238 WHERE `entry` IN (8923,9217,9218,9219,9718,10263,10809,11467); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -239 WHERE `entry` IN (30385,31474); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -240 WHERE `entry` IN (17723,20164); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -241 WHERE `entry` IN (11663); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -242 WHERE `entry` IN (21346,21612); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -243 WHERE `entry` IN (23061,23261,23281,23282); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -244 WHERE `entry` IN (18731,20636); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -245 WHERE `entry` IN (22076); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -246 WHERE `entry` IN (17990,20189); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -247 WHERE `entry` IN (17796,20630); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -248 WHERE `entry` IN (8929,9025,9502,10076,14321,14323,14326); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -249 WHERE `entry` IN (12422); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -250 WHERE `entry` IN (10376,10596); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -251 WHERE `entry` IN (8983,9018,9019,9024,9027,9028,9029,9030,9031,9032,9033,9034,9035,9036,9037,9038,9039,9040,9041,9042,9056,9156,9196,9236,9237,9319,9537,9543,9568,9736,9938,10220,10268,10339,10363,10429,10430,10503,10509,10584,10812,11486,11487,11488,11489,11490,11492,11496,11501,12459,12461,12463,12464,14322,14324,14325,14327,14354,14502,14506,15229,15230,15233,15235,15236,15240,15246,15249,15250,15252,15262,15264,15277,15311,15312,16042,16080,16097); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -252 WHERE `entry` IN (9016,9017,9816); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -253 WHERE `entry` IN (13280); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -254 WHERE `entry` IN (11662,12460,12467); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -255 WHERE `entry` IN (11981,11983,14601); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -256 WHERE `entry` IN (17862,20521); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -257 WHERE `entry` IN (24238); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -258 WHERE `entry` IN (1840); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -259 WHERE `entry` IN (20901,20902,21610,21611); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -260 WHERE `entry` IN (21218,21221,21298,21299,21301,21339,21508); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -261 WHERE `entry` IN (20885,20886,21466,21467,21590,21599,21600,21624); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -262 WHERE `entry` IN (17826,17882,18105,20168,20183,20184); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -263 WHERE `entry` IN (33836,34218); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -264 WHERE `entry` IN (30245,30249,31750,31751); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -265 WHERE `entry` IN (33202,33398); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -266 WHERE `entry` IN (15516); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -267 WHERE `entry` IN (32857,33694); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -268 WHERE `entry` IN (36272,36296,36565); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -269 WHERE `entry` IN (32916,33400); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -270 WHERE `entry` IN (12899); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -271 WHERE `entry` IN (10664); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -272 WHERE `entry` IN (16472); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -273 WHERE `entry` IN (929,1284,1748,2425,3974,3975,3976,3977,4542,4543,6487,7228,9095,10156,10162,10432,10433,10435,10436,10437,10438,10502,10504,10505,10506,10507,10508,10558,10808,10811,10813,10901,10942,10997,11032,11142,11261,11380,11439,11622,11946,11947,11948,11949,11978,11982,12129,12756,12804,13256,13419,14352,14507,14509,14510,14515,14517,14831,14861,14988,15083,15084,15085,15114,15192,15204,15205,15299,15302,15348,15362,15378,15379,15380,15381,15382,15410,15411,15412,15413,15427,15481,15491,15510,15511,15514,15543,15544,15571,15608,15625,15628,15633,15688,15776,15963,16077,16101,16102,16118,16128,16440,16524,16775,16776,16777,16778,16816,17008,17009,17010,17011,17012,17026,17076,17233,17354,17380,17381,17500,17533,17534,17651,17772,17797,17798,17852,17874,17941,17942,17948,17949,17991,18040,18041,18060,18063,18075,18076,18141,18254,18338,18343,18344,18371,18373,18472,18601,18621,18624,18667,18728,18780,18932,19214,19556,19597,19604,19647,19893,19894,19895,20266,20268,20306,20318,20629,20633,20637,20690,21003,21768,21823,21845,21932,22409,22522,22605,22606,22627,22629,22641,22644,22996,23035,23054,23230,23426,23467,23499,23508,23759,23812,23877,23878,23879,23880,23899,23912,23913,23914,23944,24143,24144,24505,31055,31818,31819,31820,31821,31822,32918,32919,33399,33401,37243,37244,37283,37444); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -274 WHERE `entry` IN (14834,15082,15369,15370,15509,17521,23578,23863,24239); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -275 WHERE `entry` IN (15667,20481,21538); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -276 WHERE `entry` IN (7846,8680,15203); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -277 WHERE `entry` IN (14435); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -278 WHERE `entry` IN (15305); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -279 WHERE `entry` IN (15552); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -280 WHERE `entry` IN (11382,15263,23576,23577); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -281 WHERE `entry` IN (12017); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -282 WHERE `entry` IN (15275); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -283 WHERE `entry` IN (37226); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -284 WHERE `entry` IN (2784,3057,3516,4949,4968,7937,7999,10181,10540,20888,21224,21227,22825,22826,22827,22828,22920); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -285 WHERE `entry` IN (16800,16802,17468,20906,21606,29611,30057,32295,33350,34057,34115); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -286 WHERE `entry` IN (16028,21964,22951,23953,23954,23980,24200,24201,26529,26530,26532,26533,27389,27390,28684,29249,29268,29278,29305,29311,29315,29324,29373,29417,29446,29447,29448,29615,29701,29718,29940,29955,29991,30061,30452,30530,30549,30600,30601,30602,30603,30748,31125,31211,31212,31215,31217,31464,31507,31534,31612,31656,31657,31673,31674,31679,31680,31722,32273,32313,32353,32368,32865,33147,33388,33453,33515,33846,33850,33851,33852,33985,33986,33988,33989,33993,33994,34015,34016,34175,34496,34497,34780,35013,35143,35216,35268,35269,35347,35348,35349,35350,35351,35352,35359,35360,36064,36067,36476,36477,36494,36502,36612,36658,36723,36855,36938,37613,37627,37629,37677,37720,37813,37957,37958,37959,38106,38112,38113,38402,38433,38462,38482,38483,38582,38583,38599,38603); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -287 WHERE `entry` IN (19622,23419,33271,33449,33524,34152,38296,38297); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -288 WHERE `entry` IN (15193); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -289 WHERE `entry` IN (20905,20910,20911,21588,21589,21618,21838); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -290 WHERE `entry` IN (24560,25560); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -291 WHERE `entry` IN (20896,21702); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -292 WHERE `entry` IN (11980,16807,16808,17711,17734,18481,20039,20045,20046,20049,20187,20568,20597,20864,20867,20868,20879,20880,21174,21303,21304,21591,21592,21593,21594,21595,21596,21608,21623,22330,22346,23397,24723,24744,25562,25573); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -293 WHERE `entry` IN (16809,20596); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -294 WHERE `entry` IN (6109); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -295 WHERE `entry` IN (20036,20037,24664,24857); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -296 WHERE `entry` IN (25772); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -297 WHERE `entry` IN (4814); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -298 WHERE `entry` IN (92); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -299 WHERE `entry` IN (329,2359,7031,7032,8324,14331); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -300 WHERE `entry` IN (575,5850,5852,6520,17003); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -301 WHERE `entry` IN (2735,2791); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -302 WHERE `entry` IN (510,691,5461,10756,10757,10955); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -303 WHERE `entry` IN (16193); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -304 WHERE `entry` IN (11321); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -305 WHERE `entry` IN (11665,11672,12076,12099,12100,12101); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -306 WHERE `entry` IN (14401); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -307 WHERE `entry` IN (16336,16338); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -308 WHERE `entry` IN (15141); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -309 WHERE `entry` IN (11669); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -310 WHERE `entry` IN (12457); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -311 WHERE `entry` IN (11658,11659,11661,11671,11673,11988,12057,12098,12118,12259,12264,14081,14465,14466,14751,14752); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -312 WHERE `entry` IN (11502,11667,11668,12056,12265); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -313 WHERE `entry` IN (24722,25552); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -314 WHERE `entry` IN (30258,31463); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -315 WHERE `entry` IN (14464); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -316 WHERE `entry` IN (14461); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -317 WHERE `entry` IN (14454); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -318 WHERE `entry` IN (14457); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -319 WHERE `entry` IN (10264); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -320 WHERE `entry` IN (15748,15751,15754,15758,15807,15810); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -321 WHERE `entry` IN (11664,15747,15753,15757,15812); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -322 WHERE `entry` IN (10516); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -323 WHERE `entry` IN (12465); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -324 WHERE `entry` IN (9499,12468,15247); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -325 WHERE `entry` IN (1842,5709,5710,5719,5720,5721,5722,7267,7271,7272,7275,7604,7795,7796,7797,8127,8443,11878,12018,15743,15744,15750,15806,15813,15814,15815,15816,15817,15818); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -326 WHERE `entry` IN (12201); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -327 WHERE `entry` IN (10184); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -328 WHERE `entry` IN (15286,15288,15290); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -329 WHERE `entry` IN (14887,14888,14889,14890); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -330 WHERE `entry` IN (14662,14663,14664,14666); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -331 WHERE `entry` IN (14503); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -332 WHERE `entry` IN (22855); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -333 WHERE `entry` IN (17770,20169); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -334 WHERE `entry` IN (15689); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -335 WHERE `entry` IN (30084); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -336 WHERE `entry` IN (32187); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -337 WHERE `entry` IN (22056); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -338 WHERE `entry` IN (25588); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -339 WHERE `entry` IN (37698,39299,39300,39301); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -340 WHERE `entry` IN (22952,34815,35262); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -341 WHERE `entry` IN (19551); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -342 WHERE `entry` IN (21362); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -343 WHERE `entry` IN (33203,33376); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -344 WHERE `entry` IN (34147,34148); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -345 WHERE `entry` IN (17767,17808,17842,17888); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -346 WHERE `entry` IN (1853,12397,15589,15690,15727,17535,18341,18473,18708,18732,20267,20653,20657,20706); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -347 WHERE `entry` IN (12435,15517,17225,17968,23574); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -348 WHERE `entry` IN (15206,15207,15208,15220,15467,15740,15741,15742,18168,33052,33116); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -349 WHERE `entry` IN (14020); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -350 WHERE `entry` IN (10340,10538); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -351 WHERE `entry` IN (15276); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -352 WHERE `entry` IN (11583,13020); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -353 WHERE `entry` IN (25741); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -354 WHERE `entry` IN (23682,23709); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -355 WHERE `entry` IN (18832,18834,18835,29306,29310,31368,31465,34796,35438); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -356 WHERE `entry` IN (15550,16152); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -357 WHERE `entry` IN (27386,30178,30756,31448); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -358 WHERE `entry` IN (22009); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -359 WHERE `entry` IN (32877,33155); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -360 WHERE `entry` IN (17281,17455,18051,18055,18069,18398,18399,18400,18401,18402,21369,21875,21920,22055,22119,22120,22140,22856,22997,23421,23469,33699,33700,33715,33722,33723,33756,33772,33773,34069,34133,34134,34139,34141,34164,34165,34185,34197,34226,37227,37488); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -361 WHERE `entry` IN (10404,17306,17308,17536,17537,17848,17879,17881,17975,17976,17977,17978,17980,18096,18432,18433,18434,18436,18805,18831,18945,19218,19219,19220,19221,19710,20243,20531,20535,20737,20738,21212,21213,21214,21217,21525,21526,21533,21536,21537,21551,21558,21559,21581,21582,21697,21712,21958,21965,21966,22887,22898,23089,23191,23197,23375,23420,23965,24850,24882,24891,24892,25502,25708,26630,26631,26632,26668,26683,26684,26685,26686,26687,26693,26723,26731,26763,26794,26796,26798,26861,26893,26918,27447,27483,27654,27655,27656,27975,27977,27978,28132,28171,28546,28586,28587,28729,28730,28731,28859,28860,28921,28922,28923,29120,29266,29281,29304,29307,29308,29309,29312,29313,29314,29316,29573,29932,30114,30176,30397,30398,30449,30451,30510,30522,30529,30532,30540,30770,30772,30774,30775,30788,30790,30803,30807,30809,30810,31134,31311,31349,31350,31360,31362,31365,31367,31370,31381,31384,31386,31441,31456,31469,31473,31506,31508,31509,31510,31511,31512,31520,31533,31535,31536,31537,31538,31558,31559,31560,31561,31592,31610,31611,31615,31616,31617,31672,31734,32845,32846,32867,32871,32872,32873,32874,32875,32876,32878,32882,32883,32885,32886,32904,32906,32907,32908,32913,32914,32915,32927,32930,32933,32934,32955,33070,33089,33118,33121,33148,33149,33150,33151,33152,33153,33154,33156,33157,33158,33159,33161,33162,33163,33186,33190,33191,33228,33288,33293,33329,33344,33360,33385,33391,33392,33393,33432,33433,33567,33651,33670,33692,33693,33716,33717,33718,33719,33720,33724,33885,33888,33890,33909,33910,33911,33943,33954,33955,33959,33966,33967,33983,33984,33990,33995,34004,34014,34035,34071,34097,34106,34108,34109,34166,34171,34215,34221,34222,34564,34566,34660,34797,34799,34800,34813,34825,34826,34928,34942,35028,35029,35030,35031,35032,35033,35034,35036,35037,35038,35039,35040,35041,35042,35043,35044,35045,35046,35047,35048,35049,35050,35051,35052,35119,35144,35263,35264,35265,35266,35267,35270,35271,35272,35278,35279,35280,35311,35439,35440,35441,35442,35443,35447,35448,35449,35451,35490,35511,35512,35513,35514,35515,35516,35517,35518,35519,35520,35521,35522,35523,35524,35525,35527,35528,35529,35530,35531,35532,35533,35534,35535,35536,35537,35538,35539,35540,35541,35542,35543,35544,35615,35616,36135,36497,36498,36597,36626,36627,36678,36701,36789,36838,36839,36853,36899,36939,36948,36950,36954,36957,36960,36961,36968,36969,36970,36971,36978,36982,37025,37098,37116,37117,37126,37182,37184,37215,37217,37230,37504,37505,37506,37531,37533,37534,37540,37833,37955,37970,37972,37973,38064,38103,38110,38123,38128,38129,38139,38156,38157,38174,38219,38220,38256,38257,38258,38261,38262,38265,38266,38267,38390,38399,38400,38401,38403,38404,38405,38406,38407,38408,38418,38431,38434,38435,38436,38444,38454,38490,38494,38549,38550,38585,38586,38637,38638,38639,38640,38675,38676,38677,38678,38679,38680,38681,38682,38683,38684,38685,38686,38687,38688,38689,38690,38691,38692,38693,38694,38699,38700,38701,38702,38769,38770,38771,38772,38775,38776,38777,38784,38785,39166,39167,39168,39302,39303,39304,39746,39747,39751,39805,39814,39815,39823,39863,39864,39899,39920,39922,39944,39945,40142,40143,40144,40145); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -362 WHERE `entry` IN (15687,15691,16151,16457,17096,17257,17880,19044,19516,19781,19782,19783,20060,20062,20063,20064,20745,21215,21216,21364,21698,22167,22841,22871,22917,22947,22948,22949,22950,23418,25038,25165,25166,25315,25840,33113,34003,36538,36633,36897,37562,37697,38138,38602,38604,38758,38759,38760,38761,39190,39287,39288,39289,39305,39306,39307); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -363 WHERE `entry` IN (38369); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -364 WHERE `entry` IN (19514); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -365 WHERE `entry` IN (25740,26338,26339); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -366 WHERE `entry` IN (25865); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -367 WHERE `entry` IN (14862); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -368 WHERE `entry` IN (8317); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -369 WHERE `entry` IN (32926,32938,33352,33353,36980,38320,38321,38322); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -370 WHERE `entry` IN (15261); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -371 WHERE `entry` IN (23394); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -372 WHERE `entry` IN (20041); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -373 WHERE `entry` IN (18503,20309); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -374 WHERE `entry` IN (36619,38233,38459,38460,38711,38712,38970,38971,38972,38973,38974,38975); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -375 WHERE `entry` IN (38028); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -376 WHERE `entry` IN (20870,20912,21601,21626); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -377 WHERE `entry` IN (17839,20744,21104,21140,21148,22170,22171,22172); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -378 WHERE `entry` IN (22128,22129); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -379 WHERE `entry` IN (23597); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -380 WHERE `entry` IN (24374); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -381 WHERE `entry` IN (2675,4277,8937); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -382 WHERE `entry` IN (16127); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -383 WHERE `entry` IN (16124); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -384 WHERE `entry` IN (16149); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -385 WHERE `entry` IN (16156,16157,16158); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -386 WHERE `entry` IN (11657,11677,12096,12097,13078,13079,13086,13088,13137,13138,13139,13143,13144,13145,13147,13152,13153,13154,13176,13179,13180,13181,13216,13218,13236,13257,13296,13297,13298,13299,13300,13318,13319,13320,13356,13357,13437,13438,13439,13441,13442,13447,13577,13597,13598,13616,13617,13797,13798); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -387 WHERE `entry` IN (16244,16360,16427,16983); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -388 WHERE `entry` IN (16150); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -389 WHERE `entry` IN (16022,16981); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -390 WHERE `entry` IN (16027,16164,16449); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -391 WHERE `entry` IN (16167); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -392 WHERE `entry` IN (16020); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -393 WHERE `entry` IN (16024); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -394 WHERE `entry` IN (16375); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -395 WHERE `entry` IN (16290); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -396 WHERE `entry` IN (16447); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -397 WHERE `entry` IN (16982,16984); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -398 WHERE `entry` IN (16036,16056,16057,16236,16297,16698,17055); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -399 WHERE `entry` IN (16037); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -400 WHERE `entry` IN (15318,15319,15320,15323,15324,15325,15336); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -401 WHERE `entry` IN (16034); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -402 WHERE `entry` IN (16428,16429); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -403 WHERE `entry` IN (16021,16165,16368,16448,16452); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -404 WHERE `entry` IN (16194,16215,16216); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -405 WHERE `entry` IN (16017,16018,16029); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -406 WHERE `entry` IN (11352,12159,14750,14762,14763,14764,14765,14766,14767,14768,14769,14770,14771,14772,14773,14774,14775,14776,14777,14943,14944,14945,14946,14947,14948,15327,15335,15338,15340,15341,15343,15355,15385,15386,15388,15389,15390,15391,15392,16062); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -407 WHERE `entry` IN (15555); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -408 WHERE `entry` IN (15339); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -409 WHERE `entry` IN (11666); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -410 WHERE `entry` IN (15928,15929,15930,15931,15932,15936,15952,15953,15954,15956,15989,15990,16011,16060,16061,16063,16064,16065); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -411 WHERE `entry` IN (16441); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -412 WHERE `entry` IN (16126); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -413 WHERE `entry` IN (15974,15975,16505,29274); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -414 WHERE `entry` IN (15980,15981,16506); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -415 WHERE `entry` IN (16573); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -416 WHERE `entry` IN (15976,15978,15979,16142,16453); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -417 WHERE `entry` IN (16243); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -418 WHERE `entry` IN (16387); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -419 WHERE `entry` IN (10440); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -420 WHERE `entry` IN (12119,15620,16168,16446); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -421 WHERE `entry` IN (13116,13117,14302,15047,15224,15922); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -422 WHERE `entry` IN (22250); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -423 WHERE `entry` IN (24175); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -424 WHERE `entry` IN (12900); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -425 WHERE `entry` IN (32357,32358,32471,32495,32501); +UPDATE `creature_template` SET `CreatureImmunitiesId` = -426 WHERE `entry` IN (32386,32398,32400,32438); + +-- ============================================================ +-- Clear no-longer-needed flags_extra bits (AOE / KNOCKBACK / TAUNT) +-- ============================================================ +SET @MASK = 0x400000 | 0x40000000 | 0x100; +UPDATE `creature_template` SET `flags_extra` = `flags_extra` & ~@MASK WHERE (`flags_extra` & @MASK) != 0; From 9d9f9bdc58aa05d07cb5960e75982831c332822c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Mar 2026 11:42:03 +0000 Subject: [PATCH 089/132] chore(DB): import pending files Referenced commit(s): cf6ba26703bd50b2b471e74f0cada1ce2776b9eb --- .../rev_1774435393371251635.sql => db_world/2026_03_25_03.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774435393371251635.sql => db_world/2026_03_25_03.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1774435393371251635.sql b/data/sql/updates/db_world/2026_03_25_03.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774435393371251635.sql rename to data/sql/updates/db_world/2026_03_25_03.sql index cdf509955..4113897c0 100644 --- a/data/sql/updates/pending_db_world/rev_1774435393371251635.sql +++ b/data/sql/updates/db_world/2026_03_25_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_25_02 -> 2026_03_25_03 -- -- ============================================================ -- creature_immunities seed From aac930864a6e2ba346348bd478640fdd28aa7ddd Mon Sep 17 00:00:00 2001 From: sogladev Date: Wed, 25 Mar 2026 12:42:25 +0100 Subject: [PATCH 090/132] fix(Core/Unit): partial immunity for mixed spells (#25219) Co-authored-by: Shauren --- src/server/game/Entities/Unit/Unit.cpp | 21 +++++++++- .../server/game/Spells/SpellImmunityTest.cpp | 40 ++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 2070ea04d..c1657cfb0 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -910,15 +910,34 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit if (!spellInfo) return false; + bool immuneToAllEffects = true; + bool hasCheckedEffect = false; + for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) { if (!(effectMask & (1u << i))) continue; + if (!spellInfo->Effects[i].IsEffect()) + continue; + + hasCheckedEffect = true; + if (IsImmunedToSpellEffect(spellInfo, i, caster)) - return true; + { + if (spellInfo->HasAttribute(SPELL_ATTR4_NO_PARTIAL_IMMUNITY)) + return true; + + continue; + } + + immuneToAllEffects = false; + break; } + if (hasCheckedEffect && immuneToAllEffects) + return true; + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { if (IsImmunedToSchool(spellInfo)) diff --git a/src/test/server/game/Spells/SpellImmunityTest.cpp b/src/test/server/game/Spells/SpellImmunityTest.cpp index 342e2818f..a88e8c048 100644 --- a/src/test/server/game/Spells/SpellImmunityTest.cpp +++ b/src/test/server/game/Spells/SpellImmunityTest.cpp @@ -105,6 +105,31 @@ namespace return false; } + bool IsEffectBlockedByStunImmunity(EffectDesc const& effect, bool immuneToStun) + { + if (!immuneToStun) + return false; + + return effect.effect == EFFECT_APPLY_AURA && effect.aura == AURA_MOD_STUN; + } + + bool IsFullyImmunedByStunImmunity(SpellDesc const& spell, bool immuneToStun) + { + bool hasAnyEffect = false; + + for (EffectDesc const& effect : spell.effects) + { + if (effect.effect == EFFECT_NONE) + continue; + + hasAnyEffect = true; + if (!IsEffectBlockedByStunImmunity(effect, immuneToStun)) + return false; + } + + return hasAnyEffect; + } + // The last parameter defaults to false to avoid updating existing tests // that don't care about mechanic immunities. Bladestorm grants a // specific mechanic immunity (including stun) that should block @@ -114,12 +139,12 @@ namespace { // Mirrors current core ordering: // 1) full spell immunity check - // 2) mechanic immunity check (e.g. bladestorm vs stun) + // 2) full immunity from mechanic immunity (all effects blocked) // 3) damage immunity only for damage-only spells if (isImmunedToSpell) return SPELL_MISS_IMMUNE; - if (immuneToStun && IsStunSpell(spell)) + if (IsFullyImmunedByStunImmunity(spell, immuneToStun)) return SPELL_MISS_IMMUNE; if (HasOnlyDamageEffects(spell) && isImmunedToDamage) @@ -328,3 +353,14 @@ TEST(SpellImmunityTest, Bladestorm_ImmuneToStun) // with a bladestorm‑style stun immunity it is blocked EXPECT_EQ(ComputeSpellHitResult(false, false, stunSpell, true), SPELL_MISS_IMMUNE); } + +TEST(SpellImmunityTest, StunImmunity_DoesNotFullyBlockMixedSpell) +{ + SpellDesc mixedSpell; + mixedSpell.effects[0] = { EFFECT_SCHOOL_DAMAGE, AURA_NONE }; + mixedSpell.effects[1] = { EFFECT_APPLY_AURA, AURA_MOD_STUN }; + + // This models partial immunity: stun effect is blocked, damage still hits. + EXPECT_TRUE(IsStunSpell(mixedSpell)); + EXPECT_EQ(ComputeSpellHitResult(false, false, mixedSpell, true), SPELL_MISS_NONE); +} From 2500804da9b37e0be52d335d6be5bd011c1a1863 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 07:19:54 -0500 Subject: [PATCH 091/132] fix(Scripts/Oculus): Fix Mage-Lord Urom not teleporting between platforms (#25220) Co-authored-by: blinkysc --- src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp b/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp index 7ed99cfb4..57eeb4b17 100644 --- a/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp +++ b/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp @@ -213,6 +213,7 @@ public: me->LoadCreaturesAddon(true); me->SetLootRecipient(nullptr); me->ResetPlayerDamageReq(); + EngagementOver(); } void JustDied(Unit* /*killer*/) override @@ -369,6 +370,10 @@ public: void EnterEvadeMode(EvadeReason why) override { + // Block evade during outer platform transitions + if (lock) + return; + me->SetCanFly(false); me->SetDisableGravity(false); me->SetControlled(false, UNIT_STATE_ROOT); From e377876c2fa4cea0f0ba0b6c7a1d72cb47105fa9 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 07:31:03 -0500 Subject: [PATCH 092/132] fix(Core/Movement): Remove unconditional SetWalk(false) from WaypointMovementGenerator::DoFinalize (#25221) Co-authored-by: blinkysc --- .../Movement/MovementGenerators/WaypointMovementGenerator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index f1db91ac9..05c83a6ff 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -95,7 +95,6 @@ void WaypointMovementGenerator::DoInitialize(Creature* creature) void WaypointMovementGenerator::DoFinalize(Creature* creature) { creature->ClearUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE); - creature->SetWalk(false); } void WaypointMovementGenerator::DoReset(Creature* creature) From 14813462eb07876cb9921aaa2520d09d94a8a754 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:17:52 -0500 Subject: [PATCH 093/132] fix(Core/Combat): Prevent NullCreatureAI creatures from entering combat (#25222) Co-authored-by: blinkysc --- src/server/game/AI/CoreAI/PassiveAI.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server/game/AI/CoreAI/PassiveAI.cpp b/src/server/game/AI/CoreAI/PassiveAI.cpp index b0d605926..1ff7b66a3 100644 --- a/src/server/game/AI/CoreAI/PassiveAI.cpp +++ b/src/server/game/AI/CoreAI/PassiveAI.cpp @@ -20,7 +20,11 @@ PassiveAI::PassiveAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); } PossessedAI::PossessedAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); } -NullCreatureAI::NullCreatureAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); } +NullCreatureAI::NullCreatureAI(Creature* c) : CreatureAI(c) +{ + me->SetReactState(REACT_PASSIVE); + me->SetIsCombatDisallowed(true); +} int32 NullCreatureAI::Permissible(Creature const* creature) { From f3d2a2808335bb3b6177157e9dead015086f2603 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:34:10 -0500 Subject: [PATCH 094/132] fix(Core/Movement): Prevent vehicles from following their own passenger on evade (#25225) Co-authored-by: blinkysc --- src/server/game/AI/CreatureAI.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index f8c69e141..665b915cb 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -246,8 +246,11 @@ void CreatureAI::EnterEvadeMode(EvadeReason why) // Owned creatures (pets/guardians) follow their owner — clear evade state // so they can re-enter combat immediately via CanBeginCombat me->ClearUnitState(UNIT_STATE_EVADE); - me->GetMotionMaster()->Clear(false); - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE); + if (!me->IsVehicle()) // vehicles should not follow their owner (passenger) + { + me->GetMotionMaster()->Clear(false); + me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE); + } } else { From 35051f935cb1ac23fadc0b78604d5182adcfb92c Mon Sep 17 00:00:00 2001 From: sogladev Date: Wed, 25 Mar 2026 18:42:12 +0100 Subject: [PATCH 095/132] fix(Scripts/Naxxramas): reset threat when Thaddius become active (#25226) --- src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp index d71e12468..c1dd286dc 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp @@ -446,6 +446,7 @@ public: else { me->SetHealth(me->GetMaxHealth()); + me->GetThreatMgr().ResetAllThreat(); } } } From 20073f734247812b0541df67794f86ec8fb96ab5 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 13:05:57 -0500 Subject: [PATCH 096/132] fix(Scripts/Naxx): Fix Heigan evading and regenerating HP at end of Phase 2 (#25223) Co-authored-by: blinkysc --- src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp index e4d642000..99c3f731e 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp @@ -159,13 +159,13 @@ struct boss_heigan : public BossAI void UpdateAI(uint32 diff) override { if (!UpdateVictim()) - { return; - } _eruptionScheduler.Update(diff); - BossAI::UpdateAI(diff); + scheduler.Update(diff); + + DoMeleeAttackIfReady(); } void CheckSafetyDance() From 76b1978be1a66efcacc9f971c69b049070019c94 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 13:57:48 -0500 Subject: [PATCH 097/132] fix(DB/Movement): Enable smooth waypoint transitions for flying creatures (#25227) Co-authored-by: blinkysc --- .../rev_1774453782753474545.sql | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774453782753474545.sql diff --git a/data/sql/updates/pending_db_world/rev_1774453782753474545.sql b/data/sql/updates/pending_db_world/rev_1774453782753474545.sql new file mode 100644 index 000000000..1cf86ba92 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774453782753474545.sql @@ -0,0 +1,335 @@ +-- Enable smooth waypoint transitions for flying creatures +-- Paths with actions or delays are excluded + +-- Buzzard (2830) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (71370, 72510, 72520, 72530, 76140); + +-- Antilus the Soarer (5347) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (518400); + +-- Soaring Razorbeak (8276) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (928800, 928810); + +-- Bloodseeker Bat (11368) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (491230, 491460, 491580, 521360, 522670, 915550); + +-- Bat Rider Guard (15242) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (548400, 548410, 548420, 548430); + +-- OLDWorld Trigger (DO NOT DELETE) (15384) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (876470); + +-- Eye of Thrallmar (16598) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (575850, 575860, 575870, 575880); + +-- Eye of Honor Hold (16887) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (584380); + +-- Skeletal Gryphon (17660) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (125540, 125550); + +-- Large AOI Underbat (18409) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1383120, 1383140, 1383160, 1383210, 1383270, 1383410, 1383480, 1383520); + +-- Night Elf Wisp (18502) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1396910, 1396920, 1396930, 1396940, 1396950, 1396960, 1396970, 1396990); + +-- Torgos (18707) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2469160); + +-- Flying Raging Soul (18726) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1324010, 1324030, 1324100, 1324150, 1324320); + +-- Wildhammer Gryphon Rider (19382) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (11580, 1259220, 1259270, 1259300, 1259310, 1259320, 1260520); + +-- Al'ar (19514) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1580410); + +-- Nuramoc (20932) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (249410, 249420, 249430); + +-- Bladewing Bloodletter (21033) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (738490, 738500, 738520, 738530, 738580, 738600); + +-- Kor'kron Wyvern Rider (21153) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (742190); + +-- Mature Netherwing Drake (21648) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (755410, 755420, 755430, 755440, 755450, 755460, 755470, 755480, 755490, 755500, 755510, 755520, 755530, 755540, 755550, 755560, 755570, 755580, 861030); + +-- Vhel'kur (21801) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (218010); + +-- Vilewing Chimaera (21879) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (844880, 845940, 845970, 846130, 846320); + +-- Avian Flyer (21931) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1387630, 1387660, 1387690); + +-- [PH] Cave Bat JZB (22040) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (771910, 771920, 771930, 771940, 771950, 771960); + +-- Dragonmaw Skybreaker (22274) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (782910, 782920, 782930, 782940, 782950, 782960, 782970, 782980, 782990, 783000); + +-- Corvax (22842) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (697120); + +-- Rook (22843) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (697110); + +-- Dragonmaw Sky Stalker (23030) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1481220, 1481230, 1481270, 1481280, 1481290, 1481380, 1481390); + +-- Invisible Stalker (Floating) (23033) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1312500, 1312520, 1338940, 1338990, 1339000); + +-- Monstrous Kaliri (23051) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (790170, 832370, 832380, 860990, 861150, 861160, 861170, 861310, 861330, 1325530, 1325540, 1325550, 1325560, 1325570, 1325580, 1325600, 1325620, 1325640, 1325650, 1325660, 1325670); + +-- Dragonmaw Transporter (23188) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1328140, 1328180); + +-- Lady Sinestra (23283) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (287960); + +-- Dragonflayer Raider (23557) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (971330, 971340, 971350, 971390, 971400); + +-- Plagued Proto-Dragon (23680) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1076260); + +-- Proto-Drake (23689) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1081500, 1081510, 1081520, 1081550, 1081560, 1081580); + +-- Spotted Hippogryph (23772) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (938020, 938030, 938040, 938050, 938060, 938070, 938080, 938090, 938100, 938110, 938120, 938130, 938140, 938150, 938160, 938170, 938180, 938190, 938200, 938210, 938220, 938230, 938240, 938250, 938260, 938270, 938280, 938290, 938300, 938310, 938320, 938330, 938340, 938350, 938360, 938370, 938380, 938390, 938400, 938410, 938420, 938430, 938440); + +-- Val'kyr Watcher (23935) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (482340, 482350, 482360, 482370, 482380, 482390, 482400, 482410, 482420, 482430, 482440); + +-- Windy Cloud (24222) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1410000, 1410010, 1410020, 1410030, 1410040, 1410060, 1410070, 1410080); + +-- Steelfeather (24514) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1142380); + +-- Talonshrike (24518) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1147130); + +-- Buoy (24707) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1078110); + +-- Fjord Hawk (24747) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (938510, 938530, 938560, 938590, 938600, 938640, 938660, 938680, 938730, 938750, 938790, 938810, 938880, 938900); + +-- Nexus Watcher (24770) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1241160, 1241170, 1241180, 1241190, 1241200, 1241210, 1241220, 1241230, 1241240, 1241250, 1241260); + +-- Fjord Hawk Matriarch (24787) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1158350, 1158370, 1158430, 1158440, 1158450); + +-- Proto-Drake Rider (24849) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1259340, 1259360, 1259370, 1259400); + +-- Dawnblade Hawkrider (25063) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (839990, 840020, 840030, 840040, 840050, 840060); + +-- Shattered Sun Bombardier (25144) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (853700, 853720, 853730, 853740); + +-- Bloodspore Moth (25464) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1327080); + +-- Spirit of the North (25711) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1210760, 1225710); + +-- Fizzcrank Bomber (25765) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1113600); + +-- D.E.H.T.A. Enforcer (25819) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1329840); + +-- Shaman Beam Bunny 000 (25964) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (125390); + +-- Shaman Beam Bunny 001 (25965) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (125400); + +-- Shaman Beam Bunny 002 (25966) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (125410); + +-- Nexus Drake Hatchling (26127) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1210410, 1210420, 1210430, 1210440, 1210450, 1210460, 1210470); + +-- Carrion Condor (26174) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1018170, 1018400, 1018550, 1018860, 1018870, 1019250, 1019290); + +-- Nexus Guardian (26276) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1113040, 1113590); + +-- Emberwyrm (26286) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1123860, 1123870); + +-- Dreadtalon (26838) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1161740); + +-- Reanimated Frost Wyrm (26841) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (404970, 404980, 404990, 405000, 405010, 405020, 405030, 405040, 405050); + +-- Sarathstra (26858) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1124010); + +-- Wyrmrest Temple Drake (26925) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1001700, 1002060, 1002070, 1002080, 1002090, 1002100, 1002130, 1002150, 1002160, 1002260, 1309990); + +-- Wyrmrest Guardian (26933) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1008230, 1008240, 1008250, 1008260, 1310350, 1310360, 1310370, 1310380); + +-- Forgotten Captain (27220) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1033880, 1033910, 1033940, 1033960, 1033980, 1034000, 1034030, 1034110); + +-- Risen Gryphon (27241) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1311770, 1311780); + +-- Emerald Skytalon (27244) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1057430, 1057510, 1057710, 1057730, 1057760, 1057900, 1057920, 1057930); + +-- Reconstructed Frost Wyrm (27285) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1099150, 1099160, 1099270); + +-- Cosmetic Drakkari Bat [PH] (27490) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1275080, 1275090, 1275100, 1275110, 1275120, 1275130); + +-- Azure Dragon (27608) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (991050, 991060, 991070, 991080, 991090, 991100, 991110, 991120, 991130, 991140, 991150, 991160, 991170, 991180); + +-- Azure Drake (27682) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (990780, 990790, 990830, 990840, 990850, 990860, 990870, 990880, 990890, 990920, 990930, 990940, 990950, 990960, 990970, 990980, 990990, 991030, 991040); + +-- Heb'Drakkar Striker (28465) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1005360, 1005400); + +-- Scarlet Gryphon (28614) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1293080, 1293090); + +-- Olrun the Battlecaller (29047) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1287390); + +-- Gundrak Bat Rider (29332) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1097750, 1097780, 1097820, 1097840, 1097860, 1097870, 1097930, 1097950, 1098560); + +-- Onslaught Gryphon Rider (29333) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1231100, 1231120, 1231290, 1231300, 1231320, 1231330, 1231420, 1231430, 1231460, 1231470, 1231560, 1231570); + +-- Vargul Plaguetalon (29453) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (976340); + +-- Frigid Proto-Drake (29460) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (979910, 979920, 979950, 980250, 980260, 980270, 980280); + +-- Nascent Val'kyr (29570) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1077080, 1077240, 1077780, 1077820, 1077870, 1077890, 1077910, 1077930, 1077950, 1077960, 1077970, 1077980, 1077990); + +-- Hyldsmeet Proto-Drake (29625) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1134270, 1134280, 1134290, 1134300, 1134460, 1134470, 1134480, 1134490, 1134500, 1134510, 1134520, 1134570, 1134580, 1134590, 1134610, 1134620, 1134630, 1134640, 1134650, 1134660, 1134670); + +-- Stormpeak Wyrm (29753) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1010000, 1011070, 1011080, 1011090, 1011100, 1011110, 1011120, 1011130, 1011140, 1011150, 1011170, 1011180, 1011190, 1011200, 1011210, 1011220); + +-- Sirana Iceshriek (29794) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2025870); + +-- Stormcrest Eagle (30013) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1017130, 1017140, 1017150, 1018010); + +-- Wild Wyrm (30275) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2029720, 2029730, 2029740, 2029750, 2029760, 2029770); + +-- Jotunheim Proto-Drake (30330) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1227590, 1227600, 1227610, 1227620, 1227630, 1227640, 1227650, 1227660, 1227670, 1227680, 1227690, 1227700, 1227710, 1227720, 1227730, 1227740, 1227750, 1227760, 1241110, 1241120); + +-- Wrathstrike Gargoyle (30482) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1348070, 1348080, 1348090, 1348100, 1348110, 1348120, 1348130, 1348140); + +-- Scourgebeak Fleshripper (30988) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1210070, 1210110, 1210140, 1210170, 1210190, 1210230, 1210250); + +-- Wrathstrike Gargoyle (31040) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1217480, 1217490, 1217500, 1217510, 1217520, 1217530, 1217540, 1217550); + +-- Frostbrood Skytalon (31137) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1234960, 1234970, 1234980, 1234990, 1235020, 1235030, 1235040, 1235070, 1235090); + +-- Kor'kron Battle Wyvern (31269) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (992270, 992280, 992300); + +-- Crystal Wyrm (31393) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1111560, 1111620); + +-- Frostbrood Spawn (31702) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1244190, 1244200, 1244210, 1244220, 1244230, 1244240, 1244250); + +-- Stabled Hunter Pet (31768) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2087870); + +-- Stabled Hunter Pet (31769) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2087880); + +-- Tempus Wyrm (32180) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1129570); + +-- Infinite Eradicator (32185) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1134360, 1134380); + +-- Skybreaker Recon Fighter (32189) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1240870, 1240880, 1240890, 1240900); + +-- Orgrim's Hammer Scout (32201) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1241600, 1241870, 1241880, 1241890, 1241900, 1241910, 1241920, 1241930, 1241940, 1241950, 1241960, 1242070, 1242080, 1242090, 1242120, 1242130, 1242140, 1242150, 1242160, 1242180, 1242190, 1242200, 1242210, 1242220); + +-- Northrend Daily Dungeon Image Bunny (32265) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (973340); + +-- Fumblub Gearwind (32358) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1519380); + +-- Ebon Blade Gargoyle (32472) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1244280, 1244290); + +-- Time-Lost Proto Drake (32491) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (392030, 392040, 392050, 392060); + +-- Frostbrood Matriarch (32492) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1181850, 1181860, 1181880, 1181890, 1181900); + +-- Vyragosa (32630) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (392030, 392040, 392050, 392060); + +-- Frostbrood Sentry (32767) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1251080, 1251090, 1251100, 1251550, 1251620); + +-- Mechanolift 304-A (33214) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (13681700, 13681801, 13681802, 13681803, 13681804, 13681805, 13681806, 13681807, 13681808, 13681809, 13681810, 13681811, 13681812, 13681813, 13681814, 13681815, 13681816, 13681817, 13681818, 13682000, 13682101, 13682102, 13682103, 13682104, 13682105, 13682106, 13682107, 13682108, 13682109, 13682110, 13682300, 13682700, 13683000, 13683100, 13685300, 13686200, 13686800, 13687000, 13688500); + +-- Guardian of Life (33528) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1375280, 1375290, 1375300, 1375310); + +-- Chillmaw (33687) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2008510); + +-- Tournament Hippogryph (33778) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (1231520); + +-- Stonespine Gargoyle (36896) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2018220, 2018240, 2018340, 2018380, 2018460); + +-- Val'kyr Herald (37098) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2012450, 2013220); + +-- Spire Frostwyrm (Ambient) (37528) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (2008870, 2009600, 2009750); + +-- Argent Hippogryph (37968) +UPDATE `waypoint_data` SET `smoothTransition` = 1 WHERE `id` IN (887940, 887960, 887980, 887990); From 1a7a9a405ae95c01e7852120ff4ed49eb08070bb Mon Sep 17 00:00:00 2001 From: sogladev Date: Wed, 25 Mar 2026 19:58:16 +0100 Subject: [PATCH 098/132] fix(DB/Creature): add Power Spark visual to Power Spark (1) (#25231) --- data/sql/updates/pending_db_world/rev_1774455411429702618.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774455411429702618.sql diff --git a/data/sql/updates/pending_db_world/rev_1774455411429702618.sql b/data/sql/updates/pending_db_world/rev_1774455411429702618.sql new file mode 100644 index 000000000..913eb3988 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774455411429702618.sql @@ -0,0 +1,2 @@ +-- Also add spark visual aura from Power Spark to Power Spark (1) +UPDATE `creature_template_addon` SET `auras` = '55845' WHERE (`entry` = 32187); From 9c7cad84c6ff161783bf85f917e57961f877ae43 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Mar 2026 18:58:55 +0000 Subject: [PATCH 099/132] chore(DB): import pending files Referenced commit(s): 76b1978be1a66efcacc9f971c69b049070019c94 --- .../rev_1774453782753474545.sql => db_world/2026_03_25_04.sql} | 1 + .../rev_1774455411429702618.sql => db_world/2026_03_25_05.sql} | 1 + 2 files changed, 2 insertions(+) rename data/sql/updates/{pending_db_world/rev_1774453782753474545.sql => db_world/2026_03_25_04.sql} (99%) rename data/sql/updates/{pending_db_world/rev_1774455411429702618.sql => db_world/2026_03_25_05.sql} (76%) diff --git a/data/sql/updates/pending_db_world/rev_1774453782753474545.sql b/data/sql/updates/db_world/2026_03_25_04.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774453782753474545.sql rename to data/sql/updates/db_world/2026_03_25_04.sql index 1cf86ba92..c5d345e67 100644 --- a/data/sql/updates/pending_db_world/rev_1774453782753474545.sql +++ b/data/sql/updates/db_world/2026_03_25_04.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_25_03 -> 2026_03_25_04 -- Enable smooth waypoint transitions for flying creatures -- Paths with actions or delays are excluded diff --git a/data/sql/updates/pending_db_world/rev_1774455411429702618.sql b/data/sql/updates/db_world/2026_03_25_05.sql similarity index 76% rename from data/sql/updates/pending_db_world/rev_1774455411429702618.sql rename to data/sql/updates/db_world/2026_03_25_05.sql index 913eb3988..da09a219e 100644 --- a/data/sql/updates/pending_db_world/rev_1774455411429702618.sql +++ b/data/sql/updates/db_world/2026_03_25_05.sql @@ -1,2 +1,3 @@ +-- DB update 2026_03_25_04 -> 2026_03_25_05 -- Also add spark visual aura from Power Spark to Power Spark (1) UPDATE `creature_template_addon` SET `auras` = '55845' WHERE (`entry` = 32187); From 2f3a90e844cbffeb23707af091446cd46224ecf2 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:13:35 -0500 Subject: [PATCH 100/132] fix(Scripts/Pet): Port TrinityCore snake trap AI to use CombatManager (#25232) Co-authored-by: blinkysc Co-authored-by: Treeston --- src/server/scripts/Pet/pet_hunter.cpp | 146 +++++++------ .../server/game/Combat/SnakeTrapEvadeTest.cpp | 197 ++++++++++++++++++ 2 files changed, 269 insertions(+), 74 deletions(-) create mode 100644 src/test/server/game/Combat/SnakeTrapEvadeTest.cpp diff --git a/src/server/scripts/Pet/pet_hunter.cpp b/src/server/scripts/Pet/pet_hunter.cpp index b325c2bc3..d9dff32f5 100644 --- a/src/server/scripts/Pet/pet_hunter.cpp +++ b/src/server/scripts/Pet/pet_hunter.cpp @@ -20,12 +20,15 @@ * Scriptnames of files in this file should be prefixed with "npc_pet_hun_". */ +#include "CombatManager.h" +#include "Containers.h" #include "CreatureScript.h" #include "PetDefines.h" #include "ScriptedCreature.h" #include "SpellAuraEffects.h" #include "SpellScript.h" #include "SpellScriptLoader.h" +#include "TemporarySummon.h" enum HunterSpells { @@ -50,99 +53,94 @@ enum PetSpellsMisc struct npc_pet_hunter_snake_trap : public ScriptedAI { - npc_pet_hunter_snake_trap(Creature* creature) : ScriptedAI(creature) { _init = false; } + npc_pet_hunter_snake_trap(Creature* creature) : ScriptedAI(creature), _isViper(false), _spellTimer(0) { } - void Reset() override + void JustEngagedWith(Unit* /*who*/) override { } + + void InitializeAI() override { - _spellTimer = urand(1500, 3000); + _isViper = me->GetEntry() == NPC_VIPER; - // Start attacking attacker of owner on first ai update after spawn - move in line of sight may choose better target - if (!me->GetVictim()) - if (Unit* tgt = me->SelectNearestTarget(10.0f)) - { - me->AddThreat(tgt, 100000.0f); - AttackStart(tgt); - } + me->SetMaxHealth(uint32(107 * (me->GetLevel() - 40) * 0.025f)); + // Add delta to make them not all hit the same time + me->SetAttackTime(BASE_ATTACK, me->GetAttackTime(BASE_ATTACK) + urandms(0, 6)); + + if (!_isViper && !me->HasAura(SPELL_HUNTER_DEADLY_POISON_PASSIVE)) + DoCast(me, SPELL_HUNTER_DEADLY_POISON_PASSIVE, true); + + // Glyph of Snake Trap — apply AoE damage reduction scaling + if (Unit* owner = me->GetOwner()) + if (owner->GetAuraEffectDummy(SPELL_HUNTER_GLYPH_OF_SNAKE_TRAP)) + me->CastSpell(me, SPELL_HUNTER_PET_SCALING, true); } - void EnterEvadeMode(EvadeReason /*why*/) override - { - // _EnterEvadeMode(); - me->AddUnitState(UNIT_STATE_EVADE); - me->GetThreatMgr().ClearAllThreat(); - me->CombatStop(true); - me->LoadCreaturesAddon(true); - me->SetLootRecipient(nullptr); - me->ResetPlayerDamageReq(); - me->ClearLastLeashExtensionTimePtr(); - me->GetMotionMaster()->MoveTargetedHome(); - - Reset(); - } - - //Redefined for random target selection: - void MoveInLineOfSight(Unit* who) override - { - if (!me->GetVictim() && who->isTargetableForAttack() && (me->IsHostileTo(who)) && who->isInAccessiblePlaceFor(me)) - { - if (me->GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE) - return; - - if (me->IsWithinDistInMap(who, 10.0f)) - { - me->AddThreat(who, 100000.0f); - AttackStart(who); - } - } - } + // Redefined for random target selection: + void MoveInLineOfSight(Unit* /*who*/) override { } void UpdateAI(uint32 diff) override { + if (me->GetVictim() && me->GetVictim()->HasBreakableByDamageCrowdControlAura(me)) + { + me->GetThreatMgr().ClearFixate(); + me->InterruptNonMeleeSpells(false); + me->AttackStop(); + return; + } + + if (me->IsSummon() && !me->GetThreatMgr().GetFixateTarget()) + { + Unit* summoner = me->ToTempSummon()->GetSummonerUnit(); + if (summoner) + { + std::vector targets; + + auto addTargetIfValid = [this, &targets, summoner](CombatReference* ref) mutable + { + Unit* enemy = ref->GetOther(summoner); + if (!enemy->HasBreakableByDamageCrowdControlAura(me) && me->CanCreatureAttack(enemy) && me->IsWithinDistInMap(enemy, me->GetAttackDistance(enemy))) + targets.push_back(enemy); + }; + + for (auto const& [guid, ref] : summoner->GetCombatManager().GetPvPCombatRefs()) + addTargetIfValid(ref); + + if (targets.empty()) + for (auto const& [guid, ref] : summoner->GetCombatManager().GetPvECombatRefs()) + addTargetIfValid(ref); + + for (Unit* target : targets) + me->EngageWithTarget(target); + + if (!targets.empty()) + { + Unit* target = Acore::Containers::SelectRandomContainerElement(targets); + me->GetThreatMgr().FixateTarget(target); + } + } + } + if (!UpdateVictim()) return; - if (me->GetVictim()->HasBreakableByDamageCrowdControlAura(me)) + // Viper + if (_isViper) { - me->InterruptNonMeleeSpells(false); - return; - } + if (_spellTimer <= diff) + { + if (!urand(0, 2)) // 33% chance to cast + DoCastVictim(RAND(SPELL_HUNTER_MIND_NUMBING_POISON, SPELL_HUNTER_CRIPPLING_POISON)); - if (!_init) - { - _init = true; - - uint32 health = uint32(107 * (me->GetLevel() - 40) * 0.025f); - me->SetCreateHealth(health); - me->SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, (float)health); - me->SetMaxHealth(health); - - //Add delta to make them not all hit the same time - uint32 delta = urand(0, 700); - me->SetAttackTime(BASE_ATTACK, me->GetAttackTime(BASE_ATTACK) + delta); - - if (me->GetEntry() == NPC_VENOMOUS_SNAKE) - DoCastSelf(SPELL_HUNTER_DEADLY_POISON_PASSIVE, true); - - // Glyph of Snake Trap - if (Unit* owner = me->GetOwner()) - if (owner->GetAuraEffectDummy(SPELL_HUNTER_GLYPH_OF_SNAKE_TRAP)) - me->CastSpell(me, SPELL_HUNTER_PET_SCALING, true); - } - - _spellTimer += diff; - if (_spellTimer >= 3000) - { - if (urand(0, 2) == 0) // 33% chance to cast - DoCastVictim(RAND(SPELL_HUNTER_MIND_NUMBING_POISON, SPELL_HUNTER_CRIPPLING_POISON)); - - _spellTimer = 0; + _spellTimer = 3000; + } + else + _spellTimer -= diff; } DoMeleeAttackIfReady(); } private: - bool _init; + bool _isViper; uint32 _spellTimer; }; diff --git a/src/test/server/game/Combat/SnakeTrapEvadeTest.cpp b/src/test/server/game/Combat/SnakeTrapEvadeTest.cpp new file mode 100644 index 000000000..9bfc3ab98 --- /dev/null +++ b/src/test/server/game/Combat/SnakeTrapEvadeTest.cpp @@ -0,0 +1,197 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "CombatManager.h" +#include "ThreatManager.h" +#include "CreatureAI.h" +#include "DBCStores.h" +#include "TestCreature.h" +#include "TestMap.h" +#include "WorldMock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace testing; + +namespace +{ + +// ============================================================================ +// Regression test for snake trap evade recursion crash. +// +// The old npc_pet_hunter_snake_trap::EnterEvadeMode called CombatStop(true) +// which triggers ClearInCombat -> EndAllCombat -> CombatReference::EndCombat +// -> JustExitedCombat -> EnterEvadeMode, causing deep recursion and a +// freeze-detector crash during Battleground::EndBattleground. +// +// The fix removes the custom EnterEvadeMode override entirely, using the base +// CreatureAI::EnterEvadeMode which properly guards against recursion via +// UNIT_STATE_EVADE before calling CombatStop. +// +// This test verifies that ending combat on a creature with multiple PvE refs +// does not cause unbounded recursion or leave stale combat state. +// ============================================================================ +class SnakeTrapEvadeTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _previousWorld = std::move(sWorld); + _worldMock = new NiceMock(); + + ON_CALL(*_worldMock, getIntConfig(_)).WillByDefault(Return(0)); + ON_CALL(*_worldMock, getFloatConfig(_)).WillByDefault(Return(1.0f)); + ON_CALL(*_worldMock, getBoolConfig(_)).WillByDefault(Return(false)); + static std::string emptyString; + ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString)); + + sWorld.reset(_worldMock); + + // Create two mutually hostile factions + auto* factionA = new FactionTemplateEntry{}; + factionA->ID = 90001; + factionA->faction = 90001; + factionA->factionFlags = 0; + factionA->ourMask = 1; + factionA->friendlyMask = 0; + factionA->hostileMask = 2; + for (auto& e : factionA->enemyFaction) e = 0; + for (auto& f : factionA->friendFaction) f = 0; + sFactionTemplateStore.SetEntry(90001, factionA); + + auto* factionB = new FactionTemplateEntry{}; + factionB->ID = 90002; + factionB->faction = 90002; + factionB->factionFlags = 0; + factionB->ourMask = 2; + factionB->friendlyMask = 0; + factionB->hostileMask = 1; + for (auto& e : factionB->enemyFaction) e = 0; + for (auto& f : factionB->friendFaction) f = 0; + sFactionTemplateStore.SetEntry(90002, factionB); + + TestMap::EnsureDBC(); + _map = new TestMap(); + + // Simulate a "snake trap snake" — a creature with multiple combat refs + _snake = new TestCreature(); + _snake->SetupForCombatTest(_map, 1, 19833); // NPC_VENOMOUS_SNAKE entry + _snake->SetFaction(90001); + + // Simulate two enemy targets (e.g., players in a BG) + _targetA = new TestCreature(); + _targetA->SetupForCombatTest(_map, 2, 50001); + _targetA->SetFaction(90002); + + _targetB = new TestCreature(); + _targetB->SetupForCombatTest(_map, 3, 50002); + _targetB->SetFaction(90002); + } + + void TearDown() override + { + _snake->CleanupCombatState(); + _targetA->CleanupCombatState(); + _targetB->CleanupCombatState(); + delete _snake; + delete _targetA; + delete _targetB; + delete _map; + sWorld = std::move(_previousWorld); + } + + std::unique_ptr _previousWorld; + NiceMock* _worldMock = nullptr; + TestMap* _map = nullptr; + TestCreature* _snake = nullptr; + TestCreature* _targetA = nullptr; + TestCreature* _targetB = nullptr; +}; + +// Verify that ending all combat on a creature with multiple refs completes +// without hanging or leaving stale state (regression: recursive EnterEvadeMode) +// cppcheck-suppress syntaxError +TEST_F(SnakeTrapEvadeTest, EndAllCombat_WithMultipleRefs_DoesNotRecurse) +{ + // Put the snake in combat with both targets + _snake->TestGetCombatMgr().SetInCombatWith(_targetA); + _snake->TestGetCombatMgr().SetInCombatWith(_targetB); + + ASSERT_TRUE(_snake->TestGetCombatMgr().HasCombat()); + ASSERT_TRUE(_snake->TestGetCombatMgr().IsInCombatWith(_targetA)); + ASSERT_TRUE(_snake->TestGetCombatMgr().IsInCombatWith(_targetB)); + + // This is the call path that caused the crash: + // EndAllCombat -> EndCombat -> JustExitedCombat -> EnterEvadeMode -> CombatStop -> EndAllCombat + // With the old custom EnterEvadeMode, this would recurse unboundedly. + _snake->TestGetCombatMgr().EndAllPvECombat(); + + // All combat state should be cleanly resolved + EXPECT_FALSE(_snake->TestGetCombatMgr().HasCombat()); + EXPECT_FALSE(_targetA->TestGetCombatMgr().HasCombat()); + EXPECT_FALSE(_targetB->TestGetCombatMgr().HasCombat()); +} + +// Verify that CombatStop on a target also clears the snake's refs cleanly +TEST_F(SnakeTrapEvadeTest, TargetCombatStop_ClearsSnakeRefs) +{ + _snake->TestGetCombatMgr().SetInCombatWith(_targetA); + _snake->TestGetCombatMgr().SetInCombatWith(_targetB); + + ASSERT_TRUE(_snake->TestGetCombatMgr().HasCombat()); + + // Simulate what Battleground::EndBattleground does: CombatStop on the target + // This triggers ClearInCombat -> EndAllCombat on _targetA, which calls + // EndCombat on the ref(targetA, snake), which triggers JustExitedCombat + // on the snake if it's the snake's last ref. + _targetA->TestGetCombatMgr().EndAllPvECombat(); + + // targetA should be out of combat + EXPECT_FALSE(_targetA->TestGetCombatMgr().HasCombat()); + // Snake should still be in combat with targetB + EXPECT_TRUE(_snake->TestGetCombatMgr().HasCombat()); + EXPECT_TRUE(_snake->TestGetCombatMgr().IsInCombatWith(_targetB)); + + // Now end targetB's combat too + _targetB->TestGetCombatMgr().EndAllPvECombat(); + + // Everything clean + EXPECT_FALSE(_snake->TestGetCombatMgr().HasCombat()); + EXPECT_FALSE(_targetB->TestGetCombatMgr().HasCombat()); +} + +// Verify that adding threat during evade is rejected (guards against +// the old Reset() -> AddThreat -> re-enter combat pattern) +TEST_F(SnakeTrapEvadeTest, AddThreat_DuringEvade_IsRejected) +{ + _snake->TestGetCombatMgr().SetInCombatWith(_targetA); + _snake->TestGetThreatMgr().AddThreat(_targetA, 100.0f); + + ASSERT_TRUE(_snake->TestGetThreatMgr().IsThreatenedBy(_targetA)); + + // Enter evade state + _snake->AddUnitState(UNIT_STATE_EVADE); + + // AddThreat should be rejected while in evade + _snake->TestGetThreatMgr().ClearAllThreat(); + _snake->AddThreat(_targetB, 100000.0f); + + // Should NOT have threat on targetB because UNIT_STATE_EVADE blocks AddThreat + EXPECT_FALSE(_snake->TestGetThreatMgr().IsThreatenedBy(_targetB)); + + _snake->ClearUnitState(UNIT_STATE_EVADE); +} + +} // namespace From e20f798a0e43f61ec7d4b9adbcbe7b1e2b2e56ec Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:16:25 -0500 Subject: [PATCH 101/132] fix(DB/Creature): Remove smoothTransition for Guardian of Life (33528) (#25233) Co-authored-by: blinkysc --- data/sql/updates/pending_db_world/rev_1774468236298225855.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774468236298225855.sql diff --git a/data/sql/updates/pending_db_world/rev_1774468236298225855.sql b/data/sql/updates/pending_db_world/rev_1774468236298225855.sql new file mode 100644 index 000000000..d8adb71c5 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774468236298225855.sql @@ -0,0 +1,2 @@ +-- Remove smoothTransition for Guardian of Life (33528) +UPDATE `waypoint_data` SET `smoothTransition` = 0 WHERE `id` IN (1375280, 1375290, 1375300, 1375310); From 200e24c66ee2bc154659762f7a371e5d98fc9dd6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Mar 2026 23:31:08 +0000 Subject: [PATCH 102/132] chore(DB): import pending files Referenced commit(s): e20f798a0e43f61ec7d4b9adbcbe7b1e2b2e56ec --- .../rev_1774468236298225855.sql => db_world/2026_03_25_06.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774468236298225855.sql => db_world/2026_03_25_06.sql} (78%) diff --git a/data/sql/updates/pending_db_world/rev_1774468236298225855.sql b/data/sql/updates/db_world/2026_03_25_06.sql similarity index 78% rename from data/sql/updates/pending_db_world/rev_1774468236298225855.sql rename to data/sql/updates/db_world/2026_03_25_06.sql index d8adb71c5..65228e388 100644 --- a/data/sql/updates/pending_db_world/rev_1774468236298225855.sql +++ b/data/sql/updates/db_world/2026_03_25_06.sql @@ -1,2 +1,3 @@ +-- DB update 2026_03_25_05 -> 2026_03_25_06 -- Remove smoothTransition for Guardian of Life (33528) UPDATE `waypoint_data` SET `smoothTransition` = 0 WHERE `id` IN (1375280, 1375290, 1375300, 1375310); From 3e0c360338761fbc5e09e95dde4d9e541c2519f2 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:20:04 -0300 Subject: [PATCH 103/132] refactor(Scripts/Ulduar): leverage DoorData, persistent data, and code standards (#25163) Co-authored-by: joschiwald Co-authored-by: Shauren Co-authored-by: ariel- Co-authored-by: Unholychick Co-authored-by: horn Co-authored-by: Machiavelli Co-authored-by: Claude Opus 4.6 --- .../Ulduar/boss_algalon_the_observer.cpp | 32 +- .../Ulduar/Ulduar/boss_flame_leviathan.cpp | 13 +- .../Ulduar/Ulduar/boss_general_vezax.cpp | 35 +- .../Northrend/Ulduar/Ulduar/boss_hodir.cpp | 114 ++--- .../Northrend/Ulduar/Ulduar/boss_kologarn.cpp | 6 +- .../Northrend/Ulduar/Ulduar/boss_mimiron.cpp | 63 +-- .../Ulduar/Ulduar/boss_yoggsaron.cpp | 108 ++--- .../Ulduar/Ulduar/instance_ulduar.cpp | 396 ++++++++---------- .../Northrend/Ulduar/Ulduar/ulduar.cpp | 8 +- .../scripts/Northrend/Ulduar/Ulduar/ulduar.h | 11 + 10 files changed, 342 insertions(+), 444 deletions(-) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp index d6833a913..0af5863dc 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp @@ -267,12 +267,12 @@ struct boss_algalon_the_observer : public ScriptedAI _fedOnTears = true; _firstPull = true; _fightWon = false; - m_pInstance = me->GetInstanceScript(); + _instance = me->GetInstanceScript(); } EventMap events; SummonList summons; - InstanceScript* m_pInstance; + InstanceScript* _instance; bool _firstPull; bool _fightWon; @@ -363,8 +363,8 @@ struct boss_algalon_the_observer : public ScriptedAI return; } - if (m_pInstance) - m_pInstance->SetData(BOSS_ALGALON, FAIL); + if (_instance) + _instance->SetData(BOSS_ALGALON, FAIL); ScriptedAI::EnterEvadeMode(why); } @@ -385,13 +385,13 @@ struct boss_algalon_the_observer : public ScriptedAI _phaseTwo = false; _heraldOfTheTitans = true; - if (m_pInstance->GetBossState(BOSS_ALGALON) == FAIL) + if (_instance->GetBossState(BOSS_ALGALON) == FAIL) { _firstPull = false; } - if (m_pInstance) - m_pInstance->SetData(BOSS_ALGALON, NOT_STARTED); + if (_instance) + _instance->SetData(BOSS_ALGALON, NOT_STARTED); } void KilledUnit(Unit* victim) override @@ -445,8 +445,8 @@ struct boss_algalon_the_observer : public ScriptedAI me->SetFaction(FACTION_FRIENDLY); me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); me->InterruptNonMeleeSpells(false); - if (m_pInstance) - m_pInstance->SetData(BOSS_ALGALON, NOT_STARTED); + if (_instance) + _instance->SetData(BOSS_ALGALON, NOT_STARTED); break; case ACTION_INIT_ALGALON: _firstPull = false; @@ -473,7 +473,7 @@ struct boss_algalon_the_observer : public ScriptedAI if (_fightWon) return; - if (!m_pInstance) + if (!_instance) { EnterEvadeMode(EVADE_REASON_OTHER); return; @@ -499,7 +499,7 @@ struct boss_algalon_the_observer : public ScriptedAI Talk(SAY_ALGALON_START_TIMER); introDelay = 22s; events.ScheduleEvent(EVENT_START_COMBAT, 14s); - m_pInstance->SetData(DATA_DESPAWN_ALGALON, 0); + _instance->SetData(DATA_DESPAWN_ALGALON, 0); } events.ScheduleEvent(EVENT_REMOVE_UNNATTACKABLE, introDelay - 500ms); @@ -656,11 +656,11 @@ struct boss_algalon_the_observer : public ScriptedAI case EVENT_INTRO_FINISH: events.Reset(); me->SetImmuneToPC(false); - if (Creature* brann = m_pInstance->GetCreature(DATA_BRANN_BRONZEBEARD_ALG)) + if (Creature* brann = _instance->GetCreature(DATA_BRANN_BRONZEBEARD_ALG)) brann->AI()->DoAction(ACTION_FINISH_INTRO); break; case EVENT_START_COMBAT: - m_pInstance->SetData(BOSS_ALGALON, IN_PROGRESS); + _instance->SetData(BOSS_ALGALON, IN_PROGRESS); Talk(SAY_ALGALON_AGGRO); break; case EVENT_REMOVE_UNNATTACKABLE: @@ -735,10 +735,10 @@ struct boss_algalon_the_observer : public ScriptedAI ScriptedAI::EnterEvadeMode(); return; case EVENT_OUTRO_START: - if (m_pInstance) + if (_instance) { - m_pInstance->SetData(BOSS_ALGALON, DONE); - m_pInstance->SetData(DATA_ALGALON_DEFEATED, 1); + _instance->SetData(BOSS_ALGALON, DONE); + _instance->SetData(DATA_ALGALON_DEFEATED, 1); } break; case EVENT_OUTRO_1: diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp index 550beac7f..610dce8ba 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp @@ -187,7 +187,7 @@ const Position homePos = {322.39f, -14.5f, 409.8f, 3.14f}; struct boss_flame_leviathan : public BossAI { - boss_flame_leviathan(Creature* pCreature) : BossAI(pCreature, BOSS_LEVIATHAN), vehicle(me->GetVehicleKit()) + boss_flame_leviathan(Creature* creature) : BossAI(creature, BOSS_LEVIATHAN), vehicle(me->GetVehicleKit()) { assert(vehicle); } @@ -214,7 +214,7 @@ struct boss_flame_leviathan : public BossAI { _JustReachedHome(); // For achievement - instance->SetData(DATA_UNBROKEN_ACHIEVEMENT, 0); + instance->StorePersistentData(PERSISTENT_DATA_UNBROKEN, 0); me->setActive(false); } @@ -1239,7 +1239,7 @@ class spell_auto_repair : public SpellScript // Achievement if (InstanceScript* instance = vehicle->GetBase()->GetInstanceScript()) - instance->SetData(DATA_UNBROKEN_ACHIEVEMENT, 0); + instance->StorePersistentData(PERSISTENT_DATA_UNBROKEN, 0); } void Register() override @@ -1717,10 +1717,9 @@ public: bool OnCheck(Player* player, Unit*, uint32 /*criteria_id*/) override { - if (player->GetInstanceScript()) - if (player->GetInstanceScript()->GetData(DATA_UNBROKEN_ACHIEVEMENT)) - return true; - return false; + InstanceScript* instance = player->GetInstanceScript(); + return instance + && instance->GetPersistentData(PERSISTENT_DATA_UNBROKEN); } }; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp index 1fed3825c..849576a15 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp @@ -108,7 +108,7 @@ enum VaporsText struct boss_vezax : public BossAI { - boss_vezax(Creature* pCreature) : BossAI(pCreature, BOSS_VEZAX) { } + boss_vezax(Creature* creature) : BossAI(creature, BOSS_VEZAX) { } uint8 vaporsCount; bool hardmodeAvailable; @@ -131,7 +131,7 @@ struct boss_vezax : public BossAI me->setActive(false); } - void JustEngagedWith(Unit* /*pWho*/) override + void JustEngagedWith(Unit* /*who*/) override { me->setActive(true); _JustEngagedWith(); @@ -326,13 +326,6 @@ struct boss_vezax : public BossAI { _JustDied(); Talk(SAY_DEATH); - - if (GameObject* door = me->FindNearestGameObject(GO_VEZAX_DOOR, 500.0f)) - if (door->GetGoState() != GO_STATE_ACTIVE) - { - door->SetLootState(GO_READY); - door->UseDoorOrButton(0, false); - } } void KilledUnit(Unit* who) override @@ -346,21 +339,21 @@ struct boss_vezax : public BossAI struct npc_ulduar_saronite_vapors : public NullCreatureAI { - npc_ulduar_saronite_vapors(Creature* pCreature) : NullCreatureAI(pCreature) + npc_ulduar_saronite_vapors(Creature* creature) : NullCreatureAI(creature) { - pInstance = pCreature->GetInstanceScript(); + _instance = creature->GetInstanceScript(); me->GetMotionMaster()->MoveRandom(4.0f); } - InstanceScript* pInstance; + InstanceScript* _instance; void JustDied(Unit* /*killer*/) override { me->CastSpell(me, SPELL_SARONITE_VAPORS_AURA, true); // killed saronite vapors, hard mode unavailable - if (pInstance) - if (Creature* vezax = pInstance->GetCreature(BOSS_VEZAX)) + if (_instance) + if (Creature* vezax = _instance->GetCreature(BOSS_VEZAX)) vezax->AI()->DoAction(1); } @@ -372,25 +365,25 @@ struct npc_ulduar_saronite_vapors : public NullCreatureAI struct npc_ulduar_saronite_animus : public ScriptedAI { - npc_ulduar_saronite_animus(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_saronite_animus(Creature* creature) : ScriptedAI(creature) { - pInstance = pCreature->GetInstanceScript(); - if (pInstance) - if (Creature* vezax = pInstance->GetCreature(BOSS_VEZAX)) + _instance = creature->GetInstanceScript(); + if (_instance) + if (Creature* vezax = _instance->GetCreature(BOSS_VEZAX)) vezax->AI()->JustSummoned(me); timer = 0; me->SetInCombatWithZone(); } - InstanceScript* pInstance; + InstanceScript* _instance; uint16 timer; void JustDied(Unit* /*killer*/) override { me->DespawnOrUnsummon(3s); - if (pInstance) - if (Creature* vezax = pInstance->GetCreature(BOSS_VEZAX)) + if (_instance) + if (Creature* vezax = _instance->GetCreature(BOSS_VEZAX)) vezax->AI()->DoAction(2); } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp index 545a3b422..46f9a3367 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp @@ -199,7 +199,7 @@ HodirHelperData hhd[4][4] = struct boss_hodir : public BossAI { - boss_hodir(Creature* pCreature) : BossAI(pCreature, BOSS_HODIR) + boss_hodir(Creature* creature) : BossAI(creature, BOSS_HODIR) { if (!me->IsAlive()) instance->SetBossState(BOSS_HODIR, DONE); @@ -239,17 +239,12 @@ struct boss_hodir : public BossAI me->RemoveAllAuras(); instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_BITING_COLD_PLAYER_AURA); - if (GameObject* go = me->FindNearestGameObject(GO_HODIR_FRONTDOOR, 900.0f)) - { - go->SetGoState(GO_STATE_ACTIVE); - } - // Reset helpers if (!summons.size()) SpawnHelpers(); } - void JustEngagedWith(Unit* /*pWho*/) override + void JustEngagedWith(Unit* /*who*/) override { me->CastSpell(me, SPELL_BITING_COLD_BOSS_AURA, true); SmallIcicles(true); @@ -261,14 +256,7 @@ struct boss_hodir : public BossAI Talk(TEXT_AGGRO); if (instance->GetBossState(BOSS_HODIR) != DONE) - { instance->SetBossState(BOSS_HODIR, IN_PROGRESS); - } - - if (GameObject* go = me->FindNearestGameObject(GO_HODIR_FRONTDOOR, 300.0f)) - { - go->SetGoState(GO_STATE_READY); - } } GameObject* GetHardmodeChest() @@ -350,28 +338,6 @@ struct boss_hodir : public BossAI events.Reset(); summons.DespawnAll(); - if (GameObject* d = me->FindNearestGameObject(GO_HODIR_FROZEN_DOOR, 250.0f)) - { - if (d->GetGoState() != GO_STATE_ACTIVE ) - { - d->SetLootState(GO_READY); - d->UseDoorOrButton(0, false); - } - } - if (GameObject* d = me->FindNearestGameObject(GO_HODIR_DOOR, 250.0f)) - { - if (d->GetGoState() != GO_STATE_ACTIVE ) - { - d->SetLootState(GO_READY); - d->UseDoorOrButton(0, false); - } - } - - if (GameObject* go = me->FindNearestGameObject(GO_HODIR_FRONTDOOR, 300.0f)) - { - go->SetGoState(GO_STATE_ACTIVE); - } - Talk(TEXT_DEATH); scheduler.Schedule(14s, [this](TaskContext /*context*/) { @@ -600,7 +566,7 @@ struct boss_hodir : public BossAI struct npc_ulduar_icicle : public NullCreatureAI { - npc_ulduar_icicle(Creature* pCreature) : NullCreatureAI(pCreature) + npc_ulduar_icicle(Creature* creature) : NullCreatureAI(creature) { timer1 = 2000; timer2 = 5000; @@ -632,20 +598,20 @@ struct npc_ulduar_icicle : public NullCreatureAI struct npc_ulduar_flash_freeze : public NullCreatureAI { - npc_ulduar_flash_freeze(Creature* pCreature) : NullCreatureAI(pCreature) + npc_ulduar_flash_freeze(Creature* creature) : NullCreatureAI(creature) { timer = 2500; - pInstance = me->GetInstanceScript(); + _instance = me->GetInstanceScript(); } - InstanceScript* pInstance; + InstanceScript* _instance; uint16 timer; void DamageTaken(Unit* doneBy, uint32& /*damage*/, DamageEffectType, SpellSchoolMask) override { - if (pInstance && doneBy) - if (pInstance->GetBossState(BOSS_HODIR) == NOT_STARTED) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance && doneBy) + if (_instance->GetBossState(BOSS_HODIR) == NOT_STARTED) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) hodir->AI()->AttackStart(doneBy); } @@ -681,7 +647,7 @@ struct npc_ulduar_flash_freeze : public NullCreatureAI struct npc_ulduar_toasty_fire : public NullCreatureAI { - npc_ulduar_toasty_fire(Creature* pCreature) : NullCreatureAI(pCreature) + npc_ulduar_toasty_fire(Creature* creature) : NullCreatureAI(creature) { me->CastSpell(me, SPELL_MAGE_TOASTY_FIRE_AURA, true); } @@ -713,15 +679,15 @@ struct npc_ulduar_toasty_fire : public NullCreatureAI struct npc_ulduar_hodir_priest : public ScriptedAI { - npc_ulduar_hodir_priest(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_hodir_priest(Creature* creature) : ScriptedAI(creature) { - pInstance = me->GetInstanceScript(); + _instance = me->GetInstanceScript(); events.Reset(); me->SetReactState(REACT_PASSIVE); } EventMap events; - InstanceScript* pInstance; + InstanceScript* _instance; void AttackStart(Unit* who) override { @@ -758,8 +724,8 @@ struct npc_ulduar_hodir_priest : public ScriptedAI case EVENT_TRY_FREE_HELPER: { if (!me->HasAura(SPELL_FLASH_FREEZE_TRAPPED_NPC)) - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) { AttackStart(hodir); ScheduleAbilities(); @@ -791,23 +757,23 @@ struct npc_ulduar_hodir_priest : public ScriptedAI void JustDied(Unit* /*killer*/) override { - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) hodir->AI()->SetData(4, 1); } }; struct npc_ulduar_hodir_druid : public ScriptedAI { - npc_ulduar_hodir_druid(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_hodir_druid(Creature* creature) : ScriptedAI(creature) { - pInstance = me->GetInstanceScript(); + _instance = me->GetInstanceScript(); events.Reset(); me->SetReactState(REACT_PASSIVE); } EventMap events; - InstanceScript* pInstance; + InstanceScript* _instance; void AttackStart(Unit* who) override { @@ -843,8 +809,8 @@ struct npc_ulduar_hodir_druid : public ScriptedAI case EVENT_TRY_FREE_HELPER: { if (!me->HasAura(SPELL_FLASH_FREEZE_TRAPPED_NPC)) - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) { AttackStart(hodir); ScheduleAbilities(); @@ -877,23 +843,23 @@ struct npc_ulduar_hodir_druid : public ScriptedAI void JustDied(Unit* /*killer*/) override { - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) hodir->AI()->SetData(4, 1); } }; struct npc_ulduar_hodir_shaman : public ScriptedAI { - npc_ulduar_hodir_shaman(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_hodir_shaman(Creature* creature) : ScriptedAI(creature) { - pInstance = me->GetInstanceScript(); + _instance = me->GetInstanceScript(); events.Reset(); me->SetReactState(REACT_PASSIVE); } EventMap events; - InstanceScript* pInstance; + InstanceScript* _instance; void AttackStart(Unit* who) override { @@ -937,8 +903,8 @@ struct npc_ulduar_hodir_shaman : public ScriptedAI case EVENT_TRY_FREE_HELPER: { if (!me->HasAura(SPELL_FLASH_FREEZE_TRAPPED_NPC)) - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) { AttackStart(hodir); ScheduleAbilities(); @@ -970,23 +936,23 @@ struct npc_ulduar_hodir_shaman : public ScriptedAI void JustDied(Unit* /*killer*/) override { - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) hodir->AI()->SetData(4, 1); } }; struct npc_ulduar_hodir_mage : public ScriptedAI { - npc_ulduar_hodir_mage(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_hodir_mage(Creature* creature) : ScriptedAI(creature) { - pInstance = me->GetInstanceScript(); + _instance = me->GetInstanceScript(); events.Reset(); me->SetReactState(REACT_PASSIVE); } EventMap events; - InstanceScript* pInstance; + InstanceScript* _instance; void AttackStart(Unit* who) override { @@ -1023,8 +989,8 @@ struct npc_ulduar_hodir_mage : public ScriptedAI case EVENT_TRY_FREE_HELPER: { if (!me->HasAura(SPELL_FLASH_FREEZE_TRAPPED_NPC)) - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) { AttackStart(hodir); ScheduleAbilities(); @@ -1074,8 +1040,8 @@ struct npc_ulduar_hodir_mage : public ScriptedAI void JustDied(Unit* /*killer*/) override { - if (pInstance) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (_instance) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) hodir->AI()->SetData(4, 1); } }; @@ -1170,8 +1136,8 @@ class spell_hodir_biting_cold_player_aura : public AuraScript if (_counter >= 4) { if (GetStackAmount() == 2) // increasing from 2 to 3 (not checking >= to improve performance) - if (InstanceScript* pInstance = target->GetInstanceScript()) - if (Creature* hodir = pInstance->GetCreature(BOSS_HODIR)) + if (InstanceScript* _instance = target->GetInstanceScript()) + if (Creature* hodir = _instance->GetCreature(BOSS_HODIR)) hodir->AI()->SetData(2, 1); ModStackAmount(1); _counter = 0; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp index a1854f5a1..804584415 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp @@ -572,10 +572,10 @@ struct boss_kologarn_eyebeam : public ScriptedAI { boss_kologarn_eyebeam(Creature* c) : ScriptedAI(c), _timer(1), _damaged(false) { - m_pInstance = (InstanceScript*)c->GetInstanceScript(); + _instance = c->GetInstanceScript(); } - InstanceScript* m_pInstance; + InstanceScript* _instance; uint32 _timer; bool _damaged; @@ -602,7 +602,7 @@ struct boss_kologarn_eyebeam : public ScriptedAI me->Attack(player, false); me->GetMotionMaster()->MoveChase(player); - if (Creature* cr = m_pInstance->GetCreature(BOSS_KOLOGARN)) + if (Creature* cr = _instance->GetCreature(BOSS_KOLOGARN)) { me->CastSpell(cr, me->GetEntry() == NPC_EYE_LEFT ? SPELL_FOCUSED_EYEBEAM_LEFT : SPELL_FOCUSED_EYEBEAM_RIGHT, true); } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp index 600c59f66..0eba106d7 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp @@ -264,7 +264,7 @@ Position const ACUSummonPos = { 2742.6265f, 2568.0571f, 377.22076f, 0.0f }; /// struct boss_mimiron : public BossAI { - boss_mimiron(Creature* pCreature) : BossAI(pCreature, BOSS_MIMIRON) + boss_mimiron(Creature* creature) : BossAI(creature, BOSS_MIMIRON) { if (!me->IsAlive()) instance->SetBossState(BOSS_MIMIRON, DONE); @@ -729,15 +729,6 @@ struct boss_mimiron : public BossAI DoCastSelf(SPELL_SLEEP_VISUAL_1); - if (instance) - for( uint16 i = 0; i < 3; ++i ) - if (GameObject* door = instance->GetGameObject(DATA_GO_MIMIRON_DOOR_1 + i)) - if (door->GetGoState() != GO_STATE_ACTIVE ) - { - door->SetLootState(GO_READY); - door->UseDoorOrButton(0, false); - } - instance->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, NPC_LEVIATHAN_MKII, 1, me); if (_hardmode) @@ -817,14 +808,6 @@ struct boss_mimiron : public BossAI void ResetGameObjects() { - for (uint16 i = 0; i < 3; ++i) - if (GameObject* door = instance->GetGameObject(DATA_GO_MIMIRON_DOOR_1 + i)) - if (door->GetGoState() != GO_STATE_ACTIVE) - { - door->SetLootState(GO_READY); - door->UseDoorOrButton(0, false); - } - if (GameObject* elevator = me->FindNearestGameObject(GO_MIMIRON_ELEVATOR, 200.0f)) { if (elevator->GetGoState() != GO_STATE_ACTIVE ) @@ -846,14 +829,6 @@ struct boss_mimiron : public BossAI void CloseDoorAndButton() { - for (uint16 i = 0; i < 3; ++i) - if (GameObject* door = instance->GetGameObject(DATA_GO_MIMIRON_DOOR_1 + i)) - if (door->GetGoState() != GO_STATE_READY) - { - door->SetLootState(GO_READY); - door->UseDoorOrButton(0, false); - } - if (GameObject* button = me->FindNearestGameObject(GO_BUTTON, 200.0f)) if (button->GetGoState() != GO_STATE_ACTIVE) { @@ -945,7 +920,7 @@ private: struct npc_ulduar_leviathan_mkii : public ScriptedAI { - npc_ulduar_leviathan_mkii(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_leviathan_mkii(Creature* creature) : ScriptedAI(creature) { instance = me->GetInstanceScript(); _isEvading = false; @@ -1074,21 +1049,21 @@ struct npc_ulduar_leviathan_mkii : public ScriptedAI break; case EVENT_SPELL_NAPALM_SHELL: { - Player* pTarget = nullptr; - std::vector pList; + Player* target = nullptr; + std::vector playerList; Map::PlayerList const& pl = me->GetMap()->GetPlayers(); for( Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr ) if (Player* plr = itr->GetSource()) if (plr->IsAlive() && plr->GetDistance2d(me) > 15.0f ) - pList.push_back(plr); + playerList.push_back(plr); - if (!pList.empty()) - pTarget = pList[urand(0, pList.size() - 1)]; + if (!playerList.empty()) + target = playerList[urand(0, playerList.size() - 1)]; else - pTarget = (Player*)SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true); + target = (Player*)SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true); - if (pTarget) - cannon->CastSpell(pTarget, SPELL_NAPALM_SHELL, false); + if (target) + cannon->CastSpell(target, SPELL_NAPALM_SHELL, false); _events.Repeat(14s); } @@ -1188,7 +1163,7 @@ private: struct npc_ulduar_vx001 : public ScriptedAI { - npc_ulduar_vx001(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_vx001(Creature* creature) : ScriptedAI(creature) { instance = me->GetInstanceScript(); _isEvading = false; @@ -1503,7 +1478,7 @@ private: struct npc_ulduar_aerial_command_unit : public ScriptedAI { - npc_ulduar_aerial_command_unit(Creature* pCreature) : ScriptedAI(pCreature), _summons(me) + npc_ulduar_aerial_command_unit(Creature* creature) : ScriptedAI(creature), _summons(me) { instance = me->GetInstanceScript(); _isEvading = false; @@ -1744,7 +1719,7 @@ private: struct npc_ulduar_proximity_mine : public ScriptedAI { - npc_ulduar_proximity_mine(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_proximity_mine(Creature* creature) : ScriptedAI(creature) { _exploded = false; _timer = 2500; @@ -1809,7 +1784,7 @@ class spell_ulduar_mimiron_mine_explosion : public SpellScript struct npc_ulduar_mimiron_rocket : public NullCreatureAI { - npc_ulduar_mimiron_rocket(Creature* pCreature) : NullCreatureAI(pCreature) {} + npc_ulduar_mimiron_rocket(Creature* creature) : NullCreatureAI(creature) {} void InitializeAI() override { @@ -1841,7 +1816,7 @@ struct npc_ulduar_mimiron_rocket : public NullCreatureAI struct npc_ulduar_bot_summon_trigger : public NullCreatureAI { - npc_ulduar_bot_summon_trigger(Creature* pCreature) : NullCreatureAI(pCreature) { } + npc_ulduar_bot_summon_trigger(Creature* creature) : NullCreatureAI(creature) { } void Reset() override { @@ -2062,7 +2037,7 @@ public: struct npc_ulduar_flames_initial : public NullCreatureAI { - npc_ulduar_flames_initial(Creature* pCreature) : NullCreatureAI(pCreature) + npc_ulduar_flames_initial(Creature* creature) : NullCreatureAI(creature) { _createTime = GameTime::GetGameTime().count(); _events.Reset(); @@ -2170,7 +2145,7 @@ private: struct npc_ulduar_flames_spread : public NullCreatureAI { - npc_ulduar_flames_spread(Creature* pCreature) : NullCreatureAI(pCreature) {} + npc_ulduar_flames_spread(Creature* creature) : NullCreatureAI(creature) {} void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override { @@ -2201,7 +2176,7 @@ struct npc_ulduar_flames_spread : public NullCreatureAI struct npc_ulduar_emergency_fire_bot : public ScriptedAI { - npc_ulduar_emergency_fire_bot(Creature* pCreature) : ScriptedAI(pCreature) + npc_ulduar_emergency_fire_bot(Creature* creature) : ScriptedAI(creature) { _events.Reset(); _events.ScheduleEvent(EVENT_EMERGENCY_BOT_CHECK, 1s); @@ -2248,7 +2223,7 @@ private: struct npc_ulduar_rocket_strike_trigger : public NullCreatureAI { - npc_ulduar_rocket_strike_trigger(Creature* pCreature) : NullCreatureAI(pCreature) {} + npc_ulduar_rocket_strike_trigger(Creature* creature) : NullCreatureAI(creature) {} void SpellHitTarget(Unit* target, SpellInfo const* spell) override { diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp index 407793986..3fd86f9de 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp @@ -365,12 +365,12 @@ const Position Middle = {1980.28f, -25.5868f, 329.397f, M_PI * 1.5f}; struct boss_yoggsaron_sara : public ScriptedAI { - boss_yoggsaron_sara(Creature* pCreature) : ScriptedAI(pCreature), summons(pCreature) + boss_yoggsaron_sara(Creature* creature) : ScriptedAI(creature), summons(creature) { - m_pInstance = pCreature->GetInstanceScript(); + _instance = creature->GetInstanceScript(); } - InstanceScript* m_pInstance; + InstanceScript* _instance; EventMap events; SummonList summons; @@ -463,27 +463,27 @@ struct boss_yoggsaron_sara : public ScriptedAI _currentIllusion = urand(1, 3); _isIllusionReversed = urand(0, 1); - if (m_pInstance) + if (_instance) { - m_pInstance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, CRITERIA_NOT_GETTING_OLDER); - m_pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_SANITY); - m_pInstance->SetData(BOSS_YOGGSARON, NOT_STARTED); - if (GameObject* go = m_pInstance->GetGameObject(DATA_YOGG_SARON_DOORS)) + _instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, CRITERIA_NOT_GETTING_OLDER); + _instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_SANITY); + _instance->SetData(BOSS_YOGGSARON, NOT_STARTED); + if (GameObject* go = _instance->GetGameObject(DATA_YOGG_SARON_DOORS)) go->SetGoState(GO_STATE_ACTIVE); } } void InitFight(Unit* target) { - if (!m_pInstance) + if (!_instance) return; // some simple hack checks - if (m_pInstance->GetBossState(BOSS_VEZAX) != DONE || m_pInstance->GetBossState(BOSS_XT002) != DONE) + if (_instance->GetBossState(BOSS_VEZAX) != DONE || _instance->GetBossState(BOSS_XT002) != DONE) return; - m_pInstance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, CRITERIA_NOT_GETTING_OLDER); - m_pInstance->SetData(BOSS_YOGGSARON, IN_PROGRESS); + _instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, CRITERIA_NOT_GETTING_OLDER); + _instance->SetData(BOSS_YOGGSARON, IN_PROGRESS); me->SetInCombatWithZone(); AttackStart(target); @@ -512,16 +512,18 @@ struct boss_yoggsaron_sara : public ScriptedAI for (uint8 i = KEEPER_FREYA; i <= KEEPER_THORIM; i++) { summons.DespawnEntry(TABLE_GOSSIP_ENTRY[i]); - if (Creature* keeper = m_pInstance->GetCreature(gossipData[i])) + if (Creature* keeper = _instance->GetCreature(gossipData[i])) keeper->DespawnOrUnsummon(); } } void UpdateKeeperSpawns() { + uint32 watchersMask = _instance->GetPersistentData( + PERSISTENT_DATA_WATCHERS_MASK); for (uint8 i = KEEPER_FREYA; i <= KEEPER_THORIM; i++) { - if (m_pInstance->GetData(TYPE_WATCHERS) & (1 << i)) + if (watchersMask & (1 << i)) { if (!summons.HasEntry(TABLE_KEEPER_ENTRY[i])) me->SummonCreature(TABLE_KEEPER_ENTRY[i], KeepersPos[i]); @@ -617,8 +619,10 @@ struct boss_yoggsaron_sara : public ScriptedAI if (param == DATA_GET_KEEPERS_COUNT) { uint8 _count = 0; + uint32 watchersMask = _instance->GetPersistentData( + PERSISTENT_DATA_WATCHERS_MASK); for (uint8 i = 0; i < 4; ++i) - if (m_pInstance->GetData(TYPE_WATCHERS) & (1 << i)) + if (watchersMask & (1 << i)) ++_count; return _count; } @@ -741,7 +745,7 @@ struct boss_yoggsaron_sara : public ScriptedAI if (!SelectTargetFromPlayerList(90, SPELL_INSANE1)) { - m_pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_INSANE1); + _instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_INSANE1); EnterEvadeMode(EVADE_REASON_OTHER); return; } @@ -787,8 +791,8 @@ struct boss_yoggsaron_sara : public ScriptedAI // Whispers of YS me->SummonCreature(NPC_VOICE_OF_YOGG_SARON, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); - if (m_pInstance) - if (GameObject* go = m_pInstance->GetGameObject(DATA_YOGG_SARON_DOORS)) + if (_instance) + if (GameObject* go = _instance->GetGameObject(DATA_YOGG_SARON_DOORS)) go->SetGoState(GO_STATE_READY); events.ScheduleEvent(EVENT_SARA_P1_SPELLS, 0ms, 1, EVENT_PHASE_ONE); @@ -912,7 +916,7 @@ struct boss_yoggsaron_sara : public ScriptedAI struct boss_yoggsaron_cloud : public npc_escortAI { - boss_yoggsaron_cloud(Creature* pCreature) : npc_escortAI(pCreature) + boss_yoggsaron_cloud(Creature* creature) : npc_escortAI(creature) { InitWaypoint(); Reset(); @@ -998,7 +1002,7 @@ struct boss_yoggsaron_cloud : public npc_escortAI struct boss_yoggsaron_guardian_of_ys : public ScriptedAI { - boss_yoggsaron_guardian_of_ys(Creature* pCreature) : ScriptedAI(pCreature) { } + boss_yoggsaron_guardian_of_ys(Creature* creature) : ScriptedAI(creature) { } uint32 _spellTimer; @@ -1031,9 +1035,9 @@ struct boss_yoggsaron_guardian_of_ys : public ScriptedAI struct boss_yoggsaron : public ScriptedAI { - boss_yoggsaron(Creature* pCreature) : ScriptedAI(pCreature), summons(pCreature) + boss_yoggsaron(Creature* creature) : ScriptedAI(creature), summons(creature) { - m_pInstance = me->GetInstanceScript(); + _instance = me->GetInstanceScript(); _thirdPhase = false; _usedInsane = false; summons.DespawnAll(); @@ -1041,10 +1045,12 @@ struct boss_yoggsaron : public ScriptedAI uint8 _count = 4; me->SetLootMode(31); // 1 + 2 + 4 + 8 + 16, remove with watchers addition - if (m_pInstance) + if (_instance) { + uint32 watchersMask = _instance->GetPersistentData( + PERSISTENT_DATA_WATCHERS_MASK); for (uint8 i = 0; i < 4; ++i) - if (m_pInstance->GetData(TYPE_WATCHERS) & (1 << i)) + if (watchersMask & (1 << i)) { me->RemoveLootMode(1 << _count); --_count; @@ -1052,7 +1058,7 @@ struct boss_yoggsaron : public ScriptedAI } } - InstanceScript* m_pInstance; + InstanceScript* _instance; EventMap events; SummonList summons; bool _thirdPhase; @@ -1077,17 +1083,17 @@ struct boss_yoggsaron : public ScriptedAI Talk(SAY_YOGG_SARON_DEATH); - if (m_pInstance) + if (_instance) { - m_pInstance->SetData(BOSS_YOGGSARON, DONE); - if (Creature* sara = m_pInstance->GetCreature(DATA_SARA)) + _instance->SetData(BOSS_YOGGSARON, DONE); + if (Creature* sara = _instance->GetCreature(DATA_SARA)) sara->AI()->DoAction(ACTION_YOGG_SARON_DEATH); - if (GameObject* go = m_pInstance->GetGameObject(DATA_YOGG_SARON_DOORS)) + if (GameObject* go = _instance->GetGameObject(DATA_YOGG_SARON_DOORS)) go->SetGoState(GO_STATE_ACTIVE); } - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr) + Map::PlayerList const& playerList = me->GetMap()->GetPlayers(); + for(Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr) { itr->GetSource()->RemoveAura(SPELL_SANITY); itr->GetSource()->RemoveAura(SPELL_INSANE1); @@ -1200,7 +1206,7 @@ struct boss_yoggsaron : public ScriptedAI struct boss_yoggsaron_brain : public NullCreatureAI { - boss_yoggsaron_brain(Creature* pCreature) : NullCreatureAI(pCreature), summons(pCreature) + boss_yoggsaron_brain(Creature* creature) : NullCreatureAI(creature), summons(creature) { me->SetDisableGravity(true); _tentacleCount = 0; @@ -1434,7 +1440,7 @@ struct boss_yoggsaron_brain : public NullCreatureAI struct boss_yoggsaron_death_orb : public NullCreatureAI { - boss_yoggsaron_death_orb(Creature* pCreature) : NullCreatureAI(pCreature) + boss_yoggsaron_death_orb(Creature* creature) : NullCreatureAI(creature) { me->CastSpell(me, SPELL_DEATH_RAY_WARNING, true); _startTimer = 1; @@ -1463,7 +1469,7 @@ struct boss_yoggsaron_death_orb : public NullCreatureAI struct boss_yoggsaron_crusher_tentacle : public ScriptedAI { - boss_yoggsaron_crusher_tentacle(Creature* pCreature) : ScriptedAI(pCreature) + boss_yoggsaron_crusher_tentacle(Creature* creature) : ScriptedAI(creature) { me->SetCombatMovement(false); me->CastSpell(me, SPELL_CRUSH, true); @@ -1514,7 +1520,7 @@ struct boss_yoggsaron_crusher_tentacle : public ScriptedAI struct boss_yoggsaron_corruptor_tentacle : public ScriptedAI { - boss_yoggsaron_corruptor_tentacle(Creature* pCreature) : ScriptedAI(pCreature) + boss_yoggsaron_corruptor_tentacle(Creature* creature) : ScriptedAI(creature) { me->SetCombatMovement(false); } @@ -1528,10 +1534,10 @@ struct boss_yoggsaron_corruptor_tentacle : public ScriptedAI Unit* SelectCorruptionTarget() { Player* target = nullptr; - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - uint8 num = urand(0, pList.getSize() - 1); + Map::PlayerList const& playerList = me->GetMap()->GetPlayers(); + uint8 num = urand(0, playerList.getSize() - 1); uint8 count = 0; - for (Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr, ++count) + for (Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr, ++count) { if (me->GetDistance(itr->GetSource()) > 200 || itr->GetSource()->GetPositionZ() < 300 || !itr->GetSource()->IsAlive() || itr->GetSource()->IsGameMaster()) continue; @@ -1560,7 +1566,7 @@ struct boss_yoggsaron_corruptor_tentacle : public ScriptedAI struct boss_yoggsaron_constrictor_tentacle : public ScriptedAI { - boss_yoggsaron_constrictor_tentacle(Creature* pCreature) : ScriptedAI(pCreature) + boss_yoggsaron_constrictor_tentacle(Creature* creature) : ScriptedAI(creature) { me->SetCombatMovement(false); _checkTimer = 1; @@ -1573,10 +1579,10 @@ struct boss_yoggsaron_constrictor_tentacle : public ScriptedAI Unit* SelectConstrictTarget() { Player* target = nullptr; - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - uint8 num = urand(0, pList.getSize() - 1); + Map::PlayerList const& playerList = me->GetMap()->GetPlayers(); + uint8 num = urand(0, playerList.getSize() - 1); uint8 count = 0; - for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr, ++count) + for(Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr, ++count) { if (me->GetDistance(itr->GetSource()) > 10 || !itr->GetSource()->IsAlive() || itr->GetSource()->IsGameMaster()) continue; @@ -1713,7 +1719,7 @@ private: struct boss_yoggsaron_influence_tentacle : public NullCreatureAI { - boss_yoggsaron_influence_tentacle(Creature* pCreature) : NullCreatureAI(pCreature) + boss_yoggsaron_influence_tentacle(Creature* creature) : NullCreatureAI(creature) { me->CastSpell(me, SPELL_GRIM_REPRISAL, true); } @@ -1734,7 +1740,7 @@ struct boss_yoggsaron_influence_tentacle : public NullCreatureAI struct boss_yoggsaron_immortal_guardian : public ScriptedAI { - boss_yoggsaron_immortal_guardian(Creature* pCreature) : ScriptedAI(pCreature) + boss_yoggsaron_immortal_guardian(Creature* creature) : ScriptedAI(creature) { Reset(); } @@ -2043,7 +2049,7 @@ struct boss_yoggsaron_neltharion : public ScriptedAI struct boss_yoggsaron_voice : public NullCreatureAI { - boss_yoggsaron_voice(Creature* pCreature) : NullCreatureAI(pCreature) + boss_yoggsaron_voice(Creature* creature) : NullCreatureAI(creature) { _targets.clear(); _current = 0; @@ -2127,13 +2133,13 @@ class spell_yogg_saron_brain_link : public SpellScript void FilterTargets(std::list& targets) { - std::list tempList; + std::list templayerList; for (std::list::iterator itr = targets.begin(); itr != targets.end(); ++itr) if ((*itr)->GetPositionZ() > 300.0f) - tempList.push_back(*itr); + templayerList.push_back(*itr); targets.clear(); - for (std::list::iterator itr = tempList.begin(); itr != tempList.end(); ++itr) + for (std::list::iterator itr = templayerList.begin(); itr != templayerList.end(); ++itr) targets.push_back(*itr); } @@ -2156,10 +2162,10 @@ class spell_yogg_saron_brain_link_aura : public AuraScript { PreventDefaultAction(); Player* target = nullptr; - Map::PlayerList const& pList = GetUnitOwner()->GetMap()->GetPlayers(); - uint8 _offset = urand(0, pList.getSize() - 1); + Map::PlayerList const& playerList = GetUnitOwner()->GetMap()->GetPlayers(); + uint8 _offset = urand(0, playerList.getSize() - 1); uint8 _counter = 0; - for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr, ++_counter) + for(Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr, ++_counter) { if (itr->GetSource() == GetUnitOwner() || GetUnitOwner()->GetDistance(itr->GetSource()) > 50.0f || !itr->GetSource()->IsAlive() || itr->GetSource()->IsGameMaster()) continue; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp index cd691e25b..80b0a0a48 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp @@ -30,13 +30,30 @@ DoorData const doorData[] = { - { GO_LEVIATHAN_DOORS, BOSS_LEVIATHAN, DOOR_TYPE_ROOM }, - { GO_XT002_DOORS, BOSS_XT002, DOOR_TYPE_ROOM }, - { GO_KOLOGARN_DOORS, BOSS_KOLOGARN, DOOR_TYPE_ROOM }, - { GO_ASSEMBLY_DOORS, BOSS_ASSEMBLY, DOOR_TYPE_ROOM }, - { GO_ARCHIVUM_DOORS, BOSS_ASSEMBLY, DOOR_TYPE_PASSAGE }, - { GO_YOGG_SARON_DOORS, BOSS_YOGGSARON, DOOR_TYPE_ROOM }, - { 0, 0, DOOR_TYPE_ROOM } + { GO_LEVIATHAN_DOORS, BOSS_LEVIATHAN, DOOR_TYPE_ROOM }, + { GO_LIGHTNING_WALL1, BOSS_LEVIATHAN, DOOR_TYPE_PASSAGE }, + { GO_XT002_DOORS, BOSS_XT002, DOOR_TYPE_ROOM }, + { GO_KOLOGARN_DOORS, BOSS_KOLOGARN, DOOR_TYPE_ROOM }, + { GO_ASSEMBLY_DOORS, BOSS_ASSEMBLY, DOOR_TYPE_ROOM }, + { GO_ARCHIVUM_DOORS, BOSS_ASSEMBLY, DOOR_TYPE_PASSAGE }, + { GO_MIMIRON_DOOR_1, BOSS_MIMIRON, DOOR_TYPE_ROOM }, + { GO_MIMIRON_DOOR_2, BOSS_MIMIRON, DOOR_TYPE_ROOM }, + { GO_MIMIRON_DOOR_3, BOSS_MIMIRON, DOOR_TYPE_ROOM }, + { GO_HODIR_FRONTDOOR, BOSS_HODIR, DOOR_TYPE_ROOM }, + { GO_HODIR_FROZEN_DOOR, BOSS_HODIR, DOOR_TYPE_PASSAGE }, + { GO_HODIR_DOOR, BOSS_HODIR, DOOR_TYPE_PASSAGE }, + { GO_KEEPERS_GATE, BOSS_HODIR, DOOR_TYPE_PASSAGE }, + { GO_KEEPERS_GATE, BOSS_MIMIRON, DOOR_TYPE_PASSAGE }, + { GO_KEEPERS_GATE, BOSS_THORIM, DOOR_TYPE_PASSAGE }, + { GO_KEEPERS_GATE, BOSS_FREYA, DOOR_TYPE_PASSAGE }, + { GO_VEZAX_DOOR, BOSS_VEZAX, DOOR_TYPE_PASSAGE }, + { GO_YOGG_SARON_DOORS, BOSS_YOGGSARON, DOOR_TYPE_ROOM }, + { GO_DOODAD_UL_SIGILDOOR_03, BOSS_ALGALON, DOOR_TYPE_ROOM }, + { GO_DOODAD_UL_UNIVERSEFLOOR_01, BOSS_ALGALON, DOOR_TYPE_ROOM }, + { GO_DOODAD_UL_UNIVERSEFLOOR_02, BOSS_ALGALON, DOOR_TYPE_SPAWN_HOLE }, + { GO_DOODAD_UL_UNIVERSEGLOBE01, BOSS_ALGALON, DOOR_TYPE_SPAWN_HOLE }, + { GO_DOODAD_UL_ULDUAR_TRAPDOOR_03, BOSS_ALGALON, DOOR_TYPE_SPAWN_HOLE }, + { 0, 0, DOOR_TYPE_ROOM } }; // Observation Ring keeper positions, indexed by KEEPER_* constants @@ -152,92 +169,68 @@ class instance_ulduar : public InstanceMapScript public: instance_ulduar() : InstanceMapScript("instance_ulduar", MAP_ULDUAR) { } - InstanceScript* GetInstanceScript(InstanceMap* pMap) const override + InstanceScript* GetInstanceScript(InstanceMap* map) const override { - return new instance_ulduar_InstanceMapScript(pMap); + return new instance_ulduar_InstanceMapScript(map); } struct instance_ulduar_InstanceMapScript : public InstanceScript { - instance_ulduar_InstanceMapScript(Map* pMap) : InstanceScript(pMap) + instance_ulduar_InstanceMapScript(Map* map) : InstanceScript(map) { SetHeaders(DataHeader); SetBossNumber(MAX_ENCOUNTER); + SetPersistentDataCount(MAX_PERSISTENT_DATA); LoadDoorData(doorData); LoadObjectData(creatureData, gameobjectData); Initialize(); - // 0: 10 man difficulty - // 1: 25 man difficulty - m_difficulty = (pMap->Is25ManRaid() ? 0 : 1); }; - // Only used for TYPE_WATCHERS bitmask - uint32 m_watchersMask; - uint32 C_of_Ulduar_MASK; - - int m_difficulty; - // Flame Leviathan - ObjectGuid m_leviathanVisualTowers[4][2]; - ObjectGuid m_RepairSGUID[2]; - bool m_leviathanTowers[4]; + ObjectGuid _leviathanVisualTowers[4][2]; + ObjectGuid _repairSGUID[2]; + bool _leviathanTowers[4]; GuidList _leviathanVehicles; - uint32 m_unbrokenAchievement; - uint32 m_mageBarrier; // Hodir - bool hmHodir; + bool _hmHodir; Position normalChestPosition = { 1967.152588f, -204.188461f, 432.686951f, 5.50957f }; Position hardChestPosition = { 2035.94600f, -202.084885f, 432.686859f, 3.164077f }; - // Freya - uint32 m_conspeedatoryAttempt; - - // Yogg-Saron - - // Algalon - uint32 m_algalonTimer; - // Ancient Gate - const Position triggerAncientGatePosition = { 1883.65f, 269.272f, 418.406f }; + Position const triggerAncientGatePosition = { 1883.65f, 269.272f, 418.406f }; // Shared EventMap _events; - bool m_mimironTramUsed; + bool _mimironTramUsed; void Initialize() override { - // Bosses - m_watchersMask = 0; - C_of_Ulduar_MASK = 0; - // Flame Leviathan for (uint8 i = 0; i < 4; ++i) - m_leviathanTowers[i] = true; + _leviathanTowers[i] = true; _leviathanVehicles.clear(); - m_unbrokenAchievement = 1; - m_mageBarrier = 0; // Hodir - hmHodir = true; // If players fail the Hardmode then becomes false - - // Freya - m_conspeedatoryAttempt = 0; - - // Algalon - m_algalonTimer = 0; + _hmHodir = true; // If players fail the Hardmode then becomes false // Shared _events.Reset(); - m_mimironTramUsed = false; + _mimironTramUsed = false; } void FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet) override { + uint32 algalonTimer = + GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER); packet.Worldstates.reserve(2); - packet.Worldstates.emplace_back(WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, (m_algalonTimer && m_algalonTimer <= 60) ? 1 : 0); - packet.Worldstates.emplace_back(WORLD_STATE_ULDUAR_ALGALON_DESPAWN_TIMER, std::min(m_algalonTimer, 60)); + packet.Worldstates.emplace_back( + WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, + (algalonTimer && algalonTimer <= 60) ? 1 : 0); + packet.Worldstates.emplace_back( + WORLD_STATE_ULDUAR_ALGALON_DESPAWN_TIMER, + std::min(algalonTimer, 60)); } void OnPlayerEnter(Player* player) override @@ -258,28 +251,36 @@ public: } // Spawn Observation Ring keepers for defeated bosses + uint32 watchersMask = + GetPersistentData(PERSISTENT_DATA_WATCHERS_MASK); for (uint8 i = KEEPER_FREYA; i <= KEEPER_THORIM; ++i) if (IsBossDone(ObservationRingKeeperBoss[i]) - && !(m_watchersMask & (1 << i)) + && !(watchersMask & (1 << i)) && !GetObjectGuid(ObservationRingKeeperData[i])) instance->SummonCreature( ObservationRingKeeperEntry[i], ObservationRingKeepersPos[i]); - if (!GetObjectGuid(BOSS_ALGALON) && m_algalonTimer && (m_algalonTimer <= 60 || m_algalonTimer == TIMER_ALGALON_TO_SUMMON)) + uint32 algalonTimer = + GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER); + if (!GetObjectGuid(BOSS_ALGALON) && algalonTimer + && (algalonTimer <= 60 + || algalonTimer == TIMER_ALGALON_TO_SUMMON)) { TempSummon* algalon = instance->SummonCreature(NPC_ALGALON, AlgalonLandPos); if (!algalon) return; - if (m_algalonTimer <= 60) + if (algalonTimer <= 60) { _events.RescheduleEvent(EVENT_UPDATE_ALGALON_TIMER, 1min); algalon->AI()->DoAction(ACTION_INIT_ALGALON); } - else // if (m_algalonTimer = TIMER_ALGALON_TO_SUMMON) + else // if (algalonTimer == TIMER_ALGALON_TO_SUMMON) { - m_algalonTimer = TIMER_ALGALON_SUMMONED; + StorePersistentData( + PERSISTENT_DATA_ALGALON_TIMER, + TIMER_ALGALON_SUMMONED); algalon->SetImmuneToPC(false); } } @@ -329,15 +330,9 @@ public: go->SetGoState(GO_STATE_ACTIVE_ALTERNATIVE); } break; - case BOSS_ASSEMBLY: - // Assembly doors handled by DoorData, archivum - // doors stay open only after DONE - if (GameObject* go = GetGameObject(DATA_ARCHIVUM_DOORS)) - go->SetGoState(state == DONE ? GO_STATE_ACTIVE : GO_STATE_READY); - break; case BOSS_MIMIRON: if (state == IN_PROGRESS) - m_mimironTramUsed = true; + _mimironTramUsed = true; [[fallthrough]]; case BOSS_HODIR: case BOSS_THORIM: @@ -346,12 +341,8 @@ public: { scheduler.Schedule(45s, [this](TaskContext /*context*/) { - if (GameObject* go = GetGameObject(DATA_KEEPERS_GATE)) - { - go->RemoveGameObjectFlag(GO_FLAG_LOCKED); - if (Creature* trigger = instance->SummonCreature(NPC_ANCIENT_GATE_WORLD_TRIGGER, triggerAncientGatePosition, nullptr, 10 * IN_MILLISECONDS)) - trigger->AI()->Talk(EMOTE_ANCIENT_GATE_UNLOCKED); - } + if (Creature* trigger = instance->SummonCreature(NPC_ANCIENT_GATE_WORLD_TRIGGER, triggerAncientGatePosition, nullptr, 10 * IN_MILLISECONDS)) + trigger->AI()->Talk(EMOTE_ANCIENT_GATE_UNLOCKED); }); } if (type == BOSS_HODIR && state == DONE) @@ -364,33 +355,6 @@ public: ObservationRingKeepersPos[keeperIdx]); } break; - case BOSS_ALGALON: - if (GameObject* go = GetGameObject(DATA_SIGILDOOR_03)) - { - go->SetGoState(state != IN_PROGRESS ? GO_STATE_ACTIVE : GO_STATE_READY); - go->EnableCollision(false); - } - if (GameObject* go = GetGameObject(DATA_UNIVERSE_FLOOR_01)) - { - go->SetGoState(state != IN_PROGRESS ? GO_STATE_ACTIVE : GO_STATE_READY); - go->EnableCollision(false); - } - if (GameObject* go = GetGameObject(DATA_UNIVERSE_FLOOR_02)) - { - go->SetGoState(state == IN_PROGRESS ? GO_STATE_ACTIVE : GO_STATE_READY); - go->EnableCollision(false); - } - if (GameObject* go = GetGameObject(DATA_UNIVERSE_GLOBE)) - { - go->SetGoState(state == IN_PROGRESS ? GO_STATE_ACTIVE : GO_STATE_READY); - go->EnableCollision(false); - } - if (GameObject* go = GetGameObject(DATA_ALGALON_TRAPDOOR)) - { - go->SetGoState(state == IN_PROGRESS ? GO_STATE_ACTIVE : GO_STATE_READY); - go->EnableCollision(false); - } - break; default: break; } @@ -408,9 +372,6 @@ public: } } - if (state == DONE) - SaveToDB(); - if (type > BOSS_LEVIATHAN && type < MAX_ENCOUNTER && state == IN_PROGRESS) { instance->DoForAllPlayers([&](Player* player) @@ -451,7 +412,7 @@ public: hardChestPosition.GetOrientation(), 0, 0, 0, 0, 0)) { go->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE); - hmHodir = true; + _hmHodir = true; } } break; @@ -480,7 +441,7 @@ public: hardChestPosition.GetOrientation(), 0, 0, 0, 0, 0)) { go->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE); - hmHodir = true; + _hmHodir = true; } } break; @@ -512,7 +473,7 @@ public: } break; case NPC_ALGALON: - if (!m_algalonTimer) + if (!GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER)) creature->DespawnOrUnsummon(); break; //! These creatures are summoned by something else than Algalon @@ -557,46 +518,43 @@ public: // Flame Leviathan case GO_REPAIR_STATION_TRAP: { - if (m_RepairSGUID[0]) - m_RepairSGUID[1] = gameObject->GetGUID(); + if (_repairSGUID[0]) + _repairSGUID[1] = gameObject->GetGUID(); else - m_RepairSGUID[0] = gameObject->GetGUID(); + _repairSGUID[0] = gameObject->GetGUID(); break; } - case GO_LIGHTNING_WALL1: - OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - break; case GO_MIMIRONS_TARGETTING_CRYSTAL: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[3][0] = gameObject->GetGUID(); + _leviathanVisualTowers[3][0] = gameObject->GetGUID(); break; case GO_FREYAS_TARGETTING_CRYSTAL: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[0][0] = gameObject->GetGUID(); + _leviathanVisualTowers[0][0] = gameObject->GetGUID(); break; case GO_HODIRS_TARGETTING_CRYSTAL: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[2][0] = gameObject->GetGUID(); + _leviathanVisualTowers[2][0] = gameObject->GetGUID(); break; case GO_THORIMS_TARGETTING_CRYSTAL: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[1][0] = gameObject->GetGUID(); + _leviathanVisualTowers[1][0] = gameObject->GetGUID(); break; case GO_MIMIRONS_GENERATOR: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[3][1] = gameObject->GetGUID(); + _leviathanVisualTowers[3][1] = gameObject->GetGUID(); break; case GO_FREYAS_GENERATOR: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[0][1] = gameObject->GetGUID(); + _leviathanVisualTowers[0][1] = gameObject->GetGUID(); break; case GO_HODIRS_GENERATOR: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[2][1] = gameObject->GetGUID(); + _leviathanVisualTowers[2][1] = gameObject->GetGUID(); break; case GO_THORIMS_GENERATOR: OpenIfDone(BOSS_LEVIATHAN, gameObject, GO_STATE_ACTIVE); - m_leviathanVisualTowers[1][1] = gameObject->GetGUID(); + _leviathanVisualTowers[1][1] = gameObject->GetGUID(); break; case GO_LEVIATHAN_DOORS: if (GetBossState(BOSS_LEVIATHAN) >= DONE) @@ -605,9 +563,6 @@ public: case GO_KOLOGARN_BRIDGE: OpenIfDone(BOSS_KOLOGARN, gameObject, GO_STATE_READY); break; - case GO_ARCHIVUM_DOORS: - OpenIfDone(BOSS_ASSEMBLY, gameObject, GO_STATE_ACTIVE); - break; case GO_KEEPERS_GATE: if (AllBossesDone({BOSS_MIMIRON, BOSS_FREYA, BOSS_HODIR, BOSS_THORIM})) gameObject->RemoveGameObjectFlag(GO_FLAG_LOCKED); @@ -616,43 +571,33 @@ public: case GO_MIMIRON_ELEVATOR: gameObject->EnableCollision(false); break; - case GO_HODIR_FROZEN_DOOR: - case GO_HODIR_DOOR: - if (GetBossState(BOSS_HODIR) == DONE) - if (gameObject->GetGoState() != GO_STATE_ACTIVE ) - { - gameObject->SetLootState(GO_READY); - gameObject->UseDoorOrButton(0, false); - } - break; - case GO_VEZAX_DOOR: - if (GetBossState(BOSS_VEZAX) == DONE ) - if (gameObject->GetGoState() != GO_STATE_ACTIVE ) - { - gameObject->SetLootState(GO_READY); - gameObject->UseDoorOrButton(0, false); - } - break; case GO_SNOW_MOUND: gameObject->EnableCollision(false); break; // Mimiron Tram case GO_MIMIRON_TRAM: if (GetBossState(BOSS_MIMIRON) == DONE) - m_mimironTramUsed = true; + _mimironTramUsed = true; break; // Algalon the Observer case GO_CELESTIAL_PLANETARIUM_ACCESS_10: case GO_CELESTIAL_PLANETARIUM_ACCESS_25: - if (m_algalonTimer) + if (GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER)) gameObject->SetGameObjectFlag(GO_FLAG_IN_USE); break; + case GO_DOODAD_UL_SIGILDOOR_03: + case GO_DOODAD_UL_UNIVERSEFLOOR_01: + case GO_DOODAD_UL_UNIVERSEFLOOR_02: + case GO_DOODAD_UL_UNIVERSEGLOBE01: + case GO_DOODAD_UL_ULDUAR_TRAPDOOR_03: + gameObject->EnableCollision(false); + break; case GO_DOODAD_UL_SIGILDOOR_01: - if (m_algalonTimer) + if (GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER)) gameObject->SetGoState(GO_STATE_ACTIVE); break; case GO_DOODAD_UL_SIGILDOOR_02: - if (m_algalonTimer) + if (GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER)) gameObject->SetGoState(GO_STATE_ACTIVE); break; // Herbs @@ -674,7 +619,7 @@ public: switch (boss) { case BOSS_HODIR: - if (hmHodir) + if (_hmHodir) { if (GameObject* go = GetHodirChest(true)) { @@ -699,30 +644,27 @@ public: case TYPE_HODIR_HM_FAIL: if (GameObject* go = GetHodirChest(true)) { - hmHodir = false; + _hmHodir = false; go->Delete(); } break; - case TYPE_WATCHERS: - m_watchersMask |= 1 << data; - [[fallthrough]]; + case DATA_MAGE_BARRIER: + StorePersistentData( + PERSISTENT_DATA_MAGE_BARRIER, data); + break; case EVENT_KEEPER_TELEPORTED: if (Creature* sara = GetCreature(DATA_SARA)) sara->AI()->DoAction(ACTION_SARA_UPDATE_SUMMON_KEEPERS); break; - case DATA_MAGE_BARRIER: - m_mageBarrier = data; - break; - case EVENT_TOWER_OF_LIFE_DESTROYED: case EVENT_TOWER_OF_STORM_DESTROYED: case EVENT_TOWER_OF_FROST_DESTROYED: case EVENT_TOWER_OF_FLAMES_DESTROYED: { - m_leviathanTowers[type - EVENT_TOWER_OF_LIFE_DESTROYED] = data; + _leviathanTowers[type - EVENT_TOWER_OF_LIFE_DESTROYED] = data; for (uint8 i = 0; i < 2; ++i) { - if (GameObject *gameObject = instance->GetGameObject(m_leviathanVisualTowers[type - EVENT_TOWER_OF_LIFE_DESTROYED][i])) + if (GameObject *gameObject = instance->GetGameObject(_leviathanVisualTowers[type - EVENT_TOWER_OF_LIFE_DESTROYED][i])) { gameObject->SetGoState(GO_STATE_ACTIVE); } @@ -733,23 +675,20 @@ public: case DATA_VEHICLE_SPAWN: SpawnLeviathanEncounterVehicles(data); return; - case DATA_UNBROKEN_ACHIEVEMENT: - m_unbrokenAchievement = data; - SaveToDB(); - return; case DATA_DESPAWN_ALGALON: DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, 1); DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_DESPAWN_TIMER, 60); - m_algalonTimer = 60; + StorePersistentData(PERSISTENT_DATA_ALGALON_TIMER, 60); _events.RescheduleEvent(EVENT_UPDATE_ALGALON_TIMER, 1min); - SaveToDB(); return; case DATA_ALGALON_SUMMON_STATE: case DATA_ALGALON_DEFEATED: DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, 0); - m_algalonTimer = (type == DATA_ALGALON_DEFEATED ? TIMER_ALGALON_DEFEATED : TIMER_ALGALON_SUMMONED); + StorePersistentData(PERSISTENT_DATA_ALGALON_TIMER, + type == DATA_ALGALON_DEFEATED + ? TIMER_ALGALON_DEFEATED + : TIMER_ALGALON_SUMMONED); _events.CancelEvent(EVENT_UPDATE_ALGALON_TIMER); - SaveToDB(); return; // Achievement case DATA_DWARFAGEDDON: @@ -820,8 +759,6 @@ public: break; } - if (type == TYPE_WATCHERS) - SaveToDB(); } ObjectGuid GetGuidData(uint32 data) const override @@ -830,9 +767,9 @@ public: { // Flame Leviathan case DATA_REPAIR_STATION1: - return m_RepairSGUID[0]; + return _repairSGUID[0]; case DATA_REPAIR_STATION2: - return m_RepairSGUID[1]; + return _repairSGUID[1]; } @@ -843,23 +780,18 @@ public: { switch (type) { - case TYPE_WATCHERS: - return m_watchersMask; - case EVENT_TOWER_OF_LIFE_DESTROYED: case EVENT_TOWER_OF_STORM_DESTROYED: case EVENT_TOWER_OF_FROST_DESTROYED: case EVENT_TOWER_OF_FLAMES_DESTROYED: - return m_leviathanTowers[type - EVENT_TOWER_OF_LIFE_DESTROYED]; + return _leviathanTowers[type - EVENT_TOWER_OF_LIFE_DESTROYED]; case DATA_MAGE_BARRIER: - return m_mageBarrier; - - case DATA_UNBROKEN_ACHIEVEMENT: - return m_unbrokenAchievement; + return GetPersistentData( + PERSISTENT_DATA_MAGE_BARRIER); case DATA_CALL_TRAM: - return m_mimironTramUsed; + return _mimironTramUsed; } return 0; @@ -876,11 +808,15 @@ public: } else if (unit->IsCreature() && unit->GetAreaId() == AREA_THE_CONSERVATORY_OF_LIFE) { - if (GameTime::GetGameTime().count() > (m_conspeedatoryAttempt + DAY)) + uint32 conspeedatory = + GetPersistentData(PERSISTENT_DATA_CONSPEEDATORY); + if (GameTime::GetGameTime().count() > (conspeedatory + DAY)) { - DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, 21597 /*CON-SPEED-ATORY_TIMED_CRITERIA*/); - m_conspeedatoryAttempt = GameTime::GetGameTime().count(); - SaveToDB(); + DoStartTimedAchievement( + ACHIEVEMENT_TIMED_TYPE_EVENT, + 21597 /*CON-SPEED-ATORY_TIMED_CRITERIA*/); + StorePersistentData(PERSISTENT_DATA_CONSPEEDATORY, + GameTime::GetGameTime().count()); } } @@ -888,55 +824,52 @@ public: if (unit->IsPlayer()) for (uint8 i = 0; i <= 12; ++i) { - bool go = false; + bool inCombat = false; if (i == BOSS_LEVIATHAN) { if (Creature* c = GetCreature(BOSS_LEVIATHAN)) if (c->IsInCombat()) - go = true; + inCombat = true; } else - go = (GetBossState(i) == IN_PROGRESS); + inCombat = (GetBossState(i) == IN_PROGRESS); - if (go && (C_of_Ulduar_MASK & (1 << i)) == 0) - { - C_of_Ulduar_MASK |= (1 << i); - SaveToDB(); - } + uint32 mask = + GetPersistentData(PERSISTENT_DATA_C_OF_ULDUAR_MASK); + if (inCombat && (mask & (1 << i)) == 0) + StorePersistentData( + PERSISTENT_DATA_C_OF_ULDUAR_MASK, + mask | (1 << i)); } } - void ReadSaveDataMore(std::istringstream& data) override + void Load(char const* data) override { - // Boss states 0-13 are read by base InstanceScript. - data >> m_watchersMask; - data >> m_conspeedatoryAttempt; - data >> m_unbrokenAchievement; - data >> m_algalonTimer; + InstanceScript::Load(data); - if (m_algalonTimer == TIMER_ALGALON_SUMMONED) - m_algalonTimer = TIMER_ALGALON_TO_SUMMON; + if (!data) + StorePersistentData(PERSISTENT_DATA_UNBROKEN, 1); - if (m_algalonTimer && m_algalonTimer <= 60 && GetBossState(BOSS_ALGALON) != DONE) + uint32 algalonTimer = + GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER); + if (algalonTimer == TIMER_ALGALON_SUMMONED) { - DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, 1); - DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_DESPAWN_TIMER, m_algalonTimer); + StorePersistentData( + PERSISTENT_DATA_ALGALON_TIMER, + TIMER_ALGALON_TO_SUMMON); } - data >> C_of_Ulduar_MASK; - data >> m_mageBarrier; - } - - void WriteSaveDataMore(std::ostringstream& data) override - { - // Boss states 0-13 are written by base InstanceScript. - // Only write extra non-boss data here. - data << m_watchersMask << ' ' - << m_conspeedatoryAttempt << ' ' - << m_unbrokenAchievement << ' ' - << m_algalonTimer << ' ' - << C_of_Ulduar_MASK << ' ' - << m_mageBarrier; + algalonTimer = + GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER); + if (algalonTimer && algalonTimer <= 60 + && GetBossState(BOSS_ALGALON) != DONE) + { + DoUpdateWorldState( + WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, 1); + DoUpdateWorldState( + WORLD_STATE_ULDUAR_ALGALON_DESPAWN_TIMER, + algalonTimer); + } } void Update(uint32 diff) override @@ -950,12 +883,19 @@ public: switch (_events.ExecuteEvent()) { case EVENT_UPDATE_ALGALON_TIMER: - if (m_algalonTimer == TIMER_ALGALON_DEFEATED) + { + uint32 algalonTimer = + GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER); + if (algalonTimer == TIMER_ALGALON_DEFEATED) return; - SaveToDB(); - DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_DESPAWN_TIMER, --m_algalonTimer); - if (m_algalonTimer) + StorePersistentData( + PERSISTENT_DATA_ALGALON_TIMER, + --algalonTimer); + DoUpdateWorldState( + WORLD_STATE_ULDUAR_ALGALON_DESPAWN_TIMER, + algalonTimer); + if (algalonTimer) { _events.Repeat(1min); return; @@ -964,6 +904,7 @@ public: SetData(DATA_ALGALON_DEFEATED, 1); if (Creature* algalon = GetCreature(BOSS_ALGALON)) algalon->AI()->DoAction(ACTION_DESPAWN_ALGALON); + } } } @@ -971,47 +912,48 @@ public: bool CheckAchievementCriteriaMeet(uint32 criteria_id, Player const* /*source*/, Unit const* /*target*/, uint32 /*miscvalue1*/) override { + uint32 mask = GetPersistentData(PERSISTENT_DATA_C_OF_ULDUAR_MASK); switch (criteria_id) { case 10042: case 10352: - return (C_of_Ulduar_MASK & (1 << BOSS_LEVIATHAN)) == 0; + return (mask & (1 << BOSS_LEVIATHAN)) == 0; case 10342: case 10355: - return (C_of_Ulduar_MASK & (1 << BOSS_IGNIS)) == 0; + return (mask & (1 << BOSS_IGNIS)) == 0; case 10340: case 10353: - return (C_of_Ulduar_MASK & (1 << BOSS_RAZORSCALE)) == 0; + return (mask & (1 << BOSS_RAZORSCALE)) == 0; case 10341: case 10354: - return (C_of_Ulduar_MASK & (1 << BOSS_XT002)) == 0; + return (mask & (1 << BOSS_XT002)) == 0; case 10598: case 10599: - return (C_of_Ulduar_MASK & (1 << BOSS_ASSEMBLY)) == 0; + return (mask & (1 << BOSS_ASSEMBLY)) == 0; case 10348: case 10357: - return (C_of_Ulduar_MASK & (1 << BOSS_KOLOGARN)) == 0; + return (mask & (1 << BOSS_KOLOGARN)) == 0; case 10351: case 10363: - return (C_of_Ulduar_MASK & (1 << BOSS_AURIAYA)) == 0; + return (mask & (1 << BOSS_AURIAYA)) == 0; case 10439: case 10719: - return (C_of_Ulduar_MASK & (1 << BOSS_HODIR)) == 0; + return (mask & (1 << BOSS_HODIR)) == 0; case 10403: case 10404: - return (C_of_Ulduar_MASK & (1 << BOSS_THORIM)) == 0; + return (mask & (1 << BOSS_THORIM)) == 0; case 10582: case 10583: - return (C_of_Ulduar_MASK & (1 << BOSS_FREYA)) == 0; + return (mask & (1 << BOSS_FREYA)) == 0; case 10347: case 10361: - return (C_of_Ulduar_MASK & (1 << BOSS_MIMIRON)) == 0; + return (mask & (1 << BOSS_MIMIRON)) == 0; case 10349: case 10362: - return (C_of_Ulduar_MASK & (1 << BOSS_VEZAX)) == 0; + return (mask & (1 << BOSS_VEZAX)) == 0; case 10350: case 10364: - return (C_of_Ulduar_MASK & (1 << BOSS_YOGGSARON)) == 0; + return (mask & (1 << BOSS_YOGGSARON)) == 0; } return false; } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.cpp index 0601363a2..411e9ac10 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.cpp @@ -137,7 +137,13 @@ public: if (spellInfo->Id == SPELL_TELEPORT) { me->DespawnOrUnsummon(); - me->GetInstanceScript()->SetData(TYPE_WATCHERS, _keeper); + InstanceScript* instance = me->GetInstanceScript(); + uint32 mask = instance->GetPersistentData( + PERSISTENT_DATA_WATCHERS_MASK); + instance->StorePersistentData( + PERSISTENT_DATA_WATCHERS_MASK, + mask | (1 << _keeper)); + instance->SetData(EVENT_KEEPER_TELEPORTED, 0); } } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h index 9883e2ea6..eb50851c8 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h @@ -306,6 +306,17 @@ enum UlduarGameObjects GO_GIFT_OF_THE_OBSERVER_25 = 194822, }; +enum UlduarPersistentData +{ + PERSISTENT_DATA_WATCHERS_MASK = 0, + PERSISTENT_DATA_CONSPEEDATORY, + PERSISTENT_DATA_UNBROKEN, + PERSISTENT_DATA_ALGALON_TIMER, + PERSISTENT_DATA_C_OF_ULDUAR_MASK, + PERSISTENT_DATA_MAGE_BARRIER, + MAX_PERSISTENT_DATA +}; + enum UlduarMisc { // Flame Leviathan From 35102dcfe95e0ddc5aa9a76dc59cf780a6e656c7 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:20:09 -0500 Subject: [PATCH 104/132] fix(Core/Movement): Clear CanNotReachTarget when chase spline finishes (#25235) Co-authored-by: ccrs Co-authored-by: TrinityCore --- .../MovementGenerators/TargetedMovementGenerator.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index 457698f18..188578ea0 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -259,12 +259,7 @@ bool ChaseMovementGenerator::DoUpdate(T* owner, uint32 time_diff) owner->SetInFront(target); if (cOwner) - { - if (cOwner->IsWithinMeleeRange(target)) - cOwner->SetCannotReachTarget(); - else - cOwner->SetCannotReachTarget(target->GetGUID()); - } + cOwner->SetCannotReachTarget(); MovementInform(owner); } From fc2d3f332e9590f4dd583a2ffe67b3bae17758bc Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 26 Mar 2026 06:59:05 -0300 Subject: [PATCH 105/132] feat(Scripts/Commands): add .mail list and .mail return commands (#25213) Co-authored-by: Claude Opus 4.6 --- .../rev_1742940000000000000.sql | 19 + src/server/game/Miscellaneous/Language.h | 13 +- src/server/scripts/Commands/cs_mail.cpp | 389 ++++++++++++++++++ .../scripts/Commands/cs_script_loader.cpp | 2 + 4 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 data/sql/updates/pending_db_world/rev_1742940000000000000.sql create mode 100644 src/server/scripts/Commands/cs_mail.cpp diff --git a/data/sql/updates/pending_db_world/rev_1742940000000000000.sql b/data/sql/updates/pending_db_world/rev_1742940000000000000.sql new file mode 100644 index 000000000..62cd350c7 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1742940000000000000.sql @@ -0,0 +1,19 @@ +-- Add mail commands +DELETE FROM `command` WHERE `name` IN ('mail', 'mail list', 'mail return'); +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('mail', 2, 'Syntax: .mail $subcommand\nType .mail to see a list of subcommands or .help mail $subcommand to see info on subcommands.'), +('mail list', 2, 'Syntax: .mail list [$player]\nDisplays all mail data (except subject and body) for the target player.'), +('mail return', 2, 'Syntax: .mail return $player $mailId\nReturns the specified mail to its original sender.'); + +-- Add acore_string entries for mail commands +DELETE FROM `acore_string` WHERE `entry` IN (5135, 5136, 5137, 5138, 5139, 5140, 5141, 5142, 5143); +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(5135, 'Mail list for player {}:', '플레이어 {}의 우편 목록:', 'Liste des courriers du joueur {} :', 'Postliste für Spieler {}:', '玩家 {} 的邮件列表:', '玩家 {} 的郵件列表:', 'Lista de correo del jugador {}:', 'Lista de correo del jugador {}:', 'Список писем игрока {}:'), +(5136, ' ID: {} | Type: {} | Stationery: {} | Template: {} | Sender: {} ({}) | Receiver: {} | Expires: {} | Delivered: {} | Money: {} | COD: {} | Flags: {} | Items: {}', ' ID: {} | 유형: {} | 편지지: {} | 템플릿: {} | 발신자: {} ({}) | 수신자: {} | 만료: {} | 배달: {} | 금액: {} | 대금상환: {} | 플래그: {} | 아이템: {}', ' ID : {} | Type : {} | Papeterie : {} | Modèle : {} | Expéditeur : {} ({}) | Destinataire : {} | Expiration : {} | Livré : {} | Argent : {} | Remboursement : {} | Drapeaux : {} | Objets : {}', ' ID: {} | Typ: {} | Briefpapier: {} | Vorlage: {} | Absender: {} ({}) | Empfänger: {} | Ablauf: {} | Zugestellt: {} | Gold: {} | Nachnahme: {} | Flags: {} | Gegenstände: {}', ' ID:{} | 类型:{} | 信纸:{} | 模板:{} | 发件人:{} ({}) | 收件人:{} | 过期:{} | 投递:{} | 金币:{} | 货到付款:{} | 标记:{} | 物品:{}', ' ID:{} | 類型:{} | 信紙:{} | 範本:{} | 寄件人:{} ({}) | 收件人:{} | 到期:{} | 投遞:{} | 金幣:{} | 貨到付款:{} | 標記:{} | 物品:{}', ' ID: {} | Tipo: {} | Papelería: {} | Plantilla: {} | Remitente: {} ({}) | Destinatario: {} | Expira: {} | Entregado: {} | Dinero: {} | Contrareembolso: {} | Marcas: {} | Objetos: {}', ' ID: {} | Tipo: {} | Papelería: {} | Plantilla: {} | Remitente: {} ({}) | Destinatario: {} | Expira: {} | Entregado: {} | Dinero: {} | Contrareembolso: {} | Marcas: {} | Objetos: {}', ' ID: {} | Тип: {} | Бланк: {} | Шаблон: {} | Отправитель: {} ({}) | Получатель: {} | Истекает: {} | Доставлено: {} | Деньги: {} | Наложенный платёж: {} | Флаги: {} | Предметы: {}'), +(5137, 'No mail found for player {}.', '플레이어 {}의 우편이 없습니다.', 'Aucun courrier trouvé pour le joueur {}.', 'Keine Post für Spieler {} gefunden.', '未找到玩家 {} 的邮件。', '未找到玩家 {} 的郵件。', 'No se encontró correo para el jugador {}.', 'No se encontró correo para el jugador {}.', 'Письма для игрока {} не найдены.'), +(5138, 'Mail #{} returned to sender for player {}.', '우편 #{}이(가) 플레이어 {}의 발신자에게 반송되었습니다.', 'Courrier #{} renvoyé à l''expéditeur pour le joueur {}.', 'Post #{} wurde an den Absender für Spieler {} zurückgesendet.', '邮件 #{} 已退回给玩家 {} 的发件人。', '郵件 #{} 已退回給玩家 {} 的寄件人。', 'Correo #{} devuelto al remitente del jugador {}.', 'Correo #{} devuelto al remitente del jugador {}.', 'Письмо #{} возвращено отправителю для игрока {}.'), +(5139, 'Mail #{} not found.', '우편 #{}을(를) 찾을 수 없습니다.', 'Courrier #{} introuvable.', 'Post #{} nicht gefunden.', '未找到邮件 #{}。', '未找到郵件 #{}。', 'Correo #{} no encontrado.', 'Correo #{} no encontrado.', 'Письмо #{} не найдено.'), +(5140, 'Only normal mail can be returned.', '일반 우편만 반송할 수 있습니다.', 'Seul le courrier normal peut être renvoyé.', 'Nur normale Post kann zurückgesendet werden.', '只能退回普通邮件。', '只能退回普通郵件。', 'Solo se puede devolver correo normal.', 'Solo se puede devolver correo normal.', 'Можно вернуть только обычные письма.'), +(5141, 'Mail has no valid sender to return to.', '반송할 유효한 발신자가 없습니다.', 'Le courrier n''a pas d''expéditeur valide.', 'Die Post hat keinen gültigen Absender.', '邮件没有有效的发件人可退回。', '郵件沒有有效的寄件人可退回。', 'El correo no tiene un remitente válido.', 'El correo no tiene un remitente válido.', 'У письма нет действительного отправителя для возврата.'), +(5142, 'Mail has already been returned.', '이미 반송된 우편입니다.', 'Ce courrier a déjà été renvoyé.', 'Diese Post wurde bereits zurückgesendet.', '邮件已被退回。', '郵件已被退回。', 'El correo ya ha sido devuelto.', 'El correo ya ha sido devuelto.', 'Письмо уже было возвращено.'), +(5143, 'A script hook prevented this mail from being returned.', '스크립트 후크가 이 우편의 반송을 차단했습니다.', 'Un hook de script a empêché le renvoi de ce courrier.', 'Ein Script-Hook hat die Rücksendung dieser Post verhindert.', '脚本钩子阻止了此邮件的退回。', '腳本鉤子阻止了此郵件的退回。', 'Un hook de script impidió la devolución de este correo.', 'Un hook de script impidió la devolución de este correo.', 'Хук скрипта предотвратил возврат этого письма.'); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 23847a5f3..5fc80fca9 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1215,7 +1215,18 @@ enum AcoreStrings LANG_AUTOBROADCAST_LOCALE_ENTRY = 5133, LANG_AUTOBROADCAST_INVALID_LOCALE = 5134, - // Room for more strings 5135-9999 + // Mail commands + LANG_MAIL_LIST_HEADER = 5135, + LANG_MAIL_LIST_ENTRY = 5136, + LANG_MAIL_LIST_EMPTY = 5137, + LANG_MAIL_RETURN_SUCCESS = 5138, + LANG_MAIL_RETURN_NOT_FOUND = 5139, + LANG_MAIL_RETURN_NOT_NORMAL = 5140, + LANG_MAIL_RETURN_NO_SENDER = 5141, + LANG_MAIL_RETURN_ALREADY_RETURNED = 5142, + LANG_MAIL_RETURN_HOOK_BLOCKED = 5143, + + // Room for more strings 5144-9999 // Level requirement notifications LANG_SAY_REQ = 6604, diff --git a/src/server/scripts/Commands/cs_mail.cpp b/src/server/scripts/Commands/cs_mail.cpp new file mode 100644 index 000000000..6f622e2b3 --- /dev/null +++ b/src/server/scripts/Commands/cs_mail.cpp @@ -0,0 +1,389 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Bag.h" +#include "CharacterCache.h" +#include "Chat.h" +#include "CommandScript.h" +#include "DatabaseEnv.h" +#include "GameTime.h" +#include "Language.h" +#include "Mail.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Timer.h" + +using namespace Acore::ChatCommands; + +class mail_commandscript : public CommandScript +{ +public: + mail_commandscript() : CommandScript("mail_commandscript") { } + + ChatCommandTable GetCommands() const override + { + static ChatCommandTable mailCommandTable = + { + { "list", HandleMailListCommand, SEC_GAMEMASTER, Console::Yes }, + { "return", HandleMailReturnCommand, SEC_GAMEMASTER, Console::Yes } + }; + + static ChatCommandTable commandTable = + { + { "mail", mailCommandTable } + }; + + return commandTable; + } + + static char const* GetMailTypeString(uint8 messageType) + { + switch (messageType) + { + case MAIL_NORMAL: return "Normal"; + case MAIL_AUCTION: return "Auction"; + case MAIL_CREATURE: return "Creature"; + case MAIL_GAMEOBJECT: return "GameObject"; + case MAIL_CALENDAR: return "Calendar"; + default: return "Unknown"; + } + } + + static char const* GetMailStationeryString(uint8 stationery) + { + switch (stationery) + { + case MAIL_STATIONERY_TEST: return "Test"; + case MAIL_STATIONERY_DEFAULT: return "Default"; + case MAIL_STATIONERY_GM: return "GM"; + case MAIL_STATIONERY_AUCTION: return "Auction"; + case MAIL_STATIONERY_VAL: return "Valentine"; + case MAIL_STATIONERY_CHR: return "Christmas"; + case MAIL_STATIONERY_ORP: return "Orphan"; + default: return "Unknown"; + } + } + + static std::string GetItemListString(std::vector const& items) + { + if (items.empty()) + return "none"; + + std::string result; + for (size_t i = 0; i < items.size(); ++i) + { + if (i > 0) + result += ", "; + + result += Acore::StringFormat("{}(guid:{})", items[i].item_template, items[i].item_guid); + } + return result; + } + + static bool HandleMailListCommand(ChatHandler* handler, Optional target) + { + if (!target) + target = PlayerIdentifier::FromTargetOrSelf(handler); + + if (!target) + return false; + + Player* player = target->GetConnectedPlayer(); + + if (player) + { + PlayerMails const& mails = player->GetMails(); + + if (mails.empty()) + { + handler->PSendSysMessage(LANG_MAIL_LIST_EMPTY, handler->playerLink(target->GetName())); + return true; + } + + handler->PSendSysMessage(LANG_MAIL_LIST_HEADER, handler->playerLink(target->GetName())); + + for (Mail const* mail : mails) + { + std::string senderName; + if (mail->messageType == MAIL_NORMAL) + sCharacterCache->GetCharacterNameByGuid(ObjectGuid(HighGuid::Player, mail->sender), senderName); + + std::string expireStr = Acore::Time::TimeToTimestampStr(Seconds(mail->expire_time)); + std::string deliverStr = Acore::Time::TimeToTimestampStr(Seconds(mail->deliver_time)); + + handler->PSendSysMessage(LANG_MAIL_LIST_ENTRY, + mail->messageID, GetMailTypeString(mail->messageType), GetMailStationeryString(mail->stationery), + mail->mailTemplateId, mail->sender, senderName, mail->receiver, + expireStr, deliverStr, mail->money, mail->COD, mail->checked, GetItemListString(mail->items)); + } + } + else + { + ObjectGuid::LowType lowGuid = target->GetGUID().GetCounter(); + + QueryResult result = CharacterDatabase.Query( + "SELECT id, messageType, sender, receiver, subject, body, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId" + " FROM mail WHERE receiver = {} AND deliver_time <= {} ORDER BY id DESC", + lowGuid, GameTime::GetGameTime().count()); + + if (!result) + { + handler->PSendSysMessage(LANG_MAIL_LIST_EMPTY, handler->playerLink(target->GetName())); + return true; + } + + handler->PSendSysMessage(LANG_MAIL_LIST_HEADER, handler->playerLink(target->GetName())); + + do + { + Field* fields = result->Fetch(); + uint32 messageID = fields[0].Get(); + uint8 messageType = fields[1].Get(); + uint32 sender = fields[2].Get(); + uint32 receiver = fields[3].Get(); + // fields[4] = subject (skipped) + // fields[5] = body (skipped) + uint32 expireTime = fields[6].Get(); + uint32 deliverTime = fields[7].Get(); + uint32 money = fields[8].Get(); + uint32 cod = fields[9].Get(); + uint32 checked = fields[10].Get(); + uint8 stationery = fields[11].Get(); + uint16 mailTemplate = fields[12].Get(); + + std::string senderName; + if (messageType == MAIL_NORMAL) + sCharacterCache->GetCharacterNameByGuid(ObjectGuid(HighGuid::Player, sender), senderName); + + std::string expireStr = Acore::Time::TimeToTimestampStr(Seconds(expireTime)); + std::string deliverStr = Acore::Time::TimeToTimestampStr(Seconds(deliverTime)); + + // For offline players we don't have item details loaded + handler->PSendSysMessage(LANG_MAIL_LIST_ENTRY, + messageID, GetMailTypeString(messageType), GetMailStationeryString(stationery), + mailTemplate, sender, senderName, receiver, + expireStr, deliverStr, money, cod, checked, "N/A (offline)"); + } while (result->NextRow()); + } + + return true; + } + + static bool HandleMailReturnCommand(ChatHandler* handler, PlayerIdentifier target, uint32 mailId) + { + // Query mail data from DB so this works for offline players + QueryResult result = CharacterDatabase.Query( + "SELECT messageType, sender, receiver, subject, body, money, mailTemplateId, checked, deliver_time" + " FROM mail WHERE id = {}", mailId); + + if (!result) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_NOT_FOUND, mailId); + return true; + } + + Field* fields = result->Fetch(); + uint8 messageType = fields[0].Get(); + uint32 sender = fields[1].Get(); + uint32 receiver = fields[2].Get(); + std::string subject = fields[3].Get(); + std::string body = fields[4].Get(); + uint32 money = fields[5].Get(); + uint16 mailTemplate = fields[6].Get(); + uint32 checked = fields[7].Get(); + uint32 deliverTime = fields[8].Get(); + + // Reject undelivered mail, same as the core handler + if (deliverTime > GameTime::GetGameTime().count()) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_NOT_FOUND, mailId); + return true; + } + + // Verify the mail belongs to the target player + if (receiver != target.GetGUID().GetCounter()) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_NOT_FOUND, mailId); + return true; + } + + if (messageType != MAIL_NORMAL) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_NOT_NORMAL); + return true; + } + + if (!sender) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_NO_SENDER); + return true; + } + + if (checked & MAIL_CHECK_MASK_RETURNED) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_ALREADY_RETURNED); + return true; + } + + Player* player = target.GetConnectedPlayer(); + + // Run the same script hook as the client return handler, failing early before any deletions + if (player) + { + Mail* m = player->GetMail(mailId); + if (m) + { + ObjectGuid senderGuid = ObjectGuid(HighGuid::Player, sender); + + if (m->HasItems()) + { + for (auto const& itemInfo : m->items) + { + Item* item = player->GetMItem(itemInfo.item_guid); + if (item && !sScriptMgr->OnPlayerCanSendMail(player, senderGuid, ObjectGuid::Empty, subject, body, money, 0, item)) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_HOOK_BLOCKED); + return true; + } + } + } + else if (!sScriptMgr->OnPlayerCanSendMail(player, senderGuid, ObjectGuid::Empty, subject, body, money, 0, nullptr)) + { + handler->SendErrorMessage(LANG_MAIL_RETURN_HOOK_BLOCKED); + return true; + } + } + } + + // Same logic as WorldSession::HandleReturnToSender + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID); + stmt->SetData(0, mailId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID); + stmt->SetData(0, mailId); + trans->Append(stmt); + + MailDraft draft(subject, body); + if (mailTemplate) + draft = MailDraft(mailTemplate, false); + + if (player) + { + // Online: same logic as WorldSession::HandleReturnToSender + // Get pointer before RemoveMail (which removes from deque but does not delete the object) + Mail* m = player->GetMail(mailId); + + player->RemoveMail(mailId); + + if (m && m->HasItems()) + { + for (auto const& itemInfo : m->items) + { + if (Item* item = player->GetMItem(itemInfo.item_guid)) + draft.AddItem(item); + + player->RemoveMItem(itemInfo.item_guid); + } + } + + delete m; + } + else + { + // Offline: load Item* objects from DB using same query shape as CHAR_SEL_MAILITEMS + // (LEFT JOIN to handle dangling mail_items) and same logic as Player::_LoadMailedItem + QueryResult itemResult = CharacterDatabase.Query( + "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments," + " randomPropertyId, durability, playedTime, text, mi.item_guid, itemEntry, ii.owner_guid" + " FROM mail_items mi LEFT JOIN item_instance ii ON mi.item_guid = ii.guid" + " WHERE mi.mail_id = {}", mailId); + + if (itemResult) + { + do + { + Field* itemFields = itemResult->Fetch(); + uint32 itemGuid = itemFields[11].Get(); + uint32 itemEntry = itemFields[12].Get(); + + // Handle dangling mail_items (missing item_instance) + if (!itemEntry) + { + LOG_ERROR("misc", "cs_mail: Mail #{} has dangling mail_items row for item_guid {}. Cleaning up.", mailId, itemGuid); + + CharacterDatabasePreparedStatement* delStmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); + delStmt->SetData(0, itemGuid); + trans->Append(delStmt); + continue; + } + + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry); + if (!proto) + { + LOG_ERROR("misc", "cs_mail: Mail #{} has unknown item (entry: {}, guid: {}). Cleaning up.", mailId, itemEntry, itemGuid); + + CharacterDatabasePreparedStatement* delStmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); + delStmt->SetData(0, itemGuid); + trans->Append(delStmt); + continue; + } + + Item* item = NewItemOrBag(proto); + ObjectGuid ownerGuid = itemFields[13].Get() + ? ObjectGuid::Create(itemFields[13].Get()) + : ObjectGuid::Empty; + + if (!item->LoadFromDB(itemGuid, ownerGuid, itemFields, itemEntry)) + { + LOG_ERROR("misc", "cs_mail: Item (GUID: {}) in mail #{} failed to load. Cleaning up.", itemGuid, mailId); + + CharacterDatabasePreparedStatement* delStmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); + delStmt->SetData(0, itemGuid); + trans->Append(delStmt); + + item->FSetState(ITEM_REMOVED); + CharacterDatabaseTransaction nullTrans = CharacterDatabaseTransaction(nullptr); + item->SaveToDB(nullTrans); + return true; + } + + draft.AddItem(item); + } while (itemResult->NextRow()); + } + } + + uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(ObjectGuid(HighGuid::Player, receiver)); + draft.AddMoney(money).SendReturnToSender(accountId, receiver, sender, trans); + + CharacterDatabase.CommitTransaction(trans); + + sCharacterCache->DecreaseCharacterMailCount(ObjectGuid(HighGuid::Player, receiver)); + + handler->PSendSysMessage(LANG_MAIL_RETURN_SUCCESS, mailId, handler->playerLink(target.GetName())); + return true; + } +}; + +void AddSC_mail_commandscript() +{ + new mail_commandscript(); +} diff --git a/src/server/scripts/Commands/cs_script_loader.cpp b/src/server/scripts/Commands/cs_script_loader.cpp index 8b867a0fa..a3e68d004 100644 --- a/src/server/scripts/Commands/cs_script_loader.cpp +++ b/src/server/scripts/Commands/cs_script_loader.cpp @@ -43,6 +43,7 @@ void AddSC_learn_commandscript(); void AddSC_lfg_commandscript(); void AddSC_list_commandscript(); void AddSC_lookup_commandscript(); +void AddSC_mail_commandscript(); void AddSC_message_commandscript(); void AddSC_misc_commandscript(); void AddSC_mmaps_commandscript(); @@ -98,6 +99,7 @@ void AddCommandsScripts() AddSC_lfg_commandscript(); AddSC_list_commandscript(); AddSC_lookup_commandscript(); + AddSC_mail_commandscript(); AddSC_message_commandscript(); AddSC_misc_commandscript(); AddSC_mmaps_commandscript(); From 17cad7afe14ae53422db613425ac058336426e32 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 10:00:14 +0000 Subject: [PATCH 106/132] chore(DB): import pending files Referenced commit(s): fc2d3f332e9590f4dd583a2ffe67b3bae17758bc --- .../rev_1742940000000000000.sql => db_world/2026_03_26_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1742940000000000000.sql => db_world/2026_03_26_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1742940000000000000.sql b/data/sql/updates/db_world/2026_03_26_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1742940000000000000.sql rename to data/sql/updates/db_world/2026_03_26_00.sql index 62cd350c7..8bed49dd2 100644 --- a/data/sql/updates/pending_db_world/rev_1742940000000000000.sql +++ b/data/sql/updates/db_world/2026_03_26_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_25_06 -> 2026_03_26_00 -- Add mail commands DELETE FROM `command` WHERE `name` IN ('mail', 'mail list', 'mail return'); INSERT INTO `command` (`name`, `security`, `help`) VALUES From 7370729919302cf4502d5a63da919e5cb5fe959b Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:37:53 +0100 Subject: [PATCH 107/132] fix(Script/ObsidianSanctum): Solve path and visual effect issues with Flame Tsunami. (#25243) Co-authored-by: Angelo Venturini --- data/sql/updates/pending_db_world/rev_1774544642.sql | 3 +++ .../ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774544642.sql diff --git a/data/sql/updates/pending_db_world/rev_1774544642.sql b/data/sql/updates/pending_db_world/rev_1774544642.sql new file mode 100644 index 000000000..73cd8005f --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774544642.sql @@ -0,0 +1,3 @@ + +-- Set Extra Flag Ignore Pathfinding +UPDATE `creature_template` SET `flags_extra` = `flags_extra` |536871042 WHERE (`entry` = 30616); diff --git a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp index 962007d02..d85f19f79 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp @@ -635,7 +635,7 @@ private: { Creature* tsunami = me->SummonCreature(NPC_FLAME_TSUNAMI, 3211.0f, FlameTsunamiLeftOffsets[i], 57.083332f, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 13500); - if (((i - 2) % 3 == 0) && tsunami) // If center of wave + if (((i - 1) % 3 == 0) && tsunami) // If center of wave tsunami->CastSpell(tsunami, SPELL_FLAME_TSUNAMI_VISUAL, true); } @@ -648,7 +648,7 @@ private: { Creature* tsunami = me->SummonCreature(NPC_FLAME_TSUNAMI, 3286.0f, FlameTsunamiRightOffsets[i], 57.083332f, 3.14f, TEMPSUMMON_TIMED_DESPAWN, 13500); - if (((i - 2) % 3 == 0) && tsunami) // If center of wave + if (((i - 1) % 3 == 0) && tsunami) // If center of wave tsunami->CastSpell(tsunami, SPELL_FLAME_TSUNAMI_VISUAL, true); } From 57a2d86862cb5378e671eb49fe5b3360c339597c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Mar 2026 19:39:01 +0000 Subject: [PATCH 108/132] chore(DB): import pending files Referenced commit(s): 7370729919302cf4502d5a63da919e5cb5fe959b --- .../rev_1774544642.sql => db_world/2026_03_26_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774544642.sql => db_world/2026_03_26_01.sql} (75%) diff --git a/data/sql/updates/pending_db_world/rev_1774544642.sql b/data/sql/updates/db_world/2026_03_26_01.sql similarity index 75% rename from data/sql/updates/pending_db_world/rev_1774544642.sql rename to data/sql/updates/db_world/2026_03_26_01.sql index 73cd8005f..7b4a9cdaf 100644 --- a/data/sql/updates/pending_db_world/rev_1774544642.sql +++ b/data/sql/updates/db_world/2026_03_26_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_26_00 -> 2026_03_26_01 -- Set Extra Flag Ignore Pathfinding UPDATE `creature_template` SET `flags_extra` = `flags_extra` |536871042 WHERE (`entry` = 30616); From d2dd5de351cb963dab964ef5dfaf11d5f85ff882 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:27:28 -0500 Subject: [PATCH 109/132] fix(Core/Movement): Pass waypoint point ID to MovementInform (#25252) Co-authored-by: blinkysc Co-authored-by: Rorschach91 <108557877+Rorschach91@users.noreply.github.com> --- .../MovementGenerators/WaypointMovementGenerator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index 05c83a6ff..926a090ae 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -172,21 +172,21 @@ void WaypointMovementGenerator::ProcessWaypointArrival(Creature* creat // Inform AI if (CreatureAI* AI = creature->AI()) { - AI->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); + AI->MovementInform(WAYPOINT_MOTION_TYPE, waypoint.Id); AI->WaypointReached(waypoint.Id, i_path->Id); } if (Unit* owner = creature->GetCharmerOrOwner()) { if (UnitAI* AI = owner->GetAI()) - AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypoint.Id); } else { if (TempSummon* tempSummon = creature->ToTempSummon()) if (Unit* owner2 = tempSummon->GetSummonerUnit()) if (UnitAI* AI = owner2->GetAI()) - AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypoint.Id); } // All hooks called and infos updated. Time to increment the waypoint node id @@ -391,7 +391,7 @@ bool WaypointMovementGenerator::DoUpdate(Creature* creature, uint32 di if (CreatureAI* AI = creature->AI()) { - AI->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); + AI->MovementInform(WAYPOINT_MOTION_TYPE, passedWp.Id); AI->WaypointReached(passedWp.Id, i_path->Id); } From 6cb375e0151eb12adfde8ef97658abd524a19b98 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:28:07 -0500 Subject: [PATCH 110/132] fix(Core/Commands): Show combat refs in .debug hostile output (#25248) Co-authored-by: blinkysc --- src/server/scripts/Commands/cs_debug.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index cda1b6244..8c80529d9 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -1042,6 +1042,22 @@ public: } handler->SendSysMessage("End of threatened by me list."); + + // Also show combat refs that may not appear in the threat list + // (e.g. creatures without threat lists like triggers/environmental hazards) + handler->PSendSysMessage("Combat refs (InCombat: {} | HasCombat: {})", target->IsInCombat(), target->GetCombatManager().HasCombat()); + for (auto const& ref : target->GetCombatManager().GetPvECombatRefs()) + { + Unit* unit = ref.second->GetOther(target); + handler->PSendSysMessage(" [PvE] {} ({}) Entry: {}", unit->GetName(), unit->GetGUID().ToString(), + unit->IsCreature() ? unit->ToCreature()->GetEntry() : 0); + } + for (auto const& ref : target->GetCombatManager().GetPvPCombatRefs()) + { + Unit* unit = ref.second->GetOther(target); + handler->PSendSysMessage(" [PvP] {} ({})", unit->GetName(), unit->GetGUID().ToString()); + } + return true; } From 44dfaf80752721a8a28b773db82bf69bf56be43c Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:29:44 -0500 Subject: [PATCH 111/132] fix(Core/Spells): Fix ranged spell minimum range incorrectly applying leeway (#25230) Co-authored-by: blinkysc Co-authored-by: Chaouki Dhib Co-authored-by: sogladev --- src/server/game/Spells/Spell.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 4830f5387..69a795012 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -7079,18 +7079,18 @@ SpellCastResult Spell::CheckRange(bool strict) else if (!m_caster->IsWithinCombatRange(target, max_range)) return SPELL_FAILED_OUT_OF_RANGE; //0x5A; - if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED && range_type == SPELL_RANGE_RANGED) - { - if (m_caster->IsWithinMeleeRange(target)) - return SPELL_FAILED_TOO_CLOSE; - } - if (m_caster->IsPlayer() && (m_spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT) && !m_caster->HasInArc(static_cast(M_PI), target) && !m_caster->IsWithinBoundaryRadius(target)) return SPELL_FAILED_UNIT_NOT_INFRONT; } - // Xinef: check min range for self casts - if (min_range && range_type != SPELL_RANGE_RANGED && m_caster->IsWithinCombatRange(target, min_range)) // skip this check if min_range = 0 + // Check min range - for ranged spells, min range is the spell's min range + melee range (no leeway) + if (range_type == SPELL_RANGE_RANGED) + { + float minRangeCombined = min_range + m_caster->GetMeleeRange(target); + if (m_caster->IsWithinRange(target, minRangeCombined)) + return SPELL_FAILED_TOO_CLOSE; + } + else if (min_range > 0 && m_caster->IsWithinCombatRange(target, min_range)) return SPELL_FAILED_TOO_CLOSE; } From df66e131c9cf584a91e83ce8ea5acaebec6c9b00 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:51:48 -0300 Subject: [PATCH 112/132] fix(Core/Spells): Fix Flexweave Underlay script targeting wrong spell (#25255) Co-authored-by: Claude Opus 4.6 --- .../pending_db_world/fix_flexweave_underlay_script.sql | 3 +++ src/server/scripts/Spells/spell_item.cpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 data/sql/updates/pending_db_world/fix_flexweave_underlay_script.sql diff --git a/data/sql/updates/pending_db_world/fix_flexweave_underlay_script.sql b/data/sql/updates/pending_db_world/fix_flexweave_underlay_script.sql new file mode 100644 index 000000000..53c0a1d7f --- /dev/null +++ b/data/sql/updates/pending_db_world/fix_flexweave_underlay_script.sql @@ -0,0 +1,3 @@ +-- Move Flexweave Underlay script from enchant application spell (55002) to parachute use-spell (55001) +DELETE FROM `spell_script_names` WHERE `spell_id` IN (55001, 55002) AND `ScriptName` = 'spell_item_flexweave_underlay'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (55001, 'spell_item_flexweave_underlay'); diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index 764af2b9a..dc9340479 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -3564,7 +3564,7 @@ class spell_item_rocket_boots : public SpellScript } }; -// 55002 - Flexweave Underlay +// 55001 - Flexweave Underlay (Parachute) class spell_item_flexweave_underlay : public SpellScript { PrepareSpellScript(spell_item_flexweave_underlay); @@ -3573,7 +3573,7 @@ class spell_item_flexweave_underlay : public SpellScript { if (GetCaster()->IsFlying() || GetCaster()->IsFalling()) return SPELL_CAST_OK; - return SPELL_FAILED_NOT_FLYING; + return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW; } void Register() override From c38b058efa5611e8d07da10eb72980f97503dba1 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:52:10 -0300 Subject: [PATCH 113/132] fix(Scripts/BRD): Fix Doom'rel and Gloom'rel gossip not working (#25256) Co-authored-by: Claude Opus 4.6 --- .../fix_tomb_of_seven_gossip.sql | 42 ++++++++ .../BlackrockDepths/boss_tomb_of_seven.cpp | 95 +++++-------------- 2 files changed, 65 insertions(+), 72 deletions(-) create mode 100644 data/sql/updates/pending_db_world/fix_tomb_of_seven_gossip.sql diff --git a/data/sql/updates/pending_db_world/fix_tomb_of_seven_gossip.sql b/data/sql/updates/pending_db_world/fix_tomb_of_seven_gossip.sql new file mode 100644 index 000000000..e0c445c38 --- /dev/null +++ b/data/sql/updates/pending_db_world/fix_tomb_of_seven_gossip.sql @@ -0,0 +1,42 @@ +-- Doomrel (9039): Create "Continue..." menu and chain from initial gossip +DELETE FROM `gossip_menu` WHERE `MenuID` = 1950; +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES (1950, 2605); + +DELETE FROM `gossip_menu_option` WHERE `MenuID` = 1950; +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`, `VerifiedBuild`) VALUES +(1950, 0, 0, 'Continue...', 5256, 1, 1, 0, 0, 0, 0, '', 0, 0); + +UPDATE `gossip_menu_option` SET `ActionMenuID` = 1950 WHERE `MenuID` = 1947 AND `OptionID` = 0; + +-- Gloomrel (9037): Create "Continue..." menus for smelt and chalice paths +DELETE FROM `gossip_menu` WHERE `MenuID` IN (1952, 1953); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(1952, 2606), +(1953, 2604); + +DELETE FROM `gossip_menu_option` WHERE `MenuID` IN (1952, 1953); +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`, `VerifiedBuild`) VALUES +(1952, 0, 0, 'Continue...', 5256, 1, 1, 0, 0, 0, 0, '', 0, 0), +(1953, 0, 0, 'Continue...', 5256, 1, 1, 0, 0, 0, 0, '', 0, 0); + +UPDATE `gossip_menu_option` SET `ActionMenuID` = 1952 WHERE `MenuID` = 1945 AND `OptionID` = 0; +UPDATE `gossip_menu_option` SET `ActionMenuID` = 1953 WHERE `MenuID` = 1945 AND `OptionID` = 1; + +-- Gloomrel: Conditional gossip text when quest completed +DELETE FROM `gossip_menu` WHERE `MenuID` = 1945 AND `TextID` = 2605; +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES (1945, 2605); + +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 14 AND `SourceGroup` = 1945 AND `SourceEntry` = 2605; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 1945, 2605, 0, 0, 8, 0, 4083, 0, 0, 0, 0, 0, '', 'Gloomrel - text requires quest 4083 rewarded'), +(14, 1945, 2605, 0, 0, 7, 0, 186, 230, 0, 0, 0, 0, '', 'Gloomrel - text requires Mining >= 230'), +(14, 1945, 2605, 0, 0, 16, 0, 14891, 0, 0, 1, 0, 0, '', 'Gloomrel - text requires NOT having Smelt Dark Iron'); + +-- Gloomrel: Conditions for gossip options +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 15 AND `SourceGroup` = 1945 AND `SourceEntry` IN (0, 1); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 1945, 0, 0, 0, 8, 0, 4083, 0, 0, 0, 0, 0, '', 'Gloomrel - option 0 requires quest 4083 rewarded'), +(15, 1945, 0, 0, 0, 7, 0, 186, 230, 0, 0, 0, 0, '', 'Gloomrel - option 0 requires Mining >= 230'), +(15, 1945, 0, 0, 0, 16, 0, 14891, 0, 0, 1, 0, 0, '', 'Gloomrel - option 0 requires NOT having Smelt Dark Iron'), +(15, 1945, 1, 0, 0, 8, 0, 4083, 0, 0, 1, 0, 0, '', 'Gloomrel - option 1 requires quest 4083 NOT rewarded'), +(15, 1945, 1, 0, 0, 7, 0, 186, 230, 0, 0, 0, 0, '', 'Gloomrel - option 1 requires Mining >= 230'); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp index da9242590..758b41a99 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp @@ -23,20 +23,9 @@ enum Spells { - SPELL_SMELT_DARK_IRON = 14891, SPELL_LEARN_SMELT = 14894, }; -enum Quests -{ - QUEST_SPECTRAL_CHALICE = 4083 -}; - -enum Misc -{ - DATA_SKILLPOINT_MIN = 230 -}; - enum Says { SAY_START_FIGHT = 0 @@ -44,57 +33,32 @@ enum Says enum Gossip { - GOSSIP_TEXT_CONTINUE = 1828, // Continue... - GOSSIP_GROOMREL = 1945, // Option 1 : Before quest(4083) accepted, option 0 after quest(4083) accepted - GOSSIP_DOOMREL_START_COMBAT = 1947, // Your bondage is at an end, Doom'rel. I challenge you! - SAY_DOOMREL_HELLO = 2601, // Our fate is the doom of all who face the Great Fire. - SAY_QUEST_ACCEPTED = 2604, // You wish to learn the old craft? You wish to smelt dark iron?$B$BAppease me, $r. Show me a sacrifice and I will consider it! - SAY_QUEST_COMPLETED = 2605, // Your will is strong, and your intent is clear.$B$BPerhaps you are worthy... - SAY_QUEST_COMPLETED_END = 2606, // You have shown me your desire, and have payed with precious stone. I will teach you... + GOSSIP_GLOOMREL_CONTINUE_SMELT = 1952, + GOSSIP_GLOOMREL_CONTINUE_CHALICE = 1953, + GOSSIP_DOOMREL_CONTINUE = 1950, }; struct boss_gloomrel : public ScriptedAI { boss_gloomrel(Creature* creature) : ScriptedAI(creature) { } - void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override + void sGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override { - ClearGossipMenuFor(player); - switch (action) + if (gossipListId != 0) + return; + + if (menuId == GOSSIP_GLOOMREL_CONTINUE_SMELT) { - case GOSSIP_ACTION_INFO_DEF+1: - AddGossipItemFor(player, GOSSIP_TEXT_CONTINUE, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 11); - SendGossipMenuFor(player, SAY_QUEST_COMPLETED_END, me->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+11: - CloseGossipMenuFor(player); - player->CastSpell(player, SPELL_LEARN_SMELT, false); - break; - case GOSSIP_ACTION_INFO_DEF+2: - AddGossipItemFor(player, GOSSIP_TEXT_CONTINUE, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 22); - SendGossipMenuFor(player, SAY_QUEST_ACCEPTED, me->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+22: - CloseGossipMenuFor(player); - if (InstanceScript* instance = me->GetInstanceScript()) - //are 5 minutes expected? go template may have data to despawn when used at quest - instance->DoRespawnGameObject(instance->GetGuidData(DATA_GO_CHALICE), MINUTE * 5); - break; + CloseGossipMenuFor(player); + player->CastSpell(player, SPELL_LEARN_SMELT, false); } - } - - void sGossipHello(Player* player) override - { - if (player->GetQuestRewardStatus(QUEST_SPECTRAL_CHALICE) == 1 && player->GetSkillValue(SKILL_MINING) >= DATA_SKILLPOINT_MIN && !player->HasSpell(SPELL_SMELT_DARK_IRON)) + else if (menuId == GOSSIP_GLOOMREL_CONTINUE_CHALICE) { - AddGossipItemFor(player, GOSSIP_GROOMREL, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, SAY_QUEST_COMPLETED, me->GetGUID()); + CloseGossipMenuFor(player); + if (InstanceScript* instance = me->GetInstanceScript()) + //are 5 minutes expected? go template may have data to despawn when used at quest + instance->DoRespawnGameObject(instance->GetGuidData(DATA_GO_CHALICE), MINUTE * 5); } - - if (player->GetQuestRewardStatus(QUEST_SPECTRAL_CHALICE) == 0 && player->GetSkillValue(SKILL_MINING) >= DATA_SKILLPOINT_MIN) - AddGossipItemFor(player, GOSSIP_GROOMREL, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - - SendGossipMenuFor(player, player->GetGossipTextId(me), me->GetGUID()); } }; @@ -126,32 +90,19 @@ struct boss_doomrel : public ScriptedAI EventMap _events; bool Voidwalkers; - void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override + void sGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override { - ClearGossipMenuFor(player); - switch (action) + if (menuId == GOSSIP_DOOMREL_CONTINUE && gossipListId == 0) { - case GOSSIP_ACTION_INFO_DEF+1: - AddGossipItemFor(player, GOSSIP_TEXT_CONTINUE, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - SendGossipMenuFor(player, SAY_QUEST_COMPLETED, me->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+2: - CloseGossipMenuFor(player); - me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); - // Start encounter - if (instance) - instance->SetData(TYPE_TOMB_OF_SEVEN, IN_PROGRESS); - Talk(SAY_START_FIGHT); - break; + CloseGossipMenuFor(player); + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + // Start encounter + if (instance) + instance->SetData(TYPE_TOMB_OF_SEVEN, IN_PROGRESS); + Talk(SAY_START_FIGHT); } } - void sGossipHello(Player* player) override - { - AddGossipItemFor(player, GOSSIP_DOOMREL_START_COMBAT, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, SAY_DOOMREL_HELLO, me->GetGUID()); - } - void Reset() override { Voidwalkers = false; From db5cc7c4f3102c0fe310bec37e046a827089c0e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 00:52:58 +0000 Subject: [PATCH 114/132] chore(DB): import pending files Referenced commit(s): df66e131c9cf584a91e83ce8ea5acaebec6c9b00 --- .../2026_03_27_00.sql} | 1 + .../fix_tomb_of_seven_gossip.sql => db_world/2026_03_27_01.sql} | 1 + 2 files changed, 2 insertions(+) rename data/sql/updates/{pending_db_world/fix_flexweave_underlay_script.sql => db_world/2026_03_27_00.sql} (88%) rename data/sql/updates/{pending_db_world/fix_tomb_of_seven_gossip.sql => db_world/2026_03_27_01.sql} (98%) diff --git a/data/sql/updates/pending_db_world/fix_flexweave_underlay_script.sql b/data/sql/updates/db_world/2026_03_27_00.sql similarity index 88% rename from data/sql/updates/pending_db_world/fix_flexweave_underlay_script.sql rename to data/sql/updates/db_world/2026_03_27_00.sql index 53c0a1d7f..a559a7265 100644 --- a/data/sql/updates/pending_db_world/fix_flexweave_underlay_script.sql +++ b/data/sql/updates/db_world/2026_03_27_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_26_01 -> 2026_03_27_00 -- Move Flexweave Underlay script from enchant application spell (55002) to parachute use-spell (55001) DELETE FROM `spell_script_names` WHERE `spell_id` IN (55001, 55002) AND `ScriptName` = 'spell_item_flexweave_underlay'; INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (55001, 'spell_item_flexweave_underlay'); diff --git a/data/sql/updates/pending_db_world/fix_tomb_of_seven_gossip.sql b/data/sql/updates/db_world/2026_03_27_01.sql similarity index 98% rename from data/sql/updates/pending_db_world/fix_tomb_of_seven_gossip.sql rename to data/sql/updates/db_world/2026_03_27_01.sql index e0c445c38..20623a290 100644 --- a/data/sql/updates/pending_db_world/fix_tomb_of_seven_gossip.sql +++ b/data/sql/updates/db_world/2026_03_27_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_27_00 -> 2026_03_27_01 -- Doomrel (9039): Create "Continue..." menu and chain from initial gossip DELETE FROM `gossip_menu` WHERE `MenuID` = 1950; INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES (1950, 2605); From 43769b909945ebc978321404ebec1d1197b00c18 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:07:49 -0500 Subject: [PATCH 115/132] fix(Core/Movement): Fire WaypointReached before PathEndReached (#25241) Co-authored-by: blinkysc --- .../WaypointMovementGenerator.cpp | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index 926a090ae..03fb5b5e8 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -143,18 +143,9 @@ void WaypointMovementGenerator::ProcessWaypointArrival(Creature* creat _waypointDelay = waypoint.Delay; } - // Check if the waypoint path has reached its end and may not repeat. Inform AI. - if ((i_currentNode == i_path->Nodes.size() - 1) && !_repeating && !_done) - { + bool pathEnded = (i_currentNode == i_path->Nodes.size() - 1) && !_repeating && !_done; + if (pathEnded) _done = true; - creature->UpdateCurrentWaypointInfo(0, 0); - - if (CreatureAI* AI = creature->AI()) - { - AI->PathEndReached(i_path->Id); - AI->WaypointPathEnded(waypoint.Id, i_path->Id); - } - } UpdateHomePosition(creature, waypoint); @@ -189,6 +180,20 @@ void WaypointMovementGenerator::ProcessWaypointArrival(Creature* creat AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypoint.Id); } + // Path end notifications fire after WaypointReached so that m_path_id + // is still valid when SmartAI checks it for SMART_EVENT_WAYPOINT_REACHED. + if (pathEnded) + { + creature->UpdateCurrentWaypointInfo(0, 0); + + if (CreatureAI* AI = creature->AI()) + AI->PathEndReached(i_path->Id); + + // Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI + if (CreatureAI* AI = creature->AI()) + AI->WaypointPathEnded(waypoint.Id, i_path->Id); + } + // All hooks called and infos updated. Time to increment the waypoint node id if (i_path && !i_path->Nodes.empty()) // ensure that the path has not been changed in one of the hooks. i_currentNode = (i_currentNode + 1) % i_path->Nodes.size(); @@ -422,10 +427,11 @@ bool WaypointMovementGenerator::DoUpdate(Creature* creature, uint32 di _smoothSplineLaunched = false; creature->UpdateCurrentWaypointInfo(0, 0); if (CreatureAI* AI = creature->AI()) - { AI->PathEndReached(i_path->Id); + + // Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI + if (CreatureAI* AI = creature->AI()) AI->WaypointPathEnded(i_path->Nodes.at(i_currentNode).Id, i_path->Id); - } } else { From 3c854bb1624f13f13183e795aa0d3611adc991d3 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:16:21 -0500 Subject: [PATCH 116/132] fix(Scripts/Icecrown): Fix idle scourge NPCs in The Battle For Crusaders' Pinnacle (#25224) Co-authored-by: blinkysc Co-authored-by: sogla Co-authored-by: ccrs --- .../scripts/Northrend/zone_icecrown.cpp | 63 +++++++------------ 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/src/server/scripts/Northrend/zone_icecrown.cpp b/src/server/scripts/Northrend/zone_icecrown.cpp index 639991ba5..6b10781cc 100644 --- a/src/server/scripts/Northrend/zone_icecrown.cpp +++ b/src/server/scripts/Northrend/zone_icecrown.cpp @@ -1659,7 +1659,7 @@ enum BlessedBanner NPC_ARGENT_MASON = 30900, NPC_REANIMATED_CAPTAIN = 30986, NPC_SCOURGE_DRUDGE = 30984, - NPC_HIDEOUS_PLAGEBRINGER = 30987, + NPC_HIDEOUS_PLAGUEBRINGER = 30987, NPC_HALOF_THE_DEATHBRINGER = 30989, NPC_LK = 31013, @@ -1771,9 +1771,20 @@ public: void MoveInLineOfSight(Unit* /*who*/) override { } - void JustSummoned(Creature* Summoned) override + void JustSummoned(Creature* summon) override { - Summons.Summon(Summoned); + Summons.Summon(summon); + if (summon->GetEntry() == NPC_SCOURGE_DRUDGE || summon->GetEntry() == NPC_REANIMATED_CAPTAIN || + summon->GetEntry() == NPC_HIDEOUS_PLAGUEBRINGER || summon->GetEntry() == NPC_HALOF_THE_DEATHBRINGER) + { + summon->SetHomePosition(DalforsPos[2]); + summon->SetReactState(REACT_PASSIVE); + summon->EngageWithTarget(me); + summon->m_Events.AddEventAtOffset([summon]() + { + summon->SetReactState(REACT_AGGRESSIVE); + }, 2s); + } } void JustDied(Unit* /*killer*/) override @@ -1926,36 +1937,16 @@ public: if (Creature* LK = GetClosestCreatureWithEntry(me, NPC_LK, 100)) LK->AI()->Talk(LK_TALK_3); } - if (Creature* tempsum = DoSummon(NPC_SCOURGE_DRUDGE, Mason3Pos[0])) - { - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); - } + DoSummon(NPC_SCOURGE_DRUDGE, Mason3Pos[0]); if (urand(0, 1) == 0) { - if (Creature* tempsum = DoSummon(NPC_HIDEOUS_PLAGEBRINGER, Mason1Pos[0])) - { - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); - } - if (Creature* tempsum = DoSummon(NPC_HIDEOUS_PLAGEBRINGER, Mason2Pos[0])) - { - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); - } + DoSummon(NPC_HIDEOUS_PLAGUEBRINGER, Mason1Pos[0]); + DoSummon(NPC_HIDEOUS_PLAGUEBRINGER, Mason2Pos[0]); } else { - if (Creature* tempsum = DoSummon(NPC_REANIMATED_CAPTAIN, Mason1Pos[0])) - { - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); - } - if (Creature* tempsum = DoSummon(NPC_REANIMATED_CAPTAIN, Mason2Pos[0])) - { - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); - } + DoSummon(NPC_REANIMATED_CAPTAIN, Mason1Pos[0]); + DoSummon(NPC_REANIMATED_CAPTAIN, Mason2Pos[0]); } PhaseCount++; @@ -1970,22 +1961,12 @@ public: { if (Creature* LK = GetClosestCreatureWithEntry(me, NPC_LK, 100)) LK->AI()->Talk(LK_TALK_4); - if (Creature* tempsum = DoSummon(NPC_SCOURGE_DRUDGE, Mason1Pos[0])) - { - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); - } - if (Creature* tempsum = DoSummon(NPC_SCOURGE_DRUDGE, Mason2Pos[0])) - { - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); - } + DoSummon(NPC_SCOURGE_DRUDGE, Mason1Pos[0]); + DoSummon(NPC_SCOURGE_DRUDGE, Mason2Pos[0]); if (Creature* tempsum = DoSummon(NPC_HALOF_THE_DEATHBRINGER, DalforsPos[0])) { HalofSpawned = true; guidHalof = tempsum->GetGUID(); - tempsum->SetHomePosition(DalforsPos[2]); - tempsum->AI()->AttackStart(GetClosestCreatureWithEntry(me, NPC_BLESSED_BANNER, 100)); } } break; @@ -2002,7 +1983,7 @@ public: if (Halof->isDead()) { DoCast(me, SPELL_CRUSADERS_SPIRE_VICTORY, true); - Summons.DespawnEntry(NPC_HIDEOUS_PLAGEBRINGER); + Summons.DespawnEntry(NPC_HIDEOUS_PLAGUEBRINGER); Summons.DespawnEntry(NPC_REANIMATED_CAPTAIN); Summons.DespawnEntry(NPC_SCOURGE_DRUDGE); Summons.DespawnEntry(NPC_HALOF_THE_DEATHBRINGER); From 755e4ac43aaa56966d6688f1f0c45a0d40f13d37 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:17:12 -0500 Subject: [PATCH 117/132] fix(Core/Combat): Re-enable combat for training dummies (#25239) Co-authored-by: blinkysc --- src/server/game/AI/CoreAI/PassiveAI.cpp | 1 + src/server/scripts/World/npcs_special.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/server/game/AI/CoreAI/PassiveAI.cpp b/src/server/game/AI/CoreAI/PassiveAI.cpp index 1ff7b66a3..79ac51fa8 100644 --- a/src/server/game/AI/CoreAI/PassiveAI.cpp +++ b/src/server/game/AI/CoreAI/PassiveAI.cpp @@ -23,6 +23,7 @@ PossessedAI::PossessedAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_ NullCreatureAI::NullCreatureAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); + // TODO: Remove once WorldObject casting is ported (triggers won't create combat refs from spell casts) me->SetIsCombatDisallowed(true); } diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 3e7ebb34e..3ddb27669 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -314,7 +314,11 @@ public: struct npc_training_dummy : NullCreatureAI { - npc_training_dummy(Creature* creature) : NullCreatureAI(creature) { } + npc_training_dummy(Creature* creature) : NullCreatureAI(creature) + { + // TODO: Remove once WorldObject casting is ported + me->SetIsCombatDisallowed(false); + } void JustEnteredCombat(Unit* who) override { @@ -359,6 +363,8 @@ struct npc_target_dummy : NullCreatureAI { npc_target_dummy(Creature* creature) : NullCreatureAI(creature) { + // TODO: Remove once WorldObject casting is ported + me->SetIsCombatDisallowed(false); _deathTimer = 15s; } From 6194a24ff9686882082bdd59910a26e7a7475061 Mon Sep 17 00:00:00 2001 From: sogladev Date: Fri, 27 Mar 2026 07:32:08 +0100 Subject: [PATCH 118/132] fix(Core/Loot): fix quest item count logic for FFA (MULTI_DROP) loot (#25257) --- src/server/game/Loot/LootMgr.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index af65a38b9..2ef13a484 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -662,7 +662,11 @@ QuestItemList* Loot::FillQuestLoot(Player* player) { item.AddAllowedLooter(player); - if (!item.is_counted) + if (item.freeforall) + { + ++unlootedCount; + } + else if (!item.is_counted) { ++unlootedCount; item.is_counted = true; From 963ec54c73f28698011fed926a7c12284b567fee Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:03:09 +0100 Subject: [PATCH 119/132] fix(DB/SAI): Restore Havenshire Stallion behavior. (#25236) --- data/sql/updates/pending_db_world/rev_1774518446.sql | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774518446.sql diff --git a/data/sql/updates/pending_db_world/rev_1774518446.sql b/data/sql/updates/pending_db_world/rev_1774518446.sql new file mode 100644 index 000000000..717c8b6d7 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774518446.sql @@ -0,0 +1,7 @@ + +-- Change row 3 event from ON_WAYPOINT_REACHED to ON_WAYPOINT_ENDED. +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = -129210); +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 +(-129210, 0, 2, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 232, 12921000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Respawn - Start Path 12921000'), +(-129210, 0, 3, 4, 109, 0, 100, 0, 0, 12921000, 0, 0, 0, 0, 234, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921000 Finished - Stop Movement'), +(-129210, 0, 4, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2860500, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921000 Finished - Run Script'); From 520d4e04361a8fcc673cb677ec14a7979b67aec9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 10:04:23 +0000 Subject: [PATCH 120/132] chore(DB): import pending files Referenced commit(s): 963ec54c73f28698011fed926a7c12284b567fee --- .../rev_1774518446.sql => db_world/2026_03_27_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774518446.sql => db_world/2026_03_27_02.sql} (96%) diff --git a/data/sql/updates/pending_db_world/rev_1774518446.sql b/data/sql/updates/db_world/2026_03_27_02.sql similarity index 96% rename from data/sql/updates/pending_db_world/rev_1774518446.sql rename to data/sql/updates/db_world/2026_03_27_02.sql index 717c8b6d7..af129ab55 100644 --- a/data/sql/updates/pending_db_world/rev_1774518446.sql +++ b/data/sql/updates/db_world/2026_03_27_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_27_01 -> 2026_03_27_02 -- Change row 3 event from ON_WAYPOINT_REACHED to ON_WAYPOINT_ENDED. DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = -129210); From 7bc19c9bcdd218ad89630ecf6fbb47fd355b2309 Mon Sep 17 00:00:00 2001 From: sogladev Date: Fri, 27 Mar 2026 14:02:16 +0100 Subject: [PATCH 121/132] fix(DB/Creature): immunities, spawn and equipment of Koltira Deathweaver (#25261) --- .../updates/pending_db_world/rev_1774608614129288192.sql | 8 ++++++++ .../scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp | 3 +++ 2 files changed, 11 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774608614129288192.sql diff --git a/data/sql/updates/pending_db_world/rev_1774608614129288192.sql b/data/sql/updates/pending_db_world/rev_1774608614129288192.sql new file mode 100644 index 000000000..dd6266f53 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774608614129288192.sql @@ -0,0 +1,8 @@ +-- +DELETE FROM `creature_equip_template` WHERE (`CreatureID` = 28912); +INSERT INTO `creature_equip_template` (`CreatureID`, `ID`, `ItemID1`, `ItemID2`, `ItemID3`, `VerifiedBuild`) VALUES +(28912, 1, 38633, 0, 0, 42010); + +UPDATE `creature` SET `spawntimesecs` = 60 WHERE (`id1` = 28912) AND (`guid` IN (130354)); + +UPDATE `creature_template` SET `CreatureImmunitiesId` = -93 WHERE (`entry` = 28912); diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp index 3ab236d8c..333c6ffee 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp @@ -92,6 +92,7 @@ public: me->m_Events.KillAllEvents(false); me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); me->setActive(false); + SetEquipmentSlots(true); } void StartEvent() @@ -244,6 +245,8 @@ public: Talk(SAY_BREAKOUT2); DoCastSelf(SPELL_ANTI_MAGIC_ZONE); break; + default: + break; } } From 740492c7c0d8fb9076baecdd8320b8af5abee9d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 13:03:25 +0000 Subject: [PATCH 122/132] chore(DB): import pending files Referenced commit(s): 7bc19c9bcdd218ad89630ecf6fbb47fd355b2309 --- .../rev_1774608614129288192.sql => db_world/2026_03_27_03.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774608614129288192.sql => db_world/2026_03_27_03.sql} (90%) diff --git a/data/sql/updates/pending_db_world/rev_1774608614129288192.sql b/data/sql/updates/db_world/2026_03_27_03.sql similarity index 90% rename from data/sql/updates/pending_db_world/rev_1774608614129288192.sql rename to data/sql/updates/db_world/2026_03_27_03.sql index dd6266f53..79ede9d56 100644 --- a/data/sql/updates/pending_db_world/rev_1774608614129288192.sql +++ b/data/sql/updates/db_world/2026_03_27_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_27_02 -> 2026_03_27_03 -- DELETE FROM `creature_equip_template` WHERE (`CreatureID` = 28912); INSERT INTO `creature_equip_template` (`CreatureID`, `ID`, `ItemID1`, `ItemID2`, `ItemID3`, `VerifiedBuild`) VALUES From b92c79b2696c7b54c2754e1891847c3e636f6c46 Mon Sep 17 00:00:00 2001 From: sogladev Date: Fri, 27 Mar 2026 16:04:40 +0100 Subject: [PATCH 123/132] fix(DB/Immunities): update magic immunity for Atal'ai Deathwalker (#25262) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- data/sql/updates/pending_db_world/rev_1774610151534669669.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1774610151534669669.sql diff --git a/data/sql/updates/pending_db_world/rev_1774610151534669669.sql b/data/sql/updates/pending_db_world/rev_1774610151534669669.sql new file mode 100644 index 000000000..9534052b3 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774610151534669669.sql @@ -0,0 +1,3 @@ +-- +-- School immunities are set by `creature_addon.auras` immunity auras +UPDATE `creature_template` SET `CreatureImmunitiesId` = -367 WHERE (`entry` = 8317); From e518f0a37c6bf1085b99e4d94b3ef1f78ab6161e Mon Sep 17 00:00:00 2001 From: Benjamin Jackson <38561765+heyitsbench@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:04:59 -0400 Subject: [PATCH 124/132] feat(CI): Add `mod-ptr-template` to list of modules to compile for workflow. (#25260) --- apps/ci/ci-install-modules.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/ci/ci-install-modules.sh b/apps/ci/ci-install-modules.sh index 7ff647075..cc27b349d 100755 --- a/apps/ci/ci-install-modules.sh +++ b/apps/ci/ci-install-modules.sh @@ -76,6 +76,7 @@ git clone --depth=1 --branch=master https://github.com/azerothcore/mod-pocket-po git clone --depth=1 --branch=master https://github.com/azerothcore/mod-premium modules/mod-premium git clone --depth=1 --branch=main https://github.com/azerothcore/mod-progression-system.git modules/mod-progression-system git clone --depth=1 --branch=master https://github.com/azerothcore/mod-promotion-azerothcore modules/mod-promotion-azerothcore +git clone --depth=1 --branch=master https://github.com/azerothcore/mod-ptr-template modules/mod-ptr-template git clone --depth=1 --branch=master https://github.com/azerothcore/mod-pvp-quests modules/mod-pvp-quests git clone --depth=1 --branch=master https://github.com/azerothcore/mod-pvp-titles modules/mod-pvp-titles git clone --depth=1 --branch=master https://github.com/azerothcore/mod-pvp-zones modules/mod-pvp-zones From 12a13ee4b17b0245ad9cc2dc05276571f5ae641e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 15:05:47 +0000 Subject: [PATCH 125/132] chore(DB): import pending files Referenced commit(s): b92c79b2696c7b54c2754e1891847c3e636f6c46 --- .../rev_1774610151534669669.sql => db_world/2026_03_27_04.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774610151534669669.sql => db_world/2026_03_27_04.sql} (78%) diff --git a/data/sql/updates/pending_db_world/rev_1774610151534669669.sql b/data/sql/updates/db_world/2026_03_27_04.sql similarity index 78% rename from data/sql/updates/pending_db_world/rev_1774610151534669669.sql rename to data/sql/updates/db_world/2026_03_27_04.sql index 9534052b3..a0ef70ced 100644 --- a/data/sql/updates/pending_db_world/rev_1774610151534669669.sql +++ b/data/sql/updates/db_world/2026_03_27_04.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_27_03 -> 2026_03_27_04 -- -- School immunities are set by `creature_addon.auras` immunity auras UPDATE `creature_template` SET `CreatureImmunitiesId` = -367 WHERE (`entry` = 8317); From 2eec7b229d1ffe579859f2067576b763dacdb055 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:45:54 -0500 Subject: [PATCH 126/132] fix(Core/Spells): resolve pet to owner in SummonGuardian (#25266) Co-authored-by: blinkysc --- src/server/game/Spells/SpellEffects.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index e8f6a6aa3..40edc0576 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -5943,6 +5943,9 @@ void Spell::SummonGuardian(uint32 i, uint32 entry, SummonPropertiesEntry const* if (caster->IsTotem()) caster = caster->ToTotem()->GetOwner(); + else if (caster->IsPet()) + if (Unit* owner = caster->GetOwner()) + caster = owner; // in another case summon new uint8 summonLevel = caster->GetLevel(); From 3bdb0723968aad939d4614153a004bcc2ce00bb7 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:46:21 -0500 Subject: [PATCH 127/132] fix(Scripts/Naxx): fix Gothik the Harvester evading during phase 2 teleport (#25265) Co-authored-by: blinkysc Co-authored-by: offl <11556157+offl@users.noreply.github.com> --- .../Northrend/Naxxramas/boss_gothik.cpp | 101 +++++++++++------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp index 6125ba262..f2be10718 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp @@ -111,7 +111,9 @@ enum Events // Intro EVENT_INTRO_2 = 15, EVENT_INTRO_3 = 16, - EVENT_INTRO_4 = 17 + EVENT_INTRO_4 = 17, + // Resume after teleport + EVENT_RESUME_ATTACK = 18 }; const uint32 gothikWaves[24][2] = @@ -172,21 +174,6 @@ const Position PosSummonDead[5] = #define POS_X_SOUTH 2633.84f #define IN_LIVE_SIDE(who) (who->GetPositionY() < POS_Y_GATE) -// Predicate function to check that the r efzr unit is NOT on the same side as the source. -struct NotOnSameSide -{ -public: - explicit NotOnSameSide(Unit* pSource) : m_inLiveSide(IN_LIVE_SIDE(pSource)) { } - - bool operator() (Unit const* pTarget) - { - return (m_inLiveSide != IN_LIVE_SIDE(pTarget)); - } - -private: - bool m_inLiveSide; -}; - class boss_gothik : public CreatureScript { public: @@ -206,6 +193,7 @@ public: SummonList summons; bool secondPhase{}; bool gateOpened{}; + bool lastTeleportDead{}; uint8 waveCount{}; bool IsInRoom() @@ -228,6 +216,7 @@ public: me->SetReactState(REACT_PASSIVE); secondPhase = false; gateOpened = false; + lastTeleportDead = false; waveCount = 0; me->NearTeleportTo(2642.139f, -3386.959f, 285.492f, 6.265f); } @@ -366,6 +355,18 @@ public: return false; } + void OpenGate() + { + if (gateOpened) + return; + + if (GameObject* go = instance->GetGameObject(DATA_GOTHIK_INNER_GATE)) + go->SetGoState(GO_STATE_ACTIVE); + + gateOpened = true; + Talk(EMOTE_GATE_OPENED); + } + void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override { if (!secondPhase) @@ -382,6 +383,35 @@ public: if (!UpdateVictim()) return; + // Safety net: if our victim is on the wrong side, find a + // same-side target or open the gate + if (secondPhase && me->HasReactState(REACT_AGGRESSIVE) && !gateOpened + && !IN_LIVE_SIDE(me) != !IN_LIVE_SIDE(me->GetVictim())) + { + Map::PlayerList const& pList = me->GetMap()->GetPlayers(); + Player* newTarget = nullptr; + for (auto const& itr : pList) + { + Player* player = itr.GetSource(); + if (player && player->IsAlive() && !player->IsGameMaster() + && me->IsValidAttackTarget(player) + && IN_LIVE_SIDE(me) == IN_LIVE_SIDE(player)) + { + newTarget = player; + break; + } + } + + if (newTarget) + { + me->GetThreatMgr().ResetAllThreat(); + me->GetThreatMgr().AddThreat(newTarget, 1.0f); + AttackStart(newTarget); + } + else + OpenGate(); + } + events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; @@ -407,32 +437,29 @@ public: break; case EVENT_TELEPORT: { + if (me->HealthBelowPct(30)) + break; + + me->CastStop(); me->AttackStop(); - if (IN_LIVE_SIDE(me)) - me->CastSpell(me, SPELL_TELEPORT_DEAD, false); - else - me->CastSpell(me, SPELL_TELEPORT_LIVE, false); + me->SetReactState(REACT_PASSIVE); + me->GetThreatMgr().ResetAllThreat(); + me->CastSpell(me, lastTeleportDead ? SPELL_TELEPORT_LIVE : SPELL_TELEPORT_DEAD, false); + lastTeleportDead = !lastTeleportDead; - // Clear threat from targets not on the same side as Gothik - NotOnSameSide notOnSameSide(me); - for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList()) - if (notOnSameSide(ref->GetVictim())) - me->GetThreatMgr().ClearThreat(ref->GetVictim()); - - if (Unit* pTarget = SelectTarget(SelectTargetMethod::MaxDistance, 0)) - { - me->GetThreatMgr().AddThreat(pTarget, 100.0f); - AttackStart(pTarget); - } + events.CancelEvent(EVENT_SHADOW_BOLT); + events.ScheduleEvent(EVENT_RESUME_ATTACK, 2s); events.Repeat(20s); break; } + case EVENT_RESUME_ATTACK: + me->SetReactState(REACT_AGGRESSIVE); + events.ScheduleEvent(EVENT_SHADOW_BOLT, 0s); + return UpdateAI(0); case EVENT_CHECK_HEALTH: if (me->HealthBelowPct(30)) { - if (GameObject* go = instance->GetGameObject(DATA_GOTHIK_INNER_GATE)) - go->SetGoState(GO_STATE_ACTIVE); - + OpenGate(); events.CancelEvent(EVENT_TELEPORT); break; } @@ -464,10 +491,7 @@ public: case EVENT_CHECK_PLAYERS: if (!CheckGroupSplitted()) { - if (GameObject* go = instance->GetGameObject(DATA_GOTHIK_INNER_GATE)) - go->SetGoState(GO_STATE_ACTIVE); - - gateOpened = true; + OpenGate(); summons.DoForAllSummons([&](WorldObject* summon) { if (Creature* gothikMinion = summon->ToCreature()) @@ -481,7 +505,6 @@ public: } } }); - Talk(EMOTE_GATE_OPENED); } break; } From 1520a6f0556df9ba5b5fd6af61e1f0c6b53cc15a Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:26:24 -0500 Subject: [PATCH 128/132] fix(Core/Movement): Fix use-after-free in WaypointMovementGenerator (#25270) Co-authored-by: blinkysc Co-authored-by: Shauren Co-authored-by: ccrs --- src/server/game/Movement/MotionMaster.cpp | 4 +- .../WaypointMovementGenerator.cpp | 52 ++++++++++++------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index e9649225c..0801edecf 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -879,10 +879,10 @@ void MotionMaster::MoveDistract(uint32 timer) void MotionMaster::Mutate(MovementGenerator* m, MovementSlot slot) { + bool const delayed = (_cleanFlag & MMCF_UPDATE); + while (MovementGenerator* curr = Impl[slot]) { - bool delayed = (_top == slot && (_cleanFlag & MMCF_UPDATE)); - // clear slot AND decrease top immediately to avoid crashes when referencing null top in DirectDelete Impl[slot] = nullptr; while (!empty() && !top()) diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index 03fb5b5e8..272a1f823 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -157,27 +157,31 @@ void WaypointMovementGenerator::ProcessWaypointArrival(Creature* creat creature->GetMap()->ScriptsStart(sWaypointScripts, waypoint.EventId, creature, nullptr); } - creature->UpdateWaypointID(waypoint.Id); - creature->UpdateCurrentWaypointInfo(waypoint.Id, i_path->Id); + // scripts can invalidate current path, store what we need + uint32 const waypointId = waypoint.Id; + uint32 const pathId = i_path->Id; + + creature->UpdateWaypointID(waypointId); + creature->UpdateCurrentWaypointInfo(waypointId, pathId); // Inform AI if (CreatureAI* AI = creature->AI()) { - AI->MovementInform(WAYPOINT_MOTION_TYPE, waypoint.Id); - AI->WaypointReached(waypoint.Id, i_path->Id); + AI->MovementInform(WAYPOINT_MOTION_TYPE, waypointId); + AI->WaypointReached(waypointId, pathId); } if (Unit* owner = creature->GetCharmerOrOwner()) { if (UnitAI* AI = owner->GetAI()) - AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypoint.Id); + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypointId); } else { if (TempSummon* tempSummon = creature->ToTempSummon()) if (Unit* owner2 = tempSummon->GetSummonerUnit()) if (UnitAI* AI = owner2->GetAI()) - AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypoint.Id); + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypointId); } // Path end notifications fire after WaypointReached so that m_path_id @@ -187,11 +191,11 @@ void WaypointMovementGenerator::ProcessWaypointArrival(Creature* creat creature->UpdateCurrentWaypointInfo(0, 0); if (CreatureAI* AI = creature->AI()) - AI->PathEndReached(i_path->Id); + AI->PathEndReached(pathId); // Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI if (CreatureAI* AI = creature->AI()) - AI->WaypointPathEnded(waypoint.Id, i_path->Id); + AI->WaypointPathEnded(waypointId, pathId); } // All hooks called and infos updated. Time to increment the waypoint node id @@ -385,8 +389,15 @@ bool WaypointMovementGenerator::DoUpdate(Creature* creature, uint32 di WaypointNode const& passedWp = i_path->Nodes.at(i_currentNode); UpdateHomePosition(creature, passedWp); - creature->UpdateWaypointID(passedWp.Id); - creature->UpdateCurrentWaypointInfo(passedWp.Id, i_path->Id); + + // Save data before AI callbacks — they can invalidate the reference + uint32 const wpId = passedWp.Id; + uint32 const wpPathId = i_path->Id; + uint32 const wpDelay = passedWp.Delay; + std::optional const wpOrientation = passedWp.Orientation; + + creature->UpdateWaypointID(wpId); + creature->UpdateCurrentWaypointInfo(wpId, wpPathId); if (passedWp.EventId && urand(0, 99) < passedWp.EventChance) { @@ -396,23 +407,24 @@ bool WaypointMovementGenerator::DoUpdate(Creature* creature, uint32 di if (CreatureAI* AI = creature->AI()) { - AI->MovementInform(WAYPOINT_MOTION_TYPE, passedWp.Id); - AI->WaypointReached(passedWp.Id, i_path->Id); + AI->MovementInform(WAYPOINT_MOTION_TYPE, wpId); + AI->WaypointReached(wpId, wpPathId); } // Advance node - i_currentNode = (i_currentNode + 1) % i_path->Nodes.size(); + if (i_path && !i_path->Nodes.empty()) + i_currentNode = (i_currentNode + 1) % i_path->Nodes.size(); // If this waypoint has a delay, stop the spline and pause - if (passedWp.Delay > 0) + if (wpDelay > 0) { creature->StopMoving(); creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); - _waypointDelay = passedWp.Delay; + _waypointDelay = wpDelay; _waypointReached = true; _smoothSplineLaunched = false; - if (passedWp.Orientation.has_value()) - creature->SetFacingTo(*passedWp.Orientation); + if (wpOrientation.has_value()) + creature->SetFacingTo(*wpOrientation); return true; } @@ -423,15 +435,17 @@ bool WaypointMovementGenerator::DoUpdate(Creature* creature, uint32 di if (!_repeating) { // Path ended + uint32 const endWpId = i_path->Nodes.at(i_currentNode).Id; + uint32 const endPathId = i_path->Id; _done = true; _smoothSplineLaunched = false; creature->UpdateCurrentWaypointInfo(0, 0); if (CreatureAI* AI = creature->AI()) - AI->PathEndReached(i_path->Id); + AI->PathEndReached(endPathId); // Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI if (CreatureAI* AI = creature->AI()) - AI->WaypointPathEnded(i_path->Nodes.at(i_currentNode).Id, i_path->Id); + AI->WaypointPathEnded(endWpId, endPathId); } else { From 8ea9252d764767dba2d9ddd7fd5c794a13fdc054 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:33:55 -0500 Subject: [PATCH 129/132] fix(Core/Combat): Immediately reselect victim after taunt aura update (#25254) Co-authored-by: blinkysc --- src/server/game/Combat/ThreatManager.cpp | 5 + src/server/game/Combat/ThreatManager.h | 2 + .../server/game/Combat/ThreatManagerTest.cpp | 243 ++++++++++++++++++ 3 files changed, 250 insertions(+) diff --git a/src/server/game/Combat/ThreatManager.cpp b/src/server/game/Combat/ThreatManager.cpp index ce37f961c..5559b8674 100644 --- a/src/server/game/Combat/ThreatManager.cpp +++ b/src/server/game/Combat/ThreatManager.cpp @@ -540,6 +540,11 @@ void ThreatManager::TauntUpdate() // taunt aura update also re-evaluates all suppressed states (retail behavior) EvaluateSuppressed(true); + + // immediately reselect victim so taunt takes effect without waiting + // for the next THREAT_UPDATE_INTERVAL (1 s) timer tick + UpdateVictim(); + _updateTimer = THREAT_UPDATE_INTERVAL; } void ThreatManager::SetTauntStateForTesting( diff --git a/src/server/game/Combat/ThreatManager.h b/src/server/game/Combat/ThreatManager.h index 261679371..f111da619 100644 --- a/src/server/game/Combat/ThreatManager.h +++ b/src/server/game/Combat/ThreatManager.h @@ -182,6 +182,8 @@ public: // Test-only: directly set taunt state on a threat ref // Uses uint32 to avoid incomplete-type issue with ThreatReference void SetTauntStateForTesting(Unit* target, uint32 state); + // Immediately reselect victim (bypasses 1-second Update timer) + void UpdateVictimForTesting() { UpdateVictim(); } ///== REDIRECT SYSTEM == void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct); diff --git a/src/test/server/game/Combat/ThreatManagerTest.cpp b/src/test/server/game/Combat/ThreatManagerTest.cpp index df29e0495..af4e48aaf 100644 --- a/src/test/server/game/Combat/ThreatManagerTest.cpp +++ b/src/test/server/game/Combat/ThreatManagerTest.cpp @@ -2313,4 +2313,247 @@ TEST_F(ThreatManagerIntegrationTest, delete creatureC; } +// ============================================================================ +// TauntUpdate immediate victim reselection tests +// Verify that TauntUpdate() calls UpdateVictim() immediately (no 1-second +// delay), and that rapid/repeated calls do not cause recursion or crashes. +// +// Note: TauntUpdate() reads real auras via GetAuraEffectsByType(), which are +// not present in unit tests. Tests that need taunt state to persist use +// SetTauntStateForTesting() + UpdateVictim() to exercise the same code path +// (ReselectVictim → ProcessAIUpdates) that TauntUpdate now invokes. +// TauntUpdate()-specific tests verify no-crash / no-recursion behaviour. +// ============================================================================ + +TEST_F(ThreatManagerIntegrationTest, + UpdateVictim_ImmediateReselectAfterTauntState) +{ + TestCreature* creatureC = new TestCreature(); + creatureC->SetupForCombatTest(_map, 3, 12347); + creatureC->SetFaction(90002); + + // B has low threat, C has high threat + _creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f); + _creatureA->TestGetThreatMgr().Update( + ThreatManager::THREAT_UPDATE_INTERVAL); + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + creatureC); + + // Set taunt on B, then call UpdateVictim directly (the call that + // TauntUpdate now makes internally) — WITHOUT waiting for the + // 1-second Update() timer + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + _creatureB, ThreatReference::TAUNT_STATE_TAUNT); + _creatureA->TestGetThreatMgr().UpdateVictimForTesting(); + + // Victim should switch immediately to B (taunted) + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + _creatureB); + + creatureC->CleanupCombatState(); + delete creatureC; +} + +TEST_F(ThreatManagerIntegrationTest, + UpdateVictim_Idempotent_MultipleCalls) +{ + TestCreature* creatureC = new TestCreature(); + creatureC->SetupForCombatTest(_map, 3, 12347); + creatureC->SetFaction(90002); + + _creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f); + _creatureA->TestGetThreatMgr().Update( + ThreatManager::THREAT_UPDATE_INTERVAL); + + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + _creatureB, ThreatReference::TAUNT_STATE_TAUNT); + + // Call UpdateVictim many times in a row — must be idempotent + for (int i = 0; i < 50; ++i) + _creatureA->TestGetThreatMgr().UpdateVictimForTesting(); + + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + _creatureB); + + creatureC->CleanupCombatState(); + delete creatureC; +} + +TEST_F(ThreatManagerIntegrationTest, + UpdateVictim_RapidTauntToggle_NoCrash) +{ + TestCreature* creatureC = new TestCreature(); + creatureC->SetupForCombatTest(_map, 3, 12347); + creatureC->SetFaction(90002); + + _creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f); + _creatureA->TestGetThreatMgr().Update( + ThreatManager::THREAT_UPDATE_INTERVAL); + + // Rapidly toggle taunt state with immediate UpdateVictim each time + for (int i = 0; i < 50; ++i) + { + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + _creatureB, ThreatReference::TAUNT_STATE_TAUNT); + _creatureA->TestGetThreatMgr().UpdateVictimForTesting(); + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + _creatureB); + + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + _creatureB, ThreatReference::TAUNT_STATE_NONE); + _creatureA->TestGetThreatMgr().UpdateVictimForTesting(); + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + creatureC); + } + + creatureC->CleanupCombatState(); + delete creatureC; +} + +TEST_F(ThreatManagerIntegrationTest, + UpdateVictim_WithConcurrentThreatChanges_NoCrash) +{ + TestCreature* creatureC = new TestCreature(); + creatureC->SetupForCombatTest(_map, 3, 12347); + creatureC->SetFaction(90002); + + _creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f); + _creatureA->TestGetThreatMgr().Update( + ThreatManager::THREAT_UPDATE_INTERVAL); + + // Simulate taunt landing while threat is also being modified + // (e.g. damage events arriving in the same server tick) + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + _creatureB, ThreatReference::TAUNT_STATE_TAUNT); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 200.0f); + _creatureA->TestGetThreatMgr().UpdateVictimForTesting(); + + // B should be victim because taunt overrides threat + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + _creatureB); + + creatureC->CleanupCombatState(); + delete creatureC; +} + +TEST_F(ThreatManagerIntegrationTest, + UpdateVictim_MultipleTaunters_HighestStateWins) +{ + TestCreature* creatureC = new TestCreature(); + creatureC->SetupForCombatTest(_map, 3, 12347); + creatureC->SetFaction(90002); + + TestCreature* creatureD = new TestCreature(); + creatureD->SetupForCombatTest(_map, 4, 12348); + creatureD->SetFaction(90002); + + _creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 200.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureD, 300.0f); + _creatureA->TestGetThreatMgr().Update( + ThreatManager::THREAT_UPDATE_INTERVAL); + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + creatureD); + + // Two taunters: B (state=TAUNT) and C (state=TAUNT+1, i.e. "last taunt") + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + _creatureB, ThreatReference::TAUNT_STATE_TAUNT); + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + creatureC, static_cast( + ThreatReference::TAUNT_STATE_TAUNT + 1)); + _creatureA->TestGetThreatMgr().UpdateVictimForTesting(); + + // C has the higher taunt state — should be victim immediately + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + creatureC); + + creatureC->CleanupCombatState(); + creatureD->CleanupCombatState(); + delete creatureC; + delete creatureD; +} + +TEST_F(ThreatManagerIntegrationTest, + UpdateVictim_FixateStillTakesPrecedence) +{ + TestCreature* creatureC = new TestCreature(); + creatureC->SetupForCombatTest(_map, 3, 12347); + creatureC->SetFaction(90002); + + _creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 200.0f); + + // Fixate on C + _creatureA->TestGetThreatMgr().FixateTarget(creatureC); + _creatureA->TestGetThreatMgr().Update( + ThreatManager::THREAT_UPDATE_INTERVAL); + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + creatureC); + + // Taunt from B + immediate UpdateVictim — fixate should still win + _creatureA->TestGetThreatMgr().SetTauntStateForTesting( + _creatureB, ThreatReference::TAUNT_STATE_TAUNT); + _creatureA->TestGetThreatMgr().UpdateVictimForTesting(); + + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + creatureC); + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetFixateTarget(), + creatureC); + + creatureC->CleanupCombatState(); + delete creatureC; +} + +TEST_F(ThreatManagerIntegrationTest, + TauntUpdate_EmptyThreatList_NoCrash) +{ + // TauntUpdate on a creature with no threats should not crash + // even though UpdateVictim is now called internally + _creatureA->TestGetThreatMgr().TauntUpdate(); + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + nullptr); +} + +TEST_F(ThreatManagerIntegrationTest, + TauntUpdate_RapidCalls_NoCrash) +{ + TestCreature* creatureC = new TestCreature(); + creatureC->SetupForCombatTest(_map, 3, 12347); + creatureC->SetFaction(90002); + + _creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f); + _creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f); + _creatureA->TestGetThreatMgr().Update( + ThreatManager::THREAT_UPDATE_INTERVAL); + + // Rapidly call TauntUpdate (no real auras — clears taunt state each + // time and calls UpdateVictim). Must not crash or recurse. + for (int i = 0; i < 50; ++i) + _creatureA->TestGetThreatMgr().TauntUpdate(); + + // C remains victim (highest threat, no taunt active) + EXPECT_EQ( + _creatureA->TestGetThreatMgr().GetCurrentVictim(), + creatureC); + + creatureC->CleanupCombatState(); + delete creatureC; +} + } // namespace From 9c1e86b12d2b5095c735147ce993f582c0c37eda Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:13:01 -0500 Subject: [PATCH 130/132] fix(Core/Scripts): Clear IMMUNE_TO_PC before engaging after threat system port (#25263) Co-authored-by: blinkysc --- .../FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp | 1 + .../scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp | 1 + .../Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp index ca9ec626d..e7e08d273 100644 --- a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp @@ -116,6 +116,7 @@ public: // start real fight me->RemoveAllAuras(); me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + me->SetImmuneToPC(false); DoZoneInCombat(); me->CastSpell(me, 43979, true); Talk(SAY_AGGRO); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp index 23c3d5549..1030753aa 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp @@ -181,6 +181,7 @@ struct boss_razorscale : public BossAI void JustEngagedWith(Unit* who) override { + me->SetImmuneToPC(false); BossAI::JustEngagedWith(who); events.ScheduleEvent(EVENT_COMMANDER_SAY_AGGRO, 5s); events.ScheduleEvent(EVENT_EE_SAY_MOVE_OUT, 10s); diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp index 3af170ba6..9d57f7be0 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp @@ -191,6 +191,7 @@ public: cr->SetDisableGravity(true); cr->GetMotionMaster()->MovePoint(0, 275.4f, -453, 110); // ROOM CENTER events.RescheduleEvent(EVENT_UNFREEZE_MONSTER, 10s); + me->SetImmuneToPC(false); me->SetInCombatWithZone(); me->SetControlled(true, UNIT_STATE_STUNNED); } @@ -373,6 +374,7 @@ public: { me->RemoveAurasDueToSpell(SPELL_FREEZE); me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(false); me->SetInCombatWithZone(); events.ScheduleEvent(EVENT_JORMUNGAR_ACID_SPIT, 3s); @@ -499,6 +501,7 @@ public: { me->RemoveAurasDueToSpell(SPELL_FREEZE); me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(false); me->SetInCombatWithZone(); events.ScheduleEvent(EVENT_RHINO_STOMP, 3s); @@ -609,6 +612,7 @@ public: { me->RemoveAurasDueToSpell(SPELL_FREEZE); me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(false); me->SetInCombatWithZone(); events.ScheduleEvent(EVENT_FURBOLG_CHAIN, 3s); @@ -717,6 +721,7 @@ public: { me->RemoveAurasDueToSpell(SPELL_FREEZE); me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetImmuneToPC(false); me->SetInCombatWithZone(); events.ScheduleEvent(EVENT_WORGEN_MORTAL, 3s); From 9d25fb3e5cef0abd4678ee7066b08bec8a8ff462 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:03:37 -0300 Subject: [PATCH 131/132] refactor(Core/Scripts): move Kalimdor gossip handlers to database (#25207) Co-authored-by: Claude Opus 4.6 --- .../rev_1774500000_gossip_kalimdor.sql | 172 ++++++++++++++++++ .../scripts/Kalimdor/zone_moonglade.cpp | 53 ------ .../scripts/Kalimdor/zone_orgrimmar.cpp | 48 +---- .../Kalimdor/zone_stonetalon_mountains.cpp | 56 ------ src/server/scripts/Kalimdor/zone_tanaris.cpp | 91 --------- .../scripts/Kalimdor/zone_winterspring.cpp | 35 ---- 6 files changed, 178 insertions(+), 277 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1774500000_gossip_kalimdor.sql diff --git a/data/sql/updates/pending_db_world/rev_1774500000_gossip_kalimdor.sql b/data/sql/updates/pending_db_world/rev_1774500000_gossip_kalimdor.sql new file mode 100644 index 000000000..fc067f69c --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1774500000_gossip_kalimdor.sql @@ -0,0 +1,172 @@ +-- Move Kalimdor gossip handlers from C++ to database +-- NPCs: Rivern Frostwind, Stone Watcher of Norgannon, Great Bear Spirit, +-- Braug Dimspirit, Steward of Time, Thrall Warchief + +-- ===================================================== +-- 1. npc_rivern_frostwind (10618) - zone_winterspring +-- Show vendor option only if Exalted with Wintersaber Trainers (faction 589) +-- ===================================================== + +-- Condition: gossip_menu_option MenuID=3130, OptionID=0 requires Exalted (rank 7, mask 128) +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=15 AND `SourceGroup`=3130 AND `SourceEntry`=0 AND `ConditionTypeOrReference`=5; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 3130, 0, 0, 0, 5, 0, 589, 128, 0, 0, 0, 0, '', 'Rivern Frostwind - Show vendor option only if Exalted with Wintersaber Trainers'); + +-- Remove ScriptName +UPDATE `creature_template` SET `ScriptName`='' WHERE `entry`=10618; + +-- ===================================================== +-- 2. npc_stone_watcher_of_norgannon (7918) - zone_tanaris +-- 5-step gossip chain, quest 2954 credit at end +-- ===================================================== + +-- Add missing gossip_menu entries (MenuID -> TextID) +DELETE FROM `gossip_menu` WHERE `MenuID` IN (57000,57001,57002,57003,57004,57005); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(57000, 1674), +(57001, 1675), +(57002, 1676), +(57003, 1677), +(57004, 1678), +(57005, 1679); + +-- Condition: show first option only if quest 2954 is taken (incomplete) +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=15 AND `SourceGroup`=57000 AND `SourceEntry`=0 AND `ConditionTypeOrReference`=9; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 57000, 0, 0, 0, 9, 0, 2954, 0, 0, 0, 0, 0, '', 'Stone Watcher of Norgannon - Show gossip option if quest 2954 is taken'); + +-- SAI: on final gossip select (menu 57005, option 0) -> close gossip + quest credit 2954 +DELETE FROM `smart_scripts` WHERE `entryorguid`=7918 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`, `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 +(7918, 0, 0, 1, 62, 0, 100, 512, 57005, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Stone Watcher of Norgannon - On Gossip Select - Close Gossip'), +(7918, 0, 1, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 15, 2954, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Stone Watcher of Norgannon - Linked - Quest Credit 2954'); + +-- Update creature_template: set gossip_menu_id, add gossip npcflag, set SmartAI +UPDATE `creature_template` SET `gossip_menu_id`=57000, `npcflag`=`npcflag`|1, `ScriptName`='', `AIName`='SmartAI' WHERE `entry`=7918; + +-- ===================================================== +-- 3. npc_great_bear_spirit (11956) - zone_moonglade +-- 4-step gossip chain, quest 5929/5930 credit at end +-- ===================================================== + +-- Add quest-active text for menu 3882 +DELETE FROM `gossip_menu` WHERE `MenuID`=3882 AND `TextID`=4719; +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(3882, 4719); + +-- Condition on gossip_menu: show TextID 4719 if quest 5929 OR 5930 is taken +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=14 AND `SourceGroup`=3882 AND `SourceEntry`=4719; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 3882, 4719, 0, 0, 9, 0, 5929, 0, 0, 0, 0, 0, '', 'Great Bear Spirit - Show text 4719 if quest 5929 is taken'), +(14, 3882, 4719, 0, 1, 9, 0, 5930, 0, 0, 0, 0, 0, '', 'Great Bear Spirit - Show text 4719 if quest 5930 is taken'); + +-- Condition on gossip_menu_option: show option if quest 5929 OR 5930 is taken +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=15 AND `SourceGroup`=3882 AND `SourceEntry`=0; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 3882, 0, 0, 0, 9, 0, 5929, 0, 0, 0, 0, 0, '', 'Great Bear Spirit - Show gossip option if quest 5929 is taken'), +(15, 3882, 0, 0, 1, 9, 0, 5930, 0, 0, 0, 0, 0, '', 'Great Bear Spirit - Show gossip option if quest 5930 is taken'); + +-- Clean up duplicate OptionID=1 on menu 3884 (identical to OptionID=0) +DELETE FROM `gossip_menu_option` WHERE `MenuID`=3884 AND `OptionID`=1; + +-- SAI: on gossip select (menu 3884, option 0) -> quest credit for both 5929 and 5930 +DELETE FROM `smart_scripts` WHERE `entryorguid`=11956 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`, `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 +(11956, 0, 0, 1, 62, 0, 100, 512, 3884, 0, 0, 0, 0, 0, 15, 5929, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Great Bear Spirit - On Gossip Select - Quest Credit 5929'), +(11956, 0, 1, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 15, 5930, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Great Bear Spirit - On Gossip Select - Quest Credit 5930'); + +-- Update creature_template +UPDATE `creature_template` SET `ScriptName`='', `AIName`='SmartAI' WHERE `entry`=11956; + +-- ===================================================== +-- 4. npc_braug_dimspirit (4489) - zone_stonetalon_mountains +-- Quiz: 5 dragon options, Neltharion = correct, others = wrong spell +-- ===================================================== + +-- Add missing Nozdormu option (OptionID=4) +DELETE FROM `gossip_menu_option` WHERE `MenuID`=4763 AND `OptionID`=4; +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`) VALUES +(4763, 4, 0, 'Nozdormu is my answer.', 0, 1, 1, 0, 0, 0, 0, ''); + +-- Condition on gossip_menu: show text 5820 (quiz text) if quest 6627 is taken +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=14 AND `SourceGroup`=4763 AND `SourceEntry`=5820; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 4763, 5820, 0, 0, 9, 0, 6627, 0, 0, 0, 0, 0, '', 'Braug Dimspirit - Show quiz text if quest 6627 is taken'); + +-- Conditions on all 5 options: show only if quest 6627 is taken +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=15 AND `SourceGroup`=4763 AND `SourceEntry` IN (0,1,2,3,4) AND `ConditionTypeOrReference`=9; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 4763, 0, 0, 0, 9, 0, 6627, 0, 0, 0, 0, 0, '', 'Braug Dimspirit - Show Ysera option if quest 6627 is taken'), +(15, 4763, 1, 0, 0, 9, 0, 6627, 0, 0, 0, 0, 0, '', 'Braug Dimspirit - Show Neltharion option if quest 6627 is taken'), +(15, 4763, 2, 0, 0, 9, 0, 6627, 0, 0, 0, 0, 0, '', 'Braug Dimspirit - Show Alexstrasza option if quest 6627 is taken'), +(15, 4763, 3, 0, 0, 9, 0, 6627, 0, 0, 0, 0, 0, '', 'Braug Dimspirit - Show Malygos option if quest 6627 is taken'), +(15, 4763, 4, 0, 0, 9, 0, 6627, 0, 0, 0, 0, 0, '', 'Braug Dimspirit - Show Nozdormu option if quest 6627 is taken'); + +-- SAI: Neltharion (option 1) = correct answer -> quest credit +-- All others (options 0,2,3,4) = wrong answer -> cast spell 6766 +DELETE FROM `smart_scripts` WHERE `entryorguid`=4489 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`, `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 +(4489, 0, 0, 0, 62, 0, 100, 512, 4763, 1, 0, 0, 0, 0, 15, 6627, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Braug Dimspirit - On Gossip Select Neltharion - Quest Credit 6627'), +(4489, 0, 1, 0, 62, 0, 100, 512, 4763, 0, 0, 0, 0, 0, 11, 6766, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Braug Dimspirit - On Gossip Select Ysera - Cast Wrong Answer'), +(4489, 0, 2, 0, 62, 0, 100, 512, 4763, 2, 0, 0, 0, 0, 11, 6766, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Braug Dimspirit - On Gossip Select Alexstrasza - Cast Wrong Answer'), +(4489, 0, 3, 0, 62, 0, 100, 512, 4763, 3, 0, 0, 0, 0, 11, 6766, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Braug Dimspirit - On Gossip Select Malygos - Cast Wrong Answer'), +(4489, 0, 4, 0, 62, 0, 100, 512, 4763, 4, 0, 0, 0, 0, 11, 6766, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Braug Dimspirit - On Gossip Select Nozdormu - Cast Wrong Answer'); + +-- Update creature_template +UPDATE `creature_template` SET `ScriptName`='', `AIName`='SmartAI' WHERE `entry`=4489; + +-- ===================================================== +-- 5. npc_steward_of_time (20142) - zone_tanaris +-- Gossip shows flight option if quest 10279 incomplete or rewarded +-- On select casts spell 34891. Keep OnQuestAccept in C++. +-- ===================================================== + +-- Condition on gossip_menu: show text 9978 if quest 10279 is taken OR rewarded +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=14 AND `SourceGroup`=8072 AND `SourceEntry`=9978; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 8072, 9978, 0, 0, 9, 0, 10279, 0, 0, 0, 0, 0, '', 'Steward of Time - Show text 9978 if quest 10279 is taken'), +(14, 8072, 9978, 0, 1, 8, 0, 10279, 0, 0, 0, 0, 0, '', 'Steward of Time - Show text 9978 if quest 10279 is rewarded'); + +-- Condition on gossip_menu_option: show option if quest 10279 is taken OR rewarded +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=15 AND `SourceGroup`=8072 AND `SourceEntry`=0; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8072, 0, 0, 0, 9, 0, 10279, 0, 0, 0, 0, 0, '', 'Steward of Time - Show gossip option if quest 10279 is taken'), +(15, 8072, 0, 0, 1, 8, 0, 10279, 0, 0, 0, 0, 0, '', 'Steward of Time - Show gossip option if quest 10279 is rewarded'); + +-- SAI: on gossip select (menu 8072, option 0) -> invoker self-casts spell 34891 +-- Using SMART_ACTION_INVOKER_CAST (134) so the player casts on themselves, +-- matching original C++: player->CastSpell(player, 34891, true) +DELETE FROM `smart_scripts` WHERE `entryorguid`=20142 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`, `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 +(20142, 0, 0, 0, 62, 0, 100, 512, 8072, 0, 0, 0, 0, 0, 134, 34891, 2, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Steward of Time - On Gossip Select - Invoker Cast Flight Through Caverns'); + +-- Update creature_template: add SmartAI (keep ScriptName for OnQuestAccept) +UPDATE `creature_template` SET `AIName`='SmartAI' WHERE `entry`=20142; + +-- ===================================================== +-- 6. npc_thrall_warchief (4949) - zone_orgrimmar +-- 7-step gossip chain, quest 6566 credit at end +-- Keep ScriptName for OnQuestReward + AI +-- ===================================================== + +-- Add missing gossip_menu entries for chain steps +DELETE FROM `gossip_menu` WHERE `MenuID` IN (3665,3666,3667,3668,3669,3670); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(3665, 5733), +(3666, 5734), +(3667, 5735), +(3668, 5736), +(3669, 5737), +(3670, 5738); + +-- Fix menu 3670: ActionMenuID should be 0 (close), not 3664 (loop) +UPDATE `gossip_menu_option` SET `ActionMenuID`=0 WHERE `MenuID`=3670 AND `OptionID`=0; + +-- Condition on gossip_menu_option: show first option only if quest 6566 is taken +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=15 AND `SourceGroup`=3664 AND `SourceEntry`=0 AND `ConditionTypeOrReference`=9; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 3664, 0, 0, 0, 9, 0, 6566, 0, 0, 0, 0, 0, '', 'Thrall - Show gossip option if quest 6566 is taken'); + +-- Quest credit on final gossip step handled via sGossipSelect in C++ AI struct. +-- Cannot use SAI because Thrall's custom C++ AI (GetAI) takes precedence over SmartAI. +-- No AIName change needed. diff --git a/src/server/scripts/Kalimdor/zone_moonglade.cpp b/src/server/scripts/Kalimdor/zone_moonglade.cpp index bc1538d2c..4aee0c0ad 100644 --- a/src/server/scripts/Kalimdor/zone_moonglade.cpp +++ b/src/server/scripts/Kalimdor/zone_moonglade.cpp @@ -104,58 +104,6 @@ public: } }; -/*###### -## npc_great_bear_spirit -######*/ - -class npc_great_bear_spirit : public CreatureScript -{ -public: - npc_great_bear_spirit() : CreatureScript("npc_great_bear_spirit") { } - - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override - { - ClearGossipMenuFor(player); - switch (action) - { - case GOSSIP_ACTION_INFO_DEF: - AddGossipItemFor(player, 3881, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, 4721, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF + 1: - AddGossipItemFor(player, 3883, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - SendGossipMenuFor(player, 4733, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF + 2: - AddGossipItemFor(player, 3884, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); - SendGossipMenuFor(player, 4734, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF + 3: - SendGossipMenuFor(player, 4735, creature->GetGUID()); - if (player->GetQuestStatus(5929) == QUEST_STATUS_INCOMPLETE) - player->AreaExploredOrEventHappens(5929); - if (player->GetQuestStatus(5930) == QUEST_STATUS_INCOMPLETE) - player->AreaExploredOrEventHappens(5930); - break; - } - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - //ally or horde quest - if (player->GetQuestStatus(5929) == QUEST_STATUS_INCOMPLETE || player->GetQuestStatus(5930) == QUEST_STATUS_INCOMPLETE) - { - AddGossipItemFor(player, 3882, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - SendGossipMenuFor(player, 4719, creature->GetGUID()); - } - else - SendGossipMenuFor(player, 4718, creature->GetGUID()); - - return true; - } -}; - /*###### ## npc_silva_filnaveth ######*/ @@ -714,7 +662,6 @@ public: void AddSC_moonglade() { new npc_bunthen_plainswind(); - new npc_great_bear_spirit(); new npc_silva_filnaveth(); new npc_clintar_spirit(); new npc_omen(); diff --git a/src/server/scripts/Kalimdor/zone_orgrimmar.cpp b/src/server/scripts/Kalimdor/zone_orgrimmar.cpp index 36c6c56c2..a2f826416 100644 --- a/src/server/scripts/Kalimdor/zone_orgrimmar.cpp +++ b/src/server/scripts/Kalimdor/zone_orgrimmar.cpp @@ -19,7 +19,6 @@ #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" -#include "ScriptedGossip.h" #include "TaskScheduler.h" #include "LFGMgr.h" @@ -142,8 +141,6 @@ enum ThrallWarchief : uint32 // What the Wind Carries (ID: 6566) QUEST_WHAT_THE_WIND_CARRIES = 6566, - GOSSIP_MENU_THRALL = 3664, - GOSSIP_RESPONSE_THRALL_FIRST = 5733, // Deathknight Starting Zone End QUEST_WARCHIEFS_BLESSING = 13189, @@ -157,45 +154,6 @@ class npc_thrall_warchief : public CreatureScript public: npc_thrall_warchief() : CreatureScript("npc_thrall_warchief") { } - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override - { - ClearGossipMenuFor(player); - - uint32 DiscussionOrder = action - GOSSIP_ACTION_INFO_DEF; - - if (DiscussionOrder>= 1 && DiscussionOrder <= 6) - { - uint32 NextAction = GOSSIP_ACTION_INFO_DEF + DiscussionOrder + 1; - uint32 GossipResponse = GOSSIP_RESPONSE_THRALL_FIRST + DiscussionOrder - 1; - - AddGossipItemFor(player, GOSSIP_MENU_THRALL + DiscussionOrder, 0, GOSSIP_SENDER_MAIN, NextAction); - SendGossipMenuFor(player, GossipResponse, creature->GetGUID()); - } - else if (DiscussionOrder == 7) - { - CloseGossipMenuFor(player); - player->AreaExploredOrEventHappens(QUEST_WHAT_THE_WIND_CARRIES); - } - - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - if (creature->IsQuestGiver()) - { - player->PrepareQuestMenu(creature->GetGUID()); - } - - if (player->GetQuestStatus(QUEST_WHAT_THE_WIND_CARRIES) == QUEST_STATUS_INCOMPLETE) - { - AddGossipItemFor(player, GOSSIP_MENU_THRALL, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - } - - SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID()); - return true; - } - bool OnQuestReward(Player* player, Creature* creature, Quest const* quest, uint32 /*item*/) override { switch (quest->GetQuestId()) @@ -232,6 +190,12 @@ public: ShockTimer = 8000; } + void sGossipSelect(Player* player, uint32 menuId, uint32 /*gossipListId*/) override + { + if (menuId == 3670) + player->AreaExploredOrEventHappens(QUEST_WHAT_THE_WIND_CARRIES); + } + void JustEngagedWith(Unit* /*who*/) override { } void JustDied(Unit* /*killer*/) override diff --git a/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp b/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp index 2aa681190..0d4bf1937 100644 --- a/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp +++ b/src/server/scripts/Kalimdor/zone_stonetalon_mountains.cpp @@ -19,61 +19,6 @@ #include "Player.h" #include "ScriptedCreature.h" #include "ScriptedEscortAI.h" -#include "ScriptedGossip.h" - -/*###### -## npc_braug_dimspirit -######*/ - -#define GOSSIP_HBD1 "Ysera" -#define GOSSIP_HBD2 "Neltharion" -#define GOSSIP_HBD3 "Nozdormu" -#define GOSSIP_HBD4 "Alexstrasza" -#define GOSSIP_HBD5 "Malygos" - -class npc_braug_dimspirit : public CreatureScript -{ -public: - npc_braug_dimspirit() : CreatureScript("npc_braug_dimspirit") { } - - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override - { - ClearGossipMenuFor(player); - if (action == GOSSIP_ACTION_INFO_DEF + 1) - { - CloseGossipMenuFor(player); - creature->CastSpell(player, 6766, false); - } - if (action == GOSSIP_ACTION_INFO_DEF + 2) - { - CloseGossipMenuFor(player); - player->AreaExploredOrEventHappens(6627); - } - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - if (creature->IsQuestGiver()) - player->PrepareQuestMenu(creature->GetGUID()); - - if (player->GetQuestStatus(6627) == QUEST_STATUS_INCOMPLETE) - { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GOSSIP_HBD1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GOSSIP_HBD2, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GOSSIP_HBD3, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GOSSIP_HBD4, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GOSSIP_HBD5, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - - SendGossipMenuFor(player, 5820, creature->GetGUID()); - } - else - SendGossipMenuFor(player, 5819, creature->GetGUID()); - - return true; - } -}; - /*###### ## npc_kaya_flathoof ######*/ @@ -162,6 +107,5 @@ public: void AddSC_stonetalon_mountains() { - new npc_braug_dimspirit(); new npc_kaya_flathoof(); } diff --git a/src/server/scripts/Kalimdor/zone_tanaris.cpp b/src/server/scripts/Kalimdor/zone_tanaris.cpp index 4a995ca46..cd5d007b3 100644 --- a/src/server/scripts/Kalimdor/zone_tanaris.cpp +++ b/src/server/scripts/Kalimdor/zone_tanaris.cpp @@ -20,7 +20,6 @@ #include "ScriptedCreature.h" #include "ScriptedEscortAI.h" #include "ScriptedFollowerAI.h" -#include "ScriptedGossip.h" /*###### ## npc_aquementas @@ -294,95 +293,6 @@ public: return false; } - - bool OnGossipSelect(Player* player, Creature* /*creature*/, uint32 /*sender*/, uint32 action) override - { - ClearGossipMenuFor(player); - if (action == GOSSIP_ACTION_INFO_DEF + 1) - player->CastSpell(player, 34891, true); //(Flight through Caverns) - - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - if (creature->IsQuestGiver()) - { - player->PrepareQuestMenu(creature->GetGUID()); - } - - if (player->GetQuestStatus(10279) == QUEST_STATUS_INCOMPLETE || player->GetQuestRewardStatus(10279)) - { - AddGossipItemFor(player, 8072, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, 9978, creature->GetGUID()); - } - else - { - SendGossipMenuFor(player, 9977, creature->GetGUID()); - } - - return true; - } -}; - -/*###### -## npc_stone_watcher_of_norgannon -######*/ - -class npc_stone_watcher_of_norgannon : public CreatureScript -{ -public: - npc_stone_watcher_of_norgannon() : CreatureScript("npc_stone_watcher_of_norgannon") { } - - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override - { - ClearGossipMenuFor(player); - switch (action) - { - case GOSSIP_ACTION_INFO_DEF: - AddGossipItemFor(player, 57001, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); - SendGossipMenuFor(player, 1675, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+1: - AddGossipItemFor(player, 57002, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); - SendGossipMenuFor(player, 1676, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+2: - AddGossipItemFor(player, 57003, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); - SendGossipMenuFor(player, 1677, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+3: - AddGossipItemFor(player, 57004, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 4); - SendGossipMenuFor(player, 1678, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+4: - AddGossipItemFor(player, 57005, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 5); - SendGossipMenuFor(player, 1679, creature->GetGUID()); - break; - case GOSSIP_ACTION_INFO_DEF+5: - CloseGossipMenuFor(player); - player->AreaExploredOrEventHappens(2954); - break; - } - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - if (creature->IsQuestGiver()) - { - player->PrepareQuestMenu(creature->GetGUID()); - } - - if (player->GetQuestStatus(2954) == QUEST_STATUS_INCOMPLETE) - { - AddGossipItemFor(player, 57000, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - } - - SendGossipMenuFor(player, 1674, creature->GetGUID()); - - return true; - } }; /*#### @@ -552,6 +462,5 @@ void AddSC_tanaris() new npc_aquementas(); new npc_custodian_of_time(); new npc_steward_of_time(); - new npc_stone_watcher_of_norgannon(); new npc_tooga(); } diff --git a/src/server/scripts/Kalimdor/zone_winterspring.cpp b/src/server/scripts/Kalimdor/zone_winterspring.cpp index 4da2a9f6a..cf9930b45 100644 --- a/src/server/scripts/Kalimdor/zone_winterspring.cpp +++ b/src/server/scripts/Kalimdor/zone_winterspring.cpp @@ -20,40 +20,6 @@ #include "Player.h" #include "ScriptedCreature.h" #include "ScriptedEscortAI.h" -#include "ScriptedGossip.h" - -/*###### -## npc_rivern_frostwind -######*/ - -class npc_rivern_frostwind : public CreatureScript -{ -public: - npc_rivern_frostwind() : CreatureScript("npc_rivern_frostwind") { } - - bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override - { - ClearGossipMenuFor(player); - if (action == GOSSIP_ACTION_TRADE) - player->GetSession()->SendListInventory(creature->GetGUID()); - - return true; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - if (creature->IsQuestGiver()) - player->PrepareQuestMenu(creature->GetGUID()); - - if (creature->IsVendor() && player->GetReputationRank(589) == REP_EXALTED) - AddGossipItemFor(player, GOSSIP_ICON_VENDOR, GOSSIP_TEXT_BROWSE_GOODS, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_TRADE); - - SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID()); - - return true; - } -}; - enum Says { // Escort texts @@ -608,7 +574,6 @@ public: void AddSC_winterspring() { - new npc_rivern_frostwind(); new npc_ranshalla(); new go_elune_fire(); } From 27ee6428556336aafef68406b2ababef0b1ae8a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 28 Mar 2026 00:04:41 +0000 Subject: [PATCH 132/132] chore(DB): import pending files Referenced commit(s): 9d25fb3e5cef0abd4678ee7066b08bec8a8ff462 --- .../2026_03_28_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1774500000_gossip_kalimdor.sql => db_world/2026_03_28_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1774500000_gossip_kalimdor.sql b/data/sql/updates/db_world/2026_03_28_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1774500000_gossip_kalimdor.sql rename to data/sql/updates/db_world/2026_03_28_00.sql index fc067f69c..5a20217e1 100644 --- a/data/sql/updates/pending_db_world/rev_1774500000_gossip_kalimdor.sql +++ b/data/sql/updates/db_world/2026_03_28_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_03_27_04 -> 2026_03_28_00 -- Move Kalimdor gossip handlers from C++ to database -- NPCs: Rivern Frostwind, Stone Watcher of Norgannon, Great Bear Spirit, -- Braug Dimspirit, Steward of Time, Thrall Warchief