From ae8a78d90af6ed3cec05670c545516be7b862a54 Mon Sep 17 00:00:00 2001 From: lineagedr Date: Tue, 31 Aug 2021 11:34:43 +0300 Subject: [PATCH] feat(Core/Gameobject): add a range check for gameobjects (#7521) --- .../game/Entities/GameObject/GameObject.cpp | 197 ++++++++++++++++-- .../game/Entities/GameObject/GameObject.h | 20 +- src/server/game/Entities/Object/Object.cpp | 4 +- src/server/game/Entities/Player/Player.cpp | 6 +- .../game/Entities/Transport/Transport.cpp | 4 +- src/server/game/Handlers/LootHandler.cpp | 10 +- src/server/game/Handlers/SpellHandler.cpp | 41 +++- src/server/game/Spells/Spell.cpp | 17 ++ src/server/scripts/Commands/cs_gobject.cpp | 2 +- src/server/shared/SharedDefines.h | 3 +- 10 files changed, 256 insertions(+), 48 deletions(-) diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index cdb813721..6614765a0 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -24,6 +24,8 @@ #include "UpdateFieldFlags.h" #include "World.h" #include +#include +#include #ifdef ELUNA #include "LuaEngine.h" @@ -277,15 +279,19 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u // used switch since there should be more case 181233: // maexxna portal effect case 181575: // maexxna portal - SetWorldRotation(rotation); + SetLocalRotation(rotation); break; default: // xinef: hackfix - but make it possible to use original WorldRotation (using special gameobject addon data) // pussywizard: temporarily calculate WorldRotation from orientation, do so until values in db are correct if (addon && addon->invisibilityType == INVISIBILITY_GENERAL && addon->InvisibilityValue == 0) - SetWorldRotation(rotation); + { + SetLocalRotation(rotation); + } else - SetWorldRotationAngles(NormalizeOrientation(GetOrientation()), 0.0f, 0.0f); + { + SetLocalRotationAngles(NormalizeOrientation(GetOrientation()), 0.0f, 0.0f); + } break; } @@ -879,7 +885,7 @@ void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask, bool data.posY = GetPositionY(); data.posZ = GetPositionZ(); data.orientation = GetOrientation(); - data.rotation = m_worldRotation; + data.rotation = m_localRotation; data.spawntimesecs = m_spawnedByDefault ? m_respawnDelayTime : -(int32)m_respawnDelayTime; data.animprogress = GetGoAnimProgress(); data.go_state = GetGoState(); @@ -905,10 +911,10 @@ void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask, bool stmt->setFloat(index++, GetPositionY()); stmt->setFloat(index++, GetPositionZ()); stmt->setFloat(index++, GetOrientation()); - stmt->setFloat(index++, m_worldRotation.x); - stmt->setFloat(index++, m_worldRotation.y); - stmt->setFloat(index++, m_worldRotation.z); - stmt->setFloat(index++, m_worldRotation.w); + stmt->setFloat(index++, m_localRotation.x); + stmt->setFloat(index++, m_localRotation.y); + stmt->setFloat(index++, m_localRotation.z); + stmt->setFloat(index++, m_localRotation.w); stmt->setInt32(index++, int32(m_respawnDelayTime)); stmt->setUInt8(index++, GetGoAnimProgress()); stmt->setUInt8(index++, uint8(GetGoState())); @@ -2003,14 +2009,14 @@ void GameObject::UpdatePackedRotation() static const int32 PACK_X = PACK_YZ << 1; static const int32 PACK_YZ_MASK = (PACK_YZ << 1) - 1; static const int32 PACK_X_MASK = (PACK_X << 1) - 1; - int8 w_sign = (m_worldRotation.w >= 0.f ? 1 : -1); - int64 x = int32(m_worldRotation.x * PACK_X) * w_sign & PACK_X_MASK; - int64 y = int32(m_worldRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK; - int64 z = int32(m_worldRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK; + int8 w_sign = (m_localRotation.w >= 0.f ? 1 : -1); + int64 x = int32(m_localRotation.x * PACK_X) * w_sign & PACK_X_MASK; + int64 y = int32(m_localRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK; + int64 z = int32(m_localRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK; m_packedRotation = z | (y << 21) | (x << 42); } -void GameObject::SetWorldRotation(G3D::Quat const& rot) +void GameObject::SetLocalRotation(G3D::Quat const& rot) { G3D::Quat rotation; // Temporary solution for gameobjects that have no rotation data in DB: @@ -2020,7 +2026,7 @@ void GameObject::SetWorldRotation(G3D::Quat const& rot) rotation = rot; rotation.unitize(); - m_worldRotation = rotation; + m_localRotation = rotation; UpdatePackedRotation(); } @@ -2032,9 +2038,26 @@ void GameObject::SetTransportPathRotation(float qx, float qy, float qz, float qw SetFloatValue(GAMEOBJECT_PARENTROTATION + 3, qw); } -void GameObject::SetWorldRotationAngles(float z_rot, float y_rot, float x_rot) +void GameObject::SetLocalRotationAngles(float z_rot, float y_rot, float x_rot) { - SetWorldRotation(G3D::Quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot))); + SetLocalRotation(G3D::Quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot))); +} + +G3D::Quat GameObject::GetWorldRotation() const +{ + G3D::Quat localRotation = GetLocalRotation(); + if (Transport* transport = GetTransport()) + { + G3D::Quat worldRotation = transport->GetWorldRotation(); + + G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w); + G3D::Quat localRotationQuat(localRotation.x, localRotation.y, localRotation.z, localRotation.w); + + G3D::Quat resultRotation = localRotationQuat * worldRotationQuat; + + return G3D::Quat(resultRotation.x, resultRotation.y, resultRotation.z, resultRotation.w); + } + return localRotation; } void GameObject::ModifyHealth(int32 change, Unit* attackerOrHealer /*= nullptr*/, uint32 spellId /*= 0*/) @@ -2509,18 +2532,39 @@ void GameObject::SetPosition(float x, float y, float z, float o) GetMap()->GameObjectRelocation(this, x, y, z, o); } -float GameObject::GetInteractionDistance() +float GameObject::GetInteractionDistance() const { switch (GetGoType()) { - /// @todo find out how the client calculates the maximal usage distance to spellless working - // gameobjects like guildbanks and mailboxes - 10.0 is a just an abitrary choosen number + case GAMEOBJECT_TYPE_AREADAMAGE: + return 0.0f; + case GAMEOBJECT_TYPE_QUESTGIVER: + case GAMEOBJECT_TYPE_TEXT: + case GAMEOBJECT_TYPE_FLAGSTAND: + case GAMEOBJECT_TYPE_FLAGDROP: + case GAMEOBJECT_TYPE_MINI_GAME: + return 5.5555553f; + case GAMEOBJECT_TYPE_BINDER: + return 10.0f; + case GAMEOBJECT_TYPE_CHAIR: + case GAMEOBJECT_TYPE_BARBER_CHAIR: + return 3.0f; + case GAMEOBJECT_TYPE_FISHINGNODE: + return 100.0f; + case GAMEOBJECT_TYPE_FISHINGHOLE: + return 20.0f + CONTACT_DISTANCE; // max spell range + case GAMEOBJECT_TYPE_CAMERA: + case GAMEOBJECT_TYPE_MAP_OBJECT: + case GAMEOBJECT_TYPE_DUNGEON_DIFFICULTY: + case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: + case GAMEOBJECT_TYPE_DOOR: + return 5.0f; + // Following values are not blizzlike case GAMEOBJECT_TYPE_GUILD_BANK: case GAMEOBJECT_TYPE_MAILBOX: - return 10.0f; - case GAMEOBJECT_TYPE_FISHINGHOLE: - case GAMEOBJECT_TYPE_FISHINGNODE: - return 20.0f + CONTACT_DISTANCE; // max spell range + // Successful mailbox interaction is rather critical to the client, failing it will start a minute-long cooldown until the next mail query may be executed. + // And since movement info update is not sent with mailbox interaction query, server may find the player outside of interaction range. Thus we increase it. + return 10.0f; // 5.0f is blizzlike default: return INTERACTION_DISTANCE; } @@ -2562,3 +2606,110 @@ GameObjectModel* GameObject::CreateModel() { return GameObjectModel::Create(std::make_unique(this), sWorld->GetDataPath()); } + +bool GameObject::IsAtInteractDistance(Player const* player, SpellInfo const* spell) const +{ + if (spell || (spell = GetSpellForLock(player))) + { + float maxRange = spell->GetMaxRange(spell->IsPositive()); + + if (GetGoType() == GAMEOBJECT_TYPE_SPELL_FOCUS) + { + return maxRange * maxRange >= GetExactDistSq(player); + } + + if (sGameObjectDisplayInfoStore.LookupEntry(GetGOInfo()->displayId)) + { + return IsAtInteractDistance(*player, maxRange); + } + } + + return IsAtInteractDistance(*player, GetInteractionDistance()); +} + +bool GameObject::IsAtInteractDistance(Position const& pos, float radius) const +{ + if (GameObjectDisplayInfoEntry const* displayInfo = sGameObjectDisplayInfoStore.LookupEntry(GetGOInfo()->displayId)) + { + float scale = GetObjectScale(); + + float minX = displayInfo->minX * scale - radius; + float minY = displayInfo->minY * scale - radius; + float minZ = displayInfo->minZ * scale - radius; + float maxX = displayInfo->maxX * scale + radius; + float maxY = displayInfo->maxY * scale + radius; + float maxZ = displayInfo->maxZ * scale + radius; + + G3D::Quat worldRotation = GetWorldRotation(); + G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w); + + return G3D::CoordinateFrame {{worldRotationQuat}, {GetPositionX(), GetPositionY(), GetPositionZ()}}.toWorldSpace(G3D::Box {{minX, minY, minZ}, {maxX, maxY, maxZ}}).contains({pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()}); + } + + return GetExactDist(&pos) <= radius; +} + +bool GameObject::IsWithinDistInMap(Player const* player) const +{ + return IsInMap(player) && InSamePhase(player) && IsAtInteractDistance(player); +} + +SpellInfo const* GameObject::GetSpellForLock(Player const* player) const +{ + if (!player) + { + return nullptr; + } + + uint32 lockId = GetGOInfo()->GetLockId(); + if (!lockId) + { + return nullptr; + } + + LockEntry const* lock = sLockStore.LookupEntry(lockId); + if (!lock) + { + return nullptr; + } + + for (uint8 i = 0; i < MAX_LOCK_CASE; ++i) + { + if (!lock->Type[i]) + { + continue; + } + + if (lock->Type[i] == LOCK_KEY_SPELL) + { + if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(lock->Index[i])) + { + return spell; + } + } + + if (lock->Type[i] != LOCK_KEY_SKILL) + { + break; + } + + for (auto&& playerSpell : player->GetSpellMap()) + { + if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(playerSpell.first)) + { + for (auto&& effect : spell->Effects) + { + if (effect.Effect == SPELL_EFFECT_OPEN_LOCK && ((uint32) effect.MiscValue) == lock->Index[i]) + { + if (effect.CalcValue(player) >= int32(lock->Skill[i])) + { + return spell; + } + } + } + } + } + } + + return nullptr; +} diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 05d0abf3e..91cf0359b 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -751,10 +751,12 @@ public: [[nodiscard]] ObjectGuid::LowType GetSpawnId() const { return m_spawnId; } // z_rot, y_rot, x_rot - rotation angles around z, y and x axes - void SetWorldRotationAngles(float z_rot, float y_rot, float x_rot); - void SetWorldRotation(G3D::Quat const& rot); + void SetLocalRotationAngles(float z_rot, float y_rot, float x_rot); + void SetLocalRotation(G3D::Quat const& rot); void SetTransportPathRotation(float qx, float qy, float qz, float qw); - [[nodiscard]] int64 GetPackedWorldRotation() const { return m_packedRotation; } + [[nodiscard]] G3D::Quat const& GetLocalRotation() const { return m_localRotation; } + [[nodiscard]] int64 GetPackedLocalRotation() const { return m_packedRotation; } + [[nodiscard]] G3D::Quat GetWorldRotation() const; // overwrite WorldObject function for proper name localization [[nodiscard]] std::string const& GetNameForLocaleIdx(LocaleConstant locale_idx) const override; @@ -946,10 +948,18 @@ public: [[nodiscard]] float GetStationaryZ() const override { if (GetGOInfo()->type != GAMEOBJECT_TYPE_MO_TRANSPORT) return m_stationaryPosition.GetPositionZ(); return GetPositionZ(); } [[nodiscard]] float GetStationaryO() const override { if (GetGOInfo()->type != GAMEOBJECT_TYPE_MO_TRANSPORT) return m_stationaryPosition.GetOrientation(); return GetOrientation(); } - float GetInteractionDistance(); + [[nodiscard]] float GetInteractionDistance() const; void UpdateModelPosition(); + [[nodiscard]] bool IsAtInteractDistance(Position const& pos, float radius) const; + [[nodiscard]] bool IsAtInteractDistance(Player const* player, SpellInfo const* spell = nullptr) const; + + [[nodiscard]] bool IsWithinDistInMap(Player const* player) const; + using WorldObject::IsWithinDistInMap; + + [[nodiscard]] SpellInfo const* GetSpellForLock(Player const* player) const; + static std::unordered_map gameObjectToEventFlag; // Gameobject -> event flag protected: @@ -979,7 +989,7 @@ protected: bool m_allowModifyDestructibleBuilding; int64 m_packedRotation; - G3D::Quat m_worldRotation; + G3D::Quat m_localRotation; Position m_stationaryPosition; ObjectGuid m_lootRecipient; diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 31902d4d4..0b8db265f 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -432,7 +432,9 @@ void Object::BuildMovementUpdate(ByteBuffer* data, uint16 flags) const // 0x200 if (flags & UPDATEFLAG_ROTATION) - *data << int64(ToGameObject()->GetPackedWorldRotation()); + { + *data << int64(ToGameObject()->GetPackedLocalRotation()); + } } void Object::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index f193835be..a7a481916 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2058,8 +2058,10 @@ GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid guid, GameobjectTy { if (go->GetGoType() == type) { - if (go->IsWithinDistInMap(this, go->GetInteractionDistance())) + if (go->IsWithinDistInMap(this)) + { return go; + } LOG_DEBUG("maps", "IsGameObjectOfTypeInRange: GameObject '%s' [%s] is too far away from player %s [%s] to be used by him (distance=%f, maximal 10 is allowed)", go->GetGOInfo()->name.c_str(), go->GetGUID().ToString().c_str(), GetName().c_str(), GetGUID().ToString().c_str(), go->GetDistance(this)); @@ -7472,7 +7474,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) // not check distance for GO in case owned GO (fishing bobber case, for example) // And permit out of range GO with no owner in case fishing hole - if (!go || (loot_type != LOOT_FISHINGHOLE && ((loot_type != LOOT_FISHING && loot_type != LOOT_FISHING_JUNK) || go->GetOwnerGUID() != GetGUID()) && !go->IsWithinDistInMap(this, INTERACTION_DISTANCE)) || (loot_type == LOOT_CORPSE && go->GetRespawnTime() && go->isSpawnedByDefault())) + if (!go || (loot_type != LOOT_FISHINGHOLE && ((loot_type != LOOT_FISHING && loot_type != LOOT_FISHING_JUNK) || go->GetOwnerGUID() != GetGUID()) && !go->IsWithinDistInMap(this)) || (loot_type == LOOT_CORPSE && go->GetRespawnTime() && go->isSpawnedByDefault())) { go->ForceValuesUpdateAtIndex(GAMEOBJECT_BYTES_1); SendLootRelease(guid); diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp index bd578f576..05ee67870 100644 --- a/src/server/game/Entities/Transport/Transport.cpp +++ b/src/server/game/Entities/Transport/Transport.cpp @@ -86,7 +86,7 @@ bool MotionTransport::CreateMoTrans(ObjectGuid::LowType guidlow, uint32 entry, u SetName(goinfo->name); // pussywizard: no WorldRotation for MotionTransports - SetWorldRotation(G3D::Quat()); + SetLocalRotation(G3D::Quat()); // pussywizard: no PathRotation for MotionTransports SetTransportPathRotation(0.0f, 0.0f, 0.0f, 1.0f); @@ -706,7 +706,7 @@ bool StaticTransport::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* m // pussywizard: temporarily calculate WorldRotation from orientation, do so until values in db are correct //SetWorldRotation( /*for StaticTransport we need 2 rotation Quats in db for World- and Path- Rotation*/ ); - SetWorldRotationAngles(NormalizeOrientation(GetOrientation()), 0.0f, 0.0f); + SetLocalRotationAngles(NormalizeOrientation(GetOrientation()), 0.0f, 0.0f); // pussywizard: PathRotation for StaticTransport (only StaticTransports have PathRotation) SetTransportPathRotation(rotation.x, rotation.y, rotation.z, rotation.w); diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 5c5637791..5644b69e2 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -40,7 +40,7 @@ void WorldSession::HandleAutostoreLootItemOpcode(WorldPacket& recvData) // go = nullptr; // not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO - if (!go || ((go->GetOwnerGUID() != _player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(_player, INTERACTION_DISTANCE))) + if (!go || ((go->GetOwnerGUID() != _player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(_player))) { player->SendLootRelease(lguid); return; @@ -111,8 +111,10 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/) GameObject* go = GetPlayer()->GetMap()->GetGameObject(guid); // do not check distance for GO if player is the owner of it (ex. fishing bobber) - if (go && ((go->GetOwnerGUID() == player->GetGUID() || go->IsWithinDistInMap(player, INTERACTION_DISTANCE)))) + if (go && ((go->GetOwnerGUID() == player->GetGUID() || go->IsWithinDistInMap(player)))) + { loot = &go->loot; + } break; } @@ -262,8 +264,10 @@ void WorldSession::DoLootRelease(ObjectGuid lguid) GameObject* go = GetPlayer()->GetMap()->GetGameObject(lguid); // not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO - if (!go || ((go->GetOwnerGUID() != _player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(_player, INTERACTION_DISTANCE))) + if (!go || ((go->GetOwnerGUID() != _player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(_player))) + { return; + } loot = &go->loot; diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index fe65762ab..95a3101ea 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -328,6 +328,7 @@ void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket) uint32 spellId; uint8 castCount, castFlags; recvPacket >> castCount >> spellId >> castFlags; + TriggerCastFlags triggerFlag = TRIGGERED_NONE; uint32 oldSpellId = spellId; @@ -350,14 +351,40 @@ void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket) return; } + // client provided targets + SpellCastTargets targets; + targets.Read(recvPacket, mover); + HandleClientCastFlags(recvPacket, castFlags, targets); + + // not have spell in spellbook if (mover->GetTypeId() == TYPEID_PLAYER) { // not have spell in spellbook or spell passive and not casted by client if( !(spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM) && (!mover->ToPlayer()->HasActiveSpell(spellId) || spellInfo->IsPassive()) ) { - //cheater? kick? ban? - recvPacket.rfinish(); // prevent spam at ignore packet - return; + bool allow = false; + + // allow casting of unknown spells for special lock cases + if (GameObject* go = targets.GetGOTarget()) + { + if (go->GetSpellForLock(mover->ToPlayer()) == spellInfo) + { + allow = true; + } + } + + // TODO: Preparation for #23204 + // allow casting of spells triggered by clientside periodic trigger auras + /* + if (caster->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellId)) + { + allow = true; + triggerFlag = TRIGGERED_FULL_MASK; + } + */ + + if (!allow) + return; } } else @@ -407,15 +434,9 @@ void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket) // can't use our own spells when we're in possession of another unit, if (_player->isPossessing()) { - recvPacket.rfinish(); // prevent spam at ignore packet return; } - // client provided targets - SpellCastTargets targets; - targets.Read(recvPacket, mover); - HandleClientCastFlags(recvPacket, castFlags, targets); - // pussywizard: HandleClientCastFlags calls HandleMovementOpcodes, which can result in pretty much anything. Caster not in map will crash at GetMap() for spell difficulty in Spell constructor. if (!mover->FindMap()) { @@ -433,7 +454,7 @@ void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket) spellInfo = actualSpellInfo; } - Spell* spell = new Spell(mover, spellInfo, TRIGGERED_NONE, ObjectGuid::Empty, false); + Spell* spell = new Spell(mover, spellInfo, triggerFlag, ObjectGuid::Empty, false); sScriptMgr->ValidateSpellAtCastSpellResult(_player, mover, spell, oldSpellId, spellId); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 756bea445..f36fe0535 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -6480,6 +6480,14 @@ SpellCastResult Spell::CheckRange(bool strict) return SPELL_FAILED_TOO_CLOSE; } + if (GameObject* goTarget = m_targets.GetGOTarget()) + { + if (!goTarget->IsAtInteractDistance(m_caster->ToPlayer(), m_spellInfo)) + { + return SPELL_FAILED_OUT_OF_RANGE; + } + } + if (m_targets.HasDst() && !m_targets.HasTraj()) { if (!m_caster->IsWithinDist3d(m_targets.GetDstPos(), max_range)) @@ -7767,6 +7775,15 @@ SpellCastResult Spell::CanOpenLock(uint32 effIndex, uint32 lockId, SkillType& sk return SPELL_CAST_OK; } + case LOCK_KEY_SPELL: + { + if (m_spellInfo->Id == lockInfo->Index[j]) + { + return SPELL_CAST_OK; + } + reqKey = true; + break; + } } } diff --git a/src/server/scripts/Commands/cs_gobject.cpp b/src/server/scripts/Commands/cs_gobject.cpp index 7d0def796..e32a72eb9 100644 --- a/src/server/scripts/Commands/cs_gobject.cpp +++ b/src/server/scripts/Commands/cs_gobject.cpp @@ -406,7 +406,7 @@ public: Map* map = object->GetMap(); object->Relocate(object->GetPositionX(), object->GetPositionY(), object->GetPositionZ(), oz); - object->SetWorldRotationAngles(oz, oy, ox); + object->SetLocalRotationAngles(oz, oy, ox); object->SaveToDB(true); diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 837b901bc..7c18c7427 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -2463,7 +2463,8 @@ enum LockKeyType { LOCK_KEY_NONE = 0, LOCK_KEY_ITEM = 1, - LOCK_KEY_SKILL = 2 + LOCK_KEY_SKILL = 2, + LOCK_KEY_SPELL = 3 }; enum LockType