diff --git a/ACE b/ACE index 05462b4..c50b222 160000 --- a/ACE +++ b/ACE @@ -1 +1 @@ -Subproject commit 05462b4c8b5e4c8cf48d5d711b37902101130ba2 +Subproject commit c50b22209369d71dd00b25f70c038b3df5b43cbc diff --git a/ACViewer/ACViewer.csproj b/ACViewer/ACViewer.csproj index 7f34520..ea743d1 100644 --- a/ACViewer/ACViewer.csproj +++ b/ACViewer/ACViewer.csproj @@ -162,6 +162,7 @@ + @@ -245,6 +246,12 @@ + + + + + + @@ -263,6 +270,7 @@ + diff --git a/ACViewer/Extensions/MathExtensions.cs b/ACViewer/Extensions/MathExtensions.cs new file mode 100644 index 0000000..c8632e9 --- /dev/null +++ b/ACViewer/Extensions/MathExtensions.cs @@ -0,0 +1,15 @@ +namespace ACViewer.Extensions +{ + public static class MathExtensions + { + public static float Clamp(float val, float min, float max) + { + if (val < min) + val = min; + else if (val > max) + val = max; + + return val; + } + } +} diff --git a/ACViewer/Physics/Animation/AFrame.cs b/ACViewer/Physics/Animation/AFrame.cs index ba01ce3..7168970 100644 --- a/ACViewer/Physics/Animation/AFrame.cs +++ b/ACViewer/Physics/Animation/AFrame.cs @@ -24,19 +24,19 @@ public AFrame(Vector3 origin, Quaternion orientation) public AFrame(AFrame frame) { - Origin = new Vector3(frame.Origin.X, frame.Origin.Y, frame.Origin.Z); + Origin = frame.Origin; Orientation = new Quaternion(frame.Orientation.X, frame.Orientation.Y, frame.Orientation.Z, frame.Orientation.W); } public AFrame(DatLoader.Entity.Frame frame) { - Origin = new Vector3(frame.Origin.X, frame.Origin.Y, frame.Origin.Z); + Origin = frame.Origin; Orientation = new Quaternion(frame.Orientation.X, frame.Orientation.Y, frame.Orientation.Z, frame.Orientation.W); } public AFrame(ACE.Entity.Frame frame) { - Origin = new Vector3(frame.Origin.X, frame.Origin.Y, frame.Origin.Z); + Origin = frame.Origin; Orientation = new Quaternion(frame.Orientation.X, frame.Orientation.Y, frame.Orientation.Z, frame.Orientation.W); } @@ -203,7 +203,7 @@ public void set_rotate(Quaternion orientation) public void set_vector_heading(Vector3 heading) { - var normal = new Vector3(heading.X, heading.Y, heading.Z); + var normal = heading; if (Vec.NormalizeCheckSmall(ref normal)) return; var zDeg = 450.0f - ((float)Math.Atan2(normal.Y, normal.X)).ToDegrees(); @@ -217,7 +217,7 @@ public void set_vector_heading(Vector3 heading) public override string ToString() { - return Origin + " " + Orientation; + return $"[{Origin.X} {Origin.Y} {Origin.Z}] {Orientation.W} {Orientation.X} {Orientation.Y} {Orientation.Z}"; } public bool Equals(AFrame frame) diff --git a/ACViewer/Physics/Animation/AnimData.cs b/ACViewer/Physics/Animation/AnimData.cs index 2a747c0..9cb94cc 100644 --- a/ACViewer/Physics/Animation/AnimData.cs +++ b/ACViewer/Physics/Animation/AnimData.cs @@ -1,4 +1,4 @@ -namespace ACE.Server.Physics.Animation +namespace ACE.Server.Physics.Animation { public class AnimData { diff --git a/ACViewer/Physics/Animation/AnimSequenceNode.cs b/ACViewer/Physics/Animation/AnimSequenceNode.cs index 1bf9fb2..d147635 100644 --- a/ACViewer/Physics/Animation/AnimSequenceNode.cs +++ b/ACViewer/Physics/Animation/AnimSequenceNode.cs @@ -30,7 +30,7 @@ public AnimSequenceNode(AnimData animData) public float get_ending_frame() { - if (Framerate > 0.0f) + if (Framerate >= 0.0f) return HighFrame + 1 - PhysicsGlobals.EPSILON; else return LowFrame; diff --git a/ACViewer/Physics/Animation/InterpretedMotionState.cs b/ACViewer/Physics/Animation/InterpretedMotionState.cs index 3fa7425..431d626 100644 --- a/ACViewer/Physics/Animation/InterpretedMotionState.cs +++ b/ACViewer/Physics/Animation/InterpretedMotionState.cs @@ -124,5 +124,11 @@ public void RemoveMotion(uint motion) break; } } + + public bool HasCommands() + { + //return ForwardCommand != 0 && ForwardCommand != (uint)MotionCommand.Ready || SideStepCommand != 0 || TurnCommand != 0; + return SideStepCommand != 0 || TurnCommand != 0; + } } } diff --git a/ACViewer/Physics/Animation/MotionInterp.cs b/ACViewer/Physics/Animation/MotionInterp.cs index bc41e22..c6435c7 100644 --- a/ACViewer/Physics/Animation/MotionInterp.cs +++ b/ACViewer/Physics/Animation/MotionInterp.cs @@ -3,7 +3,6 @@ using System.Numerics; using ACE.Entity.Enum; using ACE.Server.Physics.Common; -using ACE.Server.Physics.Extensions; namespace ACE.Server.Physics.Animation { @@ -170,6 +169,7 @@ public void HandleExitWorld() } } PendingMotions.Clear(); + if (PhysicsObj != null) PhysicsObj.IsAnimating = false; } public void HitGround() @@ -226,7 +226,10 @@ public void MotionDone(bool success) motionData = PendingMotions.First; if (motionData != null) + { PendingMotions.Remove(motionData); + PhysicsObj.IsAnimating = PendingMotions.Count > 0; + } } } @@ -385,6 +388,7 @@ public WeenieError StopMotion(uint motion, MovementParameters movementParams) public void add_to_queue(int contextID, uint motion, WeenieError jumpErrorCode) { PendingMotions.AddLast(new MotionNode(contextID, motion, jumpErrorCode)); + PhysicsObj.IsAnimating = true; } public void adjust_motion(ref uint motion, ref float speed, HoldKey holdKey) @@ -641,7 +645,7 @@ public float get_jump_v_z() return 10.0f; float vz = extent; - if (WeenieObj.InqJumpVelocity(extent, ref vz)) + if (WeenieObj.InqJumpVelocity(extent, out vz)) return vz; return 0.0f; @@ -652,8 +656,8 @@ public Vector3 get_leave_ground_velocity() var velocity = get_state_velocity(); velocity.Z = get_jump_v_z(); - if (!velocity.Equals(Vector3.Zero)) - velocity = PhysicsObj.Position.GlobalToLocalVec(velocity); + if (Vec.IsZero(velocity)) + velocity = PhysicsObj.Position.GlobalToLocalVec(PhysicsObj.Velocity); return velocity; } @@ -689,7 +693,7 @@ public Vector3 get_state_velocity() var maxSpeed = RunAnimSpeed * rate; if (velocity.Length() > maxSpeed) { - velocity = velocity.Normalize(); + velocity = Vector3.Normalize(velocity); velocity *= maxSpeed; } return velocity; @@ -774,6 +778,9 @@ public WeenieError motion_allows_jump(uint substate) return WeenieError.None; } + /// + /// Alternatively, you can use PhysicsObj.IsAnimating for better performance. + /// public bool motions_pending() { return PendingMotions.Count > 0; diff --git a/ACViewer/Physics/Animation/MotionTable.cs b/ACViewer/Physics/Animation/MotionTable.cs index abd8172..ff68732 100644 --- a/ACViewer/Physics/Animation/MotionTable.cs +++ b/ACViewer/Physics/Animation/MotionTable.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Numerics; + using ACE.DatLoader; using ACE.DatLoader.Entity; +using ACE.DatLoader.Entity.AnimationHooks; using ACE.Entity.Enum; using ACE.Server.Physics.Animation.Internal; @@ -17,15 +20,15 @@ public class MotionTable public Dictionary> Links; public uint DefaultStyle; - public static Dictionary WalkSpeed; - public static Dictionary RunSpeed; - public static Dictionary TurnSpeed; + public static ConcurrentDictionary WalkSpeed { get; set; } + public static ConcurrentDictionary RunSpeed { get; set; } + public static ConcurrentDictionary TurnSpeed { get; set; } static MotionTable() { - WalkSpeed = new Dictionary(); - RunSpeed = new Dictionary(); - TurnSpeed = new Dictionary(); + WalkSpeed = new ConcurrentDictionary(); + RunSpeed = new ConcurrentDictionary(); + TurnSpeed = new ConcurrentDictionary(); } public MotionTable() @@ -454,14 +457,28 @@ public void re_modify(Sequence sequence, MotionState pstate) } } + private static readonly List<(float, AttackHook)> emptyList = new List<(float, AttackHook)>(); + + public static List<(float time, AttackHook attackHook)> GetAttackFrames(uint motionTableId, MotionStance stance, MotionCommand motion) + { + if (motionTableId == 0) return emptyList; + + var motionTable = DatManager.PortalDat.ReadFromDat(motionTableId); + return motionTable.GetAttackFrames(motionTableId, stance, motion); + } + public static float GetAnimationLength(uint motionTableId, MotionStance stance, MotionCommand motion, float speed = 1.0f) { + if (motionTableId == 0) return 0; + var motionTable = DatManager.PortalDat.ReadFromDat(motionTableId); return motionTable.GetAnimationLength(stance, motion, null) / speed; } public static float GetAnimationLength(uint motionTableId, MotionStance stance, MotionCommand currentMotion, MotionCommand motion, float speed = 1.0f) { + if (motionTableId == 0) return 0; + var motionTable = DatManager.PortalDat.ReadFromDat(motionTableId); var animLength = 0.0f; @@ -477,6 +494,8 @@ public static float GetAnimationLength(uint motionTableId, MotionStance stance, public static float GetCycleLength(uint motionTableId, MotionStance stance, MotionCommand motion, float speed = 1.0f) { + if (motionTableId == 0) return 0; + var motionTable = DatManager.PortalDat.ReadFromDat(motionTableId); return motionTable.GetCycleLength(stance, motion) / speed; } @@ -495,7 +514,7 @@ public static float GetRunSpeed(uint motionTableID) return 0.0f; var speed = GetAnimDist(motionData); - RunSpeed.Add(motionTableID, speed); + RunSpeed[motionTableID] = speed; return speed; } @@ -513,7 +532,7 @@ public static float GetTurnSpeed(uint motionTableID) return 0.0f; var speed = Math.Abs(motionData.Omega.Z); - TurnSpeed.Add(motionTableID, speed); + TurnSpeed[motionTableID] = speed; return speed; } @@ -522,6 +541,8 @@ public static float GetTurnSpeed(uint motionTableID) /// public static MotionData GetMotionData(uint motionTableID, uint motion, uint? currentStyle = null) { + if (motionTableID == 0) return null; + var motionTable = DatManager.PortalDat.ReadFromDat(motionTableID); if (currentStyle == null) currentStyle = motionTable.DefaultStyle; @@ -533,6 +554,8 @@ public static MotionData GetMotionData(uint motionTableID, uint motion, uint? cu public static MotionData GetLinkData(uint motionTableID, uint motion, uint? currentStyle = null) { + if (motionTableID == 0) return null; + var motionTable = DatManager.PortalDat.ReadFromDat(motionTableID); if (currentStyle == null) currentStyle = motionTable.DefaultStyle; diff --git a/ACViewer/Physics/Animation/MovementSystem.cs b/ACViewer/Physics/Animation/MovementSystem.cs index 8653c2e..e9d1868 100644 --- a/ACViewer/Physics/Animation/MovementSystem.cs +++ b/ACViewer/Physics/Animation/MovementSystem.cs @@ -1,16 +1,16 @@ using System; using ACE.Server.Physics.Common; +using ACViewer.Extensions; namespace ACE.Server.Physics.Animation { public class MovementSystem { - public static float GetJumpHeight(float burden, int jumpSkill, float power, float scaling) + public static float GetJumpHeight(float burden, uint jumpSkill, float power, float scaling) { - if (power < 0.0f) power = 0.0f; - if (power > 1.0f) power = 1.0f; + power = MathExtensions.Clamp(power, 0.0f, 1.0f); - var result = EncumbranceSystem.GetBurdenMod(burden) * (jumpSkill / (jumpSkill + 1300) * 22.200001f + 0.050000001f) * power / scaling; + var result = EncumbranceSystem.GetBurdenMod(burden) * (jumpSkill / (jumpSkill + 1300.0f) * 22.2f + 0.05f) * power / scaling; if (result < 0.35f) result = 0.35f; @@ -35,5 +35,13 @@ public static int JumpStaminaCost(float power, float burden, bool pk) else return (int)Math.Ceiling((burden + 0.5f) * power * 8.0f + 2.0f); } + + public static float GetJumpPower(uint stamina, float burden, bool pk) + { + if (pk) + return stamina / 100.0f - 1.0f; + else + return (stamina - 2.0f) / (burden * 8.0f + 4.0f); + } } } diff --git a/ACViewer/Physics/Animation/Sequence.cs b/ACViewer/Physics/Animation/Sequence.cs index e31f014..4ec5c0a 100644 --- a/ACViewer/Physics/Animation/Sequence.cs +++ b/ACViewer/Physics/Animation/Sequence.cs @@ -22,6 +22,35 @@ public class Sequence public int PlacementFrameID; public bool IsTrivial; + public static HashSet PlayerIdleAnims; + + static Sequence() + { + PlayerIdleAnims = new HashSet(); + PlayerIdleAnims.Add(0x03000001); // NonCombat + PlayerIdleAnims.Add(0x0300049E); // AtlatlCombat + PlayerIdleAnims.Add(0x0300045A); // BowCombat + PlayerIdleAnims.Add(0x03000474); // CrossbowCombat + PlayerIdleAnims.Add(0x03000CA8); // DualWieldCombat + PlayerIdleAnims.Add(0x03000448); // HandCombat + PlayerIdleAnims.Add(0x0300076C); // Magic + PlayerIdleAnims.Add(0x0300043D); // SwordCombat + PlayerIdleAnims.Add(0x03000426); // SwordShieldCombat + PlayerIdleAnims.Add(0x030008DF); // ThrownShieldCombat + PlayerIdleAnims.Add(0x0300049E); // ThrownWeaponCombat + PlayerIdleAnims.Add(0x03000B05); // TwoHandedSwordCombat + } + + public bool is_idle_anim() + { + return CurrAnim == null || PlayerIdleAnims.Contains(CurrAnim.Value.Anim.ID); + } + + public bool is_first_cyclic() + { + return CurrAnim == null || CurrAnim.Equals(FirstCyclic); + } + public Sequence() { Init(); diff --git a/ACViewer/Physics/BSP/BSPNode.cs b/ACViewer/Physics/BSP/BSPNode.cs index aef74cd..b6484e3 100644 --- a/ACViewer/Physics/BSP/BSPNode.cs +++ b/ACViewer/Physics/BSP/BSPNode.cs @@ -57,7 +57,7 @@ public BSPNode(DatLoader.Entity.BSPNode node, Dictionary(); + Polygons = new List(node.InPolys.Count); foreach (var poly in node.InPolys) Polygons.Add(PolygonCache.Get(polys[poly], vertexArray)); } diff --git a/ACViewer/Physics/BSP/BSPTree.cs b/ACViewer/Physics/BSP/BSPTree.cs index 1be1779..a18d689 100644 --- a/ACViewer/Physics/BSP/BSPTree.cs +++ b/ACViewer/Physics/BSP/BSPTree.cs @@ -136,6 +136,8 @@ public TransitionState find_collisions(Transition transition, float scale) var center = path.LocalSpaceCurrCenter[0].Center; var localSphere = path.LocalSpaceSphere[0]; + var localSphere_ = path.NumSphere > 1 ? path.LocalSpaceSphere[1] : null; + var movement = localSphere.Center - center; if (path.InsertType == InsertType.Placement || path.ObstructionEthereal) @@ -144,7 +146,7 @@ public TransitionState find_collisions(Transition transition, float scale) if (path.BuildingCheck) clearCell = !path.HitsInteriorCell; - if (RootNode.sphere_intersects_solid(localSphere, clearCell)) + if (RootNode.sphere_intersects_solid(localSphere, clearCell) || path.NumSphere > 1 && RootNode.sphere_intersects_solid(localSphere_, clearCell)) return TransitionState.Collided; else return TransitionState.OK; @@ -195,7 +197,6 @@ public TransitionState find_collisions(Transition transition, float scale) if (path.NumSphere > 1) { Polygon hitPoly_ = null; - var localSphere_ = path.LocalSpaceSphere[1]; if (RootNode.sphere_intersects_poly(localSphere_, movement, ref hitPoly_, ref contactPoint)) return slide_sphere(transition, hitPoly_.Plane.Normal); @@ -219,7 +220,6 @@ public TransitionState find_collisions(Transition transition, float scale) } else if (path.NumSphere > 1) { - var localSphere_ = path.LocalSpaceSphere[1]; if (RootNode.sphere_intersects_poly(localSphere_, movement, ref hitPoly, ref contactPoint) || hitPoly != null) { var collisionNormal = path.LocalSpacePos.LocalToGlobalVec(hitPoly.Plane.Normal); diff --git a/ACViewer/Physics/Collision/BBox.cs b/ACViewer/Physics/Collision/BBox.cs index 190ad8c..b88895b 100644 --- a/ACViewer/Physics/Collision/BBox.cs +++ b/ACViewer/Physics/Collision/BBox.cs @@ -67,7 +67,7 @@ public bool Contains(Vector3 point) public void ConvertToGlobal(Position pos) { - var transform = Matrix4x4.CreateFromQuaternion(pos.Frame.Orientation) * Matrix4x4.CreateTranslation(pos.Frame.Origin); ; + var transform = Matrix4x4.CreateFromQuaternion(pos.Frame.Orientation) * Matrix4x4.CreateTranslation(pos.Frame.Origin); Min = Vector3.Transform(Min, transform); Max = Vector3.Transform(Max, transform); // adjust? @@ -104,12 +104,26 @@ public void LocalToGlobal(BBox fromBox, Position fromPos, Position toPos) { Min = toPos.LocalToGlobal(fromPos, fromBox.Min); Max = toPos.LocalToGlobal(fromPos, fromBox.Max); + + AdjustBBox(toPos.LocalToGlobal(fromPos, new Vector3(fromBox.Max.X, fromBox.Max.Y, fromBox.Min.Z))); + AdjustBBox(toPos.LocalToGlobal(fromPos, new Vector3(fromBox.Max.X, fromBox.Min.Y, fromBox.Max.Z))); + AdjustBBox(toPos.LocalToGlobal(fromPos, new Vector3(fromBox.Min.X, fromBox.Max.Y, fromBox.Max.Z))); + AdjustBBox(toPos.LocalToGlobal(fromPos, new Vector3(fromBox.Min.X, fromBox.Max.Y, fromBox.Min.Z))); + AdjustBBox(toPos.LocalToGlobal(fromPos, new Vector3(fromBox.Max.X, fromBox.Min.Y, fromBox.Min.Z))); + AdjustBBox(toPos.LocalToGlobal(fromPos, new Vector3(fromBox.Min.X, fromBox.Min.Y, fromBox.Max.Z))); } public void LocalToLocal(BBox fromBox, Position fromPos, Position toPos) { Min = toPos.LocalToLocal(fromPos, fromBox.Min); Max = toPos.LocalToLocal(fromPos, fromBox.Max); + + AdjustBBox(toPos.LocalToLocal(fromPos, new Vector3(fromBox.Max.X, fromBox.Max.Y, fromBox.Min.Z))); + AdjustBBox(toPos.LocalToLocal(fromPos, new Vector3(fromBox.Max.X, fromBox.Min.Y, fromBox.Max.Z))); + AdjustBBox(toPos.LocalToLocal(fromPos, new Vector3(fromBox.Min.X, fromBox.Max.Y, fromBox.Max.Z))); + AdjustBBox(toPos.LocalToLocal(fromPos, new Vector3(fromBox.Min.X, fromBox.Max.Y, fromBox.Min.Z))); + AdjustBBox(toPos.LocalToLocal(fromPos, new Vector3(fromBox.Max.X, fromBox.Min.Y, fromBox.Min.Z))); + AdjustBBox(toPos.LocalToLocal(fromPos, new Vector3(fromBox.Min.X, fromBox.Min.Y, fromBox.Max.Z))); } } } diff --git a/ACViewer/Physics/Collision/CollisionInfo.cs b/ACViewer/Physics/Collision/CollisionInfo.cs index 356d018..50ef12c 100644 --- a/ACViewer/Physics/Collision/CollisionInfo.cs +++ b/ACViewer/Physics/Collision/CollisionInfo.cs @@ -27,6 +27,9 @@ public class CollisionInfo public bool CollidedWithEnvironment; public int FramesStationaryFall; + // custom for server + public bool VerifiedRestrictions; + public CollisionInfo() { Init(); diff --git a/ACViewer/Physics/Collision/CollisionRecord.cs b/ACViewer/Physics/Collision/CollisionRecord.cs index 1e6429b..114161e 100644 --- a/ACViewer/Physics/Collision/CollisionRecord.cs +++ b/ACViewer/Physics/Collision/CollisionRecord.cs @@ -4,5 +4,11 @@ public class CollisionRecord { public double TouchedTime; public bool Ethereal; + + public CollisionRecord(double touchedTime, bool ethereal) + { + TouchedTime = touchedTime; + Ethereal = ethereal; + } } } diff --git a/ACViewer/Physics/Collision/ObjCollisionProfile.cs b/ACViewer/Physics/Collision/ObjCollisionProfile.cs index 66ae600..7f541ad 100644 --- a/ACViewer/Physics/Collision/ObjCollisionProfile.cs +++ b/ACViewer/Physics/Collision/ObjCollisionProfile.cs @@ -1,28 +1,44 @@ using System; using System.Numerics; +using ACE.Entity.Enum; namespace ACE.Server.Physics.Collision { [Flags] public enum ObjCollisionProfileFlags { - Undefined = 0x0, - Creature = 0x1, - Player = 0x2, + Undefined = 0x0, + Creature = 0x1, + Player = 0x2, Attackable = 0x4, - Missile = 0x8, - Contact = 0x10, - MyContact = 0x20, - Door = 0x40, - Cloaked = 0x80, + Missile = 0x8, + Contact = 0x10, + MyContact = 0x20, + Door = 0x40, + Cloaked = 0x80, }; public class ObjCollisionProfile { public uint ID; public Vector3 Velocity; - public int wcid; - public int ItemType; + public uint WCID; + public ItemType ItemType; public ObjCollisionProfileFlags Flags; + + public ObjCollisionProfile() { } + + public ObjCollisionProfile(uint id, Vector3 velocity, bool missile, bool contact, bool myContact) + { + ID = id; + Velocity = velocity; + + if (missile) + Flags |= ObjCollisionProfileFlags.Missile; + if (contact) + Flags |= ObjCollisionProfileFlags.Contact; + if (myContact) + Flags |= ObjCollisionProfileFlags.MyContact; + } } } diff --git a/ACViewer/Physics/Combat/AtkCollisionProfile.cs b/ACViewer/Physics/Combat/AtkCollisionProfile.cs index 89cc11c..b3885c9 100644 --- a/ACViewer/Physics/Combat/AtkCollisionProfile.cs +++ b/ACViewer/Physics/Combat/AtkCollisionProfile.cs @@ -1,11 +1,20 @@ +using ACE.Entity.Enum; using ACE.Server.Physics.Collision; namespace ACE.Server.Physics.Combat { - public class AtkCollisionProfile: ObjCollisionProfile + public class AtkCollisionProfile : ObjCollisionProfile { public int Part; - //public int ID; - public int Location; + public Quadrant Location; + + public AtkCollisionProfile() { } + + public AtkCollisionProfile(uint id, int part, Quadrant location) + { + ID = id; + Part = part; + Location = location; + } } } diff --git a/ACViewer/Physics/Combat/AtkObjInfo.cs b/ACViewer/Physics/Combat/AtkObjInfo.cs index 6740b42..429d8b7 100644 --- a/ACViewer/Physics/Combat/AtkObjInfo.cs +++ b/ACViewer/Physics/Combat/AtkObjInfo.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Text; +using ACE.Entity.Enum; namespace ACE.Server.Physics.Combat { public class AtkObjInfo { public uint ObjectID; - public int HitLocation; + public Quadrant HitLocation; } } diff --git a/ACViewer/Physics/Combat/AttackInfo.cs b/ACViewer/Physics/Combat/AttackInfo.cs index 2613519..624446c 100644 --- a/ACViewer/Physics/Combat/AttackInfo.cs +++ b/ACViewer/Physics/Combat/AttackInfo.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using ACE.Entity.Enum; namespace ACE.Server.Physics.Combat { @@ -11,7 +12,7 @@ public class AttackInfo public int NumObjects; public List ObjectList; - public void AddObject(uint objectID, int hitLocation) + public void AddObject(uint objectID, Quadrant hitLocation) { } diff --git a/ACViewer/Physics/Combat/TargetInfo.cs b/ACViewer/Physics/Combat/TargetInfo.cs index 60edd66..d587852 100644 --- a/ACViewer/Physics/Combat/TargetInfo.cs +++ b/ACViewer/Physics/Combat/TargetInfo.cs @@ -49,8 +49,8 @@ public TargetInfo(TargetInfo info) Quantum = info.Quantum; TargetPosition = new Position(info.TargetPosition); InterpolatedPosition = new Position(info.InterpolatedPosition); - InterpolatedHeading = new Vector3(info.InterpolatedHeading.X, info.InterpolatedHeading.Y, info.InterpolatedHeading.Z); - Velocity = new Vector3(info.Velocity.X, info.Velocity.Y, info.Velocity.Z); + InterpolatedHeading = info.InterpolatedHeading; + Velocity = info.Velocity; Status = info.Status; LastUpdateTime = info.LastUpdateTime; } diff --git a/ACViewer/Physics/Command/ACCmdInterp.cs b/ACViewer/Physics/Command/ACCmdInterp.cs new file mode 100644 index 0000000..b1a10fa --- /dev/null +++ b/ACViewer/Physics/Command/ACCmdInterp.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ACE.Entity.Enum; + +namespace ACE.Server.Physics.Command +{ + public class ACCmdInterp : CommandInterpreter + { + public void CommenceJump() + { + /*var combatSystem = ClientCombatSystem.GetCombatSystem(); + if (combatSystem != null) + combatSystem.CommenceJump();*/ + } + + public void DoJump() + { + /*var combatSystem = ClientCombatSystem.DoJump(); + if (combatSystem != null) + combatSystem.DoJump();*/ + } + + public override void HandleNewForwardMovement() + { + /*var combatSystem = ClientCombatSystem.GetCombatSystem(); + if (combatSystem != null) + combatSystem.AbortAutomaticAttack();*/ + + base.HandleNewForwardMovement(); + } + + public override void TakeControlFromServer() + { + /*var combatSystem = ClientCombatSystem.GetCombatSystem(); + if (combatSystem != null) + combatSystem.AbortAutomaticAttack();*/ + + base.TakeControlFromServer(); + } + + public bool OnAction(InputEvent evt) + { + // vfptr[12] - IsActive + if (!IsActive()) + return true; + + switch (evt.InputAction) + { + case 0x32: + // vfptr[2].OnLoseFocus + return true; + + case 0x30: + SetMotion(MotionCommand.AutoRun, true); + return true; + + case 0x29: + SetMotion(MotionCommand.WalkForward, evt.Start); + return true; + + case 0x2A: + SetMotion(MotionCommand.WalkBackwards, evt.Start); + return true; + + case 0x2B: + SetMotion(MotionCommand.Ready, true); + return true; + + case 0x2E: + SetMotion(MotionCommand.TurnRight, evt.Start); + return true; + + case 0x2F: + SetMotion(MotionCommand.TurnLeft, evt.Start); + return true; + + case 0x2C: + SetMotion(MotionCommand.SideStepRight, evt.Start); + return true; + + case 0x2D: + SetMotion(MotionCommand.SideStepLeft, evt.Start); + return true; + + case 0x31: + if (evt.Start) + CommenceJump(); // vfptr[5].OnAction - CommenceJump + else + DoJump(); // vfptr[5].OnLoseFocus - DoJump + + return true; + + default: + //var result = HashEmoteInputActionsToCommands.TryGetValue(evt.InputAction, out var emoteCommand); + var result = false; + //if (result) + //SetMotion(emoteCommand, true); + return result; + } + } + + public void SetMotion(MotionCommand motion, bool start) + { + if (Player == null) + return; + + var cmdStruct = new CmdStruct(); + cmdStruct.Args[0] = 0; + cmdStruct.Args[1] = (uint)motion; + cmdStruct.Args[2] = Convert.ToUInt32(start); + cmdStruct.Args[3] = 4; + + // vfptr[12].OnLoseFocus - HandleKeyboardCommand + HandleKeyboardCommand(cmdStruct, motion); + } + } +} diff --git a/ACViewer/Physics/Command/CmdStruct.cs b/ACViewer/Physics/Command/CmdStruct.cs new file mode 100644 index 0000000..e44ef34 --- /dev/null +++ b/ACViewer/Physics/Command/CmdStruct.cs @@ -0,0 +1,12 @@ +using ACE.Entity.Enum; + +namespace ACE.Server.Physics.Command +{ + public class CmdStruct + { + public uint[] Args = new uint[64]; + public uint Size; + public uint Curr; + public MotionCommand Command; + } +} diff --git a/ACViewer/Physics/Command/CommandInterpreter.cs b/ACViewer/Physics/Command/CommandInterpreter.cs new file mode 100644 index 0000000..1484958 --- /dev/null +++ b/ACViewer/Physics/Command/CommandInterpreter.cs @@ -0,0 +1,885 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using ACE.Entity.Enum; +using ACE.Server.Physics.Animation; +using ACE.Server.Physics.Common; + +namespace ACE.Server.Physics.Command +{ + public class CommandInterpreter + { + public PhysicsObj Player; + public SmartBox SmartBox; + public List SubstateList; + public List TurnList; + public List SidestepList; + public uint AutonomyLevel; + public bool ControlledByServer; + public bool HoldRun; + public bool HoldSidestep; + public bool TransientState { get; set; } + public bool Enabled; + public bool AutoRun; + public bool MouseLookActive; + public bool MouseLeftDown; + public float AutoRunSpeed; + public uint ActionStamp; + public DateTime LastSentPositionTime; + public Position LastSentPosition; + public Plane LastSentContactPlane; + public const double TimeBetweenPositionEvents = 1.875f; + + public CommandInterpreter() + { + SubstateList = new List(); + TurnList = new List(); + SidestepList = new List(); + + AutonomyLevel = 2; + AutoRunSpeed = 1.0f; + ControlledByServer = true; + Enabled = true; + ActionStamp = 1; + + LastSentPositionTime = DateTime.UtcNow; + LastSentPosition = new Position(); + LastSentContactPlane = new Plane(); + } + + public void AddCommand(MotionCommand command, float speed, bool mouse, bool newHoldRun) + { + // vfptr[1](command) - whichlist + var list = WhichList(command); + if (list != null) + { + // CommandList.AddCommand + list.Add(new CommandListElement(command, speed, newHoldRun)); + if (((uint)command & (uint)CommandMask.SubState) != 0) + { + if (list == SubstateList) + { + // vfptr[6] - ACCmdInterp::HandleNewForwardMovement + HandleNewForwardMovement(); + } + + TransientState = false; + } + } + else if (((uint)command & (uint)CommandMask.SubState) != 0) + { + if (((uint)command & (uint)CommandMask.Toggle) == 0) + { + // vfptr[6] - ACCmdInterp::HandleNewForwardMovement + HandleNewForwardMovement(); + if (command != MotionCommand.Ready) + TransientState = true; + } + } + } + + public void ApplyCurrentMovement(int a2) + { + if (Player == null) + return; + + if (AutoRun) + { + // vfptr[13].OnAction - MovePlayer + MovePlayer(MotionCommand.WalkForward, true, AutoRunSpeed, true, true); + // goto LABEL_9 + } + else + { + if (SubstateList.Count != 0) + { + // vfptr[3].OnLoseFocus - ApplyListHeadMovement + ApplyListHeadMovement(SubstateList); + } + else if (!TransientState) + { + MovePlayer(MotionCommand.Ready, true, AutoRunSpeed, false, true); + } + } + + // LABEL_9 + if (TurnList.Count != 0) + { + // vfptr[3].OnLoseFocus - ApplyListHeadMovement w/ extra param? + //ApplyListHeadMovement(TurnList, a2); + ApplyListHeadMovement(TurnList); + } + else + { + //MovePlayer(MotionCommand.SideStepRight, false, 1.0f /* ? */, false, 0, a2); + MovePlayer(MotionCommand.SideStepRight, false, 1.0f /* ? */, false, false); + MovePlayer(MotionCommand.TurnRight, false, 1.0f /* /? */, false, false); + } + + if (SidestepList.Count != 0) + { + ApplyListHeadMovement(SidestepList); + } + else + { + MovePlayer(MotionCommand.SideStepRight, false, 1.0f, false, false); + } + } + + public void ApplyHoldKeysToCommand(ref MotionCommand command, float speed) + { + if (!HoldSidestep) + return; + + if (command == MotionCommand.TurnRight) + { + command = MotionCommand.SideStepRight; + } + else if (command == MotionCommand.TurnLeft) + { + command = MotionCommand.SideStepLeft; + } + } + + public void ApplyListHeadMovement(List list) + { + var head = list.LastOrDefault(); + if (head == null) + return; + + // if head is mouse + //MovePlayer(head.Command, true, head.Speed, true, head.HoldRun); + + // else + MovePlayer(head.Command, true, head.Speed, false, false); // always false? + } + + public bool BookkeepCommandAndModifyIfNecessary(MotionCommand command, bool start, float speed, bool mouse, bool newHoldRun) + { + if (command == MotionCommand.Jump) + return true; + + if (start) + { + // vfptr[1].OnAction - AddCommand + AddCommand(command, speed, mouse, newHoldRun); + return true; + } + else + { + // vfptr[1].OnLoseFocus - MovePlayer? + MovePlayer(command, start, speed, mouse, newHoldRun); + } + return false; + } + + public void ClearAllCommands() + { + SubstateList.Clear(); + TurnList.Clear(); + SidestepList.Clear(); + } + + public void Disable() + { + // vfptr[3].OnAction() + // vfptr[2].OnLoseFocus(0); + var autonomy = AutonomyLevel != 0; + HoldSidestep = false; + if (AutonomyLevel != 0 && ControlledByServer) + { + // vfptr[2].OnAction(this) + // vfptr[6].OnAction(this) + } + } + + public void Enable() + { + Enabled = true; + // vfptr[2].OnLoseFocus(HoldRun); + } + + public bool GetMouseLeftDown() + { + return MouseLeftDown; + } + + public bool GetMouseLookActive() + { + return MouseLookActive; + } + + public void HandleExhaustion() + { + if (Player != null) + Player.report_exhaustion(); + } + + public void HandleKeyboardCommand(CmdStruct cmdStruct, MotionCommand command) + { + // vfptr[12] - IsActive + if (!IsActive()) return; + + bool start; + + if (cmdStruct.Command == MotionCommand.AutoRun) + { + start = Convert.ToBoolean(cmdStruct.Args[cmdStruct.Curr]); + cmdStruct.Curr++; + if (cmdStruct.Curr >= cmdStruct.Size) + { + AutoRunSpeed = 1.0f; + } + else + { + AutoRunSpeed = BitConverter.ToSingle(BitConverter.GetBytes(cmdStruct.Args[cmdStruct.Curr]), 0); + cmdStruct.Curr++; + } + // vfptr[16].OnLoseFocus - ToggleAutoRun + ToggleAutoRun(); + // vfptr[6].OnAction - SendMovementEvent + SendMovementEvent(); + return; + } + + if (((uint)cmdStruct.Command & (uint)CommandMask.UI) != 0) + return; + + start = Convert.ToBoolean(cmdStruct.Args[cmdStruct.Curr]); + cmdStruct.Curr++; + + var speed = 1.0f; + if (cmdStruct.Curr < cmdStruct.Size) + { + speed = BitConverter.ToSingle(BitConverter.GetBytes(cmdStruct.Args[cmdStruct.Curr]), 0); + cmdStruct.Curr++; + } + + if (ControlledByServer && !start) + { + // vfptr[1].OnLoseFocus - MovePlayer? + MovePlayer((MotionCommand)cmdStruct.Command, start, speed, false, false); + return; + } + + // vfptr[8].OnLoseFocus(a2) - ACCmdInterp::TakeControlFromServer? + TakeControlFromServer(); + + if (cmdStruct.Command == MotionCommand.HoldRun) + { + // vfptr[2].OnLoseFocus + + if (!IsStandingStill()) + SendMovementEvent(); + + return; + } + + if (cmdStruct.Command == MotionCommand.HoldSidestep) + { + // vfptr[3] + + if (!IsStandingStill()) + SendMovementEvent(); + + return; + } + + // vfptr[2] - Bookkeep + if (!BookkeepCommandAndModifyIfNecessary(cmdStruct.Command, start, speed, false, false)) + { + SendMovementEvent(); + return; + } + + // vfptr[4].OnAction - ApplyHoldKeysToCommand + ApplyHoldKeysToCommand(ref cmdStruct.Command, speed); + + // vfptr[13].OnAction - MovePlayer + MovePlayer(cmdStruct.Command, start, speed, false, false); + + // vfptr[6].OnAction - SendMovementEvent + if (cmdStruct.Command != MotionCommand.Jump) + SendMovementEvent(); + } + + public void HandleLogOff() + { + // vfptr[11] + Disable(); + } + + public void HandleMouseMovementCommand(CmdStruct cmdStruct) + { + + } + + public virtual void HandleNewForwardMovement() + { + // vfptr[17](0, 1) - SetAutoRun + SetAutoRun(false, true); + } + + public bool HandleSelectLeft(bool start) + { + MouseLeftDown = true; + + // CInputManager : ICIDM + return false; + } + + public bool IsActive() + { + return Enabled && Player != null; + } + + public bool IsEnabled() + { + return Enabled; + } + + public bool IsStandingStill() + { + if (Player == null) + return true; + + var minterp = Player.get_minterp(); + + return minterp.is_standing_still(); + } + + public void LoseControlToServer() + { + if (AutonomyLevel == 0) + return; + + ControlledByServer = true; + // vfptr[17](0, 0) + SetAutoRun(false, false); + // vfptr[6].OnLoseFocus(this) + } + + public void LoseKeyboardFocus() + { + ClearAllCommands(); + // vfptr[2].OnLoseFocus(this, 0) - SetHoldRun + SetHoldRun(false); + + HoldSidestep = false; + // vfptr[6].OnLoseFocus(this) - ACCmdInterp::FinishJump + + if (AutonomyLevel != 0) + { + if (!ControlledByServer) + { + // vfptr[2].OnAction + // vfptr[6].OnAction + } + } + } + + public bool MaybeStopCompletely() + { + if (ControlledByServer) + return true; + + // vfptr[15].OnLoseFocus + return false; + } + + public void MovePlayer(MotionCommand command, bool start, float speed, bool mouse, bool newHoldRun) + { + if (Player == null || Player.InqInterpretedMotionState() == null) + return; + + // if vfptr[10] - PlayerIsDead + if (PlayerIsDead()) + { + // vfptr[9].OnAction - LoseKeyboardFocus + LoseKeyboardFocus(); + // vfptr[17](0, 0) - SetAutoRun + SetAutoRun(false, false); + return; + } + + // if !ICIDM::s_cidm->m_UseMouseTurning + // - goto LABEL_55 + + var mvp = new MovementParameters(); + + if (mouse) + { + // someFlags &= 0xFFFFF7FF; + // unset bit 11 + mvp.SetHoldKey = false; + var holdRun = Convert.ToInt32(newHoldRun) + 1; + } + + var turn = (MotionCommand)MotionStance.Invalid; + var sidestep = (MotionCommand)MotionStance.Invalid; + + if (TurnList.Count != 0) + turn = TurnList.FirstOrDefault().Command; + + if (SidestepList.Count != 0) + sidestep = SidestepList.FirstOrDefault().Command; + + // vfptr[17].OnLoseFocus - GetMouseLookActive + var mouselook = GetMouseLookActive(); + + bool start_turn_left = false; + bool start_turn_right = false; + bool start_sidestep_left = false; + bool start_sidestep_right = false; + + bool cancel_turn_left = false; + bool cancel_turn_right = false; + bool cancel_sidestep_left = false; + bool cancel_sidestep_right = false; + + MotionCommand cmd1; + + if (!mouse) + { + if (!mouselook) + { + cmd1 = command; + // goto LABEL_59 + } + if (command != MotionCommand.TurnRight) + { + if (command != MotionCommand.TurnLeft) + { + if (start) + { + cancel_turn_left = true; + start_sidestep_left = true; + } + else + { + cancel_sidestep_left = true; + } + } + else + { + cancel_turn_right = true; + cancel_turn_left = true; + } + // goto LABEL_38 + } + if (!start) + { + cancel_sidestep_right = true; + // goto LABEL_38 + } + // LABEL_31: + cancel_turn_right = true; + start_sidestep_right = true; + // goto LABEL_38 + } + + if (!mouselook) + { + if (turn == MotionCommand.TurnRight) + { + cancel_sidestep_right = true; + start_turn_right = true; + } + else if (turn == MotionCommand.TurnLeft) + { + cancel_sidestep_left = true; + start_turn_left = true; + } + // goto LABEL_38 + } + + if (command != MotionCommand.MouseLook) + { + // goto LABEL_38 + } + + if (turn == MotionCommand.TurnRight) + { + cancel_turn_right = true; + + if (sidestep == MotionCommand.SideStepLeft) + start_sidestep_left = true; + else + start_sidestep_right = true; + + // goto LABEL_38 + } + + if (turn == MotionCommand.TurnLeft) + { + if (sidestep != MotionCommand.SideStepRight) + { + cancel_turn_left = true; + start_sidestep_left = true; + + // goto LABEL_38 + } + // goto LABEL_31 + } + + if (MouseLeftDown) + { + start = true; + command = MotionCommand.WalkForward; + } + + // ============ + // LABEL 38: + + // vfptr[8].OnLoseFocus - TakeControlFromServer + TakeControlFromServer(); + + if (cancel_sidestep_right) + Player.StopMotion((uint)MotionCommand.SideStepRight, mvp, true); + + if (cancel_sidestep_left) + Player.StopMotion((uint)MotionCommand.SideStepLeft, mvp, true); + + if (cancel_turn_right) + Player.StopMotion((uint)MotionCommand.TurnRight, mvp, true); + + if (cancel_turn_left) + Player.StopMotion((uint)MotionCommand.TurnLeft, mvp, true); + + if (start_turn_right) + { + start = true; + cmd1 = MotionCommand.TurnRight; + } + else + cmd1 = command; + + if (start_turn_left) + { + start = true; + cmd1 = MotionCommand.TurnLeft; + } + + if (start_sidestep_right) + { + start = true; + cmd1 = MotionCommand.SideStepRight; + speed = 1.0f; + } + + if (start_sidestep_left) + { + start = true; + command = MotionCommand.SideStepLeft; + speed = 1.0f; + + // LABEL_55: + cmd1 = command; + } + + var holdRunRel1 = 0; + if (mouse) + { + holdRunRel1 = Convert.ToInt32(newHoldRun) + 1; + // goto LABEL_60 + } + + // LABEL_59: + holdRunRel1 = 0; + + // LABEL_60: + if (AutonomyLevel != 0) + { + if (start) + { + if (cmd1 != MotionCommand.Jump) + { + mvp = new MovementParameters(); + // set 12th flag + mvp.Autonomous = true; + if (mouse) + mvp.SetHoldKey = false; // unset 11th flag + if (((uint)cmd1 & (uint)CommandMask.Action) != 0) + { + // vfptr[15].OnLoseFocus(this) + } + var werror = Player.DoMotion((uint)cmd1, mvp); + switch (werror) + { + case WeenieError.None: + if (((uint)cmd1 & (uint)CommandMask.Action) != 0) + ActionStamp++; + return; + + case WeenieError.CantCrouchInCombat: + break; // 72 + + case WeenieError.CantSitInCombat: + break; // 73 + + case WeenieError.CantLieDownInCombat: + break; // 73 + + case WeenieError.YouAreTooTiredToDoThat: + break; // 73 + + case WeenieError.CantChatEmoteInCombat: + break; // 73 + + case WeenieError.CantChatEmoteNotStanding: + break; + + default: + return; + } + } + } + else if (cmd1 != MotionCommand.Jump) + { + mvp = new MovementParameters(); + var holdRunRel = 0; + if (mouse) + { + mvp.SetHoldKey = false; + holdRunRel = Convert.ToInt32(newHoldRun) + 1; + } + Player.StopMotion((uint)cmd1, mvp, true); + } + } + else + { + // vfptr[4].OnLoseFocus - NonAutonomous? + MovePlayer_NonAutonomous(cmd1, start, speed, (HoldKey)holdRunRel1); + } + } + + public void MovePlayer_NonAutonomous(MotionCommand command, bool start, float speed, HoldKey holdKey) + { + if (start) + { + if (command == MotionCommand.Jump) + { + // vfptr[5].OnAction + } + else + { + // vfptr[19].OnAction + } + } + else if (command == MotionCommand.Jump) + { + // vfptr[5].OnLoseFocus + } + else + { + // vfptr[19].OnLoseFocus + } + } + + public void NewPlayer(PhysicsObj player, bool autonomous_movement) + { + Player = player; + if (autonomous_movement) + { + // vfptr[2].OnAction + } + else + { + // vfptr[8].OnAction + } + } + + public bool NukeCommand(MotionCommand command, bool start, float speed, bool mouse, bool newHoldRun) + { + return false; + } + + public bool PlayerIsDead() + { + if (Player != null) + { + var motionState = Player.InqInterpretedMotionState(); + if (motionState != null) + return motionState.ForwardCommand == (uint)MotionCommand.Dead; + } + return false; + } + + public void PlayerTeleported() + { + // vfptr[17](0, 1) - SetAutoRun? + SetAutoRun(false, true); + + // vfptr[6].OnAction - SendMovementEvent? + SendMovementEvent(); + } + + public void SendMovementEvent() + { + + } + + public void SendPositionEvent() + { + + } + + public void SetAutoRun(bool val, bool apply_movement) + { + if (AutoRun != val) + { + AutoRun = val; + TransientState = false; + if (val) + { + //vfptr[8].OnLoseFocus(this) + // display string: AutoRun ON + } + else + { + // display string: AutoRun OFF + } + } + if (apply_movement) + { + // vfptr[2].OnAction(this) - ApplyCurrentMovement + //ApplyCurrentMovement(); + } + } + + public bool SetAutonomyLevel(uint newLevel) + { + if (newLevel <= 2) + { + AutonomyLevel = newLevel; + // vfptr[19](newLevel) + return true; + } + return false; + } + + public void SetHoldRun(bool newVal) + { + // vfptr[5] - ACCmdInterp::UITogglesRun() + } + + public void SetHoldSidestep(bool newVal) + { + // vfptr[4](TurnList) + HoldSidestep = newVal; + // vfptr[2].OnAction + } + + public void SetMouseLeftDown(bool active) + { + MouseLeftDown = active; + } + + public void SetMouseLookActive(bool active) + { + MouseLookActive = active; + } + + public void SetSmartBox(SmartBox smartBox) + { + SmartBox = smartBox; + if (smartBox != null) + Player = smartBox.Player; + else + Player = null; + } + + public bool ShouldSendPositionEvent() + { + return true; + } + + public bool StopCompletely() + { + return false; + } + + public void StopDrift() + { + + } + + public void StopListHeadMovement(List list) + { + + } + + public virtual void TakeControlFromServer() + { + // vrptr[10] + if (ControlledByServer && AutonomyLevel != 0 && true) + { + ControlledByServer = false; + + if (Player != null) + { + Player.LastMoveWasAutonomous = true; + Player.StopCompletely(true); + Player.StopInterpolating(); + } + + // vfptr[2].OnLoseFocus(HoldRun) + // vfptr[2].OnAction + } + } + + public void ToggleAutoRun() + { + var toggle = !AutoRun; + // vfptr[17] - SetAutoRun + SetAutoRun(toggle, true); + } + + public bool TurnToHeading(float newHeading, bool run) + { + return false; + } + + public void UpdateToggleRun() + { + // vfptr[2].OnLoseFocus(HoldRun) + // vfptr[6].OnAction + } + + public bool UsePositionFromServer() + { + return AutonomyLevel != 2; + } + + public void UseTime() + { + + } + + public List WhichList(MotionCommand command) + { + switch (command) + { + case MotionCommand.TurnLeft: + case MotionCommand.TurnRight: + return TurnList; + + case MotionCommand.SideStepLeft: + case MotionCommand.SideStepRight: + return SidestepList; + + default: + if (((int)command & 0x40000000) != 0) + { + if (((int)command & 0x4000000) != 0) + { + return SubstateList; + } + } + break; + } + return null; + } + } +} diff --git a/ACViewer/Physics/Command/CommandList.cs b/ACViewer/Physics/Command/CommandList.cs new file mode 100644 index 0000000..848464d --- /dev/null +++ b/ACViewer/Physics/Command/CommandList.cs @@ -0,0 +1,127 @@ +using ACE.Entity.Enum; + +namespace ACE.Server.Physics.Command +{ + public class CommandList + { + public CommandListElement Head; + public CommandListElement MouseCommand; + public CommandListElement Current; + + public void AddCommand(MotionCommand command, float speed, bool mouse, bool holdRun) + { + var element = new CommandListElement(command, speed, holdRun); + + CommandListElement mouse_cmd = null; + + if (mouse) + { + mouse_cmd = MouseCommand; + + if (mouse_cmd == null) + { + SetMouseCommand(element); + return; + } + + var prevNext = mouse_cmd.Prev.Next; + if (prevNext != null) + { + prevNext = mouse_cmd.Next; + } + else + { + var next = mouse_cmd.Next; + var nextNull = next == null; + Head = mouse_cmd.Next; + if (nextNull) + { + // label_12 + mouse_cmd.Next = null; + mouse_cmd.Prev = null; + + SetMouseCommand(element); + return; + } + next.Prev = null; + } + if (mouse_cmd.Next != null) + mouse_cmd.Next.Prev = mouse_cmd.Prev; + // goto label_12 + } + } + + public void ClearAllCommands() + { + if (Head != null) + { + CommandListElement headPrevNext = null; + + while (true) + { + var head = Head; + headPrevNext = Head.Prev.Next; + if (headPrevNext != null) + break; + + var headNext = head.Next; + var headNextNull = head.Next == null; + Head = head.Next; + + if (!headNextNull) + { + headNext.Prev = null; + + // label_6 + if (head.Next != null) + head.Next.Prev = head.Prev; + } + + head.Next = null; + head.Prev = null; + + if (Head == null) + { + MouseCommand = null; + return; + } + } + headPrevNext = Head.Next; + // goto label_6 + } + MouseCommand = null; + } + + public void ClearKeyboardCommands() + { + + } + + public CommandListElement GetHead() + { + return Head; + } + + public bool HeadIsMouse() + { + if (Head == null) + return false; + + return Head == MouseCommand; + } + + public bool RemoveCommand(MotionCommand command, float speed, bool mouse) + { + return false; + } + + public void SetMouseCommand(CommandListElement element) + { + MouseCommand = element; + element.Next = Head; + if (Head != null) + Head.Prev = element; + Head = element; + } + } +} diff --git a/ACViewer/Physics/Command/CommandListElement.cs b/ACViewer/Physics/Command/CommandListElement.cs new file mode 100644 index 0000000..8230765 --- /dev/null +++ b/ACViewer/Physics/Command/CommandListElement.cs @@ -0,0 +1,26 @@ +using ACE.Entity.Enum; + +namespace ACE.Server.Physics.Command +{ + public class CommandListElement + { + public CommandListElement Next; + public CommandListElement Prev; + + public MotionCommand Command; + public float Speed; + public bool HoldRun; + + public CommandListElement() + { + Speed = 1.0f; + } + + public CommandListElement(MotionCommand command, float speed, bool holdRun) + { + Command = command; + Speed = speed; + HoldRun = holdRun; + } + } +} diff --git a/ACViewer/Physics/Command/InputEvent.cs b/ACViewer/Physics/Command/InputEvent.cs new file mode 100644 index 0000000..bbfce9a --- /dev/null +++ b/ACViewer/Physics/Command/InputEvent.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ACE.Server.Physics.Command +{ + public class InputEvent + { + public uint InputAction; + public bool Start; + } +} diff --git a/ACViewer/Physics/Common/BldPortal.cs b/ACViewer/Physics/Common/BldPortal.cs index 9be2b1c..ae1746d 100644 --- a/ACViewer/Physics/Common/BldPortal.cs +++ b/ACViewer/Physics/Common/BldPortal.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using ACE.Entity.Enum; namespace ACE.Server.Physics.Common diff --git a/ACViewer/Physics/Common/BuildingObj.cs b/ACViewer/Physics/Common/BuildingObj.cs index eb84e98..ebed371 100644 --- a/ACViewer/Physics/Common/BuildingObj.cs +++ b/ACViewer/Physics/Common/BuildingObj.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; + using ACE.DatLoader.Entity; using ACE.Server.Physics.Animation; @@ -125,6 +127,26 @@ public static BuildingObj makeBuilding(uint buildingID, List portals return building; } + public float GetMinZ() + { + get_building_cells(); + + var minZ = float.MaxValue; + + foreach (var buildingCell in BuildingCells.Where(i => i.Environment != null)) + { + if (buildingCell.Environment == null) continue; + + foreach (var cellStruct in buildingCell.Environment.Cells.Values) + { + foreach (var vertex in cellStruct.VertexArray.Vertices.Values) + if (vertex.Origin.Z < minZ) + minZ = vertex.Origin.Z; + } + } + return minZ; + } + public void remove() { var sortCell = (SortCell)CurCell; diff --git a/ACViewer/Physics/Common/EnvCell.cs b/ACViewer/Physics/Common/EnvCell.cs index a19acf9..621a308 100644 --- a/ACViewer/Physics/Common/EnvCell.cs +++ b/ACViewer/Physics/Common/EnvCell.cs @@ -62,10 +62,14 @@ public EnvCell(DatLoader.FileTypes.EnvCell envCell): base() SeenOutside = envCell.SeenOutside; EnvironmentID = envCell.EnvironmentId; - Environment = DBObj.GetEnvironment(EnvironmentID); + + if (EnvironmentID != 0) + Environment = DBObj.GetEnvironment(EnvironmentID); + CellStructureID = envCell.CellStructure; // environment can contain multiple? - if (Environment.Cells != null && Environment.Cells.ContainsKey(CellStructureID)) - CellStructure = new CellStruct(Environment.Cells[CellStructureID]); + + if (Environment?.Cells != null && Environment.Cells.TryGetValue(CellStructureID, out var cellStruct)) + CellStructure = new CellStruct(cellStruct); NumSurfaces = envCell.Surfaces.Count; } @@ -123,7 +127,9 @@ public void build_visible_cells() public void check_building_transit(ushort portalId, Position pos, int numSphere, List spheres, CellArray cellArray, SpherePath path) { - if (portalId == 0) return; + //if (portalId == 0) return; + if (portalId == ushort.MaxValue) return; + foreach (var sphere in spheres) { var globSphere = new Sphere(Pos.Frame.GlobalToLocal(sphere.Center), sphere.Radius); @@ -139,7 +145,8 @@ public void check_building_transit(ushort portalId, Position pos, int numSphere, public void check_building_transit(int portalId, int numParts, List parts, CellArray cellArray) { - if (portalId == 0) return; + //if (portalId == 0) return; + if (portalId == ushort.MaxValue) return; var portal = Portals[portalId]; var portalPoly = CellStructure.Portals[portalId]; @@ -195,7 +202,7 @@ public ObjCell find_visible_child_cell(Vector3 origin, bool searchCells) { if (visibleCell == null) continue; - var envCell = GetVisible(visibleCell.ID); + var envCell = GetVisible(visibleCell.ID & 0xFFFF); if (envCell != null && envCell.point_in_cell(origin)) return envCell; } @@ -289,7 +296,7 @@ public override void find_transit_cells(int numParts, List parts, C } var cellBox = new BBox(); - cellBox.LocalToLocal(bbox, Pos, otherCell.Pos); + cellBox.LocalToLocal(bbox, part.Pos, otherCell.Pos); if (otherCell.CellStructure.box_intersects_cell(cellBox)) { cellArray.add_cell(otherCell.ID, otherCell); @@ -298,7 +305,7 @@ public override void find_transit_cells(int numParts, List parts, C } } if (checkOutside) - LandCell.add_all_outside_cells(numParts, parts, cellArray); + LandCell.add_all_outside_cells(numParts, parts, cellArray, ID); } public override void find_transit_cells(Position position, int numSphere, List spheres, CellArray cellArray, SpherePath path) @@ -382,6 +389,12 @@ public void init_static_objects() var staticObj = PhysicsObj.makeObject(StaticObjectIDs[i], 0, false); staticObj.DatObject = true; staticObj.add_obj_to_cell(this, StaticObjectFrames[i]); + if (staticObj.CurCell == null) + { + //Console.WriteLine($"EnvCell {ID:X8}: failed to add {staticObj.ID:X8}"); + staticObj.DestroyObject(); + continue; + } StaticObjects.Add(staticObj); } @@ -426,5 +439,25 @@ public override int GetHashCode() { return ID.GetHashCode(); } + + public bool IsVisibleIndoors(ObjCell cell) + { + var blockDist = PhysicsObj.GetBlockDist(ID, cell.ID); + + // if landblocks equal + if (blockDist == 0) + { + // check env VisibleCells + var cellID = cell.ID & 0xFFFF; + if (VisibleCells.ContainsKey(cellID)) + return true; + } + return SeenOutside && blockDist <= 1; + } + + public override bool handle_move_restriction(Transition transition) + { + return true; + } } } diff --git a/ACViewer/Physics/Common/GfxObj.cs b/ACViewer/Physics/Common/GfxObj.cs index 2ea9c0e..6da5289 100644 --- a/ACViewer/Physics/Common/GfxObj.cs +++ b/ACViewer/Physics/Common/GfxObj.cs @@ -10,13 +10,22 @@ namespace ACE.Server.Physics.Collision { public class GfxObj { - public DatLoader.FileTypes.GfxObj _dat; public uint ID; + public DatLoader.FileTypes.GfxObj _dat; public CVertexArray VertexArray; + /// + /// Only populated if !PhysicsEngine.Instance.Server + /// public Dictionary Polygons; + /// + /// Only populated if !PhysicsEngine.Instance.Server + /// public Dictionary PhysicsPolygons; public Sphere PhysicsSphere; public BSP.BSPTree PhysicsBSP; + /// + /// Only populated if !PhysicsEngine.Instance.Server + /// public Vector3 SortCenter; public Sphere DrawingSphere; public BSP.BSPTree DrawingBSP; @@ -36,21 +45,28 @@ public GfxObj(DatLoader.FileTypes.GfxObj gfxObj) ID = gfxObj.Id; VertexArray = gfxObj.VertexArray; - Polygons = new Dictionary(); - foreach (var kvp in gfxObj.Polygons) - Polygons.Add(kvp.Key, PolygonCache.Get(kvp.Value, gfxObj.VertexArray)); + if (!PhysicsEngine.Instance.Server) + { + Polygons = new Dictionary(); + foreach (var kvp in gfxObj.Polygons) + Polygons.Add(kvp.Key, PolygonCache.Get(kvp.Value, gfxObj.VertexArray)); + } if (gfxObj.PhysicsPolygons.Count > 0) { - PhysicsPolygons = new Dictionary(); - foreach (var kvp in gfxObj.PhysicsPolygons) - PhysicsPolygons.Add(kvp.Key, PolygonCache.Get(kvp.Value, gfxObj.VertexArray)); + if (!PhysicsEngine.Instance.Server) + { + PhysicsPolygons = new Dictionary(); + foreach (var kvp in gfxObj.PhysicsPolygons) + PhysicsPolygons.Add(kvp.Key, PolygonCache.Get(kvp.Value, gfxObj.VertexArray)); + } PhysicsBSP = BSPCache.Get(gfxObj.PhysicsBSP, gfxObj.PhysicsPolygons, gfxObj.VertexArray); PhysicsSphere = PhysicsBSP.GetSphere(); } - SortCenter = gfxObj.SortCenter; + if (!PhysicsEngine.Instance.Server) + SortCenter = gfxObj.SortCenter; DrawingBSP = BSPCache.Get(gfxObj.DrawingBSP, gfxObj.Polygons, gfxObj.VertexArray); DrawingSphere = DrawingBSP.GetSphere(); diff --git a/ACViewer/Physics/Common/ImgTex.cs b/ACViewer/Physics/Common/ImgTex.cs index f2c604e..091a1fe 100644 --- a/ACViewer/Physics/Common/ImgTex.cs +++ b/ACViewer/Physics/Common/ImgTex.cs @@ -18,7 +18,7 @@ public class ImgTex public uint TextureCode; public bool IsLocked; - public DatLoader.FileTypes.Texture _renderSurface; + public Texture _texture; public SurfaceTexture _surfaceTexture; public static ImageScaleType LandTextureScale; @@ -41,9 +41,9 @@ public ImgTex() } - public ImgTex(DatLoader.FileTypes.Texture renderSurface) + public ImgTex(Texture texture) { - _renderSurface = renderSurface; + _texture = texture; } public ImgTex(SurfaceTexture surfaceTexture) @@ -59,7 +59,7 @@ public ImgTex(SurfaceTexture surfaceTexture) ID = _surfaceTexture.Id; var textureID = TextureCode = surfaceTexture.Textures[0]; // use texturecode here? //Console.WriteLine($"Loading texture {textureID:X8}"); - var renderSurface = DatManager.PortalDat.ReadFromDat(textureID); + var renderSurface = DatManager.PortalDat.ReadFromDat(textureID); ImageData = new RenderSurface(renderSurface); } diff --git a/ACViewer/Physics/Common/LScape.cs b/ACViewer/Physics/Common/LScape.cs index 77e0bc5..e3fef06 100644 --- a/ACViewer/Physics/Common/LScape.cs +++ b/ACViewer/Physics/Common/LScape.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Numerics; +using ACE.Entity; +using ACE.Server.Physics.Util; + namespace ACE.Server.Physics.Common { public static class LScape @@ -11,6 +14,9 @@ public static class LScape public static int MidWidth = 11; private static readonly object landblockMutex = new object(); + /// + /// This is not used if PhysicsEngine.Instance.Server is true + /// public static ConcurrentDictionary Landblocks = new ConcurrentDictionary(); public static Dictionary BlockDrawList = new Dictionary(); @@ -37,6 +43,8 @@ public static bool SetMidRadius(int radius) return true; } + public static int LandblocksCount => Landblocks.Count; + /// /// Loads the backing store landblock structure /// This function is thread safe @@ -44,6 +52,16 @@ public static bool SetMidRadius(int radius) /// Any landblock + cell ID within the landblock public static Landblock get_landblock(uint blockCellID) { + var landblockID = blockCellID | 0xFFFF; + + /*if (PhysicsEngine.Instance.Server) + { + var lbid = new LandblockId(landblockID); + var lbmLandblock = LandblockManager.GetLandblock(lbid, false, false); + + return lbmLandblock.PhysicsLandblock; + }*/ + // client implementation /*if (Landblocks == null || Landblocks.Count == 0) return null; @@ -62,8 +80,6 @@ public static Landblock get_landblock(uint blockCellID) return Landblocks[yDiff + xDiff * MidWidth];*/ - var landblockID = blockCellID | 0xFFFF; - // check if landblock is already cached if (Landblocks.TryGetValue(landblockID, out var landblock)) return landblock; @@ -87,14 +103,31 @@ public static Landblock get_landblock(uint blockCellID) public static bool unload_landblock(uint landblockID) { - return Landblocks.TryRemove(landblockID, out _); + if (PhysicsEngine.Instance.Server) + { + // todo: Instead of ACE.Server.Entity.Landblock.Unload() calling this function, it should be calling PhysicsLandblock.Unload() + // todo: which would then call AdjustCell.AdjustCells.Remove() + + AdjustCell.AdjustCells.TryRemove(landblockID >> 16, out _); + return true; + } + + var result = Landblocks.TryRemove(landblockID, out _); + // todo: Like mentioned above, the following function should be moved to ACE.Server.Physics.Common.Landblock.Unload() + AdjustCell.AdjustCells.TryRemove(landblockID >> 16, out _); + return result; } public static void unload_landblocks_all() { + // ServerObjectManager? Landblocks.Clear(); } - + + /// + /// Gets the landcell from a landblock. If the cell is an indoor cell and hasn't been loaded, it will be loaded. + /// This function is thread safe + /// public static ObjCell get_landcell(uint blockCellID) { //Console.WriteLine($"get_landcell({blockCellID:X8}"); @@ -117,12 +150,19 @@ public static ObjCell get_landcell(uint blockCellID) // indoor cells else { - landblock.LandCells.TryGetValue((int)cellID, out cell); - if (cell != null) return cell; - cell = DBObj.GetEnvCell(blockCellID); - landblock.LandCells.Add((int)cellID, cell); - var envCell = cell as EnvCell; - envCell.PostInit(); + if (landblock.LandCells.TryGetValue((int)cellID, out cell)) + return cell; + + lock (landblock.LandCellMutex) + { + if (landblock.LandCells.TryGetValue((int)cellID, out cell)) + return cell; + + cell = DBObj.GetEnvCell(blockCellID); + landblock.LandCells.TryAdd((int)cellID, cell); + var envCell = (EnvCell)cell; + envCell.PostInit(); + } } return cell; } diff --git a/ACViewer/Physics/Common/LandCell.cs b/ACViewer/Physics/Common/LandCell.cs index f3bfb4e..4f50237 100644 --- a/ACViewer/Physics/Common/LandCell.cs +++ b/ACViewer/Physics/Common/LandCell.cs @@ -3,6 +3,7 @@ using System.Numerics; using ACE.Entity.Enum; using ACE.Server.Physics.Animation; +using ACE.Server.Physics.Collision; namespace ACE.Server.Physics.Common { @@ -116,9 +117,92 @@ public static void add_all_outside_cells(Position position, int numSphere, List< } } - public static void add_all_outside_cells(int numParts, List parts, CellArray cellArray) + public static void add_all_outside_cells(int numParts, List parts, CellArray cellArray, uint id) { - // not implemented yet + if (cellArray.AddedOutside) + return; + + cellArray.AddedOutside = true; + + if (numParts == 0) + return; + + var min_x = 0; + var min_y = 0; + var max_x = 0; + var max_y = 0; + + for (var i = 0; i < numParts; i++) + { + var curPart = parts[i]; + + var loc = new Position(curPart.Pos); + + if (!LandDefs.AdjustToOutside(loc)) + continue; + + var _lcoord = LandDefs.gid_to_lcoord(loc.ObjCellID); + + if (_lcoord == null) + continue; + + var lcoord = _lcoord.Value; + + var lx = (int)(((loc.ObjCellID & 0xFFFF) - 1) / 8); + var ly = (int)(((loc.ObjCellID & 0xFFFF) - 1) % 8); + + for (var j = 0; j < numParts; j++) + { + var otherPart = parts[j]; + + if (otherPart == null) + continue; + + // add if missing: otherPart.Always2D, checks degrades.degradeMode != 1 + + var bbox = new BBox(); + bbox.LocalToGlobal(otherPart.GetBoundingBox(), otherPart.Pos, loc); + + var min_cx = (int)Math.Floor(bbox.Min.X / 24.0f); + var min_cy = (int)Math.Floor(bbox.Min.Y / 24.0f); + + var max_cx = (int)Math.Floor(bbox.Max.X / 24.0f); + var max_cy = (int)Math.Floor(bbox.Max.Y / 24.0f); + + min_x = Math.Min(min_cx - lx, min_x); + min_y = Math.Min(min_cy - ly, min_y); + + max_x = Math.Max(max_cx - lx, max_x); + max_y = Math.Max(max_cy - ly, max_y); + } + + add_cell_block(min_x + (int)lcoord.X, min_y + (int)lcoord.Y, max_x + (int)lcoord.X, max_y + (int)lcoord.Y, cellArray, id); + } + } + + public static void add_cell_block(int min_x, int min_y, int max_x, int max_y, CellArray cellArray, uint id) + { + for (var i = min_x; i <= max_x; i++) + { + for (var j = min_y; j <= max_y; j++) + { + if (i < 0 || j < 0 || i >= LandDefs.LandLength || j >= LandDefs.LandLength) + continue; + + var ui = (uint)i; + var uj = (uint)j; + + var cellID = (((uj >> 3) | 32 * (ui & 0xFFFFFFF8)) << 16) | ((uj & 7) + 8 * (ui & 7) + 1); + + // FIXME! + if (id >> 16 != cellID >> 16) + continue; + + var cell = LScape.get_landcell(cellID); + + cellArray.add_cell(cellID, cell); + } + } } public static void add_outside_cell(CellArray cellArray, float _x, float _y) @@ -186,7 +270,7 @@ public bool find_terrain_poly(Vector3 origin, ref Polygon walkable) public override void find_transit_cells(int numParts, List parts, CellArray cellArray) { - add_all_outside_cells(numParts, parts, cellArray); + add_all_outside_cells(numParts, parts, cellArray, ID); base.find_transit_cells(numParts, parts, cellArray); } @@ -201,5 +285,37 @@ public override bool point_in_cell(Vector3 point) Polygon poly = null; return find_terrain_poly(point, ref poly); } + + public override bool handle_move_restriction(Transition transition) + { + var offset = Pos.GetOffset(transition.SpherePath.CurPos); + + if (offset.Y >= -LandDefs.HalfSquareLength) + { + if (offset.Y <= LandDefs.HalfSquareLength) + { + offset.Y = 0; + if (offset.X < -LandDefs.HalfSquareLength) + offset.X = -1.0f; + else + offset.X = 1.0f; + } + else + { + offset.X = 0; + offset.Y = 1.0f; + } + } + else + { + offset.X = 0; + offset.Y = -1.0f; + } + var normal = new Vector3(offset.X, offset.Y, 0); + + transition.CollisionInfo.SetCollisionNormal(normal); + + return true; + } } } diff --git a/ACViewer/Physics/Common/Landblock.cs b/ACViewer/Physics/Common/Landblock.cs index 91df1a1..6af487d 100644 --- a/ACViewer/Physics/Common/Landblock.cs +++ b/ACViewer/Physics/Common/Landblock.cs @@ -67,6 +67,8 @@ public Landblock(CellLandblock landblock) public void PostInit() { + init_landcell(); + init_buildings(); init_static_objs(); } @@ -264,7 +266,12 @@ public void get_land_scenes() var physicsObj = PhysicsObj.makeObject(obj.ObjId, 0, false); physicsObj.DatObject = true; physicsObj.set_initial_frame(pos.Frame); - if (!physicsObj.obj_within_block()) continue; + if (!physicsObj.obj_within_block()) + { + //Console.WriteLine($"Landblock {ID:X8} scenery: failed to spawn {obj.ObjId:X8}"); + physicsObj.DestroyObject(); + continue; + } physicsObj.add_obj_to_cell(cell, pos.Frame); var scale = ObjectDesc.ScaleObj(obj, globalCellX, globalCellY, j); @@ -487,10 +494,29 @@ public void init_static_objs() var position = new Position(ID, new AFrame(info.Frame)); var outside = LandDefs.AdjustToOutside(position); var cell = get_landcell(position.ObjCellID); - if (cell == null) continue; + if (cell == null) + { + //Console.WriteLine($"Landblock {ID:X8} - failed to spawn static object {info.Id:X8}"); + obj.DestroyObject(); + continue; + } obj.add_obj_to_cell(cell, position.Frame); add_static_object(obj); } + + if (Info.RestrictionTables != null) + { + foreach (var kvp in Info.RestrictionTables) + { + var lcoord = LandDefs.gid_to_lcoord(kvp.Key); + + if (lcoord == null) continue; + + var idx = ((int)lcoord.Value.Y & 7) + ((int)lcoord.Value.X & 7) * SideCellCount; + + LandCells[idx].RestrictionObj = kvp.Value; + } + } } if (UseSceneFiles) get_land_scenes(); @@ -524,6 +550,15 @@ public void release_objs() DynObjsInitDone = false; } + /// + /// Release shadow objects pointing to cells in this landblock + /// + public void release_shadow_objs() + { + foreach (var cell in LandCells.Values) + cell.release_shadow_objs(); + } + public void release_visible_cells() { // legacy method @@ -544,14 +579,12 @@ public bool IsDungeon if (isDungeon != null) return isDungeon.Value; - var lbx = ID >> 24; - var lby = (ID >> 16) & 0xFF; - // hack for NW island // did a worldwide analysis for adding watercells into the formula, // but they are inconsistently defined for some of the edges of map unfortunately - if (lbx < 0x08 && lby > 0xF8) + if (BlockCoord.X < 64 && BlockCoord.Y > 1976) { + //Console.WriteLine($"Allowing {ID:X8}"); isDungeon = false; return isDungeon.Value; } @@ -597,58 +630,6 @@ public bool HasDungeon } } - private List adjacents; - - /// - /// Returns the list of adjacent landblocks - /// - public List get_adjacents(bool reload = false) - { - if (adjacents != null && !reload) return adjacents; - - var lbx = ID >> 24; - var lby = ID >> 16 & 0xFF; - - //var _adjacents = LandblockManager.GetAdjacents(new LandblockId((byte)lbx, (byte)lby)); - - adjacents = new List(); - - // dungeons have no adjacents - if (IsDungeon /*|| _adjacents == null*/) return adjacents; - - var startX = lbx > 0 ? lbx - 1 : lbx; - var startY = lby > 0 ? lby - 1 : lby; - - var endX = lbx < 254 ? lbx + 1 : lbx; - var endY = lby < 254 ? lby + 1 : lby; - - // get adjacents for outdoor landblocks - for (var curX = startX; curX <= endX; curX++) - { - for (var curY = startY; curY <= endY; curY++) - { - // exclude current landblock - if (curX == lbx && curY == lby) continue; - - var id = curX << 24 | curY << 16 | 0xFFFF; - - // ensure adjacent is loaded in ace landblock manager - //if (!IsAdjacentLoaded(_adjacents, id)) - //continue; - - var landblock = LScape.get_landblock(id); - if (landblock != null) - adjacents.Add(landblock); - } - } - return adjacents; - } - - /*public bool IsAdjacentLoaded(List adjacents, uint landblockID) - { - return adjacents.Any(l => (l.Id.Raw | 0xFFFF) == landblockID); - }*/ - private List envcells; public List get_envcells() @@ -671,5 +652,10 @@ public List get_envcells() } return envcells; } + + public void SortObjects() + { + ServerObjects = ServerObjects.OrderBy(i => i.Order).ToList(); + } } } diff --git a/ACViewer/Physics/Common/LandblockStruct.cs b/ACViewer/Physics/Common/LandblockStruct.cs index 9cbc4f5..54ef528 100644 --- a/ACViewer/Physics/Common/LandblockStruct.cs +++ b/ACViewer/Physics/Common/LandblockStruct.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Numerics; + using ACE.Entity.Enum; using ACE.DatLoader; using ACE.DatLoader.FileTypes; @@ -24,7 +25,8 @@ public class LandblockStruct public List Polygons; //public List SurfaceStrips; // SurfaceTriStrips //public int BlockSurfaceIndex; - public Dictionary LandCells; + public readonly object LandCellMutex = new object(); + public ConcurrentDictionary LandCells; public List SWtoNEcut { get; set; } // client-only @@ -492,8 +494,8 @@ public void Init() //BlockSurfaceIndex = -1; // init for landcell - LandCells = new Dictionary(); - for (uint i = 1; i <= 64; i++) LandCells.Add((int)i, new LandCell((i))); + LandCells = new ConcurrentDictionary(); + for (uint i = 1; i <= 64; i++) LandCells.TryAdd((int)i, new LandCell((i))); } /// @@ -518,9 +520,9 @@ public void InitPVArrays() for (var i = 0; i < numSquares; i++) SWtoNEcut.Add(false); - LandCells = new Dictionary(numCells); + LandCells = new ConcurrentDictionary(1, numCells); for (uint i = 0; i < numCells; i++) - LandCells.Add((int)i, new LandCell((ID & LandDefs.BlockMask) + i)); + LandCells.TryAdd((int)i, new LandCell((ID & LandDefs.BlockMask) + i)); } /// diff --git a/ACViewer/Physics/Common/ObjCell.cs b/ACViewer/Physics/Common/ObjCell.cs index 37f92ff..d11ceb8 100644 --- a/ACViewer/Physics/Common/ObjCell.cs +++ b/ACViewer/Physics/Common/ObjCell.cs @@ -2,15 +2,21 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Threading; using ACE.Entity.Enum; using ACE.Server.Physics.Animation; using ACE.Server.Physics.Combat; +using ACE.Server.Physics.Managers; + +using log4net; namespace ACE.Server.Physics.Common { public class ObjCell: PartCell, IEquatable { + private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + public uint ID; public LandDefs.WaterType WaterType; public Position Pos; @@ -27,8 +33,26 @@ public class ObjCell: PartCell, IEquatable public List VisibleCells; public bool SeenOutside; public List VoyeurTable; + public Landblock CurLandblock; + /// + /// Returns TRUE if this is a house cell that can be protected by a housing barrier + /// + public bool IsCellRestricted => RestrictionObj != 0; + + /// + /// TODO: This is a temporary locking mechanism, Mag-nus 2019-10-20 + /// TODO: The objective here is to allow multi-threading of physics, divided by landblock groups + /// TODO: This solves the issue of a player leaving one landblock group and trying to insert itself a target landblock group while that target landblock group is also in processing + /// TODO: In the future, the object should be removed from the landblock group and added to a queue of items that need to be inserted into a target + /// TODO: That list should then be processed in a single thread. + /// TODO: The above solution should remove the need for ObjCell access locking, and also increase performance + /// + private readonly ReaderWriterLockSlim readerWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + + public static readonly ObjCell EmptyCell = new ObjCell(); + public ObjCell(): base() { Init(); @@ -42,41 +66,66 @@ public ObjCell(uint cellID): base() public void AddObject(PhysicsObj obj) { - // check for existing obj? - ObjectList.Add(obj); - NumObjects++; - if (obj.ID == 0 || obj.Parent != null || obj.State.HasFlag(PhysicsState.Hidden) || VoyeurTable == null) - return; - - foreach (var voyeur_id in VoyeurTable) + readerWriterLockSlim.EnterWriteLock(); + try { - if (voyeur_id != obj.ID && voyeur_id != 0) + // check for existing obj? + ObjectList.Add(obj); + NumObjects++; + if (obj.ID == 0 || obj.Parent != null || obj.State.HasFlag(PhysicsState.Hidden) || VoyeurTable == null) + return; + + foreach (var voyeur_id in VoyeurTable) { - var voyeur = obj.GetObjectA(voyeur_id); - if (voyeur == null) continue; + if (voyeur_id != obj.ID && voyeur_id != 0) + { + var voyeur = obj.GetObjectA(voyeur_id); + if (voyeur == null) continue; - var info = new DetectionInfo(obj.ID, DetectionType.EnteredDetection); - voyeur.receive_detection_update(info); + var info = new DetectionInfo(obj.ID, DetectionType.EnteredDetection); + voyeur.receive_detection_update(info); + } } } + finally + { + readerWriterLockSlim.ExitWriteLock(); + } } + public void AddShadowObject(ShadowObj shadowObj) { - ShadowObjectList.Add(shadowObj); - NumShadowObjects++; // can probably replace with .Count - shadowObj.Cell = this; + readerWriterLockSlim.EnterWriteLock(); + try + { + ShadowObjectList.Add(shadowObj); + NumShadowObjects++; // can probably replace with .Count + shadowObj.Cell = this; + } + finally + { + readerWriterLockSlim.ExitWriteLock(); + } } public void CheckAttack(uint attackerID, Position attackerPos, float attackerScale, AttackCone attackCone, AttackInfo attackInfo) { - foreach (var shadowObj in ShadowObjectList) + readerWriterLockSlim.EnterReadLock(); + try { - var pObj = shadowObj.PhysicsObj; - if (pObj.ID == attackerID || pObj.State.HasFlag(PhysicsState.Static)) continue; + foreach (var shadowObj in ShadowObjectList) + { + var pObj = shadowObj.PhysicsObj; + if (pObj.ID == attackerID || pObj.State.HasFlag(PhysicsState.Static)) continue; - var hitLocation = pObj.check_attack(attackerPos, attackerScale, attackCone, attackInfo.AttackRadius); - if (hitLocation != 0) - attackInfo.AddObject(pObj.ID, hitLocation); + var hitLocation = pObj.check_attack(attackerPos, attackerScale, attackCone, attackInfo.AttackRadius); + if (hitLocation != 0) + attackInfo.AddObject(pObj.ID, hitLocation); + } + } + finally + { + readerWriterLockSlim.ExitReadLock(); } } @@ -100,37 +149,53 @@ public virtual TransitionState FindEnvCollisions(Transition transition) public TransitionState FindObjCollisions(Transition transition) { - var path = transition.SpherePath; - - if (path.InsertType == InsertType.InitialPlacement) - return TransitionState.OK; - - var target = transition.ObjectInfo.Object.ProjectileTarget; - - // TODO: find out what is causing the exception when .ToList() is not used. - foreach (var shadowObj in ShadowObjectList.ToList()) + readerWriterLockSlim.EnterReadLock(); + try { - var obj = shadowObj.PhysicsObj; + var path = transition.SpherePath; - if (obj.Parent != null || obj.Equals(transition.ObjectInfo.Object)) - continue; + if (path.InsertType == InsertType.InitialPlacement) + return TransitionState.OK; - // clip through dynamic non-target objects - if (target != null && !obj.Equals(target) && !obj.State.HasFlag(PhysicsState.Static)) - continue; + var target = transition.ObjectInfo.Object.ProjectileTarget; - var state = obj.FindObjCollisions(transition); - if (state != TransitionState.OK) + // If we use the following: foreach (var shadowObj in ShadowObjectList), an InvalidOperationException is thrown. + // Very rarely though, as we iterate through it, the collection will change. + // To avoid the InvalidOperationException, we use a for loop. + // We do not yet know why the collection changes. + for (int i = ShadowObjectList.Count - 1; i >= 0; i--) { - // custom: fix hellfire spawn colliding with volcano heat, and possibly other placements - if (path.InsertType == InsertType.Placement && obj.State.HasFlag(PhysicsState.Ethereal)) + var shadowObj = ShadowObjectList[i]; + + var obj = shadowObj.PhysicsObj; + + if (obj.Parent != null || obj.Equals(transition.ObjectInfo.Object)) continue; - return state; + // clip through dynamic non-target objects + // now uses ObjectInfo.TargetId in FindObjCollisions / MissileIgnore + //if (target != null && !obj.Equals(target) && /*!obj.State.HasFlag(PhysicsState.Static)*/ + //obj.WeenieObj.IsCreature()) + //continue; + + var state = obj.FindObjCollisions(transition); + if (state != TransitionState.OK) + { + // custom: fix hellfire spawn colliding with volcano heat, and possibly other placements + if (path.InsertType == InsertType.Placement && (obj.State & PhysicsState.Ethereal) != 0) + continue; + + return state; + } + } - + + return TransitionState.OK; + } + finally + { + readerWriterLockSlim.ExitReadLock(); } - return TransitionState.OK; } public static ObjCell Get(uint cellID) @@ -146,12 +211,21 @@ public static ObjCell Get(uint cellID) public PhysicsObj GetObject(int id) { - foreach (var obj in ObjectList) + readerWriterLockSlim.EnterReadLock(); + try + { + foreach (var obj in ObjectList) + { + if (obj != null && obj.ID == id) + return obj; + } + + return null; + } + finally { - if (obj != null && obj.ID == id) - return obj; + readerWriterLockSlim.ExitReadLock(); } - return null; } public static ObjCell GetVisible(uint cellID) @@ -176,33 +250,88 @@ public void Init() public void RemoveObject(PhysicsObj obj) { - ObjectList.Remove(obj); - NumObjects--; - update_all_voyeur(obj, DetectionType.LeftDetection); + readerWriterLockSlim.EnterWriteLock(); + try + { + ObjectList.Remove(obj); + NumObjects--; + update_all_voyeur(obj, DetectionType.LeftDetection); + } + finally + { + readerWriterLockSlim.ExitWriteLock(); + } } public bool check_collisions(PhysicsObj obj) { - foreach (var shadowObj in ShadowObjectList) + readerWriterLockSlim.EnterReadLock(); + try + { + foreach (var shadowObj in ShadowObjectList) + { + var pObj = shadowObj.PhysicsObj; + if (pObj.Parent == null && !pObj.Equals(obj) && pObj.check_collision(obj)) + return true; + } + + return false; + } + finally { - var pObj = shadowObj.PhysicsObj; - if (pObj.Parent == null && !pObj.Equals(obj) && pObj.check_collision(obj)) - return true; + readerWriterLockSlim.ExitReadLock(); } - return false; } public TransitionState check_entry_restrictions(Transition transition) { - var objInfo = transition.ObjectInfo; + // custom - acclient checks for entry restrictions (housing barriers) + // for each tick in the transition, regardless if there is a cell change - if (objInfo.Object == null) return TransitionState.Collided; - if (objInfo.Object.WeenieObj == null) return TransitionState.OK; + // optimizing for server here, to only check unverified cell changes + + if (!transition.ObjectInfo.Object.IsPlayer || transition.CollisionInfo.VerifiedRestrictions || transition.SpherePath.BeginCell?.ID == ID) + { + return TransitionState.OK; + } - // check against world object + if (transition.ObjectInfo.Object == null) + return TransitionState.Collided; + + var weenieObj = transition.ObjectInfo.Object.WeenieObj; + + // TODO: handle DatObject + if (weenieObj != null) + { + //if (transition.ObjectInfo.State.HasFlag(ObjectInfoState.IsPlayer)) + if (transition.ObjectInfo.Object.IsPlayer) + { + if (RestrictionObj != 0 && !weenieObj.CanBypassMoveRestrictions()) + { + var restrictionObj = ServerObjectManager.GetObjectA(RestrictionObj); + + if (restrictionObj?.WeenieObj == null) + return TransitionState.Collided; + + if (!restrictionObj.WeenieObj.CanMoveInto(weenieObj)) + { + handle_move_restriction(transition); + return TransitionState.Collided; + } + else + transition.CollisionInfo.VerifiedRestrictions = true; + } + } + } return TransitionState.OK; } + public virtual bool handle_move_restriction(Transition transition) + { + // empty base? + return false; + } + public static void find_cell_list(Position position, int numSphere, List sphere, CellArray cellArray, ref ObjCell currCell, SpherePath path) { cellArray.NumCells = 0; @@ -269,6 +398,9 @@ public static void find_cell_list(Position position, int numSphere, List foreach (var stab in ((EnvCell)visibleCell).VisibleCells.Values) { + if (stab == null) + continue; + if (cell.ID == stab.ID) { found = true; @@ -334,8 +466,8 @@ public LandDefs.WaterType get_block_water_type() { if (CurLandblock != null) return CurLandblock.WaterType; - else - return LandDefs.WaterType.NotWater; + + return LandDefs.WaterType.NotWater; } public float get_water_depth(Vector3 point) @@ -348,8 +480,8 @@ public float get_water_depth(Vector3 point) if (CurLandblock != null) return CurLandblock.calc_water_depth(ID, point); - else - return 0.1f; + + return 0.1f; } public void hide_object(PhysicsObj obj) @@ -359,9 +491,17 @@ public void hide_object(PhysicsObj obj) public void init_objects() { - foreach (var obj in ObjectList) - if (!obj.State.HasFlag(PhysicsState.Static) && !obj.is_completely_visible()) - obj.recalc_cross_cells(); + readerWriterLockSlim.EnterReadLock(); + try + { + foreach (var obj in ObjectList) + if (!obj.State.HasFlag(PhysicsState.Static) && !obj.is_completely_visible()) + obj.recalc_cross_cells(); + } + finally + { + readerWriterLockSlim.ExitReadLock(); + } } public virtual bool point_in_cell(Vector3 point) @@ -369,25 +509,48 @@ public virtual bool point_in_cell(Vector3 point) return false; } + public void release_shadow_objs() + { + foreach (var shadowObj in ShadowObjectList) + shadowObj.PhysicsObj.ShadowObjects.Remove(ID); + } + public void release_objects() { - while (NumShadowObjects > 0) + readerWriterLockSlim.EnterWriteLock(); + try { - var shadowObj = ShadowObjectList[0]; - remove_shadow_object(shadowObj); + while (NumShadowObjects > 0) + { + var shadowObj = ShadowObjectList[0]; + remove_shadow_object(shadowObj); - shadowObj.PhysicsObj.remove_parts(this); - } - //if (NumObjects > 0 && ObjMaint != null) + shadowObj.PhysicsObj.remove_parts(this); + } + + //if (NumObjects > 0 && ObjMaint != null) //ObjMaint.ReleaseObjCell(this); + } + finally + { + readerWriterLockSlim.ExitWriteLock(); + } } public void remove_shadow_object(ShadowObj shadowObj) { - // multiple shadows? - ShadowObjectList.Remove(shadowObj); - shadowObj.Cell = null; - NumShadowObjects--; + readerWriterLockSlim.EnterWriteLock(); + try + { + // multiple shadows? + ShadowObjectList.Remove(shadowObj); + shadowObj.Cell = null; + NumShadowObjects--; + } + finally + { + readerWriterLockSlim.ExitWriteLock(); + } } public void unhide_object(PhysicsObj obj) @@ -415,5 +578,53 @@ public void update_all_voyeur(PhysicsObj obj, DetectionType type, bool checkDete } } } + + public bool IsVisible(ObjCell cell) + { + if (ID == cell.ID) return true; + + if ((ID & 0xFFFF) >= 0x100) + { + if (!(this is EnvCell envCell)) + { + Console.WriteLine($"{ID:X8}.IsVisible({cell.ID:X8}): {ID:X8} not detected as EnvCell"); + return false; + } + return envCell.IsVisibleIndoors(cell); + } + else if ((cell.ID & 0xFFFF) >= 0x100) + { + if (!(cell is EnvCell envCell)) + { + Console.WriteLine($"{ID:X8}.IsVisible({cell.ID:X8}): {cell.ID:X8} not detected as EnvCell"); + return false; + } + return envCell.IsVisibleIndoors(this); + } + else + { + // outdoors + return IsVisibleOutdoors(cell); + } + } + + public bool IsVisibleOutdoors(ObjCell cell) + { + var blockDist = PhysicsObj.GetBlockDist(ID, cell.ID); + return blockDist <= 1; + } + + public void AddObjectListTo(List target) + { + readerWriterLockSlim.EnterReadLock(); + try + { + target.AddRange(ObjectList); + } + finally + { + readerWriterLockSlim.ExitReadLock(); + } + } } } diff --git a/ACViewer/Physics/Common/ObjectDesc.cs b/ACViewer/Physics/Common/ObjectDesc.cs index cff36b7..0b91c74 100644 --- a/ACViewer/Physics/Common/ObjectDesc.cs +++ b/ACViewer/Physics/Common/ObjectDesc.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Numerics; using ACE.Server.Physics.Animation; using ACE.Server.Physics.Extensions; @@ -75,7 +75,7 @@ public static float ScaleObj(DatLoader.Entity.ObjectDesc obj, uint x, uint y, ui public static AFrame RotateObj(DatLoader.Entity.ObjectDesc obj, uint x, uint y, uint k, Vector3 loc) { var frame = new AFrame(obj.BaseLoc); - frame.Origin = new Vector3(loc.X, loc.Y, loc.Z); + frame.Origin = loc; if (obj.MaxRotation > 0.0f) { var degrees = (float)((1813693831 * y - (k + 63127) * (1360117743 * y * x + 1888038839) - 1109124029 * x) * 2.3283064e-10 * obj.MaxRotation); @@ -90,7 +90,7 @@ public static AFrame RotateObj(DatLoader.Entity.ObjectDesc obj, uint x, uint y, public static AFrame ObjAlign(DatLoader.Entity.ObjectDesc obj, Plane plane, Vector3 loc) { var frame = new AFrame(obj.BaseLoc); - frame.Origin = new Vector3(loc.X, loc.Y, loc.Z); + frame.Origin = loc; var negNormal = -plane.Normal; var degrees = negNormal.get_heading(); frame.set_heading(degrees); diff --git a/ACViewer/Physics/Common/ObjectMaint.cs b/ACViewer/Physics/Common/ObjectMaint.cs index 916a085..504cef8 100644 --- a/ACViewer/Physics/Common/ObjectMaint.cs +++ b/ACViewer/Physics/Common/ObjectMaint.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; +using System.Threading; + +using ACE.Entity.Enum; +using ACE.Server.Physics.Managers; +using ACE.Server.WorldObjects; namespace ACE.Server.Physics.Common { @@ -12,30 +16,20 @@ namespace ACE.Server.Physics.Common public class ObjectMaint { /// - /// Objects are removed from the client after this amount of time + /// The client automatically removes known objects if they remain outside visibility for this amount of time /// public static readonly float DestructionTime = 25.0f; - /// - /// The distance the server sends objects to players - /// for non-dungeon landblocks - /// - public static readonly float RadiusOutside = 192.0f; - - /// - /// The cell radius to send objects to players - /// for non-dungeon landblocks (8 cell radius by default) - /// - public static readonly float CellRadiusOutside = RadiusOutside / LandDefs.CellLength; + private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); /// /// The owner of this ObjectMaint instance /// This is who we are tracking object visibility for, ie. a Player /// - public PhysicsObj PhysicsObj; + public PhysicsObj PhysicsObj { get; } /// - /// This list of objects that are known to the client + /// This list of objects that are known to a player /// /// /// @@ -46,30 +40,44 @@ public class ObjectMaint /// it is removed from the VisibleObject list, and added to the destruction queue. /// if it remains outside PVS for DestructionTime, the client automatically culls the object, /// and it is removed from this list of known objects. - /// + /// + /// - This is only maintained for players. /// - public Dictionary ObjectTable; + private Dictionary KnownObjects { get; } = new Dictionary(); /// /// This list of objects that are currently within PVS / VisibleCell range + /// only maintained for players /// - public Dictionary VisibleObjectTable; + private Dictionary VisibleObjects { get; } = new Dictionary(); /// - /// A list of objects that currently know about this object + /// Objects that were previously visible to the client, + /// but have been outside the PVS for less than 25 seconds + /// only maintained for players /// - public Dictionary VoyeurTable; + private Dictionary DestructionQueue { get; } = new Dictionary(); /// - /// Objects that were previously visible to the client, - /// but have been outside the PVS for less than 25 seconds + /// A list of players that currently know about this object + /// This is maintained for all server-spawned WorldObjects, and is used for broadcasting + /// + private Dictionary KnownPlayers { get; } = new Dictionary(); + + /// + /// For monster and CombatPet FindNextTarget + /// - for monsters, contains players and combat pets + /// - for combat pets, contains monsters /// - public Dictionary DestructionQueue; + private Dictionary VisibleTargets { get; } = new Dictionary(); /// - /// Custom lookup table of PhysicsObjs for the server + /// Handles monsters targeting things they would not normally target + /// - For faction mobs, retaliate against same-faction players and combat pets + /// - For regular monsters, retaliate against faction mobs + /// - For regular monsters that do *not* have a FoeType, retaliate against monsters that are foes with this creature /// - public static Dictionary ServerObjects; + private Dictionary RetaliateTargets { get; } = new Dictionary(); // Client structures - // When client unloads a cell/landblock, but still knows about objects in those cells? @@ -78,383 +86,1036 @@ public class ObjectMaint //public Dictionary WeenieObjectTable; //public List NullWeenieObjectTable; - static ObjectMaint() - { - ServerObjects = new Dictionary(); - } - public ObjectMaint() { } public ObjectMaint(PhysicsObj obj) { PhysicsObj = obj; - ObjectTable = new Dictionary(); - VisibleObjectTable = new Dictionary(); - VoyeurTable = new Dictionary(); - DestructionQueue = new Dictionary(); + } + + + public PhysicsObj GetKnownObject(uint objectGuid) + { + rwLock.EnterReadLock(); + try + { + KnownObjects.TryGetValue(objectGuid, out var obj); + return obj; + } + finally + { + rwLock.ExitReadLock(); + } + } + + public int GetKnownObjectsCount() + { + rwLock.EnterReadLock(); + try + { + return KnownObjects.Count; + } + finally + { + rwLock.ExitReadLock(); + } + } + + public bool KnownObjectsContainsKey(uint guid) + { + rwLock.EnterReadLock(); + try + { + return KnownObjects.ContainsKey(guid); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public bool KnownObjectsContainsValue(PhysicsObj value) + { + rwLock.EnterReadLock(); + try + { + return KnownObjects.Values.Contains(value); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List> GetKnownObjectsWhere(Func, bool> predicate) + { + rwLock.EnterReadLock(); + try + { + return KnownObjects.Where(predicate).ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List GetKnownObjectsValues() + { + rwLock.EnterReadLock(); + try + { + return KnownObjects.Values.ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List GetKnownObjectsValuesWhere(Func predicate) + { + rwLock.EnterReadLock(); + try + { + return KnownObjects.Values.Where(predicate).ToList(); + } + finally + { + rwLock.ExitReadLock(); + } } /// /// Adds an object to the list of known objects + /// only maintained for players /// /// true if previously an unknown object - public bool AddObject(PhysicsObj obj) + public bool AddKnownObject(PhysicsObj obj) { - if (!ObjectTable.ContainsKey(obj.ID)) + rwLock.EnterWriteLock(); + try { - ObjectTable.Add(obj.ID, obj); + if (KnownObjects.ContainsKey(obj.ID)) + return false; + + KnownObjects.Add(obj.ID, obj); + + // maintain KnownPlayers for both parties + if (obj.IsPlayer) + AddKnownPlayer(obj); - // add to target object's voyeurs - obj.ObjMaint.AddVoyeur(PhysicsObj); + obj.ObjMaint.AddKnownPlayer(PhysicsObj); return true; } - return false; + finally + { + rwLock.ExitWriteLock(); + } } /// /// Adds a list of objects to the known objects list + /// only maintained for players /// - /// A list of newly visible objects + /// A list of currently visible objects /// The list of visible objects that were previously unknown - public List AddObjects(List objs) + private List AddKnownObjects(IEnumerable objs) { var newObjs = new List(); foreach (var obj in objs) - if (AddObject(obj)) newObjs.Add(obj); + { + if (AddKnownObject(obj)) + newObjs.Add(obj); + } return newObjs; } - /// - /// Adds an object to the list of visible objects - /// - public bool AddVisibleObject(PhysicsObj obj) + public bool RemoveKnownObject(PhysicsObj obj, bool inversePlayer = true) { - if (!VisibleObjectTable.ContainsKey(obj.ID)) + rwLock.EnterWriteLock(); + try { - VisibleObjectTable.Add(obj.ID, obj); - return true; + var result = KnownObjects.Remove(obj.ID); + + if (inversePlayer && PhysicsObj.IsPlayer) + obj.ObjMaint.RemoveKnownPlayer(PhysicsObj); + + return result; + } + finally + { + rwLock.ExitWriteLock(); } - return false; } - /// - /// Add a list of visible objects - maintains both known and visible objects - /// - public List AddVisibleObjects(List objs) + + public int GetVisibleObjectsCount() { - foreach (var obj in objs) - AddVisibleObject(obj); + rwLock.EnterReadLock(); + try + { + return VisibleObjects.Count; + } + finally + { + rwLock.ExitReadLock(); + } + } - RemoveObjectsToBeDestroyed(objs); + public bool VisibleObjectsContainsKey(uint key) + { + rwLock.EnterReadLock(); + try + { + return VisibleObjects.ContainsKey(key); + } + finally + { + rwLock.ExitReadLock(); + } + } - return AddObjects(objs); + public List> GetVisibleObjectsWhere(Func, bool> predicate) + { + rwLock.EnterReadLock(); + try + { + return VisibleObjects.Where(predicate).ToList(); + } + finally + { + rwLock.ExitReadLock(); + } } - /// - /// Adds an object to the destruction queue - /// Called when an object exits the PVS range - /// - public bool AddObjectToBeDestroyed(PhysicsObj obj) + public List GetVisibleObjectsValues() { - var time = PhysicsTimer.CurrentTime + DestructionTime; - if (!DestructionQueue.ContainsKey(obj)) + rwLock.EnterReadLock(); + try { - DestructionQueue.Add(obj, time); - return true; + return VisibleObjects.Values.ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List GetVisibleObjectsValuesWhere(Func predicate) + { + rwLock.EnterReadLock(); + try + { + return VisibleObjects.Values.Where(predicate).ToList(); + } + finally + { + rwLock.ExitReadLock(); } - return false; } + /*public List GetVisibleObjectsValuesOfTypeCreature() + { + rwLock.EnterReadLock(); + try + { + return VisibleObjects.Values.Select(v => v.WeenieObj.WorldObject).OfType().ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + }*/ + + private static readonly List emptyList = new List(); + /// - /// Adds a list of objects to the destruction queue + /// Returns a list of objects that are currently visible from a cell /// - public List AddObjectsToBeDestroyed(List objs) + public List GetVisibleObjects(ObjCell cell, VisibleObjectType type = VisibleObjectType.All) { - var queued = new List(); - foreach (var obj in objs) + rwLock.EnterReadLock(); + try { - if (AddObjectToBeDestroyed(obj)) - queued.Add(obj); + if (PhysicsObj.CurLandblock == null || cell == null) + return new List(); + + // use PVS / VisibleCells for EnvCells not seen outside + // (mostly dungeons, also some large indoor areas ie. caves) + if (cell is EnvCell envCell) + return GetVisibleObjects(envCell, type); + + // use current landblock + adjacents for outdoors, + // and envcells seen from outside (all buildings) + //var visibleObjs = PhysicsObj.CurLandblock.GetServerObjects(true); + var visibleObjs = emptyList; - VisibleObjectTable.Remove(obj.ID); + return ApplyFilter(visibleObjs, type).Where(i => i.ID != PhysicsObj.ID && (!(i.CurCell is EnvCell indoors) || indoors.SeenOutside)).ToList(); + } + finally + { + rwLock.ExitReadLock(); } - return queued; } /// - /// Returns a list of objects that have been in the destruction queue - /// for less than 25 seconds + /// Returns a list of objects that are currently visible from a dungeon cell /// - public List GetCulledObjects(List visibleObjects) + private List GetVisibleObjects(EnvCell cell, VisibleObjectType type) { - var culledObjects = DestructionQueue.Where(kvp => kvp.Value > PhysicsTimer.CurrentTime).ToDictionary(kvp => kvp.Key, kvp => kvp.Value).Keys.ToList(); - return culledObjects; + var visibleObjs = new List(); + + // add objects from current cell + cell.AddObjectListTo(visibleObjs); + + // add objects from visible cells + foreach (var envCell in cell.VisibleCells.Values) + { + if (envCell != null) + envCell.AddObjectListTo(visibleObjs); + } + + // if SeenOutside, add objects from outdoor landblock + if (cell.SeenOutside) + { + //var outsideObjs = PhysicsObj.CurLandblock.GetServerObjects(true).Where(i => !(i.CurCell is EnvCell indoors) || indoors.SeenOutside); + var outsideObjs = emptyList; + + visibleObjs.AddRange(outsideObjs); + } + + return ApplyFilter(visibleObjs, type).Where(i => !i.DatObject && i.ID != PhysicsObj.ID).Distinct().ToList(); } - /// - /// Returns a list of objects that have been in the destruction queue - /// for more than 25 seconds - /// - public List GetDestroyedObjects() + public enum VisibleObjectType { - var destroyedObjects = DestructionQueue.Where(kvp => kvp.Value <= PhysicsTimer.CurrentTime).ToDictionary(kvp => kvp.Key, kvp => kvp.Value).Keys.ToList(); - return destroyedObjects; + All, + Players, + AttackTargets + }; + + private IEnumerable ApplyFilter(List objs, VisibleObjectType type) + { + IEnumerable results = objs; + + if (type == VisibleObjectType.Players) + { + results = objs.Where(i => i.IsPlayer); + } + else if (type == VisibleObjectType.AttackTargets) + { + if (PhysicsObj.WeenieObj.IsCombatPet) + results = objs.Where(i => i.WeenieObj.IsMonster && i.WeenieObj.PlayerKillerStatus != PlayerKillerStatus.PK); // combat pets cannot attack pk-only creatures (ie. faction banners) + else if (PhysicsObj.WeenieObj.IsFactionMob) + results = objs.Where(i => i.IsPlayer || i.WeenieObj.IsCombatPet || i.WeenieObj.IsMonster && !i.WeenieObj.SameFaction(PhysicsObj)); + else + { + // adding faction mobs here, even though they are retaliate-only, for inverse visible targets + results = objs.Where(i => i.IsPlayer || i.WeenieObj.IsCombatPet && PhysicsObj.WeenieObj.PlayerKillerStatus != PlayerKillerStatus.PK || i.WeenieObj.IsFactionMob || i.WeenieObj.PotentialFoe(PhysicsObj)); + } + } + return results; } + public static bool InitialClamp = true; + + public static float InitialClamp_Dist = 112.5f; + public static float InitialClamp_DistSq = InitialClamp_Dist * InitialClamp_Dist; + /// - /// Returns a PhysicsObj for an object ID + /// Adds an object to the list of visible objects + /// only maintained for players /// - public static PhysicsObj GetObjectA(uint objectID) + /// TRUE if object was previously not visible, and added to the visible list + public bool AddVisibleObject(PhysicsObj obj) { - ServerObjects.TryGetValue(objectID, out var obj); - return obj; + rwLock.EnterWriteLock(); + try + { + if (VisibleObjects.ContainsKey(obj.ID)) + return false; + + if (InitialClamp && !KnownObjects.ContainsKey(obj.ID)) + { + var distSq = PhysicsObj.Position.Distance2DSquared(obj.Position); + + if (distSq > InitialClamp_DistSq) + return false; + } + + //Console.WriteLine($"{PhysicsObj.Name}.AddVisibleObject({obj.Name})"); + VisibleObjects.Add(obj.ID, obj); + + if (obj.WeenieObj.IsMonster) + obj.ObjMaint.AddVisibleTarget(PhysicsObj, false); + + return true; + } + finally + { + rwLock.ExitWriteLock(); + } } /// - /// Returns a list of outdoor cells within visible range of player + /// Add a list of visible objects - maintains both known and visible objects + /// only maintained for players /// - public List GetOutdoorCells(ObjCell cell) + public List AddVisibleObjects(ICollection objs) { - // get cell x/y global offset - var lcoord = LandDefs.get_outside_lcoord(cell.ID, PhysicsObj.Position.Frame.Origin.X, PhysicsObj.Position.Frame.Origin.Y).Value; + rwLock.EnterWriteLock(); + try + { + var visibleAdded = new List(); - // includes the origin cell - var blockLength = (int)CellRadiusOutside * 2 + 1; - var cells = new List(/*blockLength * blockLength*/); + foreach (var obj in objs) + { + if (AddVisibleObject(obj)) + visibleAdded.Add(obj); + } - var start = new Vector2(lcoord.X - CellRadiusOutside, lcoord.Y - CellRadiusOutside); - var end = new Vector2(lcoord.X + CellRadiusOutside, lcoord.Y + CellRadiusOutside); + RemoveObjectsToBeDestroyed(objs); - for (var cellX = start.X; cellX <= end.X; cellX++) + return AddKnownObjects(visibleAdded); + } + finally { - for (var cellY = start.Y; cellY <= end.Y; cellY++) - { - var blockCellID = LandDefs.lcoord_to_gid(cellX, cellY); - var _cell = LScape.get_landcell((uint)blockCellID); - if (_cell == null) - continue; - cells.Add(_cell); - - // does this outdoor cell contain a building? - // if so, add all of its cells - var landCell = _cell as LandCell; - if (landCell.has_building()) - { - //Console.WriteLine($"Found building in cell {landCell.ID:X8}"); - var buildingCells = landCell.Building.get_building_cells(); - //Console.WriteLine("# cells: " + buildingCells.Count); - cells.AddRange(buildingCells); - } - } + rwLock.ExitWriteLock(); } - return cells; } /// - /// Returns a list of objects that are currently visible from a cell - /// in an outdoor landblock + /// Removes an object from the visible objects list + /// only run for players + /// and also removes the player from the object's visible targets list /// - public List GetVisibleObjects(ObjCell cell) + public bool RemoveVisibleObject(PhysicsObj obj, bool inverseTarget = true) { - if (PhysicsObj.CurLandblock == null || cell == null) return new List(); + rwLock.EnterWriteLock(); + try + { + var removed = VisibleObjects.Remove(obj.ID); - // use PVS / VisibleCells for EnvCells not seen outside - // (mostly dungeons, also some large indoor areas ie. caves) - var envCell = cell as EnvCell; - if (envCell != null && !envCell.SeenOutside) - return GetVisibleObjects(envCell); + if (inverseTarget) + obj.ObjMaint.RemoveVisibleTarget(PhysicsObj); + + return removed; + } + finally + { + rwLock.ExitWriteLock(); + } + } - // use current landblock + adjacents for outdoors, - // and envcells seen from outside (all buildings) - var visibleObjs = new List(PhysicsObj.CurLandblock.ServerObjects); - var adjacents = PhysicsObj.CurLandblock.get_adjacents(); + public int GetDestructionQueueCount() + { + rwLock.EnterReadLock(); + try + { + return DestructionQueue.Count; + } + finally + { + rwLock.ExitReadLock(); + } + } - foreach (var adjacent in adjacents) - visibleObjs.AddRange(adjacent.ServerObjects); + public Dictionary GetDestructionQueueCopy() + { + rwLock.EnterReadLock(); + try + { + var result = new Dictionary(); - return visibleObjs.Where(i => i.ID != PhysicsObj.ID).ToList(); + foreach (var kvp in DestructionQueue) + result[kvp.Key] = kvp.Value; - /*var cells = GetOutdoorCells(cell); + return result; + } + finally + { + rwLock.ExitReadLock(); + } + } - var visibleObjs = new List(); + /// + /// Adds an object to the destruction queue + /// Called when an object exits the PVS range + /// only maintained for players + /// + public bool AddObjectToBeDestroyed(PhysicsObj obj) + { + rwLock.EnterWriteLock(); + try + { + RemoveVisibleObject(obj); - foreach (var _cell in cells) - visibleObjs.AddRange(_cell.ObjectList); + if (DestructionQueue.ContainsKey(obj)) + return false; - return visibleObjs.Where(obj => !obj.DatObject).Distinct().ToList();*/ + DestructionQueue.Add(obj, PhysicsTimer.CurrentTime + DestructionTime); + return true; + } + finally + { + rwLock.ExitWriteLock(); + } } /// - /// Returns a list of objects that are currently visible from a dungeon cell + /// Adds a list of objects to the destruction queue + /// only maintained for players /// - public List GetVisibleObjects(EnvCell cell) + public List AddObjectsToBeDestroyed(List objs) { - var visibleObjs = new List(); - - // add objects from current cell - visibleObjs.AddRange(cell.ObjectList); - - // add objects from visible cells - foreach (var envCell in cell.VisibleCells.Values) + rwLock.EnterWriteLock(); + try { - if (envCell == null) continue; + var queued = new List(); + foreach (var obj in objs) + { + if (AddObjectToBeDestroyed(obj)) + queued.Add(obj); + } - visibleObjs.AddRange(envCell.ObjectList); + return queued; + } + finally + { + rwLock.ExitWriteLock(); } - - return visibleObjs.Where(i => !i.DatObject && i.ID != PhysicsObj.ID).Distinct().ToList(); } /// - /// Removes an object from the destruction queue - /// if it has been invisible for less than 25s + /// Removes an object from the destruction queue if it has been invisible for less than 25s + /// this is only used for players /// public bool RemoveObjectToBeDestroyed(PhysicsObj obj) { - double time = -1; - DestructionQueue.TryGetValue(obj, out time); - if (time != -1 && time > PhysicsTimer.CurrentTime) + rwLock.EnterWriteLock(); + try { - DestructionQueue.Remove(obj); - return true; + double time = -1; + DestructionQueue.TryGetValue(obj, out time); + if (time != -1 && time > PhysicsTimer.CurrentTime) + { + DestructionQueue.Remove(obj); + return true; + } + + return false; + } + finally + { + rwLock.ExitWriteLock(); } - return false; } /// - /// Removes objects from the destruction queue - /// when they re-enter visibility within 25s + /// Removes objects from the destruction queue when they re-enter visibility within 25s + /// this is only used for players /// - public void RemoveObjectsToBeDestroyed(List objs) + private void RemoveObjectsToBeDestroyed(IEnumerable objs) { foreach (var obj in objs) RemoveObjectToBeDestroyed(obj); } /// - /// Removes any objects that have been in the destruction queue - /// for more than 25s + /// Removes any objects that have been in the destruction queue for more than 25s + /// this is only used for players /// - /// public List DestroyObjects() { - // find the list of objects that have been in the destruction queue > 25s - var expiredObjs = DestructionQueue.Where(kvp => kvp.Value <= PhysicsTimer.CurrentTime).ToDictionary(kvp => kvp.Key, kvp => kvp.Value).Keys.ToList(); + rwLock.EnterWriteLock(); + try + { + // find the list of objects that have been in the destruction queue > 25s + var expiredObjs = DestructionQueue.Where(kvp => kvp.Value <= PhysicsTimer.CurrentTime) + .Select(kvp => kvp.Key).ToList(); - // remove expired objects from all lists - foreach (var expiredObj in expiredObjs) - RemoveObject(expiredObj); + // remove expired objects from all lists + foreach (var expiredObj in expiredObjs) + RemoveObject(expiredObj); - return expiredObjs; + return expiredObjs; + } + finally + { + rwLock.ExitWriteLock(); + } } /// - /// Removes an object from all of the tables + /// Removes an object after it has expired from the destruction queue, or it has been destroyed /// - public void RemoveObject(PhysicsObj obj) + public void RemoveObject(PhysicsObj obj, bool inverse = true) { - if (obj == null) return; + rwLock.EnterWriteLock(); + try + { + if (obj == null) return; + + RemoveKnownObject(obj, inverse); + RemoveVisibleObject(obj, inverse); + DestructionQueue.Remove(obj); - ObjectTable.Remove(obj.ID); - VisibleObjectTable.Remove(obj.ID); - DestructionQueue.Remove(obj); - VoyeurTable.Remove(obj.ID); + if (obj.IsPlayer) + RemoveKnownPlayer(obj); - obj.ObjMaint.RemoveVoyeur(PhysicsObj); + RemoveVisibleTarget(obj); + RemoveRetaliateTarget(obj); + } + finally + { + rwLock.ExitWriteLock(); + } } + + public int GetKnownPlayersCount() + { + rwLock.EnterReadLock(); + try + { + return KnownPlayers.Count; + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List> GetKnownPlayersWhere(Func, bool> predicate) + { + rwLock.EnterReadLock(); + try + { + return KnownPlayers.Where(predicate).ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List GetKnownPlayersValues() + { + rwLock.EnterReadLock(); + try + { + return KnownPlayers.Values.ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + /*public List GetKnownPlayersValuesAsPlayer() + { + rwLock.EnterReadLock(); + try + { + return KnownPlayers.Values.Select(v => v.WeenieObj.WorldObject).OfType().ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List GetKnownObjectsValuesAsCreature() + { + rwLock.EnterReadLock(); + try + { + return KnownObjects.Values.Select(v => v.WeenieObj.WorldObject).OfType().ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + }*/ + /// - /// Clears all of the ObjMaint tables for an object + /// Adds a player who currently knows about this object + /// - this is maintained for all server-spawned objects + /// + /// when an object broadcasts, it sends network messages + /// to all players who currently know about this object /// - public void RemoveAllObjects() + /// true if previously an unknown object + private bool AddKnownPlayer(PhysicsObj obj) { - foreach (var obj in ObjectTable.Values) - obj.ObjMaint.RemoveVoyeur(PhysicsObj); + // only tracking players who know about this object + if (!obj.IsPlayer) + { + Console.WriteLine($"{PhysicsObj.Name}.ObjectMaint.AddKnownPlayer({obj.Name}): tried to add a non-player"); + return false; + } + if (PhysicsObj.DatObject) + { + Console.WriteLine($"{PhysicsObj.Name}.ObjectMaint.AddKnownPlayer({obj.Name}): tried to add player for dat object"); + return false; + } - ObjectTable.Clear(); - VisibleObjectTable.Clear(); - DestructionQueue.Clear(); + //Console.WriteLine($"{PhysicsObj.Name} ({PhysicsObj.ID:X8}).ObjectMaint.AddKnownPlayer({obj.Name})"); + + // TryAdd for existing keys still modifies collection? + if (KnownPlayers.ContainsKey(obj.ID)) + return false; + + KnownPlayers.Add(obj.ID, obj); + return true; } /// - /// Adds a PhysicsObj to the static list of server-wide objects + /// Adds a list of players known to this object /// - public static void AddServerObject(PhysicsObj obj) + public List AddKnownPlayers(IEnumerable objs) { - if (obj == null) return; + rwLock.EnterWriteLock(); + try + { + var newObjs = new List(); - if (ServerObjects.ContainsKey(obj.ID)) - ServerObjects[obj.ID] = obj; - else - ServerObjects.Add(obj.ID, obj); + foreach (var obj in objs) + { + if (AddKnownPlayer(obj)) + newObjs.Add(obj); + } + + return newObjs; + } + finally + { + rwLock.ExitWriteLock(); + } } /// - /// Removes a PhysicsObj from the static list of server-wide objects + /// Removes a known player for this object /// - public static void RemoveServerObject(PhysicsObj obj) + public bool RemoveKnownPlayer(PhysicsObj obj) { - if (obj == null) return; + //Console.WriteLine($"{PhysicsObj.Name} ({PhysicsObj.ID:X8}).ObjectMaint.RemoveKnownPlayer({obj.Name})"); - if (ServerObjects.ContainsKey(obj.ID)) - ServerObjects.Remove(obj.ID); + rwLock.EnterReadLock(); + try + { + return KnownPlayers.Remove(obj.ID); + } + finally + { + rwLock.ExitReadLock(); + } + } + + + public int GetVisibleTargetsCount() + { + rwLock.EnterReadLock(); + try + { + return VisibleTargets.Count; + } + finally + { + rwLock.ExitReadLock(); + } } + public int GetRetaliateTargetsCount() + { + rwLock.EnterReadLock(); + try + { + return RetaliateTargets.Count; + } + finally + { + rwLock.ExitReadLock(); + } + } + + public bool VisibleTargetsContainsKey(uint key) + { + rwLock.EnterReadLock(); + try + { + return VisibleTargets.ContainsKey(key); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public bool RetaliateTargetsContainsKey(uint key) + { + rwLock.EnterReadLock(); + try + { + return RetaliateTargets.ContainsKey(key); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List GetVisibleTargetsValues() + { + rwLock.EnterReadLock(); + try + { + return VisibleTargets.Values.ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public List GetRetaliateTargetsValues() + { + rwLock.EnterReadLock(); + try + { + return RetaliateTargets.Values.ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + /*public List GetVisibleTargetsValuesOfTypeCreature() + { + rwLock.EnterReadLock(); + try + { + return VisibleTargets.Values.Select(v => v.WeenieObj.WorldObject).OfType().ToList(); + } + finally + { + rwLock.ExitReadLock(); + } + }*/ + /// - /// Adds an object to the list of voyeurs + /// For monster and CombatPet FindNextTarget + /// - for monsters, contains players and combat pets + /// - for combat pets, contains monsters /// - /// true if previously an unknown object - public bool AddVoyeur(PhysicsObj obj) + private bool AddVisibleTarget(PhysicsObj obj, bool clamp = true, bool foeType = false) { - // only tracking players who know about each object - if (!obj.IsPlayer) + if (PhysicsObj.WeenieObj.IsCombatPet) + { + // only tracking monsters + if (!obj.WeenieObj.IsMonster) + { + Console.WriteLine($"{PhysicsObj.Name}.ObjectMaint.AddVisibleTarget({obj.Name}): tried to add a non-monster"); + return false; + } + } + else if (PhysicsObj.WeenieObj.IsFactionMob) + { + // only tracking players, combat pets, and monsters of differing faction + if (!obj.IsPlayer && !obj.WeenieObj.IsCombatPet && (!obj.WeenieObj.IsMonster || PhysicsObj.WeenieObj.SameFaction(obj))) + { + Console.WriteLine($"{PhysicsObj.Name}.ObjectMaint.AddVisibleTarget({obj.Name}): tried to add a non-player / non-combat pet / non-opposing faction mob"); + return false; + } + } + else + { + // handle special case: + // we want to select faction mobs for monsters inverse targets, + // but not add to the original monster + if (obj.WeenieObj.IsFactionMob) + { + obj.ObjMaint.AddVisibleTarget(PhysicsObj); + return false; + } + + // handle special case: + // if obj has a FoeType of this creature, and this creature doesn't have a FoeType for obj, + // we only want to perform the inverse + /*if (obj.WeenieObj.FoeType != null && obj.WeenieObj.FoeType == PhysicsObj.WeenieObj.WorldObject?.CreatureType && + (PhysicsObj.WeenieObj.FoeType == null || obj.WeenieObj.WorldObject != null && PhysicsObj.WeenieObj.FoeType != obj.WeenieObj.WorldObject.CreatureType)) + { + obj.ObjMaint.AddVisibleTarget(PhysicsObj); + return false; + }*/ + + // only tracking players and combat pets + if (!obj.IsPlayer && !obj.WeenieObj.IsCombatPet && PhysicsObj.WeenieObj.FoeType == null) + { + Console.WriteLine($"{PhysicsObj.Name}.ObjectMaint.AddVisibleTarget({obj.Name}): tried to add a non-player / non-combat pet"); + return false; + } + } + if (PhysicsObj.DatObject) + { + Console.WriteLine($"{PhysicsObj.Name}.ObjectMaint.AddVisibleTarget({obj.Name}): tried to add player for dat object"); return false; + } - if (!VoyeurTable.ContainsKey(obj.ID)) + if (clamp && InitialClamp && obj.IsPlayer && !obj.ObjMaint.KnownObjects.ContainsKey(obj.ID)) { - VoyeurTable.Add(obj.ID, obj); - return true; + var distSq = PhysicsObj.Position.Distance2DSquared(obj.Position); + + if (distSq > InitialClamp_DistSq) + return false; } - return false; + + // TryAdd for existing keys still modifies collection? + if (VisibleTargets.ContainsKey(obj.ID)) + return false; + + //Console.WriteLine($"{PhysicsObj.Name} ({PhysicsObj.ID:X8}).ObjectMaint.AddVisibleTarget({obj.Name})"); + + VisibleTargets.Add(obj.ID, obj); + + // maintain inverse for monsters / combat pets + if (!obj.IsPlayer) + obj.ObjMaint.AddVisibleTarget(PhysicsObj); + + return true; } /// - /// Adds a list of objects watching this object + /// For monster and CombatPet FindNextTarget + /// - for monsters, contains players and combat pets + /// - for combat pets, contains monsters /// - public List AddVoyeurs(List objs) + public List AddVisibleTargets(IEnumerable objs) { - var newVoyeurs = new List(); + rwLock.EnterWriteLock(); + try + { + var visibleAdded = new List(); - foreach (var obj in objs) - if (AddVoyeur(obj)) newVoyeurs.Add(obj); + foreach (var obj in objs) + { + if (AddVisibleTarget(obj)) + visibleAdded.Add(obj); + } + + return AddKnownPlayers(visibleAdded.Where(o => o.IsPlayer)); + } + finally + { + rwLock.ExitWriteLock(); + } + } - return newVoyeurs; + private bool RemoveVisibleTarget(PhysicsObj obj) + { + //Console.WriteLine($"{PhysicsObj.Name} ({PhysicsObj.ID:X8}).ObjectMaint.RemoveVisibleTarget({obj.Name})"); + return VisibleTargets.Remove(obj.ID); + } + + public List GetVisibleObjectsDist(ObjCell cell, VisibleObjectType type) + { + rwLock.EnterReadLock(); + try + { + var visibleObjs = GetVisibleObjects(cell, type); + + var dist = new List(); + foreach (var obj in visibleObjs) + { + var distSq = PhysicsObj.Position.Distance2DSquared(obj.Position); + + if (distSq <= InitialClamp_DistSq) + dist.Add(obj); + } + + return dist; + } + finally + { + rwLock.ExitReadLock(); + } } /// - /// Removes an object from the voyeurs table + /// Adds a retaliate target for a monster that it would not normally attack /// - public bool RemoveVoyeur(PhysicsObj obj) + public void AddRetaliateTarget(PhysicsObj obj) { - return VoyeurTable.Remove(obj.ID); + rwLock.EnterWriteLock(); + try + { + if (RetaliateTargets.ContainsKey(obj.ID)) + { + //Console.WriteLine($"{PhysicsObj.Name}.AddRetaliateTarget({obj.Name}) - retaliate target already exists"); + return; + } + //Console.WriteLine($"{PhysicsObj.Name}.AddRetaliateTarget({obj.Name})"); + RetaliateTargets.Add(obj.ID, obj); + + // we're going to add retaliate targets to the list of visible targets as well, + // so that we don't have to traverse both VisibleTargets and RetaliateTargets + // in all of the logic based on VisibleTargets + if (VisibleTargets.ContainsKey(obj.ID)) + { + //Console.WriteLine($"{PhysicsObj.Name}.AddRetaliateTarget({obj.Name}) - visible target already exists"); + return; + } + VisibleTargets.Add(obj.ID, obj); + } + finally + { + rwLock.ExitWriteLock(); + } } /// - /// Called when a new PhysicsObj is first instantiated - /// Gets the list of visible players to this PhysicsObj, - /// and adds them to the voyeurs list + /// Called when a monster goes back to sleep /// - public void get_voyeurs() + public void ClearRetaliateTargets() { - if (PhysicsObj.DatObject) return; + rwLock.EnterWriteLock(); + try + { + // remove retaliate targets from visible targets + foreach (var retaliateTarget in RetaliateTargets) + VisibleTargets.Remove(retaliateTarget.Key); + + RetaliateTargets.Clear(); + } + finally + { + rwLock.ExitWriteLock(); + } + } - var visiblePlayers = GetVisibleObjects(PhysicsObj.CurCell).Where(o => o.IsPlayer).ToList(); - AddVoyeurs(visiblePlayers); + private bool RemoveRetaliateTarget(PhysicsObj obj) + { + return RetaliateTargets.Remove(obj.ID); + } + + /// + /// Clears all of the ObjMaint tables for an object + /// + private void RemoveAllObjects() + { + KnownObjects.Clear(); + VisibleObjects.Clear(); + DestructionQueue.Clear(); + KnownPlayers.Clear(); + VisibleTargets.Clear(); + RetaliateTargets.Clear(); } /// @@ -463,10 +1124,31 @@ public void get_voyeurs() /// public void DestroyObject() { - foreach (var obj in ObjectTable.Values) - obj.ObjMaint.RemoveObject(PhysicsObj); + rwLock.EnterWriteLock(); + try + { + foreach (var obj in KnownObjects.Values) + obj.ObjMaint.RemoveObject(PhysicsObj); + + // we are maintaining the inverses here, + // so passing false to iterate with modifying these collections + foreach (var obj in KnownPlayers.Values) + obj.ObjMaint.RemoveObject(PhysicsObj, false); - RemoveServerObject(PhysicsObj); + foreach (var obj in VisibleTargets.Values) + obj.ObjMaint.RemoveObject(PhysicsObj, false); + + foreach (var obj in RetaliateTargets.Values) + obj.ObjMaint.RemoveObject(PhysicsObj, false); + + RemoveAllObjects(); + + ServerObjectManager.RemoveServerObject(PhysicsObj); + } + finally + { + rwLock.ExitWriteLock(); + } } } } diff --git a/ACViewer/Physics/Common/Position.cs b/ACViewer/Physics/Common/Position.cs index d707b95..a6b7fb3 100644 --- a/ACViewer/Physics/Common/Position.cs +++ b/ACViewer/Physics/Common/Position.cs @@ -1,5 +1,8 @@ using System; using System.Numerics; + +using ACE.Entity.Enum; + using ACE.Server.Physics.Animation; using ACE.Server.Physics.Extensions; using ACE.Server.Physics.Util; @@ -11,6 +14,12 @@ public class Position: IEquatable public uint ObjCellID; public AFrame Frame; + public uint Landblock => ObjCellID >> 16; + + public uint LandblockX => ObjCellID >> 24; + + public uint LandblockY => (ObjCellID >> 16) & 0xFF; + public Position() { Init(); @@ -111,25 +120,46 @@ public static double CylinderDistance(float radius, float height, Position pos, return reach; } + // custom, based on above + public static double CylinderDistanceSq(float radius, float height, Position pos, float otherRadius, float otherHeight, Position otherPos) + { + var offset = pos.GetOffset(otherPos); + var reach = offset.Length() - (radius + otherRadius); + + var diffZ = pos.Frame.Origin.Z <= otherPos.Frame.Origin.Z ? otherPos.Frame.Origin.Z - (pos.Frame.Origin.Z + height) : + pos.Frame.Origin.Z - (otherPos.Frame.Origin.Z + otherHeight); + + if (diffZ > 0 && reach > 0) + return diffZ * diffZ + reach * reach; + else if (diffZ < 0 && reach < 0) + return -(diffZ * diffZ + reach * reach); + else + return reach * reach; + } + public static float CylinderDistanceNoZ(float radius, Position pos, float otherRadius, Position otherPos) { var offset = pos.GetOffset(otherPos); return offset.Length() - (radius + otherRadius); } - public int DetermineQuadrant(float height, Position position) + public static readonly float ThresholdMed = 1.0f / 3.0f; + public static readonly float ThresholdHigh = 2.0f / 3.0f; + + public Quadrant DetermineQuadrant(float height, Position position) { - var hitLocation = LocalToLocal(position, Vector3.Zero); + var hitpoint = LocalToLocal(position, Vector3.Zero); + + var quadrant = hitpoint.X < 0.0f ? Quadrant.Left : Quadrant.Right; - var quadrant = hitLocation.X < 0.0f ? 0x8 : 0x10; - quadrant |= hitLocation.Y >= 0.0f ? 0x20 : 0x40; + quadrant |= hitpoint.Y >= 0.0f ? Quadrant.Front : Quadrant.Back; - if (height * 0.333333333f > hitLocation.Z) - quadrant |= 4; // low - else if (height * 0.66666667f > hitLocation.Z) - quadrant |= 2; // medium + if (hitpoint.Z < height * ThresholdMed) + quadrant |= Quadrant.Low; + else if (hitpoint.Z < height * ThresholdHigh) + quadrant |= Quadrant.Medium; else - quadrant |= 1; // high + quadrant |= Quadrant.High; return quadrant; } @@ -235,6 +265,25 @@ public uint GetIndoorCell(uint blockCellID) return newCell.Value; } + /// + /// Returns the squared 2D distance between 2 positions + /// + public float Distance2DSquared(Position p) + { + if (Landblock == p.Landblock) + { + var dx = Frame.Origin.X - p.Frame.Origin.X; + var dy = Frame.Origin.Y - p.Frame.Origin.Y; + return dx * dx + dy * dy; + } + else + { + var dx = ((int)LandblockX - p.LandblockX) * 192 + Frame.Origin.X - p.Frame.Origin.X; + var dy = ((int)LandblockY - p.LandblockY) * 192 + Frame.Origin.Y - p.Frame.Origin.Y; + return dx * dx + dy * dy; + } + } + public bool Equals(Position pos) { return ObjCellID == pos.ObjCellID && Frame.Equals(pos.Frame); @@ -242,7 +291,12 @@ public bool Equals(Position pos) public override string ToString() { - return $"{ObjCellID:X8} [{Frame.Origin}] {Frame.Orientation}"; + return $"0x{ObjCellID:X8} {Frame}"; + } + + public string ShortLoc() + { + return $"0x{ObjCellID:X8} [{Frame.Origin.X} {Frame.Origin.Y} {Frame.Origin.Z}]"; } } } diff --git a/ACViewer/Physics/Common/RenderSurface.cs b/ACViewer/Physics/Common/RenderSurface.cs index 4293d63..8e51970 100644 --- a/ACViewer/Physics/Common/RenderSurface.cs +++ b/ACViewer/Physics/Common/RenderSurface.cs @@ -1,10 +1,11 @@ +using ACE.DatLoader.FileTypes; using ACE.Entity.Enum; namespace ACE.Server.Physics.Common { public class RenderSurface { - public DatLoader.FileTypes.Texture _renderSurface; + public Texture _texture; public int Width; public int Height; @@ -17,16 +18,16 @@ public RenderSurface() { } - public RenderSurface(DatLoader.FileTypes.Texture renderSurface) + public RenderSurface(Texture texture) { - _renderSurface = renderSurface; + _texture = texture; - Width = renderSurface.Width; - Height = renderSurface.Height; - Format = renderSurface.Format; - Length = renderSurface.Length; - Data = renderSurface.SourceData; - DefaultPaletteID = renderSurface.DefaultPaletteId; + Width = texture.Width; + Height = texture.Height; + Format = texture.Format; + Length = texture.Length; + Data = texture.SourceData; + DefaultPaletteID = texture.DefaultPaletteId; } // RenderSurfaceD3D.Create() diff --git a/ACViewer/Physics/Common/SetPosition.cs b/ACViewer/Physics/Common/SetPosition.cs index c3f250c..b222e41 100644 --- a/ACViewer/Physics/Common/SetPosition.cs +++ b/ACViewer/Physics/Common/SetPosition.cs @@ -21,8 +21,8 @@ public enum SetPositionFlags Restore = 0x4, Slide = 0x10, DontCreateCells = 0x20, - Scatter = 0x100, - RandomScatter = 0x200, + Scatter = 0x100, // try to spawn in original position, if that fails, try to spawn in scatter pos + RandomScatter = 0x200, // try to spawn in scatter pos Line = 0x400, SendPositionEvent = 0x1000, }; @@ -36,8 +36,19 @@ public class SetPosition public float RadY; public int NumTries; + public static int Default_NumTries = 20; + public SetPosition() { } + public SetPosition(Position pos, SetPositionFlags flags, float radius) + { + Pos = pos; + Flags = flags; + RadX = radius; + RadY = radius; + NumTries = Default_NumTries; + } + public SetPosition(Position pos, SetPositionFlags flags) { Pos = pos; diff --git a/ACViewer/Physics/Common/Vector.cs b/ACViewer/Physics/Common/Vector.cs index 0f0f621..577f594 100644 --- a/ACViewer/Physics/Common/Vector.cs +++ b/ACViewer/Physics/Common/Vector.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System; +using System.Numerics; namespace ACE.Server.Physics.Common { @@ -13,5 +14,12 @@ public static bool NormalizeCheckSmall(ref Vector3 v) v *= 1.0f / dist; return false; } + + public static bool IsZero(Vector3 v) + { + return Math.Abs(v.X) < PhysicsGlobals.EPSILON && + Math.Abs(v.Y) < PhysicsGlobals.EPSILON && + Math.Abs(v.Z) < PhysicsGlobals.EPSILON; + } } } diff --git a/ACViewer/Physics/Common/WeenieObject.cs b/ACViewer/Physics/Common/WeenieObject.cs index 69089c7..63485f0 100644 --- a/ACViewer/Physics/Common/WeenieObject.cs +++ b/ACViewer/Physics/Common/WeenieObject.cs @@ -1,8 +1,8 @@ using System; + using ACE.Entity; using ACE.Entity.Enum; using ACE.Server.Physics.Animation; -using ACE.Server.Physics.Combat; using ACE.Server.Physics.Collision; using ACE.Server.WorldObjects; @@ -14,11 +14,50 @@ public class WeenieObject public double UpdateTime; public WorldObject WorldObject; + public bool IsMonster { get; set; } + + public bool IsCombatPet { get; set; } + + public bool IsFactionMob { get; set; } + + public FactionBits Faction1Bits { get; set; } + + public CreatureType? FoeType { get; set; } + + public PlayerKillerStatus PlayerKillerStatus { get; set; } + public WeenieObject() { } public WeenieObject(WorldObject worldObject) { WorldObject = worldObject; + + /*if (!(worldObject is Creature creature)) + return; + + IsCombatPet = worldObject is CombatPet; + + IsMonster = creature.IsMonster && !IsCombatPet; + + Faction1Bits = creature.Faction1Bits ?? FactionBits.None; + + IsFactionMob = IsMonster && Faction1Bits != FactionBits.None; + + FoeType = creature.FoeType; + + PlayerKillerStatus = creature.PlayerKillerStatus;*/ + } + + public bool SameFaction(PhysicsObj obj) + { + return (Faction1Bits & obj.WeenieObj.Faction1Bits) != 0; + } + + public bool PotentialFoe(PhysicsObj obj) + { + /*return FoeType != null && FoeType == obj.WeenieObj.WorldObject?.CreatureType || + obj.WeenieObj.FoeType != null && obj.WeenieObj.FoeType == WorldObject?.CreatureType;*/ + return false; } public bool CanJump(float extent) @@ -26,19 +65,69 @@ public bool CanJump(float extent) return true; } - public bool InqJumpVelocity(float extent, ref float velocityZ) + public bool InqJumpVelocity(float extent, out float velocity_z) { - velocityZ = MovementSystem.GetJumpHeight(1.0f, 100, 1.0f, 1.0f) * 19.6f; - return true; + velocity_z = 0.0f; + + /*var player = WorldObject as Player; + + if (player == null) + return false;*/ + + /*if (!WorldObject.IsPlayer) return false; + + var burden = InqBurden(); + if (burden == null) + return false; + + var stamina = player.Stamina.Current; + + var jumpSkill = player.GetCreatureSkill(Skill.Jump).Current; + + if (stamina == 0) + jumpSkill = 0; + + var height = MovementSystem.GetJumpHeight((float)burden, jumpSkill, extent, 1.0f); + + velocity_z = (float)Math.Sqrt(height * 19.6); + + return true;*/ + return false; + } + + /// + /// Returns the player's load / burden as a percentage, + /// usually in the range 0.0 - 3.0 (max 300% typically) + /// + public float? InqBurden() + { + /*var player = WorldObject as Player; + + if (player == null) + return null; + + var strength = (int)player.Strength.Current; + + var numAugs = player.AugmentationIncreasedCarryingCapacity; + + var capacity = EncumbranceSystem.EncumbranceCapacity(strength, numAugs); + + var encumbrance = player.EncumbranceVal ?? 0; + + var burden = EncumbranceSystem.GetBurden(capacity, encumbrance); + + return burden;*/ + return null; } public bool InqRunRate(ref float rate) { // get run skill from WorldObject - uint runSkill = WorldObject.RunSkill; - /*var creature = WorldObject as Creature; - if (creature != null) + uint runSkill = 0; + /*if (WorldObject is Creature creature) runSkill = creature.GetCreatureSkill(Skill.Run).Current;*/ + if (WorldObject.IsCreature) + runSkill = WorldObject.RunSkill; //rate = (float)MovementSystem.GetRunRate(0.0f, 300, 1.0f); rate = (float)MovementSystem.GetRunRate(0.0f, (int)runSkill, 1.0f); @@ -48,41 +137,43 @@ public bool InqRunRate(ref float rate) public bool IsCorpse() { + //return WorldObject is Corpse; return false; } - public bool IsImpenetable() + public bool IsImpenetrable() { + //return WorldObject is Player player && player.PlayerKillerStatus == PlayerKillerStatus.Free; return false; } public bool IsPK() { + //return WorldObject is Player player && player.IsPK; return false; } public bool IsPKLite() { + //return WorldObject is Player player && player.IsPKL; return false; } public bool IsPlayer() { - return true; + //return WorldObject is Player; + return WorldObject.IsPlayer; } public bool IsCreature() { - //if (WorldObject == null) return false; - //var creature = WorldObject as Creature; - //return creature != null; + //return WorldObject is Creature; return WorldObject.IsCreature; - - //return true; } public bool IsStorage() { + //return WorldObject is Storage; return false; } @@ -91,18 +182,51 @@ public float JumpStaminaCost(float extent, int staminaCost) return 0; } - public int DoCollision(AtkCollisionProfile prof, ObjectGuid guid, PhysicsObj target) + public void InqCollisionProfile(ObjCollisionProfile prof) { + if (WorldObject == null) + return; + + prof.WCID = ID; + //prof.ItemType = WorldObject.ItemType; + + //if (WorldObject is Creature) + if (WorldObject.IsCreature) + prof.Flags |= ObjCollisionProfileFlags.Creature; + + //if (WorldObject is Player) + if (WorldObject.IsPlayer) + prof.Flags |= ObjCollisionProfileFlags.Player; + + //if (WorldObject.Attackable) + if (WorldObject.IsCreature) + prof.Flags |= ObjCollisionProfileFlags.Attackable; + + //if (WorldObject is Door) + //prof.Flags |= ObjCollisionProfileFlags.Door; + } + + public int DoCollision(ObjCollisionProfile prof, ObjectGuid guid, PhysicsObj target) + { + var wo = WorldObject; + + if (wo == null) + return -1; + + var targetWO = target.WeenieObj.WorldObject; + + if (targetWO == null) + return -1; + // no collision with self - if (WorldObject.Guid.Equals(target.WeenieObj.WorldObject.Guid)) + if (wo.Guid.Equals(targetWO.Guid)) return -1; - /*Console.WriteLine("AtkCollisionProfile"); + /*Console.WriteLine("ObjCollisionProfile"); Console.WriteLine("Source: " + WorldObject.Name); Console.WriteLine("Target: " + obj.WeenieObj.WorldObject.Name);*/ - //if (WorldObject != null) - //WorldObject.OnCollideObject(target.WeenieObj.WorldObject); + //wo.OnCollideObject(targetWO); return 0; } @@ -111,25 +235,76 @@ public int DoCollision(EnvCollisionProfile prof, ObjectGuid guid, PhysicsObj tar { /*Console.WriteLine("EnvCollisionProfile"); Console.WriteLine("Source: " + WorldObject.Name); - Console.WriteLine("Target: " + obj.WeenieObj.WorldObject.Name);*/ + Console.WriteLine("Target: " + target.WeenieObj.WorldObject.Name); + Console.WriteLine("Velocity: " + prof.Velocity);*/ + + var wo = WorldObject; - //if (WorldObject != null) - //WorldObject.OnCollideEnvironment(); + if (wo == null) + return 0; + + /*if (wo is Player player) + player.HandleFallingDamage(prof); + else + wo.OnCollideEnvironment();*/ return 0; } public void DoCollisionEnd(ObjectGuid targetGuid) { - /*var target = WorldObject.CurrentLandblock?.GetObject(targetGuid); + var wo = WorldObject; + + if (wo == null) + return; - if (WorldObject != null && target != null) - WorldObject.OnCollideObjectEnd(target);*/ + /*var target = wo.CurrentLandblock?.GetObject(targetGuid); + + if (target != null) + wo.OnCollideObjectEnd(target);*/ } public void OnMotionDone(uint motionID, bool success) { + //WorldObject.HandleMotionDone(motionID, success); + } + + public void OnMoveComplete(WeenieError status) + { + //WorldObject.OnMoveComplete(status); + } + public bool CanBypassMoveRestrictions() + { + // acclient checks both of these here + //return WorldObject.IgnoreHouseBarriers/* && WorldObject is Admin*/; + return true; + } + + public bool CanMoveInto(WeenieObject mover) + { + /*var house = WorldObject as House; + if (house == null) + { + log.Error($"{WorldObject?.Name} ({WorldObject?.Guid}).CanMoveInto({mover.WorldObject?.Name} ({mover.WorldObject?.Guid}) - couldn't find house"); + return true; + } + var rootHouse = house.RootHouse; + if (rootHouse == null) + { + log.Error($"{WorldObject?.Name} ({WorldObject?.Guid}).CanMoveInto({mover.WorldObject?.Name} ({mover.WorldObject?.Guid}) - couldn't find root house"); + return true; + } + var player = mover?.WorldObject as Player; + if (player == null) + { + log.Error($"{WorldObject?.Name} ({WorldObject?.Guid}).CanMoveInto({mover.WorldObject?.Name} ({mover.WorldObject?.Guid}) - couldn't find player"); + return true; + } + var result = rootHouse.HouseOwner == null || rootHouse.OpenStatus || rootHouse.HasPermission(player); + //Console.WriteLine($"{player.Name} can move into {rootHouse.Name} ({rootHouse.Guid}): {result}"); + return result;*/ + return true; } } } diff --git a/ACViewer/Physics/CylSphere.cs b/ACViewer/Physics/CylSphere.cs index f9e89b2..274f3d4 100644 --- a/ACViewer/Physics/CylSphere.cs +++ b/ACViewer/Physics/CylSphere.cs @@ -468,7 +468,7 @@ public bool CollisionNormal(Transition transition, Sphere checkPos, Vector3 _dis public bool Equals(CylSphere cylSphere) { - return cylSphere != null && Height == cylSphere.Height && Radius == cylSphere.Radius && LowPoint.IsEqual(cylSphere.LowPoint); + return cylSphere != null && Height == cylSphere.Height && Radius == cylSphere.Radius && LowPoint == cylSphere.LowPoint; } public override int GetHashCode() diff --git a/ACViewer/Physics/Entity/BSPCache.cs b/ACViewer/Physics/Entity/BSPCache.cs index 34732e5..9a30079 100644 --- a/ACViewer/Physics/Entity/BSPCache.cs +++ b/ACViewer/Physics/Entity/BSPCache.cs @@ -14,6 +14,13 @@ public static class BSPCache public static int Requests; public static int Hits; + public static int Count => BSPTrees.Count; + + public static void Clear() + { + BSPTrees.Clear(); + } + public static BSPTree Get(BSPTree bspTree) { if (!Enabled) diff --git a/ACViewer/Physics/Entity/GfxObjCache.cs b/ACViewer/Physics/Entity/GfxObjCache.cs index 90b07a0..a7123e0 100644 --- a/ACViewer/Physics/Entity/GfxObjCache.cs +++ b/ACViewer/Physics/Entity/GfxObjCache.cs @@ -1,3 +1,6 @@ +// Uncomment this if you want to use weak reference cache. It could save 10's of MB and might be useful on a micro-server +//#define USE_WEAK_REFERENCE_CACHE + using System; using System.Collections.Concurrent; @@ -8,11 +11,22 @@ namespace ACE.Server.Physics.Entity { public static class GfxObjCache { + #if !USE_WEAK_REFERENCE_CACHE public static readonly ConcurrentDictionary GfxObjs = new ConcurrentDictionary(); + #else + public static readonly ConcurrentDictionary> GfxObjs = new ConcurrentDictionary>(); + #endif public static int Requests; public static int Hits; + public static int Count => GfxObjs.Count; + + public static void Clear() + { + GfxObjs.Clear(); + } + public static GfxObj Get(uint gfxObjID) { Requests++; @@ -22,15 +36,29 @@ public static GfxObj Get(uint gfxObjID) if (GfxObjs.TryGetValue(gfxObjID, out var result)) { + #if !USE_WEAK_REFERENCE_CACHE Hits++; return result; + #else + if (result.TryGetTarget(out var target)) + { + Hits++; + return target; + } + #endif } var _gfxObj = DBObj.GetGfxObj(gfxObjID); // not cached, add it var gfxObj = new GfxObj(_gfxObj); + + #if !USE_WEAK_REFERENCE_CACHE gfxObj = GfxObjs.GetOrAdd(_gfxObj.Id, gfxObj); + #else + GfxObjs[_gfxObj.Id] = new WeakReference(gfxObj); + #endif + return gfxObj; } } diff --git a/ACViewer/Physics/Entity/PolygonCache.cs b/ACViewer/Physics/Entity/PolygonCache.cs index 5c960f8..39af68e 100644 --- a/ACViewer/Physics/Entity/PolygonCache.cs +++ b/ACViewer/Physics/Entity/PolygonCache.cs @@ -14,6 +14,13 @@ public static class PolygonCache public static int Requests; public static int Hits; + public static int Count => Polygons.Count; + + public static void Clear() + { + Polygons.Clear(); + } + public static Polygon Get(Polygon p) { if (!Enabled) diff --git a/ACViewer/Physics/Entity/VertexCache.cs b/ACViewer/Physics/Entity/VertexCache.cs index 7418c3d..52b5ac8 100644 --- a/ACViewer/Physics/Entity/VertexCache.cs +++ b/ACViewer/Physics/Entity/VertexCache.cs @@ -14,6 +14,13 @@ public static class VertexCache public static int Requests; public static int Hits; + public static int Count => Vertices.Count; + + public static void Clear() + { + Vertices.Clear(); + } + public static Vertex Get(Vertex v) { if (!Enabled) return v; diff --git a/ACViewer/Physics/Extensions/QuaternionExtensions.cs b/ACViewer/Physics/Extensions/QuaternionExtensions.cs index 5f17d20..f9bbe2a 100644 --- a/ACViewer/Physics/Extensions/QuaternionExtensions.cs +++ b/ACViewer/Physics/Extensions/QuaternionExtensions.cs @@ -7,11 +7,11 @@ public static class QuaternionExtensions { public static bool IsValid(this Quaternion q) { - if (q.X == float.NaN || q.Y == float.NaN || q.Z == float.NaN || q.W == float.NaN) + if (float.IsNaN(q.X) || float.IsNaN(q.Y) || float.IsNaN(q.Z) || float.IsNaN(q.W)) return false; var length = q.Length(); - if (length == float.NaN) + if (float.IsNaN(length)) return false; if (Math.Abs(length - 1.0f) > PhysicsGlobals.EPSILON * 5.0f) diff --git a/ACViewer/Physics/Extensions/Vector3Extensions.cs b/ACViewer/Physics/Extensions/Vector3Extensions.cs index 8aca316..8d037ac 100644 --- a/ACViewer/Physics/Extensions/Vector3Extensions.cs +++ b/ACViewer/Physics/Extensions/Vector3Extensions.cs @@ -13,7 +13,7 @@ public static float Dot2D(this Vector3 a, Vector3 b) public static bool IsValid(this Vector3 v) { - return v.X != float.NaN && v.Y != float.NaN && v.Z != float.NaN; + return !float.IsNaN(v.X) && !float.IsNaN(v.Y) && !float.IsNaN(v.Z); } public static double Length2D(this Vector3 v) @@ -23,22 +23,7 @@ public static double Length2D(this Vector3 v) public static double LengthSquared2D(this Vector3 v) { - return new Vector2(v.X, v.Y).LengthSquared(); - } - - public static Vector3 Normalize(this Vector3 v) - { - return v / v.Length(); - } - - public static bool IsEqual(this Vector3 v1, Vector3 v2) - { - return v1.X == v2.X && v1.Y == v2.Y && v1.Z == v2.Z; // epsilon? - } - - public static bool IsMoved(this Vector3 a, Vector3 b) - { - return (a.X != b.X || a.Y != b.Y || a.Z != b.Z); + return v.X * v.X + v.Y * v.Y; } public static float get_heading(this Vector3 v) diff --git a/ACViewer/Physics/Hooks/FPHook.cs b/ACViewer/Physics/Hooks/FPHook.cs index dd03fe7..81d49f7 100644 --- a/ACViewer/Physics/Hooks/FPHook.cs +++ b/ACViewer/Physics/Hooks/FPHook.cs @@ -27,7 +27,7 @@ public override bool Execute(PhysicsObj obj) scale = 1.0; } var current = (EndValue - StartValue) * scale + StartValue; - obj.process_fp_hook((int)HookType, (float)current, UserData); + obj.process_fp_hook(HookType, (float)current, UserData); return scale == 1.0; } } diff --git a/ACViewer/Physics/Hooks/PhysicsHookType.cs b/ACViewer/Physics/Hooks/PhysicsHookType.cs index 11239b7..0576624 100644 --- a/ACViewer/Physics/Hooks/PhysicsHookType.cs +++ b/ACViewer/Physics/Hooks/PhysicsHookType.cs @@ -1,28 +1,14 @@ -using System; - namespace ACE.Server.Physics.Hooks { - [Flags] public enum PhysicsHookType { - Setup = 0x1, - MotionTable = 0x2, - Velocity = 0x4, - Acceleration = 0x8, - Omega = 0x10, - Parent = 0x20, - Children = 0x40, - ObjScale = 0x80, - Friction = 0x100, - Elasticity = 0x200, - Timestamps = 0x400, - Stable = 0x800, - PETable = 0x1000, - DefaultScript = 0x2000, - DefaultScriptIntensity = 0x4000, - Position = 0x8000, - Movement = 0x10000, - AnimFrameID = 0x20000, - Translucency = 0x40000, + Scale = 0, + Translucency = 1, + PartTranslucency = 2, + Luminosity = 3, + Diffusion = 4, + PartLuminosity = 5, + PartDiffusion = 6, + CallPES = 7, } } diff --git a/ACViewer/Physics/Managers/ConstraintManager.cs b/ACViewer/Physics/Managers/ConstraintManager.cs index ce5050e..7188b79 100644 --- a/ACViewer/Physics/Managers/ConstraintManager.cs +++ b/ACViewer/Physics/Managers/ConstraintManager.cs @@ -28,7 +28,7 @@ public void ConstrainTo(Position position, float startDistance, float maxDistanc { IsConstrained = true; - ConstraintPos = position; + ConstraintPos = new Position(position); ConstraintDistanceStart = startDistance; ConstraintDistanceMax = maxDistance; ConstraintPosOffset = position.Distance(PhysicsObj.Position); diff --git a/ACViewer/Physics/Managers/InterpolationManager.cs b/ACViewer/Physics/Managers/InterpolationManager.cs index 1c503ea..6d00b95 100644 --- a/ACViewer/Physics/Managers/InterpolationManager.cs +++ b/ACViewer/Physics/Managers/InterpolationManager.cs @@ -6,7 +6,7 @@ namespace ACE.Server.Physics.Animation { public class InterpolationManager { - public Queue PositionQueue; + public LinkedList PositionQueue; public PhysicsObj PhysicsObj; public bool KeepHeading; public int FrameCounter; @@ -38,8 +38,8 @@ public void InterpolateTo(Position position, bool keepHeading) if (PhysicsObj == null) return; - var dest = PositionQueue.Count > 0 && PositionQueue.Last().Type == InterpolationNodeType.PositionType ? - PositionQueue.Last().Position : PhysicsObj.Position; + var dest = PositionQueue.Count > 0 && PositionQueue.Last.Value.Type == InterpolationNodeType.PositionType ? + PositionQueue.Last.Value.Position : PhysicsObj.Position; var dist = dest.Distance(position); @@ -49,20 +49,20 @@ public void InterpolateTo(Position position, bool keepHeading) { while (PositionQueue.Count > 0) { - var lastNode = PositionQueue.Last(); + var lastNode = PositionQueue.Last.Value; if (lastNode.Type != InterpolationNodeType.PositionType || lastNode.Position.Distance(position) >= 0.05f) break; - PositionQueue.DequeueLast(); + PositionQueue.RemoveLast(); } while (PositionQueue.Count >= 20) - PositionQueue.Dequeue(); + PositionQueue.RemoveFirst(); var interpolationNode = new InterpolationNode(InterpolationNodeType.PositionType, position); if (keepHeading) interpolationNode.Position.Frame.set_heading(PhysicsObj.get_heading()); - PositionQueue.Enqueue(interpolationNode); + PositionQueue.AddLast(interpolationNode); } else { @@ -78,7 +78,7 @@ public void InterpolateTo(Position position, bool keepHeading) if (keepHeading) interpolationNode.Position.Frame.set_heading(PhysicsObj.get_heading()); - PositionQueue.Enqueue(interpolationNode); + PositionQueue.AddLast(interpolationNode); NodeFailCounter = 4; } } @@ -96,7 +96,7 @@ public void NodeCompleted(bool success) FrameCounter = 0; ProgressQuantum = 0.0f; - var head = PositionQueue.Count == 0 ? null : PositionQueue.First(); + var head = PositionQueue.Count == 0 ? null : PositionQueue.First.Value; var next = PositionQueue.Count <= 1 ? null : PositionQueue.ElementAt(1); if (PositionQueue.Count > 1) @@ -122,7 +122,7 @@ public void NodeCompleted(bool success) StopInterpolating(); } if (PositionQueue.Count > 0) - PositionQueue.Dequeue(); + PositionQueue.RemoveFirst(); } public void SetPhysicsObject(PhysicsObj obj) @@ -148,7 +148,7 @@ public void UseTime() { if (NodeFailCounter <= 0) return; - var last = PositionQueue.Last(); + var last = PositionQueue.Last.Value; if (last.Type != InterpolationNodeType.JumpType && last.Type != InterpolationNodeType.VelocityType) { if (PhysicsObj.SetPositionSimple(last.Position, true) != SetPositionError.OK) @@ -182,7 +182,7 @@ public void UseTime() return; } - var first = PositionQueue.FirstOrDefault(); + var first = PositionQueue.First.Value; switch (first.Type) { case InterpolationNodeType.JumpType: @@ -201,7 +201,7 @@ public void adjust_offset(AFrame frame, double quantum) if (PositionQueue.Count == 0 || PhysicsObj == null || !PhysicsObj.TransientState.HasFlag(TransientStateFlags.Contact)) return; - var first = PositionQueue.First(); + var first = PositionQueue.First.Value; if (first.Type == InterpolationNodeType.JumpType || first.Type == InterpolationNodeType.VelocityType) return; diff --git a/ACViewer/Physics/Managers/MotionTableManager.cs b/ACViewer/Physics/Managers/MotionTableManager.cs index 1465221..43c2031 100644 --- a/ACViewer/Physics/Managers/MotionTableManager.cs +++ b/ACViewer/Physics/Managers/MotionTableManager.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; -using System.Linq; + using ACE.Entity.Enum; -using ACE.Server.Physics.Common; namespace ACE.Server.Physics.Animation { diff --git a/ACViewer/Physics/Managers/MoveToManager.cs b/ACViewer/Physics/Managers/MoveToManager.cs index 26cd9e6..cb01e7e 100644 --- a/ACViewer/Physics/Managers/MoveToManager.cs +++ b/ACViewer/Physics/Managers/MoveToManager.cs @@ -32,7 +32,7 @@ public class MoveToManager public List PendingActions; public PhysicsObj PhysicsObj; public WeenieObject WeenieObj; - public List Callbacks; + public bool AlwaysTurn; public MoveToManager() { @@ -59,7 +59,6 @@ public void Init() MovementParams = new MovementParameters(); PendingActions = new List(); - Callbacks = new List(); } public void InitializeLocalVars() @@ -346,12 +345,7 @@ public void BeginNextNode() } else CleanUpAndCallWeenie(WeenieError.None); - } - - if (PendingActions.Count == 0) - foreach (var callback in Callbacks.ToList()) - callback(); } public void BeginMoveForward() @@ -425,7 +419,7 @@ public void HandleMoveToPosition() movementParams.Speed = MovementParams.Speed; movementParams.HoldKeyToApply = MovementParams.HoldKeyToApply; - if (!PhysicsObj.motions_pending()) + if (!PhysicsObj.IsAnimating) { var heading = MovementParams.get_desired_heading(CurrentCommand, MovingAway) + curPos.heading(CurrentTargetPosition); if (heading >= 360.0f) heading -= 360.0f; @@ -446,7 +440,13 @@ public void HandleMoveToPosition() } } else + { + // custom: sync for server ticrate + if (AuxCommand != 0) + PhysicsObj.set_heading(heading, true); + stop_aux_command(movementParams); + } } else stop_aux_command(movementParams); @@ -455,13 +455,25 @@ public void HandleMoveToPosition() if (!CheckProgressMade(dist)) { - if (!PhysicsObj.IsInterpolating() && !PhysicsObj.motions_pending()) + if (!PhysicsObj.IsInterpolating() && !PhysicsObj.IsAnimating) FailProgressCount++; } else { + // custom for low monster update rate + var inRange = false; + + if (!MovementParams.UseSpheres) + { + if (dist < 1.0f && PreviousDistance < dist) + inRange = true; + + PreviousDistance = dist; + PreviousDistanceTime = PhysicsTimer.CurrentTime; + } + FailProgressCount = 0; - if (MovingAway && dist >= MovementParams.MinDistance || !MovingAway && dist <= MovementParams.DistanceToObject) + if (MovingAway && dist >= MovementParams.MinDistance || !MovingAway && dist <= MovementParams.DistanceToObject || inRange) { PendingActions.RemoveAt(0); _StopMotion(CurrentCommand, movementParams); @@ -505,8 +517,7 @@ public void BeginTurnToHeading() return; } - if (PendingActions.Count == 0) - return; + if (PhysicsObj.IsAnimating && !AlwaysTurn) return; var pendingAction = PendingActions[0]; var headingDiff = heading_diff(pendingAction.Heading, PhysicsObj.get_heading(), (uint)MotionCommand.TurnRight); @@ -574,6 +585,7 @@ public void HandleTurnToHeading() var pendingAction = PendingActions[0]; var heading = PhysicsObj.get_heading(); + if (heading_greater(heading, pendingAction.Heading, CurrentCommand)) { FailProgressCount = 0; @@ -603,7 +615,7 @@ public void HandleTurnToHeading() { PreviousHeading = heading; - if (!PhysicsObj.IsInterpolating() && !PhysicsObj.motions_pending()) + if (!PhysicsObj.IsInterpolating() && !PhysicsObj.IsAnimating) FailProgressCount++; } } @@ -677,6 +689,8 @@ public bool CheckProgressMade(float currDistance) public void CancelMoveTo(WeenieError retval) { + //Console.WriteLine($"CancelMoveTo({retval})"); + if (MovementType == MovementType.Invalid) return; @@ -710,7 +724,8 @@ public void CleanUpAndCallWeenie(WeenieError status) if (PhysicsObj != null) PhysicsObj.StopCompletely(false); - // server - handle move to done? + // server custom + WeenieObj.OnMoveComplete(status); } public float GetCurrentDistance() @@ -855,15 +870,5 @@ public void stop_aux_command(MovementParameters movementParams) AuxCommand = 0; } } - - public void add_listener(Action listener) - { - Callbacks.Add(listener); - } - - public void remove_listener(Action listener) - { - Callbacks.Remove(listener); - } } } diff --git a/ACViewer/Physics/Managers/MovementManager.cs b/ACViewer/Physics/Managers/MovementManager.cs index 916785d..f075850 100644 --- a/ACViewer/Physics/Managers/MovementManager.cs +++ b/ACViewer/Physics/Managers/MovementManager.cs @@ -124,6 +124,7 @@ public void MotionDone(uint motion, bool success) public WeenieError PerformMovement(MovementStruct mvs) { PhysicsObj.set_active(true); + switch (mvs.Type) { case MovementType.RawCommand: @@ -188,6 +189,9 @@ public MotionInterp get_minterp() return MotionInterpreter; } + /// + /// Alternatively, you can use PhysicsObj.IsAnimating for better performance. + /// public bool motions_pending() { if (MotionInterpreter == null) return false; diff --git a/ACViewer/Physics/Managers/ServerObjectManager.cs b/ACViewer/Physics/Managers/ServerObjectManager.cs new file mode 100644 index 0000000..5c6d641 --- /dev/null +++ b/ACViewer/Physics/Managers/ServerObjectManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Concurrent; + +namespace ACE.Server.Physics.Managers +{ + public static class ServerObjectManager + { + /// + /// Custom lookup table of PhysicsObjs for the server + /// + public static ConcurrentDictionary ServerObjects { get; } = new ConcurrentDictionary(); + + /// + /// Adds a PhysicsObj to the static list of server-wide objects + /// + public static void AddServerObject(PhysicsObj obj) + { + if (obj != null) + ServerObjects[obj.ID] = obj; + } + + /// + /// Removes a PhysicsObj from the static list of server-wide objects + /// + public static void RemoveServerObject(PhysicsObj obj) + { + if (obj != null) + ServerObjects.TryRemove(obj.ID, out _); + } + + /// + /// Returns a PhysicsObj for an object ID + /// + public static PhysicsObj GetObjectA(uint objectID) + { + ServerObjects.TryGetValue(objectID, out var obj); + + return obj; + } + } +} diff --git a/ACViewer/Physics/Managers/StickyManager.cs b/ACViewer/Physics/Managers/StickyManager.cs index 3ee08a2..d4742d6 100644 --- a/ACViewer/Physics/Managers/StickyManager.cs +++ b/ACViewer/Physics/Managers/StickyManager.cs @@ -17,30 +17,19 @@ public class StickyManager public bool Initialized; public double StickyTimeoutTime; - public List StickyCallbacks; - public List UnstickyCallbacks; - public static readonly float StickyRadius = 0.3f; public static readonly float StickyTime = 1.0f; public StickyManager() { - Init(); } public StickyManager(PhysicsObj obj) { - Init(); SetPhysicsObject(obj); } - public void Init() - { - StickyCallbacks = new List(); - UnstickyCallbacks = new List(); - } - public void ClearTarget() { if (TargetID == 0) return; @@ -50,9 +39,6 @@ public void ClearTarget() PhysicsObj.clear_target(); PhysicsObj.cancel_moveto(); - - foreach (var unstickyCallback in UnstickyCallbacks.ToList()) - unstickyCallback(); } public static StickyManager Create(PhysicsObj obj) @@ -94,9 +80,6 @@ public void StickTo(uint objectID, float targetRadius, float targetHeight) TargetRadius = targetRadius; StickyTimeoutTime = PhysicsTimer.CurrentTime + StickyTime; PhysicsObj.set_target(0, objectID, 0.5f, 0.5f); - - foreach (var stickyCallback in StickyCallbacks.ToList()) - stickyCallback(); } public void UseTime() @@ -148,25 +131,5 @@ public void adjust_offset(AFrame offset, double quantum) //Console.WriteLine($"StickyManager.AdjustOffset(targetHeading={targetHeading}, curHeading={curHeading}, setHeading={heading})"); offset.set_heading(heading); } - - public void add_sticky_listener(Action listener) - { - StickyCallbacks.Add(listener); - } - - public void remove_sticky_listener(Action listener) - { - StickyCallbacks.Remove(listener); - } - - public void add_unsticky_listener(Action listener) - { - UnstickyCallbacks.Add(listener); - } - - public void remove_unsticky_listener(Action listener) - { - UnstickyCallbacks.Remove(listener); - } } } diff --git a/ACViewer/Physics/ObjectInfo.cs b/ACViewer/Physics/ObjectInfo.cs index 89916fb..aba39d3 100644 --- a/ACViewer/Physics/ObjectInfo.cs +++ b/ACViewer/Physics/ObjectInfo.cs @@ -7,19 +7,19 @@ namespace ACE.Server.Physics.Animation [Flags] public enum ObjectInfoState { - Default = 0x0, - Contact = 0x1, - OnWalkable = 0x2, - IsViewer = 0x4, - PathClipped = 0x8, - FreeRotate = 0x10, - PerfectClip = 0x40, - IsImpenetrable = 0x80, - IsPlayer = 0x100, - EdgeSlide = 0x200, + Default = 0x0, + Contact = 0x1, + OnWalkable = 0x2, + IsViewer = 0x4, + PathClipped = 0x8, + FreeRotate = 0x10, + PerfectClip = 0x40, + IsImpenetrable = 0x80, + IsPlayer = 0x100, + EdgeSlide = 0x200, IgnoreCreatures = 0x400, - IsPK = 0x800, - IsPKLite = 0x1000 + IsPK = 0x800, + IsPKLite = 0x1000 }; public class ObjectInfo @@ -31,7 +31,7 @@ public class ObjectInfo public float StepDownHeight; public bool Ethereal; public bool StepDown; - public int TargetID; + public uint TargetID; public float GetWalkableZ() { @@ -46,11 +46,11 @@ public void Init(PhysicsObj obj, ObjectInfoState state) StepUpHeight = Object.GetStepUpHeight(); StepDownHeight = Object.GetStepDownHeight(); Ethereal = Object.State.HasFlag(PhysicsState.Ethereal); - StepDown = Object.State.HasFlag(PhysicsState.Missile); + StepDown = !Object.State.HasFlag(PhysicsState.Missile); var wobj = Object.WeenieObj; if (wobj != null) { - if (wobj.IsImpenetable()) + if (wobj.IsImpenetrable()) State |= ObjectInfoState.IsImpenetrable; if (wobj.IsPlayer()) State |= ObjectInfoState.IsPlayer; @@ -59,17 +59,28 @@ public void Init(PhysicsObj obj, ObjectInfoState state) if (wobj.IsPKLite()) State |= ObjectInfoState.IsPKLite; } + if (obj.ProjectileTarget != null) + TargetID = obj.ProjectileTarget.ID; } public bool IsValidWalkable(Vector3 normal) { - return Object.is_valid_walkable(normal); + return PhysicsObj.is_valid_walkable(normal); } public bool MissileIgnore(PhysicsObj collideObj) { + // modified for 2-way if (collideObj.State.HasFlag(PhysicsState.Missile)) + { + if (!Object.IsPlayer) + return true; + + if (collideObj.ProjectileTarget == null || collideObj.ProjectileTarget == Object) + return false; + return true; + } if (Object.State.HasFlag(PhysicsState.Missile)) { @@ -116,7 +127,7 @@ public TransitionState ValidateWalkable(Sphere checkPos, Plane contactPlane, boo if (dist > PhysicsGlobals.EPSILON) return TransitionState.OK; - if (path.StepDown || !State.HasFlag(ObjectInfoState.OnWalkable) || Object.is_valid_walkable(contactPlane.Normal)) + if (path.StepDown || !State.HasFlag(ObjectInfoState.OnWalkable) || PhysicsObj.is_valid_walkable(contactPlane.Normal)) { collision.SetContactPlane(contactPlane, isWater); collision.ContactPlaneCellID = landCellID; @@ -133,7 +144,7 @@ public TransitionState ValidateWalkable(Sphere checkPos, Plane contactPlane, boo if (path.CheckWalkable) return TransitionState.Collided; var zDist = dist / contactPlane.Normal.Z; - if (path.StepDown || !State.HasFlag(ObjectInfoState.OnWalkable) || Object.is_valid_walkable(contactPlane.Normal)) + if (path.StepDown || !State.HasFlag(ObjectInfoState.OnWalkable) || PhysicsObj.is_valid_walkable(contactPlane.Normal)) { collision.SetContactPlane(contactPlane, isWater); collision.ContactPlaneCellID = landCellID; diff --git a/ACViewer/Physics/PartArray.cs b/ACViewer/Physics/PartArray.cs index 30c9349..9769590 100644 --- a/ACViewer/Physics/PartArray.cs +++ b/ACViewer/Physics/PartArray.cs @@ -278,8 +278,7 @@ public void InitDefaults() var animData = new Animation.AnimData(); animData.AnimID = Setup._dat.DefaultAnimation; animData.LowFrame = 0; - animData.HighFrame = -1; - animData.Framerate = 30.0f; + animData.HighFrame = Int32.MaxValue; Sequence.append_animation(animData); WeenieDesc.Destroy(animData); } @@ -595,11 +594,13 @@ public void Update(double quantum, ref AFrame offsetFrame) public void UpdateParts(AFrame frame) { var curFrame = Sequence.GetCurrAnimFrame(); - - if (curFrame == null) return; - + if (curFrame == null) + { + /*if (Parts.Count == 1) // ?? - not in acclient, why is this in server? + Parts[0].Pos = Owner.Position;*/ + return; + } var numParts = Math.Min(NumParts, curFrame.Frames.Count); - for (var i = 0; i < numParts; i++) Parts[i].Pos.Frame.Combine(frame, new AFrame(curFrame.Frames[i]), Scale); } diff --git a/ACViewer/Physics/Particles/Particle.cs b/ACViewer/Physics/Particles/Particle.cs index 8297d5b..600eba9 100644 --- a/ACViewer/Physics/Particles/Particle.cs +++ b/ACViewer/Physics/Particles/Particle.cs @@ -1,9 +1,10 @@ using System; using System.Numerics; + +using ACE.Common; using ACE.Entity.Enum; using ACE.Server.Physics.Animation; using ACE.Server.Physics.Common; -using ACE.Server.Physics.Extensions; namespace ACE.Server.Physics { @@ -64,8 +65,8 @@ public bool Init(ParticleEmitterInfo info, PhysicsObj parent, int partIdx, AFram A = a; B = b; - var ra = Common.Random.RollDice(-(float)Math.PI, (float)Math.PI); - var po = Common.Random.RollDice(-(float)Math.PI, (float)Math.PI); + var ra = ThreadSafeRandom.Next(-(float)Math.PI, (float)Math.PI); + var po = ThreadSafeRandom.Next(-(float)Math.PI, (float)Math.PI); var rb = Math.Cos(po); C.X = (float)(Math.Cos(ra) * c.X * rb); diff --git a/ACViewer/Physics/Particles/ParticleEmitterInfo.cs b/ACViewer/Physics/Particles/ParticleEmitterInfo.cs index c5d23d3..7db7093 100644 --- a/ACViewer/Physics/Particles/ParticleEmitterInfo.cs +++ b/ACViewer/Physics/Particles/ParticleEmitterInfo.cs @@ -1,5 +1,7 @@ using System; using System.Numerics; + +using ACE.Common; using ACE.Entity.Enum; using ACE.Server.Physics.Common; using ACE.Server.Physics.Extensions; @@ -96,7 +98,7 @@ public ParticleEmitterInfo(DatLoader.FileTypes.ParticleEmitterInfo info) public double GetRandomStartScale() { - var result = Common.Random.RollDice(-1.0f, 1.0f) * ScaleRand + StartScale; + var result = (float)ThreadSafeRandom.Next(-1.0f, 1.0f) * ScaleRand + StartScale; result = result.Clamp(0.1f, 10.0f); return result; @@ -104,7 +106,7 @@ public double GetRandomStartScale() public double GetRandomFinalScale() { - var result = Common.Random.RollDice(-1.0f, 1.0f) * ScaleRand * FinalScale; + var result = (float)ThreadSafeRandom.Next(-1.0f, 1.0f) * ScaleRand * FinalScale; result = result.Clamp(0.1f, 10.0f); return result; @@ -112,7 +114,7 @@ public double GetRandomFinalScale() public double GetRandomStartTrans() { - var result = Common.Random.RollDice(-1.0f, 1.0f) * TransRand * StartTrans; + var result = (float)ThreadSafeRandom.Next(-1.0f, 1.0f) * TransRand * StartTrans; result = result.Clamp(0.0f, 1.0f); return result; @@ -120,7 +122,7 @@ public double GetRandomStartTrans() public double GetRandomFinalTrans() { - var result = Common.Random.RollDice(-1.0f, 1.0f) * TransRand * FinalTrans; + var result = (float)ThreadSafeRandom.Next(-1.0f, 1.0f) * TransRand * FinalTrans; result = result.Clamp(0.0f, 1.0f); return result; @@ -128,7 +130,7 @@ public double GetRandomFinalTrans() public double GetRandomLifespan() { - var result = Common.Random.RollDice(-1.0f, 1.0f) * LifeSpanRand + LifeSpan; + var result = ThreadSafeRandom.Next(-1.0f, 1.0f) * LifeSpanRand + LifeSpan; result = Math.Max(0.0f, result); return result; @@ -171,41 +173,41 @@ public bool ShouldEmitParticle(int numParticles, int totalEmitted, Vector3 emitt public Vector3 GetRandomOffset() { var rng = new Vector3( - Common.Random.RollDice(-1.0f, 1.0f), - Common.Random.RollDice(-1.0f, 1.0f), - Common.Random.RollDice(-1.0f, 1.0f)); + (float)ThreadSafeRandom.Next(-1.0f, 1.0f), + (float)ThreadSafeRandom.Next(-1.0f, 1.0f), + (float)ThreadSafeRandom.Next(-1.0f, 1.0f)); var randomAngle = rng - OffsetDir * Vector3.Dot(OffsetDir, rng); if (Vec.NormalizeCheckSmall(ref randomAngle)) return Vector3.Zero; - var scaled = randomAngle * ((MaxOffset - MinOffset) + MinOffset) * Common.Random.RollDice(0.0f, 1.0f); + var scaled = randomAngle * ((MaxOffset - MinOffset) + MinOffset) * (float)ThreadSafeRandom.Next(0.0f, 1.0f); return scaled; } public Vector3 GetRandomA() { - var rng = Common.Random.RollDice(0.0f, 1.0f); + var rng = ThreadSafeRandom.Next(0.0f, 1.0f); var magnitude = (MaxA - MinA) * rng + MinA; - return A * magnitude; + return A * (float)magnitude; } public Vector3 GetRandomB() { - var rng = Common.Random.RollDice(0.0f, 1.0f); + var rng = ThreadSafeRandom.Next(0.0f, 1.0f); var magnitude = (MaxB - MinB) * rng + MinB; - return B * magnitude; + return B * (float)magnitude; } public Vector3 GetRandomC() { - var rng = Common.Random.RollDice(0.0f, 1.0f); + var rng = ThreadSafeRandom.Next(0.0f, 1.0f); var magnitude = (MaxC - MinC) * rng + MinC; - return C * magnitude; + return C * (float)magnitude; } } } diff --git a/ACViewer/Physics/PhysicsEngine.cs b/ACViewer/Physics/PhysicsEngine.cs index 09b4737..c8b55e9 100644 --- a/ACViewer/Physics/PhysicsEngine.cs +++ b/ACViewer/Physics/PhysicsEngine.cs @@ -43,12 +43,15 @@ public class PhysicsEngine public static PhysicsEngine Instance; public bool Server; - public static List StaticAnimatingObjects; + //public static List StaticAnimatingObjects; // This is not used public static double LastUpdate; static PhysicsEngine() { - StaticAnimatingObjects = new List(); + //StaticAnimatingObjects = new List(); + + Instance = new PhysicsEngine(new ObjectMaint(), new SmartBox()); + Instance.Server = false; } public PhysicsEngine(ObjectMaint objMaint, SmartBox smartBox) @@ -60,15 +63,15 @@ public PhysicsEngine(ObjectMaint objMaint, SmartBox smartBox) Instance = this; } - public static void AddStaticAnimatingObject(PhysicsObj obj) + /*public static void AddStaticAnimatingObject(PhysicsObj obj) // Was used in PhysicsObj.InitDefaults { StaticAnimatingObjects.Add(obj); - } + }*/ - public static void RemoveStaticAnimatingObject(PhysicsObj obj) + /*public static void RemoveStaticAnimatingObject(PhysicsObj obj) // Was used in PhysicsObj.Destroy { StaticAnimatingObjects.Remove(obj); - } + }*/ public static bool SetObjectMovement(PhysicsObj obj, object buffer, int size, int movementTimestamp, int serverControlTimestamp, bool autonomous) { @@ -123,8 +126,8 @@ public void UseTime() if (Player.Equals(obj)) SmartBox.PlayerPhysicsUpdatedCallback(); } - foreach (var obj in StaticAnimatingObjects) - obj.animate_static_object(); + //foreach (var obj in StaticAnimatingObjects) + // obj.animate_static_object(); LastUpdate = PhysicsTimer.CurrentTime; } diff --git a/ACViewer/Physics/PhysicsGlobals.cs b/ACViewer/Physics/PhysicsGlobals.cs index bbee338..befbda3 100644 --- a/ACViewer/Physics/PhysicsGlobals.cs +++ b/ACViewer/Physics/PhysicsGlobals.cs @@ -6,13 +6,15 @@ namespace ACE.Server.Physics { public class PhysicsGlobals { - public static readonly float EPSILON = 0.00019999999f; + public static readonly float EPSILON = 0.0002f; - public static readonly float Gravity = -9.8000002f; + public static readonly float EpsilonSq = EPSILON * EPSILON; - public static readonly float DefaultFriction = 0.94999999f; + public static readonly float Gravity = -9.8f; - public static readonly float DefaultElasticity = 0.050000001f; + public static readonly float DefaultFriction = 0.95f; + + public static readonly float DefaultElasticity = 0.05f; public static readonly float DefaultTranslucency = 0.0f; @@ -48,13 +50,12 @@ public class PhysicsGlobals public static readonly float FloorZ = 0.66417414618662751f; - //public static readonly float DummySphereRadius = 0.1f; - public static readonly float DummySphereRadius = 0.0f; // ?? + public static readonly float DummySphereRadius = 0.1f; - public static readonly Sphere DummySphere = new Sphere(Vector3.Zero, DummySphereRadius); + public static readonly Sphere DummySphere = new Sphere(new Vector3(0, 0, DummySphereRadius), DummySphereRadius); public static readonly Sphere DefaultSortingSphere; - public static readonly float DefaultStepHeight = 0.0099999998f; + public static readonly float DefaultStepHeight = 0.01f; } } diff --git a/ACViewer/Physics/PhysicsObj.cs b/ACViewer/Physics/PhysicsObj.cs index 5e34d2f..a1810cf 100644 --- a/ACViewer/Physics/PhysicsObj.cs +++ b/ACViewer/Physics/PhysicsObj.cs @@ -3,16 +3,17 @@ using System.Linq; using System.Numerics; +using ACE.Common; using ACE.Entity.Enum; +using ACE.Entity.Enum.Properties; using ACE.Server.Physics.Animation; using ACE.Server.Physics.Collision; using ACE.Server.Physics.Combat; using ACE.Server.Physics.Common; -using ACE.Server.Physics.Extensions; using ACE.Server.Physics.Hooks; -using ACE.Server.WorldObjects; +using ACE.Server.Physics.Managers; -using log4net; +using ACViewer.Extensions; using Landblock = ACE.Server.Physics.Common.Landblock; using ObjectGuid = ACE.Entity.ObjectGuid; @@ -24,8 +25,6 @@ namespace ACE.Server.Physics /// public class PhysicsObj { - private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); - public uint ID; public ObjectGuid ObjID; public PartArray PartArray; @@ -81,6 +80,16 @@ public class PhysicsObj public PhysicsObj ProjectileTarget; public double PhysicsTimer_CurrentTime; public bool DatObject = false; + public int Order = 1; + + /// + /// This is managed by MovementManager.MotionInterpreter, and should not be updated anywhere else. + /// + public bool IsAnimating; + + // this is used by the 1991 branch to determine when physics updates need to be run + public bool IsMovingOrAnimating => IsAnimating || !PartArray.Sequence.is_first_cyclic() || CachedVelocity != Vector3.Zero || Velocity != Vector3.Zero || + MovementManager.MotionInterpreter.InterpretedState.HasCommands() || MovementManager.MoveToManager.Initialized; // server public Position RequestPos; @@ -98,15 +107,11 @@ public string Name public CellArray CellArray; public ObjectMaint ObjMaint; - public static List Players; - public bool IsPlayer; + public bool IsPlayer => ID >= 0x50000001 && ID <= 0x5FFFFFFF; public static readonly int UpdateTimeLength = 9; - static PhysicsObj() - { - Players = new List(); - } + public bool IsSticky => PositionManager?.StickyManager != null && PositionManager.StickyManager.TargetID != 0; public PhysicsObj() { @@ -135,6 +140,9 @@ public PhysicsObj() UpdateTime = PhysicsTimer.CurrentTime; UpdateTimes = new int[UpdateTimeLength]; PhysicsTimer_CurrentTime = PhysicsTimer.CurrentTime; + + // todo: only allocate these for server objects + // get rid of 'DatObject', use the existing WeenieObj == null WeenieObj = new WeenieObject(); ObjMaint = new ObjectMaint(this); @@ -144,6 +152,8 @@ public PhysicsObj() } } + // calling DestroyObject() twice can be bad - especially if a new object has been re-created for it, + // such as the player already relogging back in ~PhysicsObj() { DestroyObject(); @@ -157,8 +167,8 @@ public void Destroy() ScriptManager = null; Hooks = null; - if (State.HasFlag(PhysicsState.Static) && (State.HasFlag(PhysicsState.HasDefaultAnim) || State.HasFlag(PhysicsState.HasDefaultScript))) - PhysicsEngine.RemoveStaticAnimatingObject(this); + //if (State.HasFlag(PhysicsState.Static) && (State.HasFlag(PhysicsState.HasDefaultAnim) || State.HasFlag(PhysicsState.HasDefaultScript))) + //PhysicsEngine.RemoveStaticAnimatingObject(this); if (PhysicsScriptTable != null) PhysicsScriptTable.Release(); @@ -227,7 +237,7 @@ public ObjCell AdjustPosition(Position position, Vector3 low_pt, bool dontCreate return ObjCell.GetVisible(position.ObjCellID); } - var visibleCell = (Common.EnvCell)ObjCell.GetVisible(position.ObjCellID); + var visibleCell = (EnvCell)ObjCell.GetVisible(position.ObjCellID); if (visibleCell == null) return null; var point = position.LocalToGlobal(low_pt); @@ -267,8 +277,8 @@ public void CallPES(uint pes, double delta) // long double? return; } var upperBound = (float)delta; - var randp = Common.Random.RollDice(0.0f, upperBound); - var hook = new FPHook(PhysicsHookType.Velocity | PhysicsHookType.MotionTable | PhysicsHookType.Setup, PhysicsTimer_CurrentTime, randp, 0.0f, 1.0f, pes); + var randp = ThreadSafeRandom.Next(0.0f, upperBound); + var hook = new FPHook(PhysicsHookType.CallPES, PhysicsTimer_CurrentTime, randp, 0.0f, 1.0f, pes); Hooks.Add(hook); } @@ -382,11 +392,15 @@ public TransitionState FindObjCollisions(Transition transition) transition.SpherePath.ObstructionEthereal = ethereal; var state = transition.ObjectInfo.State; - var exemption = (WeenieObj == null || !WeenieObj.IsPlayer() || !state.HasFlag(ObjectInfoState.IsPlayer) || - state.HasFlag(ObjectInfoState.IsImpenetrable) || WeenieObj.IsImpenetable() || + + // TODO: reverse this check to make it more readable + // TODO: investigate not initting WeenieObj for DatObjects + var exemption = !( /*WeenieObj == null*/ DatObject || !WeenieObj.IsPlayer() || !state.HasFlag(ObjectInfoState.IsPlayer) || + state.HasFlag(ObjectInfoState.IsImpenetrable) || WeenieObj.IsImpenetrable() || state.HasFlag(ObjectInfoState.IsPK) && WeenieObj.IsPK() || state.HasFlag(ObjectInfoState.IsPKLite) && WeenieObj.IsPKLite()); var missileIgnore = transition.ObjectInfo.MissileIgnore(this); + var isCreature = State.HasFlag(PhysicsState.Missile) || WeenieObj != null && WeenieObj.IsCreature(); //isCreature = false; // hack? @@ -456,11 +470,66 @@ public TransitionState FindObjCollisions_Inner(Transition transition, Transition return result; } + public bool is_touching(PhysicsObj obj) + { + // custom for hotspots + + // possible collision detection object types: + // - bsp + // - sphere + // - cylsphere + + // player has 2 spheres + // hotspots appear to sphere or cylsphere? + + // ensure same landblock + // no cross-landblock collision detection here, + // although it could be added if needed + + if (CurLandblock != obj.CurLandblock) + return false; + + var pSpheres = PartArray.GetSphere(); + + var spheres = obj.PartArray.GetSphere(); + var cylspheres = obj.PartArray.GetCylSphere(); + + if (pSpheres.Count == 0 || (spheres.Count == 0 && cylspheres.Count == 0)) + return false; + + foreach (var pSphere in pSpheres) + { + foreach (var sphere in spheres) + { + // convert to landblock coordinates + var playerSphere = new Sphere(Position.Frame.LocalToGlobal(pSphere.Center), pSphere.Radius); + var globSphere = new Sphere(obj.Position.Frame.LocalToGlobal(sphere.Center), sphere.Radius); + + if (playerSphere.Intersects(globSphere)) + return true; + } + + foreach (var cylsphere in cylspheres) + { + // convert to landblock coordinates + var center = Position.Frame.LocalToGlobal(pSphere.Center); + var lowpoint = obj.Position.Frame.LocalToGlobal(cylsphere.LowPoint); + + var disp = center - lowpoint; + var radsum = pSphere.Radius + cylsphere.Radius - PhysicsGlobals.EPSILON; + + if (cylsphere.CollidesWithSphere(pSphere, disp, radsum)) + return true; + } + } + return false; + } + public SetPositionError ForceIntoCell(ObjCell newCell, Position pos) { if (newCell == null) return SetPositionError.NoCell; set_frame(pos.Frame); - if (!CurCell.Equals(newCell)) + if (CurCell != newCell) { change_cell(newCell); calc_cross_cells(); @@ -472,7 +541,7 @@ public double GetAutonomyBlipDistance() { if ((Position.ObjCellID & 0xFFFF) < 0x100) return 100.0f; - return Players.Contains(this) ? 25.0f : 20.0f; + return IsPlayer ? 25.0f : 20.0f; } public BBox GetBoundingBox() @@ -500,7 +569,7 @@ public double GetMaxConstraintDistance() public PhysicsObj GetObjectA(uint objectID) { - return ObjectMaint.GetObjectA(objectID); + return ServerObjectManager.GetObjectA(objectID); } public float GetRadius() @@ -906,6 +975,15 @@ public void MoveToObject_Internal(PhysicsObj obj, uint topLevelID, float objRadi MovementManager.PerformMovement(mvs); } + public void MoveToPosition(Position pos, MovementParameters movementParams) + { + var mvs = new MovementStruct(); + mvs.Position = new Position(pos); + mvs.Type = MovementType.MoveToPosition; + mvs.Params = movementParams; + MovementManager.PerformMovement(mvs); + } + public void RemoveLinkAnimations() { if (PartArray != null) @@ -955,7 +1033,7 @@ public void SetDiffusion(float start, float end, double delta) return; } var hook = new FPHook(HookType.Velocity, PhysicsTimer.CurrentTime, delta, start, end, 0); - Hooks.Add(hook);*/ + Hooks.AddLast(hook);*/ } @@ -973,7 +1051,7 @@ public void SetLuminosity(float start, float end, double delta) PartArray.SetLuminosityInternal(end); return; } - var hook = new FPHook(PhysicsHookType.MotionTable | PhysicsHookType.Setup, PhysicsTimer_CurrentTime, delta, start, end, 0); + var hook = new FPHook(PhysicsHookType.Luminosity, PhysicsTimer_CurrentTime, delta, start, end, 0); Hooks.Add(hook); } @@ -1007,7 +1085,7 @@ public void SetPartDiffusion(int part, float start, float end, double delta) PartArray.SetPartDiffusionInternal(part, end); return; } - var hook = new FPHook(PhysicsHookType.Velocity | PhysicsHookType.MotionTable, PhysicsTimer_CurrentTime, delta, start, end, part); + var hook = new FPHook(PhysicsHookType.PartDiffusion, PhysicsTimer_CurrentTime, delta, start, end, part); Hooks.Add(hook); } @@ -1027,7 +1105,7 @@ public void SetPartLuminosity(int part, float start, float end, double delta) PartArray.SetPartLuminosityInternal(part, end); return; } - var hook = new FPHook(PhysicsHookType.Velocity | PhysicsHookType.Setup, PhysicsTimer_CurrentTime, delta, start, end, part); + var hook = new FPHook(PhysicsHookType.PartLuminosity, PhysicsTimer_CurrentTime, delta, start, end, part); Hooks.Add(hook); } @@ -1045,7 +1123,7 @@ public void SetPartTranslucency(int partIdx, float startTrans, float endTrans, d PartArray.SetPartTranslucencyInternal(partIdx, endTrans); return; } - var hook = new FPHook(PhysicsHookType.MotionTable, PhysicsTimer_CurrentTime, delta, startTrans, endTrans, partIdx); + var hook = new FPHook(PhysicsHookType.PartTranslucency, PhysicsTimer_CurrentTime, delta, startTrans, endTrans, partIdx); Hooks.Add(hook); } @@ -1066,14 +1144,6 @@ public bool SetPlacementFrameInternal(int frameID) return result; } - public void SetPlayer() - { - if (Players.Contains(this)) - Players.Add(this); - - IsPlayer = true; - } - public SetPositionError SetPosition(SetPosition setPos) { var transition = Transition.MakeTransition(); @@ -1111,6 +1181,9 @@ public bool SetPositionInternal(Transition transition) return true; } + // modified: maintain consistency for Position.Frame in change_cell + set_frame(curPos.Frame); + if (transitCell.Equals(CurCell)) { Position.ObjCellID = curPos.ObjCellID; @@ -1132,7 +1205,7 @@ public bool SetPositionInternal(Transition transition) change_cell(transitCell); } - set_frame(curPos.Frame); + //set_frame(curPos.Frame); var collisions = transition.CollisionInfo; @@ -1223,16 +1296,54 @@ public SetPositionError SetPositionInternal(Position pos, SetPosition setPos, Tr if (transition.SpherePath.CurCell == null) return SetPositionError.NoCell; + // custom: + // test for non-ethereal spell projectile collision on world entry + /*var spellCollide = WeenieObj.WorldObject is SpellProjectile && transition.CollisionInfo.CollideObject.Count > 0 && !PropertyManager.GetBool("spell_projectile_ethereal").Item; + + if (spellCollide) + { + // send initial CO as ethereal + WeenieObj.WorldObject.SetProperty(PropertyBool.Ethereal, true); + }*/ + + if (entering_world && transition.SpherePath.CurPos.Landblock != pos.Landblock) + { + // AdjustToOutside and find_cell_list can inconsistently result in 2 different cells for edges + // if something directly on a landblock edge has resulted in a different landblock from find_cell_list, discard completely + + // this can also (more legitimately) happen even if the object isn't directly on landblock edge, but is near it + // an object trying to spawn on a hillside near a landblock edge might get pushed slightly during spawning, + // resulting in a successful spawn in a neighboring landblock. we don't handle adjustments to the actual landblock reference in here + + // ideally CellArray.LoadCells = false would be passed to find_cell_list to prevent it from even attempting to load an unloaded neighboring landblock + + Console.WriteLine($"{Name} ({ID:X8}) AddPhysicsObj() - {pos.ShortLoc()} resulted in {transition.SpherePath.CurPos.ShortLoc()}, discarding"); + return SetPositionError.NoValidPosition; + } + if (!SetPositionInternal(transition)) return SetPositionError.GeneralFailure; + //if (spellCollide) + //handle_all_collisions(transition.CollisionInfo, false, false); + return SetPositionError.OK; } public SetPositionError SetPositionInternal(SetPosition setPos, Transition transition) { - if (setPos.Flags.HasFlag(SetPositionFlags.RandomScatter)) - return SetScatterPositionInternal(setPos, transition); + var wo = WeenieObj.WorldObject; + + if (wo == null) + return SetPositionError.GeneralFailure; + + //if (setPos.Flags.HasFlag(SetPositionFlags.RandomScatter)) + //return SetScatterPositionInternal(setPos, transition); + /*if (wo.ScatterPos != null) + { + wo.ScatterPos.Flags |= setPos.Flags; + return SetScatterPositionInternal(wo.ScatterPos, transition); + }*/ // frame ref? var result = SetPositionInternal(setPos.Pos, setPos, transition); @@ -1245,10 +1356,12 @@ public SetPositionError SetPositionInternal(SetPosition setPos, Transition trans public SetPositionError SetPositionSimple(Position pos, bool sliding) { - var flags = sliding ? 4114 : 4098; // ?? var setPos = new SetPosition(); setPos.Pos = pos; - setPos.Flags = (SetPositionFlags)flags; + setPos.Flags = SetPositionFlags.Teleport | SetPositionFlags.SendPositionEvent; + + if (sliding) + setPos.Flags |= SetPositionFlags.Slide; return SetPosition(setPos); } @@ -1261,7 +1374,7 @@ public void SetScale(float scale, double delta = 0.0) PartArray.SetScaleInternal(new Vector3(scale, scale, scale)); return; } - var hook = new FPHook((PhysicsHookType)0, PhysicsTimer_CurrentTime, delta, Scale, scale, 0); + var hook = new FPHook(PhysicsHookType.Scale, PhysicsTimer_CurrentTime, delta, Scale, scale, 0); Hooks.Add(hook); } @@ -1272,22 +1385,97 @@ public void SetScaleStatic(float scale) PartArray.SetScaleInternal(new Vector3(scale, scale, scale)); } + public static float ScatterThreshold_Z = 10.0f; + public SetPositionError SetScatterPositionInternal(SetPosition setPos, Transition transition) { var result = SetPositionError.GeneralFailure; for (var i = 0; i < setPos.NumTries; i++) { - Position newPos = null; - var origin = newPos.Frame.Origin; - newPos = setPos.Pos; // ?? + var newPos = new Position(setPos.Pos); + + newPos.Frame.Origin.X += (float)ThreadSafeRandom.Next(-1.0f, 1.0f) * setPos.RadX; + newPos.Frame.Origin.Y += (float)ThreadSafeRandom.Next(-1.0f, 1.0f) * setPos.RadY; + + // customized + if ((newPos.ObjCellID & 0xFFFF) < 0x100) + { + newPos.Frame.Origin.X = MathExtensions.Clamp(newPos.Frame.Origin.X, 0.5f, 191.5f); + newPos.Frame.Origin.Y = MathExtensions.Clamp(newPos.Frame.Origin.Y, 0.5f, 191.5f); + } + + // get cell for this position + var indoors = (newPos.ObjCellID & 0xFFFF) >= 0x100; + + if (!indoors) + { + LandDefs.AdjustToOutside(newPos); + + // ensure walkable slope + var landcell = (LandCell)LScape.get_landcell(newPos.ObjCellID); - newPos.Frame.Origin.X += Common.Random.RollDice(-1.0f, 1.0f) * setPos.RadX; - newPos.Frame.Origin.Y += Common.Random.RollDice(-1.0f, 1.0f) * setPos.RadY; + Polygon walkable = null; + var terrainPoly = landcell.find_terrain_poly(newPos.Frame.Origin, ref walkable); + if (walkable == null || !is_valid_walkable(walkable.Plane.Normal)) continue; + + // account for buildings + // if original position was outside, and scatter position is in a building, should we even try to spawn? + // compare: rabbits occasionally spawning in buildings in yaraq, + // vs. lich tower @ 3D31FFFF + + var sortCell = LScape.get_landcell(newPos.ObjCellID) as SortCell; + if (sortCell == null || !sortCell.has_building()) + { + // set to ground pos + var landblock = LScape.get_landblock(newPos.ObjCellID); + var groundZ = landblock.GetZ(newPos.Frame.Origin) + 0.05f; + + if (Math.Abs(newPos.Frame.Origin.Z - groundZ) > ScatterThreshold_Z) + Console.WriteLine($"{Name} ({ID:X8}).SetScatterPositionInternal() - tried to spawn outdoor object @ {newPos} ground Z {groundZ} (diff: {newPos.Frame.Origin.Z - groundZ}), investigate ScatterThreshold_Z"); + else + newPos.Frame.Origin.Z = groundZ; + + } + //else + //indoors = true; + + /*if (sortCell != null && sortCell.has_building()) + { + var building = sortCell.Building; + + var minZ = building.GetMinZ(); + + if (minZ > 0 && minZ < float.MaxValue) + newPos.Frame.Origin.Z += minZ; + + //indoors = true; + }*/ + } + else + { + var landblock = LScape.get_landblock(newPos.ObjCellID); + var envcells = landblock.get_envcells(); + var found = false; + foreach (var envCell in envcells) + { + if (envCell.point_in_cell(newPos.Frame.Origin)) + { + newPos.ObjCellID = envCell.ID; + found = true; + break; + } + } + if (!found) continue; + } result = SetPositionInternal(newPos, setPos, transition); if (result == SetPositionError.OK) break; } + + //if (result != SetPositionError.OK) + //Console.WriteLine($"Couldn't spawn {Name} after {setPos.NumTries} retries @ {setPos.Pos}"); + return result; } @@ -1307,7 +1495,7 @@ public void SetTranslucency(float translucency, double delta) if (PartArray != null) PartArray.SetTranslucencyInternal(Translucency); return; } - var hook = new FPHook(PhysicsHookType.Setup, PhysicsTimer_CurrentTime, delta, 0.0f, translucency, 0); + var hook = new FPHook(PhysicsHookType.Translucency, PhysicsTimer_CurrentTime, delta, 0.0f, translucency, 0); Hooks.Add(hook); } @@ -1320,7 +1508,7 @@ public void SetTranslucency2(float startTrans, float endTrans, double delta) if (PartArray != null) PartArray.SetTranslucencyInternal(startTrans); return; } - var hook = new FPHook(PhysicsHookType.Setup, PhysicsTimer_CurrentTime, delta, startTrans, endTrans, 0); + var hook = new FPHook(PhysicsHookType.Translucency, PhysicsTimer_CurrentTime, delta, startTrans, endTrans, 0); Hooks.Add(hook); } @@ -1406,14 +1594,15 @@ public void TurnToHeading(MovementParameters movementParams) MovementManager.PerformMovement(mvs); } - public void TurnToObject(uint objectID, MovementParameters movementParams) + public bool TurnToObject(PhysicsObj obj, MovementParameters movementParams) { - if (ObjMaint == null) return; - var obj = ObjectMaint.GetObjectA(objectID); - if (obj == null) return; + if (obj == null) return false; + var parent = obj.Parent != null ? obj.Parent : obj; - TurnToObject_Internal(objectID, parent.ID, movementParams); + TurnToObject_Internal(obj.ID, parent.ID, movementParams); + + return true; } public void TurnToObject_Internal(uint objectID, uint topLevelID, MovementParameters movementParams) @@ -1440,7 +1629,7 @@ public void TurnToObject_Internal(uint objectID, uint topLevelID, MovementParame public void UpdateChild(PhysicsObj childObj, int partIdx, AFrame childFrame) { - var frame = partIdx == -1 || partIdx >= PartArray.NumParts ? + var frame = partIdx == -1 ? AFrame.Combine(Position.Frame, childFrame) : AFrame.Combine(PartArray.Parts[partIdx].Pos.Frame, childFrame); childObj.set_frame(frame); @@ -1457,12 +1646,14 @@ public void UpdateChildrenInternal() UpdateChild(Children.Objects[i], Children.PartNumbers[i], Children.Frames[i]); } + public int InitialUpdates; + public void UpdateObjectInternal(double quantum) { - if (!TransientState.HasFlag(TransientStateFlags.Active) || CurCell == null) + if ((TransientState & TransientStateFlags.Active) == 0 || CurCell == null) return; - if (TransientState.HasFlag(TransientStateFlags.CheckEthereal)) + if ((TransientState & TransientStateFlags.CheckEthereal) != 0) set_ethereal(false, false); JumpedThisFrame = false; @@ -1475,21 +1666,30 @@ public void UpdateObjectInternal(double quantum) { CachedVelocity = Vector3.Zero; set_frame(newPos.Frame); + InitialUpdates++; } else { - if (State.HasFlag(PhysicsState.AlignPath)) + if ((State & PhysicsState.AlignPath) != 0) { var diff = newPos.Frame.Origin - Position.Frame.Origin; - newPos.Frame.set_vector_heading(diff.Normalize()); + newPos.Frame.set_vector_heading(Vector3.Normalize(diff)); } - else if (State.HasFlag(PhysicsState.Sledding) && Velocity != Vector3.Zero) - newPos.Frame.set_vector_heading(Velocity.Normalize()); + else if ((State & PhysicsState.Sledding) != 0 && Velocity != Vector3.Zero) + newPos.Frame.set_vector_heading(Vector3.Normalize(Velocity)); + } + + if (GetBlockDist(Position, newPos) > 1) + { + Console.WriteLine($"WARNING: failed transition for {Name} from {Position} to {newPos}"); + return; } var transit = transition(Position, newPos, false); - if (transit != null) + + // temporarily modified while debug path is examined + if (transit != null && transit.SpherePath.CurCell != null) { CachedVelocity = Position.GetOffset(transit.SpherePath.CurPos) / (float)quantum; @@ -1497,6 +1697,11 @@ public void UpdateObjectInternal(double quantum) } else { + if (IsPlayer) + Console.WriteLine($"{Name} ({ID:X8}).UpdateObjectInternal({quantum}) - failed transition from {Position} to {newPos}"); + else if (transit != null && transit.SpherePath.CurCell == null) + Console.WriteLine($"{Name} ({ID:X8}).UpdateObjectInternal({quantum}) - avoided CurCell=null from {Position} to {newPos}"); + newPos.Frame.Origin = Position.Frame.Origin; set_initial_frame(newPos.Frame); CachedVelocity = Vector3.Zero; @@ -1504,12 +1709,13 @@ public void UpdateObjectInternal(double quantum) } else { - if (MovementManager == null && TransientState.HasFlag(TransientStateFlags.OnWalkable)) + if (MovementManager == null && (TransientState & TransientStateFlags.OnWalkable) != 0) TransientState &= ~TransientStateFlags.Active; newPos.Frame.Origin = Position.Frame.Origin; set_frame(newPos.Frame); CachedVelocity = Vector3.Zero; + InitialUpdates++; } if (DetectionManager != null) DetectionManager.CheckDetection(); @@ -1527,14 +1733,52 @@ public void UpdateObjectInternal(double quantum) if (ScriptManager != null) ScriptManager.UpdateScripts(); } - public void UpdateObjectInternalServer(double quantum) + public static int GetBlockDist(Position a, Position b) + { + // protection, figure out FastTeleport state + if (a == null || b == null) + return 0; + + return GetBlockDist(a.ObjCellID, b.ObjCellID); + } + + public static int GetBlockDist(uint a, uint b) + { + var lbx_a = a >> 24; + var lby_a = (a >> 16) & 0xFF; + + var lbx_b = b >> 24; + var lby_b = (b >> 16) & 0xFF; + + var dx = (int)Math.Abs((int)lbx_a - lbx_b); + var dy = (int)Math.Abs((int)lby_a - lby_b); + + return Math.Max(dx, dy); + } + + /// + /// This is for legacy movement system + /// + public bool UpdateObjectInternalServer(double quantum) { + //var offsetFrame = new AFrame(); + //UpdatePhysicsInternal((float)quantum, ref offsetFrame); + if (GetBlockDist(Position, RequestPos) > 1) + { + Console.WriteLine($"WARNING: failed transition for {Name} from {Position} to {RequestPos}"); + return false; + } + + var requestCell = RequestPos.ObjCellID; + var transit = transition(Position, RequestPos, false); if (transit != null) { CachedVelocity = Position.GetOffset(transit.SpherePath.CurPos) / (float)quantum; SetPositionInternal(transit); } + else + Console.WriteLine($"{Name}.UpdateObjectInternalServer({quantum}) - failed transition from {Position} to {RequestPos}"); if (DetectionManager != null) DetectionManager.CheckDetection(); @@ -1549,6 +1793,8 @@ public void UpdateObjectInternalServer(double quantum) if (ParticleManager != null) ParticleManager.UpdateParticles(); if (ScriptManager != null) ScriptManager.UpdateScripts(); + + return requestCell >> 16 != 0x18A || CurCell?.ID >> 16 == requestCell >> 16; } public void UpdateAnimationInternal(double quantum) @@ -1594,7 +1840,7 @@ public void UpdatePhysicsInternal(float quantum, ref AFrame frameOffset) { if (velocity_mag2 > PhysicsGlobals.MaxVelocitySquared) { - Velocity = Velocity.Normalize() * PhysicsGlobals.MaxVelocity; + Velocity = Vector3.Normalize(Velocity) * PhysicsGlobals.MaxVelocity; velocity_mag2 = PhysicsGlobals.MaxVelocitySquared; } // todo: collision normals @@ -1635,11 +1881,6 @@ public void UpdatePositionInternal(double quantum, ref AFrame newFrame) process_hooks(); } - public void UpdatePositionInternalServer(double quantum, ref AFrame offsetFrame) - { - UpdatePositionInternal(quantum, ref offsetFrame); - } - public void UpdateViewerDistance(float cypt, Vector3 heading) { if (PartArray != null) PartArray.UpdateViewerDistance(cypt, heading); @@ -1827,37 +2068,6 @@ public void attack(AttackCone attackCone) report_attacks(attackInfo); } - public AtkCollisionProfile build_collision_profile(PhysicsObj obj, bool prev_has_contact, Vector3 velocityCollide) - { - AtkCollisionProfile profile = null; - - if (!State.HasFlag(PhysicsState.Missile)) - profile = (AtkCollisionProfile)build_collision_profile(obj, velocityCollide, prev_has_contact, - obj.State.HasFlag(PhysicsState.Missile), obj.TransientState.HasFlag(TransientStateFlags.Contact)); - else - { - profile = new AtkCollisionProfile(); - profile.ID = obj.ID; - profile.Part = -1; - profile.Location = obj.Position.DetermineQuadrant(obj.GetHeight(), Position); - } - return profile; - } - - public static ObjCollisionProfile build_collision_profile(PhysicsObj obj, Vector3 velocity, bool amIInContact, bool objIsMissile, bool objHasContact) - { - if (obj.WeenieObj != null /* && vfptr */) return null; - var prof = new ObjCollisionProfile(); - prof.Velocity = velocity; - if (objIsMissile) - prof.Flags |= ObjCollisionProfileFlags.Missile; - if (objHasContact) - prof.Flags |= ObjCollisionProfileFlags.Contact; - if (amIInContact) - prof.Flags |= ObjCollisionProfileFlags.MyContact; - return prof; - } - public void calc_acceleration() { if (TransientState.HasFlag(TransientStateFlags.Contact) && TransientState.HasFlag(TransientStateFlags.OnWalkable)) @@ -1970,7 +2180,7 @@ public void change_cell_server(ObjCell newCell) enter_cell_server(newCell); } - public int check_attack(Position attackerPos, float attackerScale, AttackCone attackCone, float attackerAttackRadius) + public Quadrant check_attack(Position attackerPos, float attackerScale, AttackCone attackCone, float attackerAttackRadius) { if (Parent != null || State.HasFlag(PhysicsState.IgnoreCollisions) || State.HasFlag(PhysicsState.ReportCollisionsAsEnvironment)) return 0; @@ -2059,13 +2269,84 @@ public void destroy_particle_manager() ParticleManager = null; } + /// + /// This is to mitigate possible decal crashes w/ CO messages being sent + /// for objects when the client landblock is very early in the loading state + /// + public static TimeSpan TeleportCreateObjectDelay = TimeSpan.FromSeconds(1); + public void enqueue_objs(IEnumerable newlyVisible) { - /*var player = WeenieObj.WorldObject as Player; - if (player == null) return; + /*if (!IsPlayer || !(WeenieObj.WorldObject is Player player)) + return; - foreach (var obj in newlyVisible) - player.TrackObject(obj.WeenieObj.WorldObject);*/ + if (DateTime.UtcNow - player.LastTeleportTime < TeleportCreateObjectDelay) + { + var actionChain = new ActionChain(); + actionChain.AddDelaySeconds(TeleportCreateObjectDelay.TotalSeconds); + actionChain.AddAction(player, () => + { + foreach (var obj in newlyVisible) + { + var wo = obj.WeenieObj.WorldObject; + if (wo != null) + player.TrackObject(wo, true); + } + }); + actionChain.EnqueueChain(); + } + else + { + foreach (var obj in newlyVisible) + { + var wo = obj.WeenieObj.WorldObject; + + if (wo == null) + continue; + + if (wo.Teleporting) + { + // ensure post-teleport position is sent + var actionChain = new ActionChain(); + actionChain.AddDelayForOneTick(); + actionChain.AddAction(player, () => player.TrackObject(wo)); + actionChain.EnqueueChain(); + } + else + player.TrackObject(wo); + } + }*/ + } + + public void enqueue_obj(PhysicsObj newlyVisible) + { + /*if (!IsPlayer || !(WeenieObj.WorldObject is Player player)) + return; + + var wo = newlyVisible.WeenieObj.WorldObject; + if (wo == null) + return; + + if (DateTime.UtcNow - player.LastTeleportTime < TeleportCreateObjectDelay) + { + var actionChain = new ActionChain(); + actionChain.AddDelaySeconds(TeleportCreateObjectDelay.TotalSeconds); + actionChain.AddAction(player, () => player.TrackObject(wo, true)); + actionChain.EnqueueChain(); + } + else + { + if (wo.Teleporting) + { + // ensure post-teleport position is sent + var actionChain = new ActionChain(); + actionChain.AddDelayForOneTick(); + actionChain.AddAction(player, () => player.TrackObject(wo)); + actionChain.EnqueueChain(); + } + else + player.TrackObject(wo); + }*/ } public void enter_cell(ObjCell newCell) @@ -2076,7 +2357,7 @@ public void enter_cell(ObjCell newCell) child.enter_cell(newCell); CurCell = newCell; - Position.ObjCellID = newCell.ID; + Position.ObjCellID = newCell.ID; // warning: Position will be in an inconsistent state here, until set_frame() is run! if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetCellID(newCell.ID); @@ -2090,46 +2371,48 @@ public void enter_cell(ObjCell newCell) public void enter_cell_server(ObjCell newCell) { + //Console.WriteLine($"{Name}.enter_cell_server({newCell.ID:X8})"); + enter_cell(newCell); - RequestPos.ObjCellID = newCell.ID; + RequestPos.ObjCellID = newCell.ID; // document this control flow better - // handle indoor cell visibility - //if ((newCell.ID & 0xFFFF) >= 0x100) - //{ - if (IsPlayer) - { - // player entering new indoor cell - var newlyVisible = handle_visible_cells(); - enqueue_objs(newlyVisible); - } + // sync location for initial CO + //if (entering_world) + //WeenieObj.WorldObject.SyncLocation(); - foreach (var player in Players) - { - // is other player in same indoor landblock? - if (player.CurCell != null && (player.CurCell.ID & 0xFFFF) >= 0x100 && player.CurCell.ID >> 16 == newCell.ID >> 16) - { - var envCell = player.CurCell as EnvCell; - if (envCell != null) - { - if (envCell.VisibleCells.ContainsKey(newCell.ID & 0xFFFF)) - { - //Console.WriteLine($"Informing {player.WeenieObj.WorldObject.Name} about {WeenieObj.WorldObject.Name}"); + // handle self + if (IsPlayer) + { + var newlyVisible = handle_visible_cells(); + enqueue_objs(newlyVisible); + } + else + { + handle_visible_cells_non_player(); + } - // inform other player about this object - var newlyVisible = player.handle_visible_cells(); - player.enqueue_objs(newlyVisible); - } - } - } - } - //} - //Console.WriteLine("Cell: " + newCell.ID.ToString("X8") + " (" + newCell.ShadowObjectList.Count + ")"); + // handle known players + foreach (var player in ObjMaint.GetKnownPlayersValues()) + { + var added = player.handle_visible_obj(this); + + if (added) + player.enqueue_obj(this); + } } + public bool entering_world; + public bool enter_world(Position pos) { + entering_world = true; + store_position(pos); - return enter_world(true); + bool slide = ProjectileTarget == null /*|| WeenieObj.WorldObject is SpellProjectile*/; + var result = enter_world(slide); + + entering_world = false; + return result; } public bool enter_world(bool slide) @@ -2165,7 +2448,7 @@ public bool ethereal_check_for_collisions() { foreach (var shadowObj in ShadowObjects.Values) { - if (shadowObj.Cell.check_collisions(this)) + if (shadowObj.Cell != null && shadowObj.Cell.check_collisions(this)) return true; } return false; @@ -2230,6 +2513,21 @@ public double get_distance_to_object(PhysicsObj obj, bool use_cyls) return Position.CylinderDistance(curRadius, curHeight, Position, radius, height, obj.Position); } + // custom, based on above + public double get_distance_sq_to_object(PhysicsObj obj, bool use_cyls) + { + if (!use_cyls) + return Position.DistanceSquared(obj.Position); + + var height = obj.PartArray != null ? obj.PartArray.GetHeight() : 0.0f; + var radius = obj.PartArray != null ? obj.PartArray.GetRadius() : 0.0f; + + var curHeight = PartArray != null ? PartArray.GetHeight() : 0.0f; + var curRadius = PartArray != null ? PartArray.GetRadius() : 0.0f; + + return Position.CylinderDistanceSq(curRadius, curHeight, Position, radius, height, obj.Position); + } + public AFrame get_frame() { return Position.Frame; @@ -2401,7 +2699,7 @@ public bool handle_all_collisions(CollisionInfo collisions, bool prev_has_contac } else { - Velocity = Vector3.Zero; + Velocity = Vector3.Zero; // gets objects stuck in falling state? if (collisions.FramesStationaryFall == 3) { TransientState &= ~TransientStateFlags.StationaryComplete; @@ -2445,8 +2743,8 @@ public List handle_visible_cells() //Console.WriteLine(visibleObject.Name); // get the difference between current and previous visible - var newlyVisible = visibleObjects.Except(ObjMaint.VisibleObjectTable.Values).ToList(); - var newlyOccluded = ObjMaint.VisibleObjectTable.Values.Except(visibleObjects).ToList(); + //var newlyVisible = visibleObjects.Except(ObjMaint.VisibleObjects.Values).ToList(); + var newlyOccluded = ObjMaint.GetVisibleObjectsValues().Except(visibleObjects).ToList(); //Console.WriteLine("Newly visible objects: " + newlyVisible.Count); //Console.WriteLine("Newly occluded objects: " + newlyOccluded.Count); //foreach (var obj in newlyOccluded) @@ -2454,6 +2752,7 @@ public List handle_visible_cells() // add newly visible objects, and get the previously unknowns var createObjs = ObjMaint.AddVisibleObjects(visibleObjects); + //Console.WriteLine("Create objects: " + createObjs.Count); /*if (createObjs.Count != newlyVisible.Count) { Console.WriteLine($"Create objs differs from newly visible ({createObjs.Count} vs. {newlyVisible.Count})"); @@ -2471,6 +2770,72 @@ public List handle_visible_cells() return createObjs; } + public void handle_visible_cells_non_player() + { + if (WeenieObj.IsMonster) + { + // players and combat pets + var visibleTargets = ObjMaint.GetVisibleObjects(CurCell, ObjectMaint.VisibleObjectType.AttackTargets); + + var newTargets = ObjMaint.AddVisibleTargets(visibleTargets); + } + else + { + // everything except monsters + // usually these are server objects whose position never changes + var knownPlayers = ObjectMaint.InitialClamp ? ObjMaint.GetVisibleObjectsDist(CurCell, ObjectMaint.VisibleObjectType.Players) + : ObjMaint.GetVisibleObjects(CurCell, ObjectMaint.VisibleObjectType.Players); + + ObjMaint.AddKnownPlayers(knownPlayers); + } + + if (WeenieObj.IsCombatPet) + { + var visibleMonsters = ObjMaint.GetVisibleObjects(CurCell, ObjectMaint.VisibleObjectType.AttackTargets); + + var newTargets = ObjMaint.AddVisibleTargets(visibleMonsters); + } + } + + public bool handle_visible_obj(PhysicsObj obj) + { + if (CurCell == null || obj.CurCell == null) + { + if (CurCell == null) + Console.WriteLine($"{Name}.handle_visible_obj({obj.Name}): CurCell null"); + else + Console.WriteLine($"{Name}.handle_visible_obj({obj.Name}): obj.CurCell null"); + + return false; + } + + var isVisible = CurCell.IsVisible(obj.CurCell); + + if (isVisible) + { + var prevKnown = ObjMaint.KnownObjectsContainsKey(obj.ID); + + var newlyVisible = ObjMaint.AddVisibleObject(obj); + + if (newlyVisible) + { + ObjMaint.AddKnownObject(obj); + ObjMaint.RemoveObjectToBeDestroyed(obj); + } + + return !prevKnown && newlyVisible; + } + else + { + var newlyOccluded = ObjMaint.VisibleObjectsContainsKey(obj.ID); + + if (newlyOccluded) + ObjMaint.AddObjectToBeDestroyed(obj); + + return false; + } + } + public bool is_completely_visible() { if (CurCell == null || NumShadowObjects == 0) @@ -2492,7 +2857,7 @@ public bool is_newer(int timestamp, int newTime) return timestamp < newTime; } - public bool is_valid_walkable(Vector3 normal) + public static bool is_valid_walkable(Vector3 normal) { return normal.Z >= PhysicsGlobals.FloorZ; } @@ -2579,28 +2944,29 @@ public static PhysicsObj makeObject(PhysicsObj template) return obj; } - public static PhysicsObj makeObject(uint dataDID, uint objectIID, bool dynamic) + public bool IsSightObj; + + public static PhysicsObj makeObject(uint dataDID, uint objectIID, bool dynamic, bool sightObj = false) { var obj = new PhysicsObj(); obj.InitObjectBegin(objectIID, dynamic); obj.InitPartArrayObject(dataDID, true); obj.InitObjectEnd(); + + // for direct visibility testing + obj.IsSightObj = sightObj; + return obj; } public static PhysicsObj makeParticleObject(int numParts, Sphere sortingSphere) { var particle = new PhysicsObj(); - particle.State = PhysicsState.Static | PhysicsState.ParticleEmitter; + particle.State = PhysicsState.Static | PhysicsState.ReportCollisions; particle.PartArray = PartArray.CreateParticle(particle, numParts, sortingSphere); return particle; } - public bool motions_pending() - { - return MovementManager != null && MovementManager.motions_pending(); - } - public bool movement_is_autonomous() { return LastMoveWasAutonomous; @@ -2758,32 +3124,32 @@ public bool prepare_to_leave_visibility() return true; } - public void process_fp_hook(int type, float curr_value, Object userData) + public void process_fp_hook(PhysicsHookType type, float curr_value, Object userData) { switch (type) { - case 0: + case PhysicsHookType.Scale: SetScaleStatic(curr_value); break; - case 1: + case PhysicsHookType.Translucency: SetTranslucencyInternal(curr_value); break; - case 2: + case PhysicsHookType.PartTranslucency: if (PartArray != null) PartArray.SetPartTranslucencyInternal((int)userData, curr_value); break; - case 3: + case PhysicsHookType.Luminosity: if (PartArray != null) PartArray.SetLuminosityInternal(curr_value); break; - case 5: // combined types? + case PhysicsHookType.PartLuminosity: if (PartArray != null) PartArray.SetPartLuminosityInternal((int)userData, curr_value); break; - case 4: + case PhysicsHookType.Diffusion: if (PartArray != null) PartArray.SetDiffusionInternal(curr_value); break; - case 6: + case PhysicsHookType.PartDiffusion: if (PartArray != null) PartArray.SetPartDiffusionInternal((int)userData, curr_value); break; - case 7: + case PhysicsHookType.CallPES: CallPESInternal((uint)userData, curr_value); break; } @@ -2860,7 +3226,7 @@ public void remove_parts(ObjCell objCell) public void remove_shadows_from_cells() { - foreach (var shadowObj in ShadowObjects.Values.ToList()) + foreach (var shadowObj in ShadowObjects.Values) { if (shadowObj.Cell == null) continue; var cell = shadowObj.Cell; @@ -2940,7 +3306,7 @@ public void report_collision_start() foreach (var objectID in CollisionTable.Keys) { - var obj = ObjectMaint.GetObjectA(objectID); + var obj = ServerObjectManager.GetObjectA(objectID); if (obj != null) report_object_collision(obj, TransientState.HasFlag(TransientStateFlags.Contact)); } @@ -2977,35 +3343,36 @@ public bool report_object_collision(PhysicsObj obj, bool prev_has_contact) var velocityCollide = Velocity - obj.Velocity; bool collided = false; + if (!obj.State.HasFlag(PhysicsState.IgnoreCollisions)) { if (State.HasFlag(PhysicsState.ReportCollisions) && WeenieObj != null) { var profile = build_collision_profile(obj, prev_has_contact, velocityCollide); + WeenieObj.DoCollision(profile, ObjID, obj); - collided = true; - if (!CollisionTable.ContainsKey(obj.ID)) - CollisionTable.Add(obj.ID, new CollisionRecord() { TouchedTime = PhysicsTimer.CurrentTime }); - else - CollisionTable[obj.ID] = new CollisionRecord() { TouchedTime = PhysicsTimer.CurrentTime }; + collided = true; } if (State.HasFlag(PhysicsState.Missile)) State &= ~(PhysicsState.Missile | PhysicsState.AlignPath | PhysicsState.PathClipped); } - if (obj.State.HasFlag(PhysicsState.ReportCollisions) && !State.HasFlag(PhysicsState.IgnoreCollisions) && WeenieObj != null) + if (obj.State.HasFlag(PhysicsState.ReportCollisions) && !State.HasFlag(PhysicsState.IgnoreCollisions) && obj.WeenieObj != null) { - var profile = obj.build_collision_profile(this, prev_has_contact, velocityCollide); + // acclient might have a bug here, + // prev_has_contact and missile state params swapped? + var profile = obj.build_collision_profile(this, obj.TransientState.HasFlag(TransientStateFlags.Contact), velocityCollide); + + // ObjID and obj are custom parameters added by ace + // if obj. and obj) are the same, all of these calls seem to effectively get dropped + // is this intended for 1-way collisions?? obj.WeenieObj.DoCollision(profile, ObjID, obj); - collided = true; - if (!CollisionTable.ContainsKey(obj.ID)) - CollisionTable.Add(obj.ID, new CollisionRecord() { TouchedTime = PhysicsTimer.CurrentTime }); - else - CollisionTable[obj.ID] = new CollisionRecord() { TouchedTime = PhysicsTimer.CurrentTime }; + collided = true; } + return collided; } @@ -3013,7 +3380,7 @@ public bool report_object_collision_end(uint objectID) { if (ObjMaint != null) { - var collision = ObjectMaint.GetObjectA(objectID); + var collision = ServerObjectManager.GetObjectA(objectID); if (collision != null) { if (!collision.State.HasFlag(PhysicsState.ReportCollisionsAsEnvironment)) @@ -3033,6 +3400,30 @@ public bool report_object_collision_end(uint objectID) return false; } + public ObjCollisionProfile build_collision_profile(PhysicsObj obj, bool amIInContact, Vector3 velocityCollide) + { + if (State.HasFlag(PhysicsState.Missile)) + { + return new AtkCollisionProfile(obj.ID, -1, obj.Position.DetermineQuadrant(obj.GetHeight(), Position)); + } + else + { + return build_collision_profile(obj, velocityCollide, amIInContact, obj.State.HasFlag(PhysicsState.Missile), obj.TransientState.HasFlag(TransientStateFlags.Contact)); + } + } + + public ObjCollisionProfile build_collision_profile(PhysicsObj obj, Vector3 velocity, bool amIInContact, bool objIsMissile, bool objHasContact) + { + if (WeenieObj == null) + return null; + + var profile = new ObjCollisionProfile(obj.ID, velocity, objIsMissile, objHasContact, amIInContact); + + WeenieObj.InqCollisionProfile(profile); + + return profile; + } + public void rotate(Vector3 offset) { var offsetFrame = new AFrame(); @@ -3052,7 +3443,7 @@ public bool set_active(bool active) if (State.HasFlag(PhysicsState.Static)) return false; - if (TransientState.HasFlag(TransientStateFlags.Active)) + if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState |= TransientStateFlags.Active; @@ -3067,7 +3458,7 @@ public bool set_active(bool active) public bool is_active() { - return TransientState.HasFlag(TransientStateFlags.Active); + return (TransientState & TransientStateFlags.Active) != 0; } public void set_current_pos(Position newPos) @@ -3078,8 +3469,20 @@ public void set_current_pos(Position newPos) if (CurCell == null || CurCell.ID != Position.ObjCellID) { var newCell = LScape.get_landcell(newPos.ObjCellID); + + /*if (WeenieObj.WorldObject.IsPlayer && player.LastContact && newCell is LandCell landCell) + { + Polygon walkable = null; + if (landCell.find_terrain_poly(newPos.Frame.Origin, ref walkable)) + { + ContactPlaneCellID = newPos.ObjCellID; + ContactPlane = walkable.Plane; + } + }*/ change_cell_server(newCell); } + + CachedVelocity = requestCachedVelocity; } /// @@ -3209,14 +3612,16 @@ public bool set_ethereal(bool ethereal, bool sendEvent) State &= ~PhysicsState.Ethereal; - if (Parent != null || CurCell != null || ethereal_check_for_collisions()) + if (Parent != null || CurCell == null || !ethereal_check_for_collisions()) { TransientState &= ~TransientStateFlags.CheckEthereal; return true; } - TransientState |= TransientStateFlags.CheckEthereal; + // error path - go back to ethereal, start loop in CheckEthereal state State |= PhysicsState.Ethereal; + TransientState |= TransientStateFlags.CheckEthereal; + return false; } @@ -3366,7 +3771,7 @@ public void set_object_guid(ObjectGuid guid) ObjID = guid; ID = guid.Full; - ObjectMaint.AddServerObject(this); + ServerObjectManager.AddServerObject(this); } /// @@ -3473,6 +3878,8 @@ public bool set_parent(PhysicsObj obj, int partIdx, AFrame frame) return true; } + private Vector3 requestCachedVelocity; + /// /// Sets the requested position to the AutonomousPosition /// received from the client @@ -3493,6 +3900,8 @@ public void set_request_pos(Vector3 pos, Quaternion rotation, ObjCell cell, uint RequestPos.ObjCellID = RequestPos.GetCell(CurCell.ID); else RequestPos.ObjCellID = cell.ID; + + requestCachedVelocity = CachedVelocity; } public void set_sequence_animation(int animID, bool interrupt, int startFrame, float framerate) @@ -3591,7 +4000,7 @@ public void stick_to_object(uint objectID) MakePositionManager(); if (ObjMaint == null) return; - var objectA = ObjectMaint.GetObjectA(objectID); + var objectA = ServerObjectManager.GetObjectA(objectID); if (objectA == null) return; if (objectA.Parent != null) objectA = Parent; @@ -3648,11 +4057,8 @@ public bool track_object_collision(PhysicsObj obj, bool prev_has_contact) if (CollisionTable == null) CollisionTable = new Dictionary(); - //if (!CollisionTable.ContainsKey(obj.ID)) - // CollisionTable.Add(obj.ID, null); + CollisionTable[obj.ID] = new CollisionRecord(PhysicsTimer.CurrentTime, obj.State.HasFlag(PhysicsState.Ethereal)); - //if (!CollisionTable.ContainsKey(obj.ID)) return false; - //CollisionTable.Remove(obj.ID); return report_object_collision(obj, prev_has_contact); } @@ -3677,11 +4083,11 @@ public Transition transition(Position oldPos, Position newPos, bool adminMove) trans.InitPath(CurCell, oldPos, newPos); - if (TransientState.HasFlag(TransientStateFlags.StationaryStuck)) + if ((TransientState & TransientStateFlags.StationaryStuck) != 0) trans.CollisionInfo.FramesStationaryFall = 3; - else if (TransientState.HasFlag(TransientStateFlags.StationaryStop)) + else if ((TransientState & TransientStateFlags.StationaryStop) != 0) trans.CollisionInfo.FramesStationaryFall = 2; - else if (TransientState.HasFlag(TransientStateFlags.StationaryFall)) + else if ((TransientState & TransientStateFlags.StationaryFall) != 0) trans.CollisionInfo.FramesStationaryFall = 1; var validPos = trans.FindValidPosition(); @@ -3834,18 +4240,142 @@ public void ShowPendingMotions() Console.WriteLine($"{(MotionCommand)motion.Motion}"); } - public void update_object_server(bool forcePos = true) + public bool motions_pending() + { + return IsAnimating; + } + + /// + /// This is for legacy movement system + /// + public bool update_object_server(bool forcePos = true) { var deltaTime = PhysicsTimer.CurrentTime - UpdateTime; - UpdateObjectInternalServer(deltaTime); - if (forcePos) + var wo = WeenieObj.WorldObject; + var success = true; + //if (wo != null && !wo.Teleporting) + success = UpdateObjectInternalServer(deltaTime); + + if (forcePos && success) set_current_pos(RequestPos); // temp for players - CachedVelocity = Vector3.Zero; + if ((TransientState & TransientStateFlags.Contact) != 0) + CachedVelocity = Vector3.Zero; + + /*if (wo != null && wo.Teleporting) + { + //Console.WriteLine($"*** SETTING TELEPORT ***"); + + var setPosition = new SetPosition(); + setPosition.Pos = RequestPos; + setPosition.Flags = SetPositionFlags.SendPositionEvent | SetPositionFlags.Slide | SetPositionFlags.Placement | SetPositionFlags.Teleport; + + SetPosition(setPosition); + + // hack... + if (!TransientState.HasFlag(TransientStateFlags.OnWalkable)) + { + //Console.WriteLine($"Setting velocity"); + Velocity = new Vector3(0, 0, -PhysicsGlobals.EPSILON); + } + }*/ UpdateTime = PhysicsTimer.CurrentTime; + + return success; + } + + /// + /// This is for full / updated movement system + /// + public bool update_object_server_new(bool forcePos = true) + { + if (Parent != null || CurCell == null || State.HasFlag(PhysicsState.Frozen)) + { + TransientState &= ~TransientStateFlags.Active; + return false; + } + + PhysicsTimer_CurrentTime = UpdateTime; + + var deltaTime = PhysicsTimer.CurrentTime - UpdateTime; + + //Console.WriteLine($"{Name}.update_object_server({forcePos}) - deltaTime: {deltaTime}"); + + //var isTeleport = WeenieObj.WorldObject?.Teleporting ?? false; + var isTeleport = false; + + // commented out for debugging + if (deltaTime > PhysicsGlobals.HugeQuantum && !isTeleport) + { + UpdateTime = PhysicsTimer.CurrentTime; // consume time? + return false; + } + + var requestCell = RequestPos.ObjCellID; + + var success = true; + + if (!isTeleport) + { + if (GetBlockDist(Position, RequestPos) > 1) + { + Console.WriteLine($"WARNING: failed transition for {Name} from {Position} to {RequestPos}"); + success = false; + } + + while (deltaTime > PhysicsGlobals.MaxQuantum) + { + PhysicsTimer_CurrentTime += PhysicsGlobals.MaxQuantum; + UpdateObjectInternal(PhysicsGlobals.MaxQuantum); + deltaTime -= PhysicsGlobals.MaxQuantum; + } + + if (deltaTime > PhysicsGlobals.MinQuantum) + { + PhysicsTimer_CurrentTime += deltaTime; + UpdateObjectInternal(deltaTime); + } + + success &= requestCell >> 16 != 0x18A || CurCell?.ID >> 16 == requestCell >> 16; + } + + RequestPos.ObjCellID = requestCell; + + if (forcePos && success) + { + // attempt transition to request pos, + // to trigger any collision detection + var transit = transition(Position, RequestPos, false); + + if (transit != null) + { + var prevContact = (TransientState & TransientStateFlags.Contact) != 0; + + foreach (var collideObject in transit.CollisionInfo.CollideObject) + track_object_collision(collideObject, prevContact); + } + + set_current_pos(RequestPos); + } + + // for teleport, use SetPosition? + if (isTeleport) + { + //Console.WriteLine($"*** SETTING TELEPORT ***"); + + var setPosition = new SetPosition(); + setPosition.Pos = RequestPos; + setPosition.Flags = SetPositionFlags.SendPositionEvent | SetPositionFlags.Slide | SetPositionFlags.Placement | SetPositionFlags.Teleport; + + SetPosition(setPosition); + } + + UpdateTime = PhysicsTimer_CurrentTime; + + return success; } public void update_position() @@ -3874,44 +4404,6 @@ public void update_position() UpdateTime = PhysicsTimer.CurrentTime; } - public void get_voyeurs() - { - ObjMaint.get_voyeurs(); - } - - public void add_moveto_listener(Action listener) - { - MovementManager.MoveToManager.add_listener(listener); - } - - public void remove_moveto_listener(Action listener) - { - MovementManager.MoveToManager.remove_listener(listener); - } - - public void add_sticky_listener(Action listener) - { - MakePositionManager(); - PositionManager.MakeStickyManager(); - - PositionManager.StickyManager.add_sticky_listener(listener); - } - - public void remove_sticky_listener(Action listener) - { - PositionManager.StickyManager.remove_sticky_listener(listener); - } - - public void add_unsticky_listener(Action listener) - { - PositionManager.StickyManager.add_unsticky_listener(listener); - } - - public void remove_unsticky_listener(Action listener) - { - PositionManager.StickyManager.remove_unsticky_listener(listener); - } - public bool IsGrounded { get => TransientState.HasFlag(TransientStateFlags.OnWalkable) && CachedVelocity.Equals(Vector3.Zero); } /*public bool Equals(PhysicsObj obj) @@ -3924,5 +4416,10 @@ public override int GetHashCode() { return ID.GetHashCode(); }*/ + + public override string ToString() + { + return $"{Name} ({ID:X8})"; + } } } diff --git a/ACViewer/Physics/Polygon.cs b/ACViewer/Physics/Polygon.cs index 0678d10..67b4405 100644 --- a/ACViewer/Physics/Polygon.cs +++ b/ACViewer/Physics/Polygon.cs @@ -14,6 +14,7 @@ public class Polygon: IEquatable public List VertexIDs; //public List Screen; public int PolyID; // not directly in this DAT structure + public int NumPoints; public StipplingType Stippling; public CullMode SidesType; public List PosUVIndices; // texture coordinates unused by server @@ -30,6 +31,7 @@ public Polygon(int idx, int numPoints, CullMode cullMode) Init(); PolyID = idx; + NumPoints = numPoints; SidesType = cullMode; VertexIDs = new List(numPoints); @@ -48,6 +50,7 @@ public Polygon(DatLoader.Entity.Polygon polygon, DatLoader.Entity.CVertexArray v { NegSurface = polygon.NegSurface; //NegUVIndices = polygon.NegUVIndices; + NumPoints = polygon.NumPts; PosSurface = polygon.PosSurface; //PosUVIndices = polygon.PosUVIndices; SidesType = polygon.SidesType; @@ -138,7 +141,7 @@ public bool check_walkable(Sphere sphere, Vector3 up, bool small = false) var radsum = sphere.Radius * sphere.Radius; if (small) radsum *= 0.25f; - var prevIdx = Vertices.Count - 1; + var prevIdx = NumPoints - 1; for (var i = 0; i < Vertices.Count; i++) { var vertex = Vertices[i]; @@ -178,7 +181,7 @@ public bool find_crossed_edge(Sphere sphere, Vector3 up, ref Vector3 normal) var angle = (Vector3.Dot(Plane.Normal, sphere.Center) + Plane.D) / angleUp; var center = sphere.Center - up * angle; - var prevIdx = Vertices.Count - 1; + var prevIdx = NumPoints - 1; for (var i = 0; i < Vertices.Count; i++) { var vertex = Vertices[i]; @@ -207,24 +210,23 @@ public bool hits_sphere(Sphere sphere) public void make_plane() { var normal = Vector3.Zero; - var numPoints = Vertices.Count; // calculate plane normal - for (int i = numPoints - 2, spreadIdx = 1; i > 0; i--) + for (int i = NumPoints - 2, spreadIdx = 1; i > 0; i--) { var v1 = Vertices[spreadIdx++] - Vertices[0]; var v2 = Vertices[spreadIdx] - Vertices[0]; normal += Vector3.Cross(v1, v2); } - normal = normal.Normalize(); + normal = Vector3.Normalize(normal); // calculate distance var distSum = 0.0f; - for (int i = numPoints, spread = 0; i > 0; i--, spread++) + for (int i = NumPoints, spread = 0; i > 0; i--, spread++) distSum += Vector3.Dot(normal, Vertices[spread].Origin); - var dist = -(distSum / numPoints); + var dist = -(distSum / NumPoints); Plane = new Plane(normal, dist); } @@ -232,7 +234,7 @@ public void make_plane() public bool point_in_poly2D(Vector3 point, Sidedness side) { var prevIdx = 0; - for (var i = Vertices.Count - 1; i >= 0; i--) + for (var i = NumPoints - 1; i >= 0; i--) { var prevVertex = Vertices[prevIdx]; var vertex = Vertices[i]; @@ -259,7 +261,7 @@ public bool point_in_poly2D(Vector3 point, Sidedness side) public bool point_in_polygon(Vector3 point) { - var lastVertex = Vertices[Vertices.Count - 1]; + var lastVertex = Vertices[NumPoints - 1]; foreach (var vertex in Vertices) { @@ -296,7 +298,7 @@ public bool polygon_hits_sphere(Sphere sphere, ref Vector3 contactPoint) contactPoint = sphere.Center - Plane.Normal * dpPos; - var prevIdx = Vertices.Count - 1; + var prevIdx = NumPoints - 1; for (var i = 0; i < Vertices.Count; i++) { var vertex = Vertices[i]; @@ -328,8 +330,7 @@ public bool polygon_hits_sphere(Sphere sphere, ref Vector3 contactPoint) public bool polygon_hits_sphere_precise(Sphere sphere, ref Vector3 contactPoint) { - var numPoints = Vertices.Count; - if (numPoints == 0) return true; + if (NumPoints == 0) return true; var dpPos = Vector3.Dot(Plane.Normal, sphere.Center) + Plane.D; var rad = sphere.Radius - PhysicsGlobals.EPSILON; @@ -338,7 +339,7 @@ public bool polygon_hits_sphere_precise(Sphere sphere, ref Vector3 contactPoint) var diff = rad * rad - dpPos * dpPos; contactPoint = sphere.Center - Plane.Normal * dpPos; - var prevIdx = numPoints - 1; + var prevIdx = NumPoints - 1; for (var i = 0; i < Vertices.Count; i++) { var vertex = Vertices[i]; @@ -352,7 +353,7 @@ public bool polygon_hits_sphere_precise(Sphere sphere, ref Vector3 contactPoint) if (Vector3.Dot(disp, cross) >= 0.0f) continue; // inner loop - prevIdx = numPoints - 1; // alt idx? + prevIdx = NumPoints - 1; // alt idx? for (var j = 0; j < Vertices.Count; j++) { vertex = Vertices[j]; @@ -412,12 +413,10 @@ public bool walkable_hits_sphere(SpherePath path, Sphere sphere, Vector3 up) public bool Equals(Polygon p) { - var numPoints = Vertices.Count; - - if (PolyID != p.PolyID || numPoints != p.Vertices.Count || Stippling != p.Stippling || SidesType != p.SidesType || + if (PolyID != p.PolyID || NumPoints != p.NumPoints || Stippling != p.Stippling || SidesType != p.SidesType || PosSurface != p.PosSurface || NegSurface != p.NegSurface) return false; - for (var i = 0; i < numPoints; i++) + for (var i = 0; i < NumPoints; i++) { if (!p.Vertices[i].Equals(Vertices[i])) return false; @@ -433,12 +432,13 @@ public override int GetHashCode() int hash = 0; hash = (hash * 397) ^ PolyID.GetHashCode(); + hash = (hash * 397) ^ NumPoints.GetHashCode(); hash = (hash * 397) ^ Stippling.GetHashCode(); hash = (hash * 397) ^ SidesType.GetHashCode(); hash = (hash * 397) ^ PosSurface.GetHashCode(); hash = (hash * 397) ^ NegSurface.GetHashCode(); - for (var i = 0; i < Vertices.Count; i++) + for (var i = 0; i < NumPoints; i++) { hash = (hash * 397) ^ Vertices[i].GetHashCode(); hash = (hash * 397) ^ VertexIDs[i].GetHashCode(); diff --git a/ACViewer/Physics/Sphere.cs b/ACViewer/Physics/Sphere.cs index d10af3c..1d680cf 100644 --- a/ACViewer/Physics/Sphere.cs +++ b/ACViewer/Physics/Sphere.cs @@ -1,7 +1,9 @@ using System; using System.Numerics; + +using ACE.Entity.Enum; + using ACE.Server.Physics.Animation; -using ACE.Server.Physics.Collision; using ACE.Server.Physics.Common; using ACE.Server.Physics.Extensions; @@ -34,7 +36,7 @@ public Sphere() /// public Sphere(Sphere sphere) { - Center = new Vector3(sphere.Center.X, sphere.Center.Y, sphere.Center.Z); + Center = sphere.Center; Radius = sphere.Radius; } @@ -58,65 +60,78 @@ public Sphere(DatLoader.Entity.Sphere sphere) Radius = sphere.Radius; } - public static int Attack(Position targetPos, float targetRadius, float targetHeight, Position attackPos, Vector2 left, Vector2 right, float attackRadius, float attackHeight) + public static readonly float ThresholdMed = 1.0f / 3.0f; + public static readonly float ThresholdHigh = 2.0f / 3.0f; + + public static Quadrant Attack(Position targetPos, float targetRadius, float targetHeight, Position attackPos, Vector2 left, Vector2 right, float attackRadius, float attackHeight) { var center = attackPos.LocalToLocal(targetPos, Vector3.Zero); + if (attackHeight < 0.0f || attackHeight > targetHeight) - return 0; + return Quadrant.None; + var radsum = targetRadius + attackRadius; + var distSq = center.LengthSquared2D(); if (distSq > radsum * radsum) - return 0; + return Quadrant.None; var hitLoc = targetPos.LocalToLocal(attackPos, Vector3.Zero); - var quadrant = 8; - int quadmod, quadbit; - if (hitLoc.X > 0.0f) - quadrant = 16; - if (hitLoc.Y > 0.0f) - quadmod = quadrant | 0x20; - else - quadmod = quadrant | 0x40; - if (targetHeight * 0.333333f <= attackHeight) - { - if (targetHeight * 0.666667f <= attackHeight) - quadbit = quadmod | 1; - else - quadbit = quadmod | 2; - } + + var quadrant = hitLoc.X <= 0.0f ? Quadrant.Left : Quadrant.Right; + + quadrant |= hitLoc.Y > 0.0f ? Quadrant.Front : Quadrant.Back; + + if (attackHeight < targetHeight * ThresholdMed) + quadrant |= Quadrant.Low; + else if (attackHeight < targetHeight * ThresholdHigh) + quadrant |= Quadrant.Medium; else - quadbit = quadmod | 4; - var attackhta = center.Y * left.X - center.X * left.Y; - var rightDist = center.X * right.Y - center.Y * right.X; - if (attackhta <= 0.0f && rightDist <= 0.0f) - return quadbit; + quadrant |= Quadrant.High; + + // 2d cross product? + var attack_ht = center.Y * left.X - center.X * left.Y; + var right_dist = center.X * right.Y - center.Y * right.X; + + if (attack_ht <= 0.0f && right_dist <= 0.0f) + return quadrant; + if (left.X * right.Y - left.Y * right.X >= 0.0f) { - if (rightDist * attackhta <= 0.0f || attackhta <= targetRadius) - return quadbit; + if (right_dist * attack_ht <= 0.0f || attack_ht <= targetRadius || right_dist <= targetRadius) + return quadrant; + else + return Quadrant.None; } - else if (attackhta <= 0.0f) + + if (attack_ht < 0.0f) { - // drop down + if (right_dist <= targetRadius) + return quadrant; + else + return Quadrant.None; } - else if (rightDist >= 0.0f) + + if (right_dist >= 0.0f) { - if (targetRadius * targetRadius >= distSq) - return quadbit; + if (distSq <= targetRadius * targetRadius) + return quadrant; else - return 0; + return Quadrant.None; } - else if (attackhta >= 0.0f) + + if (attack_ht < 0.0f) { - if (attackhta <= targetRadius) - return quadbit; + if (right_dist <= targetRadius) + return quadrant; else - return 0; + return Quadrant.None; } - if (rightDist >= targetRadius) - return 0; + + if (attack_ht <= targetRadius) + return quadrant; else - return quadbit; + return Quadrant.None; } /// @@ -220,7 +235,8 @@ public bool Intersects(Sphere sphere) public TransitionState IntersectsSphere(Position position, float scale, Transition transition, bool isCreature) { var globPos = transition.SpherePath.CheckPos.LocalToGlobal(position, Center * scale); - return new Sphere(globPos, Radius * scale).IntersectsSphere(transition, isCreature); + var sphere = new Sphere(globPos, Radius * scale); + return sphere.IntersectsSphere(transition, isCreature); } /// @@ -247,7 +263,7 @@ public TransitionState IntersectsSphere(Transition transition, bool isCreature) if (transition.SpherePath.ObstructionEthereal || transition.SpherePath.InsertType == InsertType.Placement) { - if (disp.LengthSquared() <= radsum) + if (disp.LengthSquared() <= radsum * radsum) return TransitionState.Collided; if (transition.SpherePath.NumSphere > 1) @@ -338,7 +354,7 @@ public TransitionState IntersectsSphere(Transition transition, bool isCreature) t = diff * 2 - t; var time = (float)t / lenSq; var timecheck = (1 - time) * transition.SpherePath.WalkInterp; - if (timecheck < transition.SpherePath.WalkableAllowance && timecheck < -0.1f) + if (timecheck >= transition.SpherePath.WalkInterp || timecheck < -0.1f) return TransitionState.Collided; movement *= time; @@ -395,10 +411,11 @@ public TransitionState SlideSphere(Transition transition, Vector3 disp, float ra var contactPlane = collisions.ContactPlaneValid ? collisions.ContactPlane : collisions.LastKnownContactPlane; var skid_dir = contactPlane.Normal; - var direction = Vector3.Cross(skid_dir, collisionNormal); + //var direction = Vector3.Cross(skid_dir, collisionNormal); + var direction = Vector3.Cross(collisionNormal, skid_dir); var blockOffset = LandDefs.GetBlockOffset(path.CurPos.ObjCellID, path.CheckPos.ObjCellID); - var globOffset = globSphere.Center - Center + blockOffset; + var globOffset = globSphere.Center - path.GlobalCurrCenter[sphereNum].Center + blockOffset; var dirLenSq = direction.LengthSquared(); if (dirLenSq >= PhysicsGlobals.EPSILON) { @@ -411,7 +428,8 @@ public TransitionState SlideSphere(Transition transition, Vector3 disp, float ra direction = skid_dir; // only x? - if (direction.X * direction.X < PhysicsGlobals.EPSILON) + //if (direction.X * direction.X < PhysicsGlobals.EPSILON) + if (direction.LengthSquared() < PhysicsGlobals.EPSILON) return TransitionState.Collided; direction -= globOffset; diff --git a/ACViewer/Physics/SpherePath.cs b/ACViewer/Physics/SpherePath.cs index 7d8976e..a15ddc7 100644 --- a/ACViewer/Physics/SpherePath.cs +++ b/ACViewer/Physics/SpherePath.cs @@ -296,7 +296,7 @@ public void SetWalkable(Sphere sphere, Polygon poly, Vector3 zAxis, Position loc { WalkableCheckPos = new Sphere(sphere); Walkable = poly; - WalkableUp = new Vector3(zAxis.X, zAxis.Y, zAxis.Z); + WalkableUp = zAxis; WalkablePos = new Position(localPos); WalkableScale = scale; } diff --git a/ACViewer/Physics/Trajectory.cs b/ACViewer/Physics/Trajectory.cs index 421ff17..ec93f44 100644 --- a/ACViewer/Physics/Trajectory.cs +++ b/ACViewer/Physics/Trajectory.cs @@ -28,8 +28,6 @@ // int SolveQuartic(double c0, double c1, double c2, double c3, double c4, out double s0, out double s1, out double s2, out double s3); -using ACE.Server.Physics.Extensions; - using System; using System.Diagnostics; using System.Numerics; @@ -262,7 +260,9 @@ public static int SolveQuartic(double c0, double c1, double c2, double c3, doubl public static float ballistic_range(float speed, float gravity, float initial_height) { // Handling these cases is up to your project's coding standards - Debug.Assert(speed > 0 && gravity > 0 && initial_height >= 0, "fts.ballistic_range called with invalid data"); + //Debug.Assert(speed > 0 && gravity > 0 && initial_height >= 0, "fts.ballistic_range called with invalid data"); + if (speed <= 0 || gravity <= 0 || initial_height < 0) + return 0.0f; // Derivation // (1) x = speed * time * cos O @@ -290,9 +290,8 @@ public static float ballistic_range(float speed, float gravity, float initial_he // return (int): number of unique solutions found: 0, 1, or 2. public static int solve_ballistic_arc(Vector3 proj_pos, float proj_speed, Vector3 target, float gravity, out Vector3 s0, out Vector3 s1, out float t0, out float t1) { - // Handling these cases is up to your project's coding standards - Debug.Assert(proj_pos != target && proj_speed > 0 && gravity > 0, "fts.solve_ballistic_arc called with invalid data"); + //Debug.Assert(proj_pos != target && proj_speed > 0 && gravity > 0, "fts.solve_ballistic_arc called with invalid data"); // C# requires out variables be set s0 = Vector3.Zero; @@ -300,6 +299,9 @@ public static int solve_ballistic_arc(Vector3 proj_pos, float proj_speed, Vector t0 = float.PositiveInfinity; t1 = float.PositiveInfinity; + if (proj_pos == target || proj_speed <= 0 || gravity <= 0) + return 0; + // Derivation // (1) x = v*t*cos O // (2) z = v*t*sin O - .5*g*t^2 @@ -338,7 +340,7 @@ public static int solve_ballistic_arc(Vector3 proj_pos, float proj_speed, Vector var highAng = Math.Atan2(speed2 + root, gx); int numSolutions = lowAng != highAng ? 2 : 1; - Vector3 groundDir = diffXY.Normalize(); + Vector3 groundDir = Vector3.Normalize(diffXY); s0 = groundDir * (float)Math.Cos(lowAng) * proj_speed + Vector3.UnitZ * (float)Math.Sin(lowAng) * proj_speed; if (numSolutions > 1) s1 = groundDir * (float)Math.Cos(highAng) * proj_speed + Vector3.UnitZ * (float)Math.Sin(highAng) * proj_speed; @@ -465,13 +467,14 @@ public static int solve_ballistic_arc(Vector3 proj_pos, float proj_speed, Vector /// public static bool SolveBallisticArc(Vector3 projectilePosition, float lateralSpeed, Vector3 targetPosition, out Vector3 velocityVector, out float time) { - // Handling these cases is up to your project's coding standards - Debug.Assert(projectilePosition != targetPosition && lateralSpeed > 0, "fts.solve_ballistic_arc called with invalid data"); - + //Debug.Assert(projectilePosition != targetPosition && lateralSpeed > 0, "fts.solve_ballistic_arc called with invalid data"); velocityVector = Vector3.Zero; time = float.NaN; + if (projectilePosition == targetPosition || lateralSpeed <= 0) + return false; + Vector3 diff = targetPosition - projectilePosition; Vector3 diffXY = new Vector3(diff.X, diff.Y, 0f); float lateralDist = diffXY.Length(); @@ -481,7 +484,7 @@ public static bool SolveBallisticArc(Vector3 projectilePosition, float lateralSp time = lateralDist / lateralSpeed; - velocityVector = diffXY.Normalize() * lateralSpeed; + velocityVector = Vector3.Normalize(diffXY) * lateralSpeed; // System of equations. Hit max_height at t=.5*time. Hit target at t=time. // @@ -514,13 +517,15 @@ public static bool SolveBallisticArc(Vector3 projectilePosition, float lateralSp // return (bool): true if a valid solution was found public static bool solve_ballistic_arc_lateral(Vector3 proj_pos, float lateral_speed, Vector3 target_pos, float max_height, out Vector3 fire_velocity, out float gravity) { - // Handling these cases is up to your project's coding standards Debug.Assert(proj_pos != target_pos && lateral_speed > 0 && max_height > proj_pos.Z, "fts.solve_ballistic_arc called with invalid data"); fire_velocity = Vector3.Zero; gravity = float.NaN; + if (proj_pos == target_pos || lateral_speed <= 0 || max_height <= proj_pos.Z) + return false; + Vector3 diff = target_pos - proj_pos; Vector3 diffXY = new Vector3(diff.X, diff.Y, 0f); float lateralDist = diffXY.Length(); @@ -530,7 +535,7 @@ public static bool solve_ballistic_arc_lateral(Vector3 proj_pos, float lateral_s float time = lateralDist / lateral_speed; - fire_velocity = diffXY.Normalize() * lateral_speed; + fire_velocity = Vector3.Normalize(diffXY) * lateral_speed; // System of equations. Hit max_height at t=.5*time. Hit target at t=time. // @@ -562,15 +567,17 @@ public static bool solve_ballistic_arc_lateral(Vector3 proj_pos, float lateral_s // return (bool): true if a valid solution was found public static bool solve_ballistic_arc_lateral(Vector3 proj_pos, float lateral_speed, Vector3 target, Vector3 target_velocity, float gravity, out Vector3 fire_velocity, out float time, out Vector3 impact_point) { - // Handling these cases is up to your project's coding standards - Debug.Assert(proj_pos != target && lateral_speed > 0, "fts.solve_ballistic_arc_lateral called with invalid data"); + //Debug.Assert(proj_pos != target && lateral_speed > 0, "fts.solve_ballistic_arc_lateral called with invalid data"); // Initialize output variables fire_velocity = Vector3.Zero; time = 0.0f; impact_point = Vector3.Zero; + if (proj_pos == target || lateral_speed <= 0) + return false; + // Ground plane terms Vector3 targetVelXY = new Vector3(target_velocity.X, target_velocity.Y, 0f); Vector3 diffXY = target - proj_pos; @@ -604,7 +611,7 @@ public static bool solve_ballistic_arc_lateral(Vector3 proj_pos, float lateral_s // Calculate fire velocity along XZ plane Vector3 dir = impact_point - proj_pos; - fire_velocity = new Vector3(dir.X, dir.Y, 0f).Normalize() * lateral_speed; + fire_velocity = Vector3.Normalize(new Vector3(dir.X, dir.Y, 0f)) * lateral_speed; // Solve system of equations. Hit max_height at t=.5*time. Hit target at t=time. // diff --git a/ACViewer/Physics/Trajectory2.cs b/ACViewer/Physics/Trajectory2.cs new file mode 100644 index 0000000..4a6b72b --- /dev/null +++ b/ACViewer/Physics/Trajectory2.cs @@ -0,0 +1,64 @@ +using System; +using System.Numerics; + +using ACE.Server.Physics.Common; + +namespace ACE.Server.Physics +{ + public static class Trajectory2 + { + public static Vector3 CalculateTrajectory(Vector3 startPos, Vector3 endPos, Vector3 targetVelocity, float speed, bool gravity) + { + var targetOffset = endPos - startPos; + + Vector3 result; + float t = 0.0f; + + if (targetVelocity == Vector3.Zero) + { + var targetDist = targetOffset.Length(); + + t = targetDist / speed; + result = targetOffset / t; + } + else + { + var p0 = targetOffset; + var p1 = Vector3.Zero; + + var v0 = targetVelocity; + if (Vec.NormalizeCheckSmall(ref v0)) + v0 = Vector3.Zero; + + var s1 = speed; + + var a = (v0.X * v0.X) + (v0.Y * v0.Y) - (s1 * s1); + var b = 2 * ((p0.X * v0.X) + (p0.Y * v0.Y) - (p1.X * v0.X) - (p1.Y * v0.Y)); + var c = (p0.X * p0.X) + (p0.Y * p0.Y) + (p1.X * p1.X) + (p1.Y * p1.Y) - (2 * p1.X * p0.X) - (2 * p1.Y * p0.Y); + + var t0 = Math.Sqrt((b * b) - (4 * a * c)); + var t1 = (-b + t0) / (2 * a); + var t2 = (-b - t0) / (2 * a); + + if (t1 < 0) + t1 = float.MaxValue; + if (t2 < 0) + t2 = float.MaxValue; + + t = (float)Math.Min(t1, t2); + + if (t >= 100) + return CalculateTrajectory(startPos, endPos, Vector3.Zero, speed, gravity); + + var s0 = targetVelocity.Length(); + + result = (p0 + t * s0 * v0) / t; + } + + if (gravity) + result.Z -= PhysicsGlobals.Gravity * t * 0.5f; + + return result; + } + } +} diff --git a/ACViewer/Physics/Transition.cs b/ACViewer/Physics/Transition.cs index 9986292..e577cbb 100644 --- a/ACViewer/Physics/Transition.cs +++ b/ACViewer/Physics/Transition.cs @@ -24,7 +24,7 @@ public class Transition public SpherePath SpherePath; public CollisionInfo CollisionInfo; public CellArray CellArray; - public ObjCell NewCellPtr; + //public ObjCell NewCellPtr; public Transition() { @@ -155,7 +155,7 @@ public TransitionState CheckOtherCells(ObjCell currCell) SpherePath.HitsInteriorCell = false; //ObjCell newCell = null; - var newCell = new ObjCell(); // null check? + var newCell = ObjCell.EmptyCell; // null check? ObjCell.find_cell_list(CellArray, ref newCell, SpherePath); for (var i = 0; i < CellArray.Cells.Count; i++) @@ -260,7 +260,7 @@ public TransitionState CliffSlide(Plane contactPlane) else { SpherePath.AddOffsetToCheckPos(collideNormal * -angle); - CollisionInfo.SetCollisionNormal(collideNormal * angle); // verify + CollisionInfo.SetCollisionNormal(-collideNormal); } return TransitionState.Adjusted; } @@ -503,14 +503,14 @@ public bool FindTransitionalPosition() CalcNumSteps(ref offset, ref offsetPerStep, ref numSteps); // restructure as retval? //var maxSteps = 30; - var maxSteps = 200; // for debugging - if (numSteps > maxSteps) + var maxSteps = 1000; + if (numSteps > maxSteps && !ObjectInfo.Object.IsSightObj) { //Console.WriteLine("NumSteps: " + numSteps); return false; } - if (ObjectInfo.State.HasFlag(ObjectInfoState.FreeRotate)) + if ((ObjectInfo.State & ObjectInfoState.FreeRotate) != 0) SpherePath.CurPos.Frame.set_rotate(SpherePath.EndPos.Frame.Orientation); SpherePath.SetCheckPos(SpherePath.CurPos, SpherePath.CurCell); @@ -518,7 +518,7 @@ public bool FindTransitionalPosition() var redo = 0; if (numSteps <= 0) { - if (!ObjectInfo.State.HasFlag(ObjectInfoState.FreeRotate)) // ? + if ((ObjectInfo.State & ObjectInfoState.FreeRotate) == 0) // ? SpherePath.CurPos.Frame.set_rotate(SpherePath.EndPos.Frame.Orientation); SpherePath.CellArrayValid = true; @@ -531,7 +531,7 @@ public bool FindTransitionalPosition() for (var step = 0; step < numSteps; step++) { - if (ObjectInfo.State.HasFlag(ObjectInfoState.IsViewer)) + if ((ObjectInfo.State & ObjectInfoState.IsViewer) != 0) { var lastStep = numSteps - 1; @@ -546,14 +546,14 @@ public bool FindTransitionalPosition() } } SpherePath.GlobalOffset = AdjustOffset(offsetPerStep); - if (!ObjectInfo.State.HasFlag(ObjectInfoState.IsViewer)) + if ((ObjectInfo.State & ObjectInfoState.IsViewer) == 0) { if (SpherePath.GlobalOffset.LengthSquared() < PhysicsGlobals.EPSILON * PhysicsGlobals.EPSILON) { return (step != 0 && transitionState == TransitionState.OK); } } - if (!ObjectInfo.State.HasFlag(ObjectInfoState.FreeRotate)) + if ((ObjectInfo.State & ObjectInfoState.FreeRotate) == 0) { redo = step + 1; var delta = (float)redo / numSteps; @@ -587,7 +587,7 @@ public bool FindTransitionalPosition() if (CollisionInfo.FramesStationaryFall > 0) break; } - if (CollisionInfo.CollisionNormalValid && ObjectInfo.State.HasFlag(ObjectInfoState.PathClipped)) break; + if (CollisionInfo.CollisionNormalValid && (ObjectInfo.State & ObjectInfoState.PathClipped) != 0) break; } return transitionState == TransitionState.OK; @@ -607,7 +607,7 @@ public void Init() SpherePath = new SpherePath(); CollisionInfo = new CollisionInfo(); CellArray = new CellArray(); - NewCellPtr = new ObjCell(); + //NewCellPtr = new ObjCell(); } public void InitContactPlane(uint cellID, Plane contactPlane, bool isWater) @@ -729,7 +729,7 @@ public bool StepDown(float stepDownHeight, float zVal) SpherePath.StepDown = false; if (transitionState == TransitionState.OK && CollisionInfo.ContactPlaneValid && CollisionInfo.ContactPlane.Normal.Z >= zVal && - (!ObjectInfo.State.HasFlag(ObjectInfoState.EdgeSlide) || SpherePath.StepUp || CheckWalkable(zVal))) + ((ObjectInfo.State & ObjectInfoState.EdgeSlide) == 0 || SpherePath.StepUp || CheckWalkable(zVal))) { SpherePath.Backup = SpherePath.InsertType; SpherePath.InsertType = InsertType.Placement; @@ -840,8 +840,8 @@ public TransitionState TransitionalInsert(int num_insertion_attempts) } else { - if (CollisionInfo.ContactPlaneValid || !ObjectInfo.State.HasFlag(ObjectInfoState.Contact) || - SpherePath.StepDown || SpherePath.CheckCell == null || ObjectInfo.StepDown) + if (CollisionInfo.ContactPlaneValid || (ObjectInfo.State & ObjectInfoState.Contact) == 0 || + SpherePath.StepDown || SpherePath.CheckCell == null || !ObjectInfo.StepDown) { return TransitionState.OK; } @@ -849,7 +849,7 @@ public TransitionState TransitionalInsert(int num_insertion_attempts) var zVal = PhysicsGlobals.LandingZ; var stepDownHeight = 0.039999999f; // set global - if (ObjectInfo.State.HasFlag(ObjectInfoState.OnWalkable)) + if ((ObjectInfo.State & ObjectInfoState.OnWalkable) != 0) { zVal = ObjectInfo.GetWalkableZ(); stepDownHeight = ObjectInfo.StepDownHeight; @@ -864,21 +864,23 @@ public TransitionState TransitionalInsert(int num_insertion_attempts) stepDownHeight = SpherePath.GlobalSphere[0].Radius * 0.5f; } - if (radsum < stepDownHeight) + if (radsum >= stepDownHeight) { - // bad path - stepDownHeight *= 0.5f; - if (StepDown(stepDownHeight, zVal) || StepDown(stepDownHeight, zVal)) // double step.. + if (StepDown(stepDownHeight, zVal)) { SpherePath.Walkable = null; return TransitionState.OK; } } - - if (StepDown(stepDownHeight, zVal)) // triple step? + else { - SpherePath.Walkable = null; - return TransitionState.OK; + // 2 half-steps + stepDownHeight *= 0.5f; + if (StepDown(stepDownHeight, zVal) || StepDown(stepDownHeight, zVal)) + { + SpherePath.Walkable = null; + return TransitionState.OK; + } } if (EdgeSlide(ref transitState, stepDownHeight, zVal)) @@ -969,7 +971,11 @@ public TransitionState ValidatePlacementTransition(TransitionState transitionSta case TransitionState.Collided: case TransitionState.Adjusted: case TransitionState.Slid: - if (SpherePath.PlacementAllowsSliding) CollisionInfo.Init(); + + // added target id + if (SpherePath.PlacementAllowsSliding && ObjectInfo.TargetID == 0) + CollisionInfo.Init(); + break; } return transitionState; diff --git a/ACViewer/Physics/Util/AdjustCell.cs b/ACViewer/Physics/Util/AdjustCell.cs index f3f771e..2ee1c43 100644 --- a/ACViewer/Physics/Util/AdjustCell.cs +++ b/ACViewer/Physics/Util/AdjustCell.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Concurrent; using System.Numerics; using ACE.DatLoader; using ACE.DatLoader.FileTypes; @@ -7,9 +8,8 @@ namespace ACE.Server.Physics.Util { public class AdjustCell { - public List EnvCells; - public List NewEnvCells; - public static Dictionary AdjustCells = new Dictionary(); + public List EnvCells; + public static ConcurrentDictionary AdjustCells = new ConcurrentDictionary(); public AdjustCell(uint dungeonID) { @@ -22,31 +22,23 @@ public AdjustCell(uint dungeonID) public void BuildEnv(uint dungeonID, uint numCells) { - EnvCells = new List(); - NewEnvCells = new List(); + EnvCells = new List(); uint firstCellID = 0x100; for (uint i = 0; i < numCells; i++) { uint cellID = firstCellID + i; uint blockCell = dungeonID << 16 | cellID; - var cell = DatManager.CellDat.ReadFromDat(blockCell); - EnvCells.Add(new Environment(cell)); var objCell = Common.LScape.get_landcell(blockCell); var envCell = objCell as Common.EnvCell; if (envCell != null) - NewEnvCells.Add(envCell); + EnvCells.Add(envCell); } } public uint? GetCell(Vector3 point) { - /*foreach (var envCell in EnvCells) - if (envCell.BBox.Contains(point)) - return envCell.EnvCell.Id; - return null;*/ - - foreach (var envCell in NewEnvCells) + foreach (var envCell in EnvCells) if (envCell.point_in_cell(point)) return envCell.ID; return null; @@ -59,7 +51,7 @@ public static AdjustCell Get(uint dungeonID) if (adjustCell == null) { adjustCell = new AdjustCell(dungeonID); - AdjustCells.Add(dungeonID, adjustCell); + AdjustCells.TryAdd(dungeonID, adjustCell); } return adjustCell; } diff --git a/ACViewer/Physics/Util/AdjustPos.cs b/ACViewer/Physics/Util/AdjustPos.cs index 3fa9d18..45417a9 100644 --- a/ACViewer/Physics/Util/AdjustPos.cs +++ b/ACViewer/Physics/Util/AdjustPos.cs @@ -15,32 +15,32 @@ static AdjustPos() { DungeonProfiles = new Dictionary(); - // Burial Temple - var burialTemple = new AdjustPosProfile(); - burialTemple.BadPosition = new Vector3(30.389999f, -37.439999f, 0.000000f); - burialTemple.BadRotation = new Quaternion(-0f, 0, 0, -1f); - burialTemple.GoodPosition = new Vector3(30f, -146.30799865723f, 0.0049999998882413f); - burialTemple.GoodRotation = new Quaternion(1, 0, 0, 0); - - DungeonProfiles.Add(0x13e, burialTemple); - - // North Glenden Prison - var glendenPrison = new AdjustPosProfile(); - glendenPrison.BadPosition = new Vector3(38.400002f, -18.600000f, 6.000000f); - glendenPrison.BadRotation = new Quaternion(-0.782608f, 0, 0, -0.622514f); - glendenPrison.GoodPosition = new Vector3(61, -20, -17.995000839233f); - glendenPrison.GoodRotation = new Quaternion(-0.70710700750351f, 0, 0, -0.70710700750351f); - - DungeonProfiles.Add(0x1e4, glendenPrison); - - // Nuhmudira's Dungeon - var nuhmudirasDungeon = new AdjustPosProfile(); - nuhmudirasDungeon.BadPosition = new Vector3(149.242996f, -49.946301f, -5.995000f); - nuhmudirasDungeon.BadRotation = new Quaternion(-0.707107f, 0, 0, -0.707107f); - nuhmudirasDungeon.GoodPosition = new Vector3(149.24299621582f, -129.94599914551f, -5.9949998855591f); - nuhmudirasDungeon.GoodRotation = new Quaternion(0.6967059969902f, 0, 0, 0.71735697984695f); - - DungeonProfiles.Add(0x536d, nuhmudirasDungeon); + // Burial Temple // No longer needed as of 11/24/19 + //var burialTemple = new AdjustPosProfile(); + //burialTemple.BadPosition = new Vector3(30.389999f, -37.439999f, 0.000000f); + //burialTemple.BadRotation = new Quaternion(-0f, 0, 0, -1f); + //burialTemple.GoodPosition = new Vector3(30f, -146.30799865723f, 0.0049999998882413f); + //burialTemple.GoodRotation = new Quaternion(1, 0, 0, 0); + + //DungeonProfiles.Add(0x13e, burialTemple); // No longer needed as of 11/24/19 + + // North Glenden Prison // No longer needed as of 09/22/19 + //var glendenPrison = new AdjustPosProfile(); + //glendenPrison.BadPosition = new Vector3(38.400002f, -18.600000f, 6.000000f); + //glendenPrison.BadRotation = new Quaternion(-0.782608f, 0, 0, -0.622514f); + //glendenPrison.GoodPosition = new Vector3(61, -20, -17.995000839233f); + //glendenPrison.GoodRotation = new Quaternion(-0.70710700750351f, 0, 0, -0.70710700750351f); + + //DungeonProfiles.Add(0x1e4, glendenPrison); // No longer needed as of 09/22/19 + + // Nuhmudira's Dungeon // No longer needed as of 11/24/19 + //var nuhmudirasDungeon = new AdjustPosProfile(); + //nuhmudirasDungeon.BadPosition = new Vector3(149.242996f, -49.946301f, -5.995000f); + //nuhmudirasDungeon.BadRotation = new Quaternion(-0.707107f, 0, 0, -0.707107f); + //nuhmudirasDungeon.GoodPosition = new Vector3(149.24299621582f, -129.94599914551f, -5.9949998855591f); + //nuhmudirasDungeon.GoodRotation = new Quaternion(0.6967059969902f, 0, 0, 0.71735697984695f); + + //DungeonProfiles.Add(0x536d, nuhmudirasDungeon); // No longer needed as of 11/24/19 } public static bool Adjust(uint dungeonID, Position pos) diff --git a/ACViewer/Physics/Util/Environment.cs b/ACViewer/Physics/Util/Environment.cs index b7ed403..2306731 100644 --- a/ACViewer/Physics/Util/Environment.cs +++ b/ACViewer/Physics/Util/Environment.cs @@ -49,7 +49,7 @@ public void BuildBBox() { var origin = EnvCell.Position.Origin; var orientation = EnvCell.Position.Orientation; - var translate = Matrix4x4.CreateTranslation(new Vector3(origin.X, origin.Y, origin.Z)); + var translate = Matrix4x4.CreateTranslation(origin); var rotate = Matrix4x4.CreateFromQuaternion(new Quaternion(orientation.X, orientation.Y, orientation.Z, orientation.W)); BBox = new BBox(Polygons, rotate * translate); } diff --git a/ACViewer/Physics/WorldObject.cs b/ACViewer/Physics/WorldObject.cs index 92b5164..46162f7 100644 --- a/ACViewer/Physics/WorldObject.cs +++ b/ACViewer/Physics/WorldObject.cs @@ -11,6 +11,7 @@ public class WorldObject { public ObjectGuid Guid; public string Name; + public bool IsPlayer; public bool IsCreature; public uint RunSkill; } diff --git a/ACViewer/WorldViewer.cs b/ACViewer/WorldViewer.cs index 615deaa..5c223f8 100644 --- a/ACViewer/WorldViewer.cs +++ b/ACViewer/WorldViewer.cs @@ -47,10 +47,6 @@ public KeyboardState PrevKeyboardState public WorldViewer() { Instance = this; - - Physics = new PhysicsEngine(new ObjectMaint(), new SmartBox()); - - Physics.Server = false; } public void LoadLandblock(uint landblockID, uint radius = 1)