From b4febb11c8e3b170f99a174d95efc65a441f22b6 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Wed, 1 Dec 2021 22:57:42 -0500 Subject: [PATCH] Core/Object: Range check, Needs tested. Port From (https://github.com/TrinityCore/TrinityCore/commit/b717603a9b3a676e800fc401f4544735287791a9) --- Source/Framework/GameMath/Box.cs | 101 ++++++++++ Source/Framework/Util/MathFunctions.cs | 18 ++ .../Game/Chat/Commands/GameObjectCommands.cs | 2 +- Source/Game/Entities/GameObject/GameObject.cs | 188 ++++++++++++++++-- Source/Game/Entities/Object/WorldObject.cs | 2 +- Source/Game/Entities/Transport.cs | 2 +- Source/Game/Spells/Spell.cs | 9 +- 7 files changed, 291 insertions(+), 31 deletions(-) create mode 100644 Source/Framework/GameMath/Box.cs diff --git a/Source/Framework/GameMath/Box.cs b/Source/Framework/GameMath/Box.cs new file mode 100644 index 000000000..bf0d41338 --- /dev/null +++ b/Source/Framework/GameMath/Box.cs @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012-2020 CypherCore + * + * 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 3 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 . + */ + +using System; +using System.Numerics; + +namespace Framework.GameMath +{ + public class Box + { + public Vector3[] _edgeVector = new Vector3[3]; + public Vector3 _center; + public float _area; + public float _volume; + + public Box(Vector3 min, Vector3 max) + { + Vector3 bounds = new Vector3(max.X - min.X, max.Y - min.Y, max.Z - min.Z); + _edgeVector[0] = new Vector3(bounds.X, 0, 0); + _edgeVector[1] = new Vector3(0, bounds.Y, 0); + _edgeVector[2] = new Vector3(0, 0, bounds.Z); + bool finiteExtent = true; + + for (int i = 0; i < 3; ++i) + { + if (!float.IsInfinity(_edgeVector[i].Length())) + { + finiteExtent = false; + // If the extent is infinite along an axis, make the center zero to avoid NaNs + _center.SetAt(0.0f, i); + } + } + + + if (finiteExtent) + { + _volume = bounds.X * bounds.Y * bounds.Z; + } + else + { + _volume = float.PositiveInfinity; + } + + _area = 2 * + (bounds.X * bounds.Y + + bounds.Y * bounds.Z + + bounds.Z * bounds.X); + } + + public bool Contains(Vector3 point) + { + Vector3 u = _edgeVector[2]; + Vector3 v = _edgeVector[1]; + Vector3 w = _edgeVector[0]; + + Matrix4x4 M = new(u.X, v.X, w.X, 0.0f, u.Y, v.Y, w.Y, 0.0f, u.Z, v.Z, w.Z, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + + // M^-1 * (point - _corner[0]) = point in unit cube's object space + // compute the inverse of M + Matrix4x4.Invert(M, out M); + Vector3 osPoint = Vector3.Transform(point - Corner(0), M); + + return (osPoint.X >= 0) && (osPoint.Y >= 0) && (osPoint.Z >= 0) && + (osPoint.X <= 1) && (osPoint.Y <= 1) && (osPoint.Z <= 1); + } + + Vector3 Corner(int i) + { + switch (i) + { + case 0: return _center + (0.5f * (-_edgeVector[0] - _edgeVector[1] - _edgeVector[2])); + case 1: return _center + (0.5f * (_edgeVector[0] - _edgeVector[1] - _edgeVector[2])); + case 2: return _center + (0.5f * (-_edgeVector[0] + _edgeVector[1] - _edgeVector[2])); + case 3: return _center + (0.5f * (_edgeVector[0] + _edgeVector[1] - _edgeVector[2])); + case 4: return _center + (0.5f * (-_edgeVector[0] - _edgeVector[1] + _edgeVector[2])); + case 5: return _center + (0.5f * (_edgeVector[0] - _edgeVector[1] + _edgeVector[2])); + case 6: return _center + (0.5f * (-_edgeVector[0] + _edgeVector[1] + _edgeVector[2])); + default: return _center + (0.5f * (_edgeVector[0] + _edgeVector[1] + _edgeVector[2]));//case 7 + } + } + + public bool isFinite() + { + return float.IsInfinity(_volume); + } + } +} diff --git a/Source/Framework/Util/MathFunctions.cs b/Source/Framework/Util/MathFunctions.cs index 3a6ca98fe..6215dd460 100644 --- a/Source/Framework/Util/MathFunctions.cs +++ b/Source/Framework/Util/MathFunctions.cs @@ -16,7 +16,9 @@ */ using Framework.Constants; +using Framework.GameMath; using System; +using System.Numerics; public static class MathFunctions { @@ -266,4 +268,20 @@ public static class MathFunctions { return (ushort)((byte)l | (ushort)h << 8); } + + //3d math + public static Box toWorldSpace(Matrix4x4 rotation, Vector3 translation, Box box) + { + if (!box.isFinite()) + return box; + + box._center = new(rotation.M11 * box._center.GetAt(0) + rotation.M12 * box._center.GetAt(1) + rotation.M13 * box._center.GetAt(2) + translation.GetAt(0), + rotation.M21 * box._center.GetAt(0) + rotation.M22 * box._center.GetAt(1) + rotation.M23 * box._center.GetAt(2) + translation.GetAt(1), + rotation.M31 * box._center.GetAt(0) + rotation.M32 * box._center.GetAt(1) + rotation.M33 * box._center.GetAt(2) + translation.GetAt(2)); + + for (int i = 0; i < 3; ++i) + box._edgeVector[i] = Vector3.Transform(box._edgeVector[i], rotation); + + return box; + } } diff --git a/Source/Game/Chat/Commands/GameObjectCommands.cs b/Source/Game/Chat/Commands/GameObjectCommands.cs index fd423de69..83e59dd60 100644 --- a/Source/Game/Chat/Commands/GameObjectCommands.cs +++ b/Source/Game/Chat/Commands/GameObjectCommands.cs @@ -555,7 +555,7 @@ namespace Game.Chat Map map = obj.GetMap(); obj.Relocate(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ()); - obj.SetWorldRotationAngles(oz, oy, ox); + obj.SetLocalRotationAngles(oz, oy, ox); obj.SaveToDB(); // Generate a completely new spawn with new guid diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index fbad7921d..dc95e55a1 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -17,6 +17,7 @@ using Framework.Constants; using Framework.Database; +using Framework.GameMath; using Game.AI; using Game.BattleGrounds; using Game.Collision; @@ -242,7 +243,7 @@ namespace Game.Entities return false; } - SetWorldRotation(rotation.X, rotation.Y, rotation.Z, rotation.W); + SetLocalRotation(rotation.X, rotation.Y, rotation.Z, rotation.W); GameObjectAddon gameObjectAddon = Global.ObjectMgr.GetGameObjectAddon(GetSpawnId()); // For most of gameobjects is (0, 0, 0, 1) quaternion, there are only some transports with not standard rotation @@ -883,7 +884,7 @@ namespace Game.Entities return m_goTemplateAddon; } - + public void Refresh() { // not refresh despawned not casted GO (despawned casted GO destroyed in all cases anyway) @@ -1025,7 +1026,7 @@ namespace Game.Entities data.Id = GetEntry(); data.spawnPoint.WorldRelocate(this); - data.rotation = m_worldRotation; + data.rotation = m_localRotation; data.spawntimesecs = (int)(m_spawnedByDefault ? m_respawnDelayTime : -m_respawnDelayTime); data.animprogress = GetGoAnimProgress(); data.goState = GetGoState(); @@ -1054,10 +1055,10 @@ namespace Game.Entities stmt.AddValue(index++, GetPositionY()); stmt.AddValue(index++, GetPositionZ()); stmt.AddValue(index++, GetOrientation()); - stmt.AddValue(index++, m_worldRotation.X); - stmt.AddValue(index++, m_worldRotation.Y); - stmt.AddValue(index++, m_worldRotation.Z); - stmt.AddValue(index++, m_worldRotation.W); + stmt.AddValue(index++, m_localRotation.X); + stmt.AddValue(index++, m_localRotation.Y); + stmt.AddValue(index++, m_localRotation.Z); + stmt.AddValue(index++, m_localRotation.W); stmt.AddValue(index++, m_respawnDelayTime); stmt.AddValue(index++, GetGoAnimProgress()); stmt.AddValue(index++, (byte)GetGoState()); @@ -2207,22 +2208,22 @@ namespace Game.Entities const int PACK_YZ_MASK = (PACK_YZ << 1) - 1; const int PACK_X_MASK = (PACK_X << 1) - 1; - sbyte w_sign = (sbyte)(m_worldRotation.W >= 0.0f ? 1 : -1); - long x = (int)(m_worldRotation.X * PACK_X) * w_sign & PACK_X_MASK; - long y = (int)(m_worldRotation.Y * PACK_YZ) * w_sign & PACK_YZ_MASK; - long z = (int)(m_worldRotation.Z * PACK_YZ) * w_sign & PACK_YZ_MASK; + sbyte w_sign = (sbyte)(m_localRotation.W >= 0.0f ? 1 : -1); + long x = (int)(m_localRotation.X * PACK_X) * w_sign & PACK_X_MASK; + long y = (int)(m_localRotation.Y * PACK_YZ) * w_sign & PACK_YZ_MASK; + long z = (int)(m_localRotation.Z * PACK_YZ) * w_sign & PACK_YZ_MASK; m_packedRotation = z | (y << 21) | (x << 42); } - public void SetWorldRotation(float qx, float qy, float qz, float qw) + public void SetLocalRotation(float qx, float qy, float qz, float qw) { Quaternion rotation = new Quaternion(qx, qy, qz, qw); rotation = Quaternion.Multiply(rotation, 1.0f / MathF.Sqrt(Quaternion.Dot(rotation, rotation))); - m_worldRotation.X = rotation.X; - m_worldRotation.Y = rotation.Y; - m_worldRotation.Z = rotation.Z; - m_worldRotation.W = rotation.W; + m_localRotation.X = rotation.X; + m_localRotation.Y = rotation.Y; + m_localRotation.Z = rotation.Z; + m_localRotation.W = rotation.W; UpdatePackedRotation(); } @@ -2231,12 +2232,158 @@ namespace Game.Entities SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.ParentRotation), rotation); } - public void SetWorldRotationAngles(float z_rot, float y_rot, float x_rot) + public void SetLocalRotationAngles(float z_rot, float y_rot, float x_rot) { Quaternion quat = Quaternion.CreateFromRotationMatrix(Extensions.fromEulerAnglesZYX(z_rot, y_rot, x_rot)); - SetWorldRotation(quat.X, quat.Y, quat.Z, quat.W); + SetLocalRotation(quat.X, quat.Y, quat.Z, quat.W); } + public Quaternion GetWorldRotation() + { + Quaternion localRotation = GetLocalRotation(); + + Transport transport = GetTransport(); + if (transport != null) + { + Quaternion worldRotation = transport.GetWorldRotation(); + + Quaternion worldRotationQuat = new(worldRotation.X, worldRotation.Y, worldRotation.Z, worldRotation.W); + Quaternion localRotationQuat = new(localRotation.X, localRotation.Y, localRotation.Z, localRotation.W); + + Quaternion resultRotation = localRotationQuat * worldRotationQuat; + + return resultRotation; + } + + return localRotation; + } + + public bool IsAtInteractDistance(Player player, SpellInfo spell) + { + if (spell != null || (spell = GetSpellForLock(player)) != null) + { + float maxRange = spell.GetMaxRange(spell.IsPositive()); + + if (GetGoType() == GameObjectTypes.SpellFocus) + return maxRange * maxRange >= GetExactDistSq(player); + + var displayInfo = CliDB.GameObjectDisplayInfoStorage.LookupByKey(GetGoInfo().displayId); + if (displayInfo != null) + return IsAtInteractDistance(player, maxRange); + } + + float distance; + switch (GetGoType()) + { + case GameObjectTypes.AreaDamage: + distance = 0.0f; + break; + case GameObjectTypes.QuestGiver: + case GameObjectTypes.Text: + case GameObjectTypes.FlagStand: + case GameObjectTypes.FlagDrop: + case GameObjectTypes.MiniGame: + distance = 5.5555553f; + break; + case GameObjectTypes.Binder: + distance = 10.0f; + break; + case GameObjectTypes.Chair: + case GameObjectTypes.BarberChair: + distance = 3.0f; + break; + case GameObjectTypes.FishingNode: + distance = 100.0f; + break; + case GameObjectTypes.Camera: + case GameObjectTypes.MapObject: + case GameObjectTypes.DungeonDifficulty: + case GameObjectTypes.DestructibleBuilding: + case GameObjectTypes.Door: + default: + distance = 5.0f; + break; + + // Following values are not blizzlike + case GameObjectTypes.Mailbox: + // 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. + distance = 10.0f; // 5.0f is blizzlike + break; + } + + return IsAtInteractDistance(player, distance); + } + + bool IsAtInteractDistance(Position pos, float radius) + { + var displayInfo = CliDB.GameObjectDisplayInfoStorage.LookupByKey(GetGoInfo().displayId); + if (displayInfo != null) + { + float scale = GetObjectScale(); + + float minX = displayInfo.GeoBoxMin.X * scale - radius; + float minY = displayInfo.GeoBoxMin.Y * scale - radius; + float minZ = displayInfo.GeoBoxMin.Z * scale - radius; + float maxX = displayInfo.GeoBoxMax.X * scale + radius; + float maxY = displayInfo.GeoBoxMax.Y * scale + radius; + float maxZ = displayInfo.GeoBoxMax.Z * scale + radius; + + Quaternion localRotation = GetLocalRotation(); + + //Todo Test this. Needs checked. + var worldSpaceBox = MathFunctions.toWorldSpace(Matrix4x4.CreateFromQuaternion(localRotation), new Vector3(GetPositionX(), GetPositionY(), GetPositionZ()), new Box(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ))); + return worldSpaceBox.Contains(new Vector3(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ())); + } + + return GetExactDist(pos) <= radius; + } + + SpellInfo GetSpellForLock(Player player) + { + if (!player) + return null; + + uint lockId = GetGoInfo().GetLockId(); + if (lockId == 0) + return null; + + var lockEntry = CliDB.LockStorage.LookupByKey(lockId); + if (lockEntry == null) + return null; + + for (byte i = 0; i < SharedConst.MaxLockCase; ++i) + { + if (lockEntry.LockType[i] == 0) + continue; + + if (lockEntry.LockType[i] == (byte)LockKeyType.Skill) + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo((uint)lockEntry.Index[i], GetMap().GetDifficultyID()); + if (spell != null) + return spell; + } + + if (lockEntry.LockType[i] != (byte)LockKeyType.Skill) + break; + + foreach (var playerSpell in player.GetSpellMap()) + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo(playerSpell.Key, GetMap().GetDifficultyID()); + if (spell != null) + { + foreach (var effect in spell.GetEffects()) + if (effect.Effect == SpellEffectName.OpenLock && effect.MiscValue == lockEntry.Index[i]) + if (effect.CalcValue(player) >= lockEntry.Skill[i]) + return spell; + } + } + } + + return null; + } + + public void ModifyHealth(int change, WorldObject attackerOrHealer = null, uint spellId = 0) { if (m_goValue.Building.MaxHealth == 0 || change == 0) @@ -2729,7 +2876,8 @@ namespace Game.Entities public ulong GetSpawnId() { return m_spawnId; } - public long GetPackedWorldRotation() { return m_packedRotation; } + public Quaternion GetLocalRotation() { return m_localRotation; } + public long GetPackedLocalRotation() { return m_packedRotation; } public void SetOwnerGUID(ObjectGuid owner) { @@ -2900,7 +3048,7 @@ namespace Game.Entities public uint m_groupLootTimer; // (msecs)timer used for group loot public ObjectGuid lootingGroupLowGUID; // used to find group which is looting long m_packedRotation; - Quaternion m_worldRotation; + Quaternion m_localRotation; public Position StationaryPosition { get; set; } GameObjectAI m_AI; diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index b78732ecd..361e56211 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -381,7 +381,7 @@ namespace Game.Entities } if (flags.Rotation) - data.WriteInt64(ToGameObject().GetPackedWorldRotation()); // Rotation + data.WriteInt64(ToGameObject().GetPackedLocalRotation()); // Rotation if (go) { diff --git a/Source/Game/Entities/Transport.cs b/Source/Game/Entities/Transport.cs index 9f8819dca..91f4a7300 100644 --- a/Source/Game/Entities/Transport.cs +++ b/Source/Game/Entities/Transport.cs @@ -131,7 +131,7 @@ namespace Game.Entities SetGoType(GameObjectTypes.MapObjTransport); SetGoAnimProgress(animprogress); SetName(goinfo.name); - SetWorldRotation(0.0f, 0.0f, 0.0f, 1.0f); + SetLocalRotation(0.0f, 0.0f, 0.0f, 1.0f); SetParentRotation(Quaternion.Identity); CreateModel(); diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 22180ba90..69f770453 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -5736,10 +5736,6 @@ namespace Game.Spells if (m_spellInfo.RangeEntry != null && m_spellInfo.RangeEntry.Flags != SpellRangeFlag.Melee && !strict) maxRange += Math.Min(3.0f, maxRange * 0.1f); // 10% but no more than 3.0f - // save the original values before squaring them - float origMinRange = minRange; - float origMaxRange = maxRange; - // get square values for sqr distance checks minRange *= minRange; maxRange *= maxRange; @@ -5762,10 +5758,7 @@ namespace Game.Spells GameObject goTarget = m_targets.GetGOTarget(); if (goTarget != null) { - if (origMinRange > 0.0f && goTarget.IsInRange(m_caster.GetPositionX(), m_caster.GetPositionY(), m_caster.GetPositionZ(), origMinRange)) - return SpellCastResult.OutOfRange; - - if (!goTarget.IsInRange(m_caster.GetPositionX(), m_caster.GetPositionY(), m_caster.GetPositionZ(), origMaxRange)) + if (!goTarget.IsAtInteractDistance(m_caster.ToPlayer(), m_spellInfo)) return SpellCastResult.OutOfRange; }