using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using ACE.Common; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; using ACE.Server.Entity.Actions; using ACE.Server.Managers; using ACE.Server.Physics.Animation; using ACE.Server.Physics.Collision; using ACE.Server.Physics.Combat; using ACE.Server.Physics.Common; using ACE.Server.Physics.Hooks; using ACE.Server.Physics.Managers; using ACE.Server.WorldObjects; using log4net; using Landblock = ACE.Server.Physics.Common.Landblock; using ObjectGuid = ACE.Entity.ObjectGuid; namespace ACE.Server.Physics { /// /// The base class for all physics objects /// public class PhysicsObj { private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public uint ID; public ObjectGuid ObjID; public PartArray PartArray; public Vector3 PlayerVector; public float PlayerDistance; public float CYpt; public Sound.SoundTable SoundTable; public bool ExaminationObject; public ScriptManager ScriptManager; public PhysicsScriptTable PhysicsScriptTable; public PlayScript? DefaultScript; public float? DefaultScriptIntensity; public bool HasDefaultAnimation; public bool HasDefaultScript; public PhysicsObj Parent; public ChildList Children; public Position Position; public ObjCell CurCell; public Landblock CurLandblock; public int NumShadowObjects; public Dictionary ShadowObjects; public PhysicsState State; public TransientStateFlags TransientState; public float Elasticity; public float Translucency; public float TranslucencyOriginal; public float Friction; public float MassInv; public MovementManager MovementManager; public PositionManager PositionManager; public bool LastMoveWasAutonomous; public bool JumpedThisFrame; public double UpdateTime; public Vector3 Velocity; public Vector3 Acceleration; public Vector3 Omega; public List Hooks; public List AnimHooks; public float Scale; public float AttackRadius; public DetectionManager DetectionManager; public AttackManager AttackManager; public TargetManager TargetManager; public ParticleManager ParticleManager; public WeenieObject WeenieObj; public Plane ContactPlane; public uint ContactPlaneCellID; public Vector3 SlidingNormal; public Vector3 CachedVelocity; public Dictionary CollisionTable; public bool CollidingWithEnvironment; public int[] UpdateTimes; 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; public string Name { get { if (WeenieObj == null || WeenieObj.WorldObject == null) return "NULL"; else return WeenieObj.WorldObject.Name; } } public CellArray CellArray; public ObjectMaint ObjMaint; public bool IsPlayer => ID >= 0x50000001 && ID <= 0x5FFFFFFF; public const int UpdateTimeLength = 9; public bool IsSticky => PositionManager?.StickyManager != null && PositionManager.StickyManager.TargetID != 0; public PhysicsObj() { PlayerVector = new Vector3(0, 0, 1); PlayerDistance = float.MaxValue; CYpt = float.MaxValue; Position = new Position(); Elasticity = PhysicsGlobals.DefaultElasticity; Translucency = PhysicsGlobals.DefaultTranslucency; Friction = PhysicsGlobals.DefaultFriction; State = PhysicsGlobals.DefaultState; TranslucencyOriginal = PhysicsGlobals.DefaultTranslucency; MassInv = 1.0f / PhysicsGlobals.DefaultMass; Velocity = Vector3.Zero; Acceleration = Vector3.Zero; Omega = Vector3.Zero; Scale = PhysicsGlobals.DefaultScale; SlidingNormal = Vector3.Zero; CachedVelocity = Vector3.Zero; Hooks = new List(); AnimHooks = new List(); Children = new ChildList(); ShadowObjects = new Dictionary(); CollisionTable = new Dictionary(); CellArray = new CellArray(); 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 = WeenieObject.DummyObject; ObjMaint = new ObjectMaint(this); if (PhysicsEngine.Instance != null && PhysicsEngine.Instance.Server) { RequestPos = new Position(); } } // 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(); }*/ public void Destroy() { MovementManager = null; PositionManager = null; ParticleManager = null; ScriptManager = null; Hooks = null; //if (State.HasFlag(PhysicsState.Static) && (State.HasFlag(PhysicsState.HasDefaultAnim) || State.HasFlag(PhysicsState.HasDefaultScript))) // PhysicsEngine.RemoveStaticAnimatingObject(this); if (PhysicsScriptTable != null) PhysicsScriptTable.Release(); // PartArray.SetCellID ? State = PhysicsGlobals.DefaultState; } /// /// Called to completely remove a PhysicsObj from the server /// public void DestroyObject() { leave_cell(false); remove_shadows_from_cells(); leave_world(); exit_world(); ObjMaint.DestroyObject(); } public void AddObjectToSingleCell(ObjCell objCell) { if (CurCell != null) { if (CurCell.Equals(objCell)) return; RemoveObjectFromSingleCell(CurCell); } change_cell(objCell); if (objCell == null) return; NumShadowObjects = 1; var shadowObj = new ShadowObj(this, objCell); objCell.AddShadowObject(shadowObj); if (PartArray != null) PartArray.AddPartsShadow(objCell, 1); } public void AddPartToShadowCells(PhysicsPart part) { if (CurCell != null) part.Pos.ObjCellID = CurCell.ID; foreach (var shadowObj in ShadowObjects.Values) { var shadowCell = shadowObj.Cell; if (shadowCell != null) { shadowCell.AddPart(part, null, shadowCell.Pos.Frame, ShadowObjects.Count); } } } public ObjCell AdjustPosition(Position position, Vector3 low_pt, bool dontCreateCells, bool searchCells) { var cellID = position.ObjCellID & 0xFFFF; if ((cellID < 1 || cellID > 0x40) && (cellID < 0x100 || cellID > 0xFFFD) && cellID != 0xFFFF) return null; if (cellID < 0x100) { LandDefs.AdjustToOutside(position); return ObjCell.GetVisible(position.ObjCellID); } var visibleCell = (EnvCell)ObjCell.GetVisible(position.ObjCellID); if (visibleCell == null) return null; var point = position.LocalToGlobal(low_pt); var child = visibleCell.find_visible_child_cell(point, searchCells); if (child != null) { position.ObjCellID = child.ID; return child; } if (!visibleCell.SeenOutside) return null; position.adjust_to_outside(); return ObjCell.GetVisible(position.ObjCellID); } public bool CacheHasPhysicsBSP() { if (PartArray != null && PartArray.CacheHasPhysicsBSP()) { State |= PhysicsState.HasPhysicsBSP; return true; } else { State &= ~PhysicsState.HasPhysicsBSP; return false; } } public void CallPES(uint pes, double delta) // long double? { if (delta < PhysicsGlobals.EPSILON) { if (CurCell != null) play_script_internal(pes); return; } var upperBound = (float)delta; var randp = ThreadSafeRandom.Next(0.0f, upperBound); var hook = new FPHook(PhysicsHookType.CallPES, PhysicsTimer_CurrentTime, randp, 0.0f, 1.0f, pes); Hooks.Add(hook); } public void CallPESInternal(uint pes, float curValue) { if (CurCell != null && curValue >= 1.0f) play_script_internal(pes); } public void CheckForCompletedMotions() { if (PartArray != null) PartArray.CheckForCompletedMotions(); } public bool CheckPositionInternal(ObjCell newCell, Position newPos, Transition transition, SetPosition setPos) { transition.InitPath(newCell, null, newPos); if (!setPos.Flags.HasFlag(SetPositionFlags.Slide)) transition.SpherePath.PlacementAllowsSliding = false; if (!transition.FindValidPosition()) return false; if (setPos.Flags.HasFlag(SetPositionFlags.Slide)) return true; var diff = transition.SpherePath.CurPos.Frame.Origin - newPos.Frame.Origin; // should be using Math.Abs(), bug in original? if (transition.SpherePath.CurPos.ObjCellID == newCell.ID && diff.X < 0.05f && diff.Y < 0.05f) { newPos.Frame.Origin = transition.SpherePath.CurPos.Frame.Origin; return true; } return false; } public void ConstrainTo(Position pos, float startDistance, float maxDistance) { MakePositionManager(); if (PositionManager != null) PositionManager.ConstrainTo(pos, startDistance, maxDistance); } public WeenieError DoInterpretedMotion(uint motion, MovementParameters movementParams) { if (PartArray == null) return WeenieError.GeneralMovementFailure; return PartArray.DoInterpretedMotion(motion, movementParams); } public WeenieError DoMotion(uint motion, MovementParameters movementParams) { LastMoveWasAutonomous = true; if (MovementManager == null) return WeenieError.NoAnimationTable; var mvs = new MovementStruct(MovementType.RawCommand, motion, movementParams); return MovementManager.PerformMovement(mvs); } public void DoObjDescChanges(int objDesc) { if (PartArray != null) PartArray.DoObjDescChanges(objDesc); if (Translucency == 0.0f) return; Translucency = Translucency < TranslucencyOriginal ? TranslucencyOriginal : Translucency; if (PartArray != null) PartArray.SetTranslucencyInternal(Translucency); } public void DoObjDescChangesFromDefault(int objDesc) { if (PartArray != null) PartArray.DoObjDescChangesFromDefault(objDesc); if (Translucency == 0.0f) return; Translucency = Translucency < TranslucencyOriginal ? TranslucencyOriginal : Translucency; if (PartArray != null) PartArray.SetTranslucencyInternal(Translucency); } public void DrawRecursive() { // not required for server physics? } public TransitionState FindObjCollisions(Transition transition) { bool ethereal = false; if (State.HasFlag(PhysicsState.Ethereal) && State.HasFlag(PhysicsState.IgnoreCollisions)) return TransitionState.OK; if (WeenieObj != null && transition.ObjectInfo.State.HasFlag(ObjectInfoState.IsViewer) && WeenieObj.IsCreature) return TransitionState.OK; if (State.HasFlag(PhysicsState.Ethereal) || !State.HasFlag(PhysicsState.Static) && transition.ObjectInfo.Ethereal) { if (transition.SpherePath.StepDown) return TransitionState.OK; ethereal = true; } transition.SpherePath.ObstructionEthereal = ethereal; var state = transition.ObjectInfo.State; // 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? if (!State.HasFlag(PhysicsState.HasPhysicsBSP) || missileIgnore || exemption) { if (PartArray == null || PartArray.GetNumCylsphere() == 0 || missileIgnore || exemption) { if (PartArray != null && PartArray.GetNumSphere() != 0 && !missileIgnore && !exemption) { var spheres = PartArray.GetSphere(); for (var i = 0; i < PartArray.GetNumSphere(); i++) { var intersects = spheres[i].IntersectsSphere(Position, Scale, transition, isCreature); if (intersects != TransitionState.OK) { return FindObjCollisions_Inner(transition, intersects, ethereal, isCreature); } } } } else { if (PartArray != null && PartArray.GetNumCylsphere() != 0 && !missileIgnore && !exemption) { var cylSpheres = PartArray.GetCylSphere(); for (var i = 0; i < PartArray.GetNumCylsphere(); i++) { var intersects = cylSpheres[i].IntersectsSphere(Position, Scale, transition); if (intersects != TransitionState.OK) { return FindObjCollisions_Inner(transition, intersects, ethereal, isCreature); } } } } } else if (PartArray != null) { var collided = PartArray.FindObjCollisions(transition); if (collided != TransitionState.OK) return FindObjCollisions_Inner(transition, collided, ethereal, isCreature); } transition.SpherePath.ObstructionEthereal = false; return TransitionState.OK; } public TransitionState FindObjCollisions_Inner(Transition transition, TransitionState result, bool ethereal, bool isCreature) { if (!transition.SpherePath.StepDown) { if (State.HasFlag(PhysicsState.Static)) { if (!transition.ObjectInfo.State.HasFlag(ObjectInfoState.Contact)) transition.CollisionInfo.CollidedWithEnvironment = true; } else if (ethereal || isCreature && transition.ObjectInfo.State.HasFlag(ObjectInfoState.IgnoreCreatures)) { result = TransitionState.OK; transition.CollisionInfo.CollisionNormalValid = false; transition.CollisionInfo.AddObject(this, TransitionState.OK); } else transition.CollisionInfo.AddObject(this, result); } transition.SpherePath.ObstructionEthereal = false; 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 != newCell) { change_cell(newCell); calc_cross_cells(); } return SetPositionError.OK; } public double GetAutonomyBlipDistance() { if ((Position.ObjCellID & 0xFFFF) < 0x100) return 100.0f; return IsPlayer ? 25.0f : 20.0f; } public BBox GetBoundingBox() { if (PartArray == null) return null; return PartArray.GetBoundingBox(); } public uint GetDataID() { if (PartArray == null) return 0; return PartArray.GetDataID(); } public float GetHeight() { if (PartArray == null) return 0.0f; return PartArray.GetHeight(); } public double GetMaxConstraintDistance() { return (Position.ObjCellID & 0xFFFF) < 0x100 ? 50.0f : 20.0f; } public PhysicsObj GetObjectA(uint objectID) { return ServerObjectManager.GetObjectA(objectID); } public float GetRadius() { if (PartArray != null) return PartArray.GetRadius(); else return 0; } public float GetPhysicsRadius() { if (State.HasFlag(PhysicsState.HasPhysicsBSP)) return 0.0f; if (PartArray.GetNumCylsphere() > 0) { var cylSpheres = PartArray.GetCylSphere(); return cylSpheres[0].Radius * Scale; } if (PartArray.GetNumSphere() > 0) { var spheres = PartArray.GetSphere(); return spheres[0].Radius * Scale; } return 0.0f; } public Sphere GetSelectionSphere(Sphere selectionSphere) { if (PartArray != null) return PartArray.GetSelectionSphere(selectionSphere); else return null; } public uint GetSetupID() { if (PartArray == null) return 0; return PartArray.GetSetupID(); } public double GetStartConstraintDistance() { return (Position.ObjCellID & 0xFFFF) < 0x100 ? 5.0f : 10.0f; } public float GetStepDownHeight() { if (PartArray == null) return 0; return PartArray.GetStepDownHeight(); } public float GetStepUpHeight() { if (PartArray == null) return 0; return PartArray.GetStepUpHeight(); } public void HandleUpdateTarget(TargetInfo targetInfo) { if (targetInfo.ContextID != 0) return; if (MovementManager != null) MovementManager.HandleUpdateTarget(targetInfo); if (PositionManager != null) PositionManager.HandleUpdateTarget(targetInfo); } public bool HasAnims() { if (PartArray == null) return false; return PartArray.HasAnims(); } public void Hook_AnimDone() { if (PartArray != null) PartArray.AnimationDone(true); } /// /// Initializes the physics object from defaults in setup /// public void InitDefaults(Setup setup) { if (setup._dat.DefaultScript != 0) play_script_internal(setup._dat.DefaultScript); if (setup._dat.DefaultMotionTable != 0) SetMotionTableID(setup._dat.DefaultMotionTable); if (setup._dat.DefaultSoundTable != 0) { //var qdid = new QualifiedDataID(0x22, setup.DefaultSTableID); //SoundTable = (SoundTable)DBObj.Get(qdid); //log.Warn($"PhysicsObj has DefaultSTableID, (SoundTable)DBObj.Get(qdid) not implemented yet, qdid = new QualifiedDataID(0x22, {setup.DefaultSTableID});"); } if (setup._dat.DefaultScriptTable != 0) { // var qdid = new QualifiedDataID(0x2C, setup.DefaultPhsTableID); // PhysicsScriptTable = (PhysicsScriptTable)DBObj.Get(qdid); //log.Warn($"PhysicsObj has DefaultPhsTableID, (PhysicsScriptTable)DBObj.Get(qdid) not implemented yet, qdid = new QualifiedDataID(0x2C, {setup.DefaultPhsTableID});"); } if (State.HasFlag(PhysicsState.Static)) { if (setup._dat.DefaultAnimation != 0) State |= PhysicsState.HasDefaultAnim; if (setup._dat.DefaultScript != 0) State |= PhysicsState.HasDefaultScript; //PhysicsEngine.AddStaticAnimatingObject(this); } } /// /// Initializes the PartArray from input dataDID /// public bool InitNullObject(uint dataDID) { bool createdParts; if (PartArray != null) createdParts = PartArray.SetSetupID(dataDID, true); else createdParts = InitPartArrayObject(dataDID, true); if (createdParts) { if (PartArray != null) { PartArray.SetPlacementFrame(0x65); if (!State.HasFlag(PhysicsState.IgnoreCollisions)) PartArray.SetFrame(Position.Frame); } return true; } return createdParts; } /// /// Initializes a static or dynamic object from input ID /// public bool InitObjectBegin(uint objectIID, bool dynamic) { ID = objectIID; if (!dynamic) State |= PhysicsState.Static; else State &= ~PhysicsState.Static; TransientState &= ~TransientStateFlags.Active; UpdateTime = PhysicsTimer.CurrentTime; return true; } /// /// Sets the placement frame for a part frame /// public bool InitObjectEnd() { if (PartArray != null) { PartArray.SetPlacementFrame(0x65); if (!State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetFrame(Position.Frame); } return true; } /// /// Initializes a new PartArray from a dataDID /// public bool InitPartArrayObject(uint dataDID, bool createParts) { if (dataDID == 0) return false; // stru_843D84 var ethereal = false; var divineType = MasterDBMap.DivineType(dataDID); if (divineType == 6) { PartArray = PartArray.CreateMesh(this, dataDID); } else { if (divineType != 7) { if ((dataDID & 0xFF000000) != 0) return false; ethereal = true; if (!makeAnimObject(dataDID | 0x2000000, createParts)) return false; } if (!ethereal) PartArray = PartArray.CreateSetup(this, dataDID, createParts); } if (PartArray == null) return false; CacheHasPhysicsBSP(); if (ethereal) { State |= PhysicsState.Ethereal; TransientState &= ~TransientStateFlags.CheckEthereal; SetTranslucencyInternal(0.25f); State |= PhysicsState.IgnoreCollisions; } return true; } /// /// Initializes the motion tables for the physics parts /// public void InitializeMotionTables() { if (PartArray != null) PartArray.InitializeMotionTables(); } public InterpretedMotionState InqInterpretedMotionState() { if (MovementManager == null) return null; else return MovementManager.InqInterpretedMotionState(); } public RawMotionState InqRawMotionState() { if (MovementManager == null) return null; else return MovementManager.InqRawMotionState(); } public void InterpolateTo(Position p, bool keepHeading) { MakePositionManager(); PositionManager.InterpolateTo(p, keepHeading); } public bool IsFullyConstrained() { if (PositionManager == null) return false; else return PositionManager.IsFullyConstrained(); } public bool IsInterpolating() { if (PositionManager == null) return false; else return PositionManager.IsInterpolating(); } public bool IsMovingTo() { if (MovementManager == null) return false; else return MovementManager.IsMovingTo(); } public void MakeMovementManager(bool init_motion) { if (MovementManager != null) return; MovementManager = MovementManager.Create(this, WeenieObj); if (init_motion) MovementManager.EnterDefaultState(); if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState |= TransientStateFlags.Active; } } public void MakePositionManager() { if (PositionManager != null) return; PositionManager = PositionManager.Create(this); if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState |= TransientStateFlags.Active; } } public bool MorphToExistingObject(PhysicsObj obj) { var result = PartArray != null && obj.PartArray != null ? PartArray.MorphToExistingObject(obj.PartArray) : false; TranslucencyOriginal = Translucency = obj.TranslucencyOriginal; ExaminationObject = true; if (PartArray != null && Translucency != 0.0f) PartArray.SetTranslucencyInternal(Translucency); return result; } public void MotionDone(uint motion, bool success) { if (MovementManager != null) MovementManager.MotionDone(motion, success); } public bool MoveOrTeleport(Position pos, int timestamp, bool contact, Vector3 velocity) { var updateTime = UpdateTimes[4]; bool timeDiff; if (Math.Abs(updateTime - timestamp) > Int16.MaxValue) timeDiff = updateTime < timestamp; else timeDiff = timestamp < updateTime; if (timeDiff) return false; if (CurCell == null || newer_event((int)PhysicsTimeStamp.Teleport, timestamp)) { teleport_hook(true); var setPos = new SetPosition(pos, SetPositionFlags.Teleport | SetPositionFlags.DontCreateCells); SetPosition(setPos); return true; } else { if (!contact) return false; if (PlayerDistance < 96.0f) InterpolateTo(pos, IsMovingTo()); else { if (PositionManager != null) PositionManager.StopInterpolating(); SetPositionSimple(pos, true); } } return true; } public void MoveToObject(PhysicsObj obj, MovementParameters movementParams) { if (MovementManager == null) { MovementManager = MovementManager.Create(this, WeenieObj); MovementManager.EnterDefaultState(); if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState &= ~TransientStateFlags.Active; } } var height = obj.PartArray != null ? obj.PartArray.GetHeight() : 0; var radius = obj.PartArray != null ? obj.PartArray.GetRadius() : 0; var parent = obj.Parent != null ? obj.Parent : obj; MoveToObject_Internal(obj, parent.ID, radius, height, movementParams); } public void MoveToObject_Internal(PhysicsObj obj, uint topLevelID, float objRadius, float objHeight, MovementParameters movementParams) { if (MovementManager == null) { MovementManager = MovementManager.Create(this, WeenieObj); MovementManager.EnterDefaultState(); if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState &= ~TransientStateFlags.Active; } } var mvs = new MovementStruct(); // packobj vtable mvs.TopLevelId = topLevelID; mvs.Radius = objRadius; mvs.ObjectId = obj.ID; mvs.Params = movementParams; mvs.Type = MovementType.MoveToObject; mvs.Height = objHeight; 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) PartArray.HandleEnterWorld(); } public void RemoveObjectFromSingleCell(ObjCell objCell) { if (CurCell != null) leave_cell(true); Position.ObjCellID = 0; if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetCellID(0); CurCell = null; if (objCell != null) { objCell.remove_shadow_object(ShadowObjects[0]); NumShadowObjects = 0; if (PartArray != null) PartArray.RemoveParts(objCell); } } public void RemovePartFromShadowCells(PhysicsPart part) { if (part == null) return; if (CurCell != null) part.Pos.ObjCellID = CurCell.ID; foreach (var shadowObj in ShadowObjects.Values) { if (shadowObj.Cell != null) shadowObj.Cell.RemovePart(part); } } public void RestoreLighting() { /*if (PartArray != null) PartArray.RestoreLightingInternal();*/ } public void SetDiffusion(float start, float end, double delta) { /*if (delta < PhysicsGlobals.EPSILON) { if (PartArray != null) PartArray.SetDiffusionInternal(end); return; } var hook = new FPHook(HookType.Velocity, PhysicsTimer.CurrentTime, delta, start, end, 0); Hooks.AddLast(hook);*/ } public void SetLighting(float luminosity, float diffuse) { if (PartArray != null) PartArray.SetLightingInternal(luminosity, diffuse); } public void SetLuminosity(float start, float end, double delta) { if (delta < PhysicsGlobals.EPSILON) { if (PartArray != null) PartArray.SetLuminosityInternal(end); return; } var hook = new FPHook(PhysicsHookType.Luminosity, PhysicsTimer_CurrentTime, delta, start, end, 0); Hooks.Add(hook); } public bool SetMotionTableID(uint mtableID) { if (PartArray == null) return false; if (!PartArray.SetMotionTableID(mtableID)) return false; MovementManager = null; if (mtableID != 0) MakeMovementManager(true); return true; } public void SetNoDraw(bool noDraw) { if (PartArray != null) PartArray.SetNoDrawInternal(noDraw); } public void SetObjectMaintainer(ObjectMaint objMaint) { ObjMaint = objMaint; } public void SetPartDiffusion(int part, float start, float end, double delta) { if (delta < PhysicsGlobals.EPSILON) { if (PartArray != null) PartArray.SetPartDiffusionInternal(part, end); return; } var hook = new FPHook(PhysicsHookType.PartDiffusion, PhysicsTimer_CurrentTime, delta, start, end, part); Hooks.Add(hook); } public bool SetPartLighting(int partIdx, float luminosity, float diffuse) { if (PartArray != null) return PartArray.SetPartLightingInternal(partIdx, luminosity, diffuse); else return false; } public void SetPartLuminosity(int part, float start, float end, double delta) { if (delta < PhysicsGlobals.EPSILON) { if (PartArray != null) PartArray.SetPartLuminosityInternal(part, end); return; } var hook = new FPHook(PhysicsHookType.PartLuminosity, PhysicsTimer_CurrentTime, delta, start, end, part); Hooks.Add(hook); } public void SetPartTextureVelocity(int partIdx, float du, float dv) { if (PartArray != null) PartArray.SetPartTextureVelocityInternal(partIdx, du, dv); } public void SetPartTranslucency(int partIdx, float startTrans, float endTrans, double delta) { if (delta < PhysicsGlobals.EPSILON) { if (PartArray != null) PartArray.SetPartTranslucencyInternal(partIdx, endTrans); return; } var hook = new FPHook(PhysicsHookType.PartTranslucency, PhysicsTimer_CurrentTime, delta, startTrans, endTrans, partIdx); Hooks.Add(hook); } public bool SetPlacementFrame(int frameID, bool sendEvent) { return SetPlacementFrameInternal(frameID); } public bool SetPlacementFrameInternal(int frameID) { bool result = false; if (PartArray != null) { result = PartArray.SetPlacementFrame(frameID); if (!State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetFrame(Position.Frame); } return result; } public SetPositionError SetPosition(SetPosition setPos) { var transition = Transition.MakeTransition(); if (transition == null) return SetPositionError.GeneralFailure; transition.InitObject(this, ObjectInfoState.Default); if (PartArray != null && PartArray.GetNumSphere() != 0) transition.InitSphere(PartArray.GetNumSphere(), PartArray.GetSphere(), Scale); else transition.InitSphere(1, PhysicsGlobals.DummySphere, 1.0f); var result = SetPositionInternal(setPos, transition); transition.CleanupTransition(); return result; } public bool SetPositionInternal(Transition transition) { var prevOnWalkable = (TransientState & TransientStateFlags.OnWalkable) != 0; var transitCell = transition.SpherePath.CurCell; var prevContact = (TransientState & TransientStateFlags.Contact) != 0; var curPos = transition.SpherePath.CurPos; if (transitCell == null) { prepare_to_leave_visibility(); store_position(curPos); //ObjMaint.GotoLostCell(this, Position.ObjCellID); set_active(false); return true; } // modified: maintain consistency for Position.Frame in change_cell set_frame(curPos.Frame); if (transitCell.Equals(CurCell)) { Position.ObjCellID = curPos.ObjCellID; if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetCellID(curPos.ObjCellID); if (Children != null) { for (var i = 0; i < Children.NumObjects; i++) { var child = Children.Objects[i]; child.Position.ObjCellID = curPos.ObjCellID; if (child.PartArray != null && !child.State.HasFlag(PhysicsState.ParticleEmitter)) child.PartArray.SetCellID(curPos.ObjCellID); } } } else { change_cell(transitCell); } //set_frame(curPos.Frame); var collisions = transition.CollisionInfo; ContactPlaneCellID = collisions.ContactPlaneCellID; ContactPlane = collisions.ContactPlane; if (collisions.ContactPlaneValid) TransientState |= TransientStateFlags.Contact; else TransientState &= ~TransientStateFlags.Contact; calc_acceleration(); if (collisions.ContactPlaneIsWater) TransientState |= TransientStateFlags.WaterContact; else TransientState &= ~TransientStateFlags.WaterContact; if (TransientState.HasFlag(TransientStateFlags.Contact)) { if (ContactPlane.Normal.Z < PhysicsGlobals.FloorZ) set_on_walkable(false); else set_on_walkable(true); } else { TransientState &= ~TransientStateFlags.OnWalkable; if (MovementManager != null && prevOnWalkable) MovementManager.LeaveGround(); calc_acceleration(); } SlidingNormal = collisions.SlidingNormal; if (collisions.SlidingNormalValid) TransientState |= TransientStateFlags.Sliding; else TransientState &= ~TransientStateFlags.Sliding; handle_all_collisions(collisions, prevContact, prevOnWalkable); if (CurCell != null) { if (State.HasFlag(PhysicsState.HasPhysicsBSP)) { calc_cross_cells(); return true; } if (transition.CellArray.Cells.Count > 0) { remove_shadows_from_cells(); add_shadows_to_cell(transition.CellArray); return true; } } return true; } public SetPositionError SetPositionInternal(Position pos, SetPosition setPos, Transition transition) { if (CurCell == null) prepare_to_enter_world(); var newCell = AdjustPosition(pos, transition.SpherePath.LocalSphere[0].Center, setPos.Flags.HasFlag(SetPositionFlags.DontCreateCells), true); if (newCell == null) { prepare_to_leave_visibility(); store_position(pos); //ObjMaint.GotoLostCell(this, Position.ObjCellID); set_active(false); return SetPositionError.OK; } if (WeenieObj != null && (WeenieObj.IsStorage || WeenieObj.IsCorpse)) return ForceIntoCell(newCell, pos); //if (setPos.Flags.HasFlag(SetPositionFlags.DontCreateCells)) //transition.CellArray.DoNotLoadCells = true; if (!CheckPositionInternal(newCell, pos, transition, setPos)) return handle_all_collisions(transition.CollisionInfo, false, false) ? SetPositionError.Collided : SetPositionError.NoValidPosition; 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 log.DebugFormat("{0} ({1:X8}) AddPhysicsObj() - {2}) resulted in {3}, discarding", Name, ID, pos.ShortLoc(), transition.SpherePath.CurPos.ShortLoc()); 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) { 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); if (result != SetPositionError.OK && setPos.Flags.HasFlag(SetPositionFlags.Scatter)) return SetScatterPositionInternal(setPos, transition); return result; } public SetPositionError SetPositionSimple(Position pos, bool sliding) { var setPos = new SetPosition(); setPos.Pos = pos; setPos.Flags = SetPositionFlags.Teleport | SetPositionFlags.SendPositionEvent; if (sliding) setPos.Flags |= SetPositionFlags.Slide; return SetPosition(setPos); } public void SetScale(float scale, double delta = 0.0) { if (delta < PhysicsGlobals.EPSILON) { if (PartArray != null) PartArray.SetScaleInternal(new Vector3(scale, scale, scale)); return; } var hook = new FPHook(PhysicsHookType.Scale, PhysicsTimer_CurrentTime, delta, Scale, scale, 0); Hooks.Add(hook); } public void SetScaleStatic(float scale) { Scale = scale; if (PartArray != null) 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++) { 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 = Math.Clamp(newPos.Frame.Origin.X, 0.5f, 191.5f); newPos.Frame.Origin.Y = Math.Clamp(newPos.Frame.Origin.Y, 0.5f, 191.5f); } // get cell for this position var indoors = (newPos.ObjCellID & 0xFFFF) >= 0x100; if ((newPos.ObjCellID & 0xFFFF) < 0x100) { LandDefs.AdjustToOutside(newPos); // ensure walkable slope var landcell = (LandCell)LScape.get_landcell(newPos.ObjCellID); 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) log.DebugFormat("{0} ({1:X8}).SetScatterPositionInternal() - tried to spawn outdoor object @ {2} ground Z {3} (diff: {4}), investigate ScatterThreshold_Z", Name, ID, newPos, groundZ, newPos.Frame.Origin.Z - groundZ); 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; }*/ } if (indoors) { 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; } public void SetTextureVelocity(float du, float dv) { if (PartArray != null) PartArray.SetTextureVelocityInternal(du, dv); } public void SetTranslucency(float translucency, double delta) { if (delta < PhysicsGlobals.EPSILON) { Translucency = translucency < TranslucencyOriginal ? translucency : TranslucencyOriginal; if (PartArray != null) PartArray.SetTranslucencyInternal(Translucency); return; } var hook = new FPHook(PhysicsHookType.Translucency, PhysicsTimer_CurrentTime, delta, 0.0f, translucency, 0); Hooks.Add(hook); } public void SetTranslucency2(float startTrans, float endTrans, double delta) { if (delta < PhysicsGlobals.EPSILON) { Translucency = endTrans < TranslucencyOriginal ? endTrans : TranslucencyOriginal; if (PartArray != null) PartArray.SetTranslucencyInternal(startTrans); return; } var hook = new FPHook(PhysicsHookType.Translucency, PhysicsTimer_CurrentTime, delta, startTrans, endTrans, 0); Hooks.Add(hook); } public void SetTranslucencyHierarchial(float translucency) { Translucency = translucency < TranslucencyOriginal ? translucency : TranslucencyOriginal; if (PartArray != null) PartArray.SetTranslucencyInternal(translucency); for (var i = 0; i < Children.NumObjects; i++) Children.Objects[i].SetTranslucencyHierarchial(translucency); } public void SetTranslucencyInternal(float translucency) { Translucency = translucency < TranslucencyOriginal ? translucency : TranslucencyOriginal; if (PartArray != null) PartArray.SetTranslucencyInternal(translucency); } public bool ShouldDrawParticles(float degradeDistance) { if (!ExaminationObject) return true; return !(CYpt > degradeDistance || CurCell == null /*|| vftable unknown release */); } public void StopCompletely(bool sendEvent) { if (MovementManager == null) return; var mvs = new MovementStruct(MovementType.StopCompletely); MovementManager.PerformMovement(mvs); } public void StopCompletely_Internal() { if (PartArray != null) PartArray.StopCompletelyInternal(); } public void StopInterpolating() { if (PositionManager != null) PositionManager.StopInterpolating(); } public WeenieError StopInterpretedMotion(uint motion, MovementParameters movementParams) { if (PartArray == null) return WeenieError.GeneralMovementFailure; return PartArray.StopInterpretedMotion(motion, movementParams); } public WeenieError StopMotion(uint motion, MovementParameters movementParams, bool sendEvent) { LastMoveWasAutonomous = true; if (MovementManager == null) return WeenieError.NoAnimationTable; var mvs = new MovementStruct(MovementType.StopRawCommand); mvs.Motion = motion; mvs.Params = movementParams; return MovementManager.PerformMovement(mvs); } public void TurnToHeading(MovementParameters movementParams) { if (MovementManager == null) { // refactor into common method MovementManager = MovementManager.Create(this, WeenieObj); MovementManager.EnterDefaultState(); if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState &= ~TransientStateFlags.Active; } } var mvs = new MovementStruct(MovementType.TurnToHeading); mvs.Params = movementParams; MovementManager.PerformMovement(mvs); } public bool TurnToObject(PhysicsObj obj, MovementParameters movementParams) { if (obj == null) return false; var parent = obj.Parent != null ? obj.Parent : obj; TurnToObject_Internal(obj.ID, parent.ID, movementParams); return true; } public void TurnToObject_Internal(uint objectID, uint topLevelID, MovementParameters movementParams) { if (MovementManager == null) { // refactor into common method MovementManager = MovementManager.Create(this, WeenieObj); MovementManager.EnterDefaultState(); if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState &= ~TransientStateFlags.Active; } } var mvs = new MovementStruct(MovementType.TurnToObject); mvs.ObjectId = objectID; mvs.TopLevelId = topLevelID; mvs.Params = movementParams; MovementManager.PerformMovement(mvs); } public void UpdateChild(PhysicsObj childObj, int partIdx, AFrame childFrame) { var frame = partIdx >= PartArray.NumParts ? AFrame.Combine(Position.Frame, childFrame) : AFrame.Combine(PartArray.Parts[partIdx].Pos.Frame, childFrame); childObj.set_frame(frame); if (childObj.ParticleManager != null) childObj.ParticleManager.UpdateParticles(); if (childObj.ScriptManager != null) childObj.ScriptManager.UpdateScripts(); } public void UpdateChildrenInternal() { if (PartArray == null || Children == null || Children.NumObjects == 0) return; for (var i = 0; i < Children.Objects.Count; i++) UpdateChild(Children.Objects[i], Children.PartNumbers[i], Children.Frames[i]); } public int InitialUpdates; public void UpdateObjectInternal(double quantum) { if ((TransientState & TransientStateFlags.Active) == 0 || CurCell == null) return; if ((TransientState & TransientStateFlags.CheckEthereal) != 0) set_ethereal(false, false); JumpedThisFrame = false; var newPos = new Position(Position.ObjCellID); UpdatePositionInternal(quantum, ref newPos.Frame); if (PartArray != null && PartArray.GetNumSphere() != 0) { if (newPos.Frame.Equals(Position.Frame)) { CachedVelocity = Vector3.Zero; set_frame(newPos.Frame); InitialUpdates++; } else { if ((State & PhysicsState.AlignPath) != 0) { var diff = newPos.Frame.Origin - Position.Frame.Origin; newPos.Frame.set_vector_heading(Vector3.Normalize(diff)); } else if ((State & PhysicsState.Sledding) != 0 && Velocity != Vector3.Zero) newPos.Frame.set_vector_heading(Vector3.Normalize(Velocity)); if (GetBlockDist(Position, newPos) > 1) { log.Warn($"WARNING: failed transition for {Name} from {Position} to {newPos}"); return; } var transit = transition(Position, newPos, false); // temporarily modified while debug path is examined if (transit != null && transit.SpherePath.CurCell != null) { CachedVelocity = Position.GetOffset(transit.SpherePath.CurPos) / (float)quantum; SetPositionInternal(transit); } else { if (IsPlayer) log.DebugFormat("{0} ({1:X8}).UpdateObjectInternal({2}) - failed transition from {3} to {4}", Name, ID, quantum, Position, newPos); else if (transit != null && transit.SpherePath.CurCell == null) log.Warn($"{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; } } } else { 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(); if (TargetManager != null) TargetManager.HandleTargetting(); if (MovementManager != null) MovementManager.UseTime(); if (PartArray != null) PartArray.HandleMovement(); if (PositionManager != null) PositionManager.UseTime(); if (ParticleManager != null) ParticleManager.UpdateParticles(); if (ScriptManager != null) ScriptManager.UpdateScripts(); } 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) { log.Warn($"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 log.DebugFormat("{0}.UpdateObjectInternalServer({1}) - failed transition from {2} to {3}", Name, quantum, Position, RequestPos); if (DetectionManager != null) DetectionManager.CheckDetection(); if (TargetManager != null) TargetManager.HandleTargetting(); if (MovementManager != null) MovementManager.UseTime(); if (PartArray != null) PartArray.HandleMovement(); if (PositionManager != null) PositionManager.UseTime(); if (ParticleManager != null) ParticleManager.UpdateParticles(); if (ScriptManager != null) ScriptManager.UpdateScripts(); return requestCell >> 16 != 0x18A || CurCell?.ID >> 16 == requestCell >> 16; } public void UpdateAnimationInternal(double quantum) { if (!TransientState.HasFlag(TransientStateFlags.Active)) return; if (TransientState.HasFlag(TransientStateFlags.CheckEthereal)) set_ethereal(false, false); JumpedThisFrame = false; var newPos = new Position(Position.ObjCellID); //UpdatePositionInternal(quantum, ref newPos.Frame); if (PartArray != null) PartArray.Update(quantum, ref newPos.Frame); set_frame(newPos.Frame); if (PartArray != null) PartArray.HandleMovement(); } public void UpdatePartsInternal() { if (PartArray == null || State.HasFlag(PhysicsState.ParticleEmitter)) return; PartArray.SetFrame(Position.Frame); } public void UpdatePhysicsInternal(float quantum, ref AFrame frameOffset) { var velocity_mag2 = Velocity.LengthSquared(); if (velocity_mag2 <= 0.0f) { if (MovementManager == null && TransientState.HasFlag(TransientStateFlags.OnWalkable)) TransientState &= ~TransientStateFlags.Active; } else { if (velocity_mag2 > PhysicsGlobals.MaxVelocitySquared) { Velocity = Vector3.Normalize(Velocity) * PhysicsGlobals.MaxVelocity; velocity_mag2 = PhysicsGlobals.MaxVelocitySquared; } // todo: collision normals calc_friction(quantum, velocity_mag2); if (velocity_mag2 - PhysicsGlobals.SmallVelocitySquared < PhysicsGlobals.EPSILON) Velocity = Vector3.Zero; var movement = Acceleration * 0.5f * quantum * quantum + Velocity * quantum; frameOffset.Origin += movement; } Velocity += Acceleration * quantum; frameOffset.GRotate(Omega * quantum); } public void UpdatePositionInternal(double quantum, ref AFrame newFrame) { var offsetFrame = new AFrame(); if (!State.HasFlag(PhysicsState.Hidden)) { if (PartArray != null) PartArray.Update(quantum, ref offsetFrame); if (TransientState.HasFlag(TransientStateFlags.OnWalkable)) offsetFrame.Origin *= Scale; else offsetFrame.Origin *= 0.0f; // OnWalkable getting reset? } if (PositionManager != null) PositionManager.AdjustOffset(offsetFrame, quantum); newFrame = AFrame.Combine(Position.Frame, offsetFrame); if (!State.HasFlag(PhysicsState.Hidden)) UpdatePhysicsInternal((float)quantum, ref newFrame); process_hooks(); } public void UpdateViewerDistance(float cypt, Vector3 heading) { if (PartArray != null) PartArray.UpdateViewerDistance(cypt, heading); CYpt = cypt; } public void UpdateViewerDistance() { var min_2D_degrade_dist_sq = State.HasFlag(PhysicsState.ParticleEmitter) ? Render.ParticleDistance2DSquared : Render.ObjectDistance2DSquared; var viewerHeading = Render.ViewerPos.GetOffset(Position); // v11?? var lenSq = viewerHeading.LengthSquared(); CYpt = viewerHeading.Length(); if (PartArray != null) PartArray.UpdateViewerDistance(CYpt, viewerHeading); } public void UpdateViewerDistanceRecursive() { UpdateViewerDistance(); if (Children == null || Children.NumObjects == 0) return; foreach (var child in Children.Objects) child.UpdateViewerDistanceRecursive(); } public void add_anim_hook(DatLoader.Entity.AnimationHook hook) { AnimHooks.Add(hook); } public bool add_child(PhysicsObj obj, int where) { if (PartArray == null || obj.Equals(this)) return false; var setup = PartArray.Setup.GetHoldingLocation(where); if (setup == null) return false; if (Children == null) Children = new ChildList(); Children.AddChild(obj, new AFrame(setup.Frame), setup.PartId, where); return true; } public bool add_child(PhysicsObj obj, int partIdx, AFrame frame) { if (obj.Equals(this)) return false; if (PartArray == null || partIdx != -1 && partIdx >= PartArray.NumParts) return false; if (Children == null) Children = new ChildList(); Children.AddChild(obj, frame, partIdx, 0); return true; } public void add_obj_to_cell(ObjCell newCell, AFrame newFrame) { enter_cell(newCell); Position.Frame = newFrame; if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetFrame(Position.Frame); UpdateChildrenInternal(); calc_cross_cells_static(); } public void add_particle_shadow_to_cell() { NumShadowObjects = 1; var shadowObj = new ShadowObj(this, CurCell); ShadowObjects.Add(1, shadowObj); if (PartArray != null) PartArray.AddPartsShadow(CurCell, 1); } public void add_shadows_to_cell(CellArray cellArray) { if (State.HasFlag(PhysicsState.ParticleEmitter)) add_particle_shadow_to_cell(); else { foreach (var cell in cellArray.Cells.Values) { if (cell == null) continue; // fixme var shadowObj = new ShadowObj(this, cell); if (!ShadowObjects.ContainsKey(cell.ID)) ShadowObjects.Add(cell.ID, shadowObj); else ShadowObjects[cell.ID] = shadowObj; if (cell != null) cell.AddShadowObject(shadowObj); if (PartArray != null) PartArray.AddPartsShadow(cell, NumShadowObjects); } } if (Children != null) { foreach (var child in Children.Objects) child.add_shadows_to_cell(cellArray); } } public void add_shadows_to_cell(ObjCell cell) { var shadowObj = new ShadowObj(this, cell); if (!ShadowObjects.ContainsKey(cell.ID)) ShadowObjects.Add(cell.ID, shadowObj); else ShadowObjects[cell.ID] = shadowObj; if (cell != null) cell.AddShadowObject(shadowObj); if (PartArray != null) PartArray.AddPartsShadow(cell, NumShadowObjects); } public void add_voyeur(uint objectID, float radius, double quantum) { if (TargetManager == null) TargetManager = new TargetManager(this); TargetManager.AddVoyeur(objectID, radius, quantum); } public void animate_static_object() { if (CurCell == null) return; PhysicsTimer_CurrentTime = PhysicsTimer.CurrentTime; var deltaTime = PhysicsTimer.CurrentTime - UpdateTime; if (deltaTime < PhysicsGlobals.MinQuantum) return; if (PartArray == null || deltaTime < PhysicsGlobals.EPSILON || deltaTime > PhysicsGlobals.MaxQuantum) { UpdateTime = PhysicsTimer.CurrentTime; return; } if (State.HasFlag(PhysicsState.HasDefaultAnim)) { AFrame empty = null; PartArray.Update(deltaTime, ref empty); Position.Frame.Rotate(Omega); UpdatePartsInternal(); UpdateChildrenInternal(); } if (ScriptManager != null && State.HasFlag(PhysicsState.HasDefaultScript)) ScriptManager.UpdateScripts(); if (ParticleManager != null) { ParticleManager.UpdateParticles(); } process_hooks(); UpdateTime = PhysicsTimer.CurrentTime; } public void attack(AttackCone attackCone) { if (CurCell.ID == 0) return; if (AttackManager == null) AttackManager = new AttackManager(); var sphere = new Sphere(); sphere.Center.Z = attackCone.Height * Scale; sphere.Radius = AttackManager.AttackRadius + attackCone.Radius * Scale; var cellArray = new CellArray(); ObjCell.find_cell_list(Position, sphere, cellArray, null); var attackInfo = AttackManager.NewAttack(attackCone.PartIdx); foreach (var cell in cellArray.Cells.Values) { if (cell == null) continue; cell.CheckAttack(ID, Position, Scale, attackCone, attackInfo); } if (!attackInfo.WaitingForCells) report_attacks(attackInfo); } public void calc_acceleration() { if (TransientState.HasFlag(TransientStateFlags.Contact) && TransientState.HasFlag(TransientStateFlags.OnWalkable)) Omega = Acceleration = Vector3.Zero; else { if (State.HasFlag(PhysicsState.Gravity)) Acceleration = new Vector3(0, 0, PhysicsGlobals.Gravity); else Acceleration = Vector3.Zero; } } public void calc_cross_cells() { CellArray.SetDynamic(); if (State.HasFlag(PhysicsState.HasPhysicsBSP)) find_bbox_cell_list(CellArray); else { if (PartArray != null && PartArray.GetNumCylsphere() != 0) ObjCell.find_cell_list(Position, PartArray.GetNumCylsphere(), PartArray.GetCylSphere(), CellArray, null); else { // added sorting sphere null check var sphere = PartArray != null && PartArray.Setup.SortingSphere != null ? PartArray.GetSortingSphere() : PhysicsGlobals.DummySphere; ObjCell.find_cell_list(Position, sphere, CellArray, null); } } remove_shadows_from_cells(); add_shadows_to_cell(CellArray); } public void calc_cross_cells_static() { CellArray.SetStatic(); if (PartArray != null && PartArray.GetNumCylsphere() != 0 && !State.HasFlag(PhysicsState.HasPhysicsBSP)) ObjCell.find_cell_list(Position, PartArray.GetNumCylsphere(), PartArray.GetCylSphere(), CellArray, null); else find_bbox_cell_list(CellArray); remove_shadows_from_cells(); add_shadows_to_cell(CellArray); } public void calc_friction(double quantum, float velocity_mag2) { if (!TransientState.HasFlag(TransientStateFlags.OnWalkable)) return; var angle = Vector3.Dot(Velocity, ContactPlane.Normal); if (angle >= 0.25f) return; Velocity -= ContactPlane.Normal * angle; var friction = Friction; if (State.HasFlag(PhysicsState.Sledding)) { if (velocity_mag2 < 1.5625f) friction = 1.0f; else if (velocity_mag2 >= 6.25f && ContactPlane.Normal.Z > 0.99999536f) friction = 0.2f; } var scalar = (float)Math.Pow(1.0f - friction, quantum); Velocity *= scalar; } public void cancel_moveto() { if (MovementManager != null) MovementManager.CancelMoveTo(WeenieError.ActionCancelled); } public void change_cell(ObjCell newCell) { if (PhysicsEngine.Instance.Server) { change_cell_server(newCell); return; } if (CurCell != null) leave_cell(true); if (newCell == null) { Position.ObjCellID = 0; if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetCellID(0); CurCell = null; } else enter_cell(newCell); } public void change_cell_server(ObjCell newCell) { if (CurCell != null) leave_cell(true); if (newCell == null) { Position.ObjCellID = 0; if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetCellID(0); CurCell = null; } else enter_cell_server(newCell); } 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; var targetHeight = PartArray != null ? PartArray.GetHeight() : 0; var targetRadius = PartArray != null ? PartArray.GetRadius() : 0; var attackHeight = attackerScale * attackCone.Height; var attackRad = attackerScale * attackCone.Radius + attackerAttackRadius; return Sphere.Attack(Position, targetRadius, targetHeight, attackerPos, attackCone.Left, attackCone.Right, attackRad, attackHeight); } public bool check_collision(PhysicsObj obj) { if (State.HasFlag(PhysicsState.Static)) return false; var trans = Transition.MakeTransition(); var objectInfo = get_object_info(trans, false); trans.InitObject(this, objectInfo.State); if (PartArray != null && PartArray.GetNumSphere() != 0) trans.InitSphere(PartArray.GetNumSphere(), PartArray.GetSphere(), Scale); else trans.InitSphere(1, PhysicsGlobals.DummySphere, Scale); trans.InitPath(CurCell, Position, Position); return trans.CheckCollisions(obj); } public bool check_contact(bool contact) { if (TransientState.HasFlag(TransientStateFlags.Contact) && Vector3.Dot(Velocity, ContactPlane.Normal) > PhysicsGlobals.EPSILON) return false; else return contact; } public void clear_sequence_anims() { if (PartArray != null) PartArray.Sequence.clear_animations(); } public void clear_target() { if (TargetManager != null) TargetManager.ClearTarget(); } public void clear_transient_states() { TransientState &= ~TransientStateFlags.Contact; calc_acceleration(); var walkable = TransientState.HasFlag(TransientStateFlags.OnWalkable); TransientState &= ~(TransientStateFlags.OnWalkable | TransientStateFlags.WaterContact); if (MovementManager != null && walkable) MovementManager.LeaveGround(); calc_acceleration(); TransientState = 0; } public int create_blocking_particle_emitter(uint emitterInfoID, int partIdx, AFrame offset, int emitterID) { if (ParticleManager == null) ParticleManager = new ParticleManager(); return ParticleManager.CreateBlockingParticleEmitter(this, emitterInfoID, partIdx, offset, emitterID); } public int create_particle_emitter(uint emitterInfoID, int partIdx, AFrame offset, int emitterID) { if (ParticleManager == null) ParticleManager = new ParticleManager(); return ParticleManager.CreateParticleEmitter(this, emitterInfoID, partIdx, offset, emitterID); } public bool destroy_particle_emitter(int emitterID) { if (ParticleManager == null) return false; return ParticleManager.DestroyParticleEmitter(emitterID); } 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) { if (!IsPlayer || !(WeenieObj.WorldObject is Player player)) return; 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) { if (PartArray == null) return; newCell.AddObject(this); foreach (var child in Children.Objects) child.enter_cell(newCell); CurCell = newCell; 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); if (!DatObject && newCell != null) { CurLandblock = LScape.get_landblock(newCell.ID); if (CurLandblock != null) CurLandblock.add_server_object(this); } } public void enter_cell_server(ObjCell newCell) { //Console.WriteLine($"{Name}.enter_cell_server({newCell.ID:X8})"); enter_cell(newCell); RequestPos.ObjCellID = newCell.ID; // document this control flow better // sync location for initial CO if (entering_world) WeenieObj.WorldObject.SyncLocation(); // handle self if (IsPlayer) { var newlyVisible = handle_visible_cells(); enqueue_objs(newlyVisible); } else { handle_visible_cells_non_player(); } // 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); bool slide = ProjectileTarget == null || WeenieObj.WorldObject is SpellProjectile; var result = enter_world(slide); entering_world = false; return result; } public bool enter_world(bool slide) { if (Parent != null) return false; UpdateTime = PhysicsTimer.CurrentTime; var setPos = new SetPosition(); setPos.Pos = Position; setPos.Flags = SetPositionFlags.Placement; if (slide) setPos.Flags |= SetPositionFlags.Slide; var result = SetPosition(setPos); if (result != SetPositionError.OK) return false; if (!State.HasFlag(PhysicsState.Static)) TransientState |= TransientStateFlags.Active; if (PartArray != null) PartArray.HandleEnterWorld(); if (MovementManager != null) MovementManager.HandleEnterWorld(); return true; } public bool ethereal_check_for_collisions() { foreach (var shadowObj in ShadowObjects.Values) { if (shadowObj.Cell != null && shadowObj.Cell.check_collisions(this)) return true; } return false; } public void exit_world() { if (PartArray != null) PartArray.HandleExitWorld(); if (MovementManager != null) MovementManager.HandleExitWorld(); if (PositionManager != null) PositionManager.Unstick(); if (TargetManager != null) { TargetManager.ClearTarget(); TargetManager.NotifyVoyeurOfEvent(TargetStatus.ExitWorld); } if (DetectionManager != null) DetectionManager.DestroyDetectionCylsphere(0); report_collision_end(true); } public void find_bbox_cell_list(CellArray cellArray) { if (PartArray == null || CurCell == null) return; cellArray.NumCells = 0; cellArray.AddedOutside = false; cellArray.add_cell(CurCell.ID, CurCell); //var checkCells = cellArray.Cells.Values.ToList(); for (var i = 0; i < cellArray.Cells.Count; i++) //foreach (var cell in checkCells) { var cell = cellArray.Cells.Values.ElementAt(i); if (cell == null) continue; PartArray.calc_cross_cells_static(cell, cellArray); } } public int get_curr_frame_number() { if (PartArray != null) return PartArray.Sequence.get_curr_frame_number(); else return 0; } public double get_distance_to_object(PhysicsObj obj, bool use_cyls) { if (!use_cyls) return Position.Distance(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.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; } public float get_heading() { return Position.Frame.get_heading(); } public Vector2 get_landscape_coord() { var lcoord = LandDefs.gid_to_lcoord(Position.ObjCellID); if (lcoord != null) return lcoord.Value; else return Vector2.Zero; } public Vector3 get_local_physics_velocity() { return Position.GlobalToLocalVec(Velocity); } public MotionInterp get_minterp() { if (MovementManager == null) { MovementManager = MovementManager.Create(this, WeenieObj); MovementManager.EnterDefaultState(); if (State.HasFlag(PhysicsState.Static)) { if (TransientState.HasFlag(TransientStateFlags.Active)) { // loword = cmd, hiword = param // refactor... UpdateTime = PhysicsTimer.CurrentTime; } TransientState |= TransientStateFlags.Active; } } return MovementManager.get_minterp(); } public int get_num_emitters() { if (ParticleManager == null) return 0; return ParticleManager.GetNumEmitters(); } public ObjectInfo get_object_info(Transition transition, bool adminMove) { var objInfo = new ObjectInfo(); if (State.HasFlag(PhysicsState.EdgeSlide)) objInfo.State |= ObjectInfoState.EdgeSlide; if (!adminMove) { if (TransientState.HasFlag(TransientStateFlags.Contact)) { var isWater = TransientState.HasFlag(TransientStateFlags.WaterContact); if (check_contact(true)) { transition.InitContactPlane(ContactPlaneCellID, ContactPlane, isWater); objInfo.State |= ObjectInfoState.Contact; if (TransientState.HasFlag(TransientStateFlags.OnWalkable)) objInfo.State |= ObjectInfoState.OnWalkable; } else transition.InitLastKnownContactPlane(ContactPlaneCellID, ContactPlane, isWater); } if (TransientState.HasFlag(TransientStateFlags.Sliding)) transition.InitSlidingNormal(SlidingNormal); } if (PartArray != null && PartArray.AllowsFreeHeading()) objInfo.State |= ObjectInfoState.FreeRotate; if (State.HasFlag(PhysicsState.Missile)) objInfo.State |= ObjectInfoState.PathClipped; return objInfo; } public PositionManager get_position_manager() { MakePositionManager(); return PositionManager; } public uint get_sticky_object() { if (PositionManager == null) return 0; return PositionManager.GetStickyObjectID(); } public double get_target_quantum() { if (TargetManager == null || TargetManager.TargetInfo == null) return 0.0f; return TargetManager.TargetInfo.Quantum; } public Vector3 get_velocity() { return CachedVelocity; } public float get_walkable_z() { return PhysicsGlobals.FloorZ; } public bool handle_all_collisions(CollisionInfo collisions, bool prev_has_contact, bool prev_on_walkable) { var apply_bounce = true; if (prev_on_walkable && TransientState.HasFlag(TransientStateFlags.OnWalkable) && !State.HasFlag(PhysicsState.Sledding)) apply_bounce = false; var retval = false; foreach (var collideObject in collisions.CollideObject) if (collideObject != null && track_object_collision(collideObject, prev_has_contact)) retval = true; report_collision_end(false); if (CollidingWithEnvironment) { CollidingWithEnvironment = collisions.CollidedWithEnvironment; } else if (collisions.CollidedWithEnvironment || !prev_on_walkable && TransientState.HasFlag(TransientStateFlags.OnWalkable)) { if (report_environment_collision(prev_has_contact)) retval = true; } if (collisions.FramesStationaryFall <= 1) { if (apply_bounce && collisions.CollisionNormalValid) { if (State.HasFlag(PhysicsState.Inelastic)) { Velocity = Vector3.Zero; // custom for spell projectiles: explode on server report_environment_collision(prev_has_contact); } else { var collisionAngle = Vector3.Dot(Velocity, collisions.CollisionNormal); if (collisionAngle < 0.0f) { var elasticAngle = -(collisionAngle * (Elasticity + 1.0f)); Velocity += collisions.CollisionNormal * elasticAngle; } } } } else { Velocity = Vector3.Zero; if (collisions.FramesStationaryFall == 3) { TransientState &= ~TransientStateFlags.StationaryComplete; return retval; } } if (collisions.FramesStationaryFall == 0) TransientState &= ~TransientStateFlags.StationaryComplete; else if (collisions.FramesStationaryFall == 1) TransientState |= TransientStateFlags.StationaryFall; else if (collisions.FramesStationaryFall == 2) TransientState |= TransientStateFlags.StationaryStop; else TransientState |= TransientStateFlags.StationaryStuck; return retval; } /// /// Maintains the list of visible objects for a player /// /// The list of newly visible objects since last call public List handle_visible_cells() { //return new List(); //Console.WriteLine($"handle_visible_cells({CurCell.ID:X8}) for {Name}"); // remove any objects that have been in the destruction queue > 25s var expiredObjs = ObjMaint.DestroyObjects(); //Console.WriteLine("Destroyed objects: " + expiredObjs.Count); //foreach (var expiredObj in expiredObjs) //Console.WriteLine(expiredObj.Name); // get the list of visible objects from this cell var visibleObjects = ObjMaint.GetVisibleObjects(CurCell); //Console.WriteLine("Visible objects from this cell: " + visibleObjects.Count); //foreach (var visibleObject in visibleObjects) //Console.WriteLine(visibleObject.Name); // get the difference between current and previous visible //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) //Console.WriteLine(obj.Name); // 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})"); Console.WriteLine("CreateObjs:"); for (var i = 0; i < createObjs.Count; i++) Console.WriteLine($"{i} - {createObjs[i].Name}"); Console.WriteLine("Newly Visible:"); for (var i = 0; i < newlyVisible.Count; i++) Console.WriteLine($"{i} = {newlyVisible[i].Name}"); }*/ // add newly occluded objects to the destruction queue ObjMaint.AddObjectsToBeDestroyed(newlyOccluded); 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) log.Warn($"{Name}.handle_visible_obj({obj.Name}): CurCell null"); else log.Warn($"{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) return false; foreach (var shadowObj in ShadowObjects.Values) { if (shadowObj.Cell == null) return false; } return true; } public bool is_newer(int timestamp, int newTime) { if (Math.Abs(newTime - timestamp) > Int16.MaxValue) return newTime < timestamp; else return timestamp < newTime; } public static bool is_valid_walkable(Vector3 normal) { return normal.Z >= PhysicsGlobals.FloorZ; } public void leave_cell(bool is_changing_cell) { if (CurCell == null) return; CurCell.RemoveObject(this); foreach (var child in Children.Objects) child.leave_cell(is_changing_cell); // removed lighting CurCell = null; if (CurLandblock != null && !DatObject) { CurLandblock.remove_server_object(this); CurLandblock = null; } } public void leave_visibility() { prepare_to_leave_visibility(); store_position(Position); //ObjMaint.GotoLostCell(this, Position.ObjCellID); TransientState &= ~TransientStateFlags.Active; } public void leave_world() { report_collision_end(true); if (ObjMaint != null) { //ObjMaint.RemoveFromLostCell(this); ObjMaint.RemoveObjectToBeDestroyed(this); } TransientState &= ~TransientStateFlags.Active; remove_shadows_from_cells(); leave_cell(false); Position.ObjCellID = 0; if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetCellID(0); TransientState &= ~TransientStateFlags.Contact; calc_acceleration(); var walkable = TransientState.HasFlag(TransientStateFlags.OnWalkable); TransientState &= ~(TransientStateFlags.OnWalkable | TransientStateFlags.WaterContact); if (MovementManager != null && walkable) MovementManager.LeaveGround(); calc_acceleration(); TransientState = 0; } public bool makeAnimObject(uint setupID, bool createParts) { PartArray = PartArray.CreateSetup(this, setupID, createParts); return PartArray != null; } public static PhysicsObj makeNullObject(uint objectIID, bool dynamic) { var obj = new PhysicsObj(); obj.InitObjectBegin(objectIID, dynamic); return obj; } public static PhysicsObj makeObject(PhysicsObj template) { uint dataID = 0; if (template.PartArray != null) dataID = template.PartArray.GetDataID(); else template = new PhysicsObj(); var obj = makeObject(dataID, 0, true); if (obj == null) return null; obj.MorphToExistingObject(template); if (obj.PartArray != null && obj.PartArray.Setup != null) obj.play_script_internal(obj.PartArray.Setup._dat.DefaultScript); return obj; } 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.ReportCollisions; particle.PartArray = PartArray.CreateParticle(particle, numParts, sortingSphere); return particle; } public bool movement_is_autonomous() { return LastMoveWasAutonomous; } public bool newer_event(int stamp, int newTime) { var updateTime = UpdateTimes[stamp]; if (Math.Abs(newTime - updateTime) > Int16.MaxValue) return newTime < updateTime; else return updateTime < newTime; } public bool obj_within_block() { var sortingSphere = PartArray != null ? PartArray.GetSortingSphere() : PhysicsGlobals.DummySphere; var globCenter = Position.Frame.LocalToGlobal(sortingSphere.Center); if (State.HasFlag(PhysicsState.HasPhysicsBSP)) { if (globCenter.X >= sortingSphere.Radius && globCenter.Y >= sortingSphere.Radius) { var blockRadius = LandDefs.BlockLength - sortingSphere.Radius; if (globCenter.X < blockRadius) { if (globCenter.Y >= blockRadius) return false; return true; } } return false; } if (PartArray != null && PartArray.GetNumCylsphere() > 0) { for (var i = 0; i < PartArray.GetNumCylsphere(); i++) { var cylSphere = PartArray.GetCylSphere()[i]; globCenter = Position.Frame.LocalToGlobal(cylSphere.LowPoint); if (globCenter.X < cylSphere.Radius || globCenter.Y < cylSphere.Radius) return false; var blockRadius = LandDefs.BlockLength - cylSphere.Radius; if (globCenter.X >= blockRadius || globCenter.Y >= blockRadius) return false; } return true; } if (PartArray != null && PartArray.GetNumSphere() > 0) return LandDefs.InBlock(globCenter, sortingSphere.Radius); else return LandDefs.InBlock(Position.Frame.Origin, 0.0f); } public bool on_ground() { return TransientState.HasFlag(TransientStateFlags.Contact) && TransientState.HasFlag(TransientStateFlags.OnWalkable); } public bool play_default_script(int partIdx) { if (Children == null) return false; for (var i = 0; i < Children.NumObjects; i++) { if (Children.PartNumbers[i] == partIdx && Children.Objects[i] != null) { var childObj = Children.Objects[i]; if (childObj.CurCell != null) { if (childObj.PhysicsScriptTable == null) return false; var script = childObj.PhysicsScriptTable.GetScript(childObj.DefaultScript, childObj.DefaultScriptIntensity); return childObj.play_script_internal(script); } return true; } } return false; } public bool play_default_script() { if (CurCell == null) return true; if (PhysicsScriptTable == null) return false; var script = PhysicsScriptTable.GetScript(DefaultScript, DefaultScriptIntensity); play_script_internal(script); return true; } public bool play_script(uint scriptID) { if (CurCell != null) return play_script_internal(scriptID); else return false; } public bool play_script(PlayScript scriptType, float mod) { if (CurCell == null) return true; if (PhysicsScriptTable == null) return false; var script = PhysicsScriptTable.GetScript(scriptType, mod); return play_script_internal(script); } public bool play_script_internal(uint scriptID) { if (scriptID == 0) return false; if (ScriptManager == null) ScriptManager = new ScriptManager(this); return ScriptManager.AddScript(scriptID); } public bool play_sound(int soundType, float volume) { if (SoundTable == null) return false; //SoundManager.PlaySoundA(soundType, volume); return true; } public void prepare_to_enter_world() { UpdateTime = PhysicsTimer.CurrentTime; //ObjMaint.RemoveFromLostCell(this); ObjMaint.RemoveObjectToBeDestroyed(this); foreach (var child in Children.Objects) ObjMaint.RemoveObjectToBeDestroyed(child); if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; // hiword? TransientState |= TransientStateFlags.Active; } } public bool prepare_to_leave_visibility() { remove_shadows_from_cells(); //ObjMaint.RemoveFromLostCell(this); leave_cell(false); ObjMaint.AddObjectToBeDestroyed(this); foreach (var child in Children.Objects) { ObjMaint.AddObjectToBeDestroyed(child); } return true; } public void process_fp_hook(PhysicsHookType type, float curr_value, Object userData) { switch (type) { case PhysicsHookType.Scale: SetScaleStatic(curr_value); break; case PhysicsHookType.Translucency: SetTranslucencyInternal(curr_value); break; case PhysicsHookType.PartTranslucency: if (PartArray != null) PartArray.SetPartTranslucencyInternal((int)userData, curr_value); break; case PhysicsHookType.Luminosity: if (PartArray != null) PartArray.SetLuminosityInternal(curr_value); break; case PhysicsHookType.PartLuminosity: if (PartArray != null) PartArray.SetPartLuminosityInternal((int)userData, curr_value); break; case PhysicsHookType.Diffusion: if (PartArray != null) PartArray.SetDiffusionInternal(curr_value); break; case PhysicsHookType.PartDiffusion: if (PartArray != null) PartArray.SetPartDiffusionInternal((int)userData, curr_value); break; case PhysicsHookType.CallPES: CallPESInternal((uint)userData, curr_value); break; } } public void process_hooks() { for (var i = 0; i < Hooks.Count; i++) { var hook = Hooks[i]; hook.Execute(this); } Hooks.Clear(); for (var i = 0; i < AnimHooks.Count; i++) { var animHook = AnimHooks[i]; AnimHook.Execute(this, animHook); } AnimHooks.Clear(); } public void queue_netblob(int blob) { // convert to ACE network protocol } public void recalc_cross_cells() { if (PartArray == null) return; if (Position.ObjCellID != 0) calc_cross_cells(); else { if (!ExaminationObject || !State.HasFlag(PhysicsState.ParticleEmitter)) return; add_particle_shadow_to_cell(); } foreach (var child in Children.Objects) child.recalc_cross_cells(); } public void receive_detection_update(DetectionInfo info) { if (DetectionManager == null) return; DetectionManager.ReceiveDetectionUpdate(info); if (State.HasFlag(PhysicsState.Static)) return; if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState |= TransientStateFlags.Active; } public void receive_target_update(TargetInfo info) { if (TargetManager != null) TargetManager.ReceiveUpdate(info); } public bool reenter_visibility() { prepare_to_enter_world(); var setPos = new SetPosition(Position, SetPositionFlags.Placement | SetPositionFlags.SendPositionEvent); return SetPosition(setPos) == SetPositionError.OK; } public void remove_parts(ObjCell objCell) { if (PartArray != null) PartArray.RemoveParts(objCell); } public void remove_shadows_from_cells() { foreach (var shadowObj in ShadowObjects.Values) { if (shadowObj.Cell == null) continue; var cell = shadowObj.Cell; shadowObj.Cell.remove_shadow_object(shadowObj); if (PartArray != null) PartArray.RemoveParts(cell); } NumShadowObjects = 0; // should be ShadowObjects.Count var i = 0; while (true) { var totalChilds = Children == null ? 0 : Children.NumObjects; if (i >= totalChilds) break; if (Children != null) Children.Objects[i++].remove_shadows_from_cells(); else remove_shadows_from_cells(); } } public void remove_visible_cells() { ObjMaint.RemoveObject(this); } public bool remove_voyeur(uint objectID) { if (TargetManager == null) return false; return TargetManager.RemoveVoyeur(objectID); } public void report_attacks(AttackInfo attackInfo) { // wobj virtual function call for (var i = 0; i < attackInfo.NumObjects; i++) { var obj = attackInfo.ObjectList[i]; var prof = new AtkCollisionProfile(); prof.ID = obj.ObjectID; prof.Location = obj.HitLocation; prof.Part = attackInfo.PartIndex; // atkcollisionprofile virtual function call } if (AttackManager != null) AttackManager.AttackDone(attackInfo); } public void report_collision_end(bool forceEnd) { if (CollisionTable == null) return; var ends = new List(); foreach (var kvp in CollisionTable) { var collision_id = kvp.Key; var collision = kvp.Value; var deltaTime = PhysicsTimer.CurrentTime - collision.TouchedTime; if (deltaTime > 1.0f || collision.Ethereal && deltaTime > 0.0f || forceEnd) ends.Add(collision_id); } foreach (var end in ends) { CollisionTable.Remove(end); report_object_collision_end(end); } } public void report_collision_start() { if (CollisionTable == null || ObjMaint == null) return; foreach (var objectID in CollisionTable.Keys) { var obj = ServerObjectManager.GetObjectA(objectID); if (obj != null) report_object_collision(obj, TransientState.HasFlag(TransientStateFlags.Contact)); } } public bool report_environment_collision(bool prev_has_contact) { if (CollidingWithEnvironment) return false; var result = false; if (State.HasFlag(PhysicsState.ReportCollisions) && WeenieObj != null) { var collision = new EnvCollisionProfile(); collision.Velocity = Velocity; collision.SetMeInContact(prev_has_contact); WeenieObj.DoCollision(collision, ObjID, this); result = true; } CollidingWithEnvironment = true; if (State.HasFlag(PhysicsState.Missile)) State &= ~PhysicsState.Missile; return result; } public void report_exhaustion() { if (MovementManager != null) MovementManager.ReportExhaustion(); } public bool report_object_collision(PhysicsObj obj, bool prev_has_contact) { if (obj.State.HasFlag(PhysicsState.ReportCollisionsAsEnvironment)) return report_environment_collision(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 (State.HasFlag(PhysicsState.Missile)) State &= ~(PhysicsState.Missile | PhysicsState.AlignPath | PhysicsState.PathClipped); } if (obj.State.HasFlag(PhysicsState.ReportCollisions) && !State.HasFlag(PhysicsState.IgnoreCollisions) && obj.WeenieObj != null) { // 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; } return collided; } public bool report_object_collision_end(uint objectID) { if (ObjMaint != null) { var collision = ServerObjectManager.GetObjectA(objectID); if (collision != null) { if (!collision.State.HasFlag(PhysicsState.ReportCollisionsAsEnvironment)) { if (State.HasFlag(PhysicsState.ReportCollisions) && WeenieObj != null) WeenieObj.DoCollisionEnd(new ObjectGuid(objectID)); if (collision.State.HasFlag(PhysicsState.ReportCollisions) && collision.WeenieObj != null) collision.WeenieObj.DoCollisionEnd(ObjID); } } return true; } if (State.HasFlag(PhysicsState.ReportCollisions) && WeenieObj != null) WeenieObj.DoCollisionEnd(new ObjectGuid(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(); offsetFrame.Orientation = AFrame.get_rotate_offset(offset); //set_frame(newFrame); Position.Frame.GRotate(offset); PartArray.SetFrame(offsetFrame); } /// /// Sets the active transient state flags /// public bool set_active(bool active) { if (active) { if (State.HasFlag(PhysicsState.Static)) return false; if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState |= TransientStateFlags.Active; return true; } else { TransientState &= ~TransientStateFlags.Active; return true; } } public bool is_active() { return (TransientState & TransientStateFlags.Active) != 0; } public void set_current_pos(Position newPos) { Position.ObjCellID = newPos.ObjCellID; Position.Frame = new AFrame(newPos.Frame); if (CurCell == null || CurCell.ID != Position.ObjCellID) { var newCell = LScape.get_landcell(newPos.ObjCellID); if (WeenieObj.WorldObject is Player player && 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; } /// /// Sets the cell ID for an object and all its parts /// public void set_cell_id(uint newCellID) { Position.ObjCellID = newCellID; if (!State.HasFlag(PhysicsState.ParticleEmitter) && PartArray != null) PartArray.SetCellID(newCellID); } /// /// Sets the cell ID for this object and all its children /// public void set_cell_id_recursive(uint newCellID) { set_cell_id(newCellID); for (var i = 0; i < Children.Objects.Count; i++) { var child = Children.Objects[i]; if (child != null) child.set_cell_id_recursive(newCellID); } } /// /// Sets all of the description fields for this object /// /// The physics description fields /// If true, unpacks the movement from the physics description /// success boolean public bool set_description(PhysicsDesc desc, bool setMovement) { var mTableID = desc.GetMTableID(); if (!SetMotionTableID(mTableID)) return false; if (desc.STableID != 0) { var qdid = new QualifiedDataID(desc.STableID, 0x22); SoundTable = (Sound.SoundTable)DBObj.Get(qdid); } if (desc.PhsTableID != 0) { var qdid = new QualifiedDataID(desc.PhsTableID, 0x2C); PhysicsScriptTable = (PhysicsScriptTable)DBObj.Get(qdid); } var packedMovement = HookAppraisalProfile.GetValidLocations(desc); if (packedMovement) { LastMoveWasAutonomous = desc.get_autonomous_movement(); if (setMovement) { var tradeRoomID = ChatRoomTracker.GetGlobalTradeRoomID(desc); unpack_movement(packedMovement, tradeRoomID); } } else { var animFrameID = desc.get_animframe_id(); unpack_movement(packedMovement, animFrameID); } set_state(desc.State, true); Scale = desc.ObjectScale; if (PartArray != null) PartArray.SetScaleInternal(new Vector3(Scale, Scale, Scale)); if (desc.Friction >= 0.0f && desc.Friction <= 1.0f) Friction = desc.Friction; set_elasticity(desc.Elasticity); TranslucencyOriginal = desc.Translucency; if (desc.Translucency != 0.0f) { Translucency = desc.Translucency; if (PartArray != null) PartArray.SetTranslucencyInternal(desc.Translucency); } set_velocity(desc.Velocity, true); DefaultScriptIntensity = desc.DefaultScriptIntensity; DefaultScript = desc.DefaultScript; for (var i = 0; i < UpdateTimes.Length; i++) UpdateTimes[i] = desc.get_timestamp(i); return true; } /// /// Sets the elasticity for a physics object /// /// A value between 0 and MaxElasticity /// boolean success public bool set_elasticity(float elasticity) { if (elasticity < 0.0f) { Elasticity = 0.0f; return true; } if (elasticity < PhysicsGlobals.MaxElasticity) Elasticity = elasticity; else Elasticity = PhysicsGlobals.MaxElasticity; return true; } /// /// Sets the ethereal (semi-transparent/walkthrough) flags /// public bool set_ethereal(bool ethereal, bool sendEvent) { if (ethereal) { State |= PhysicsState.Ethereal; TransientState &= ~TransientStateFlags.CheckEthereal; return true; } State &= ~PhysicsState.Ethereal; if (Parent != null || CurCell == null || !ethereal_check_for_collisions()) { TransientState &= ~TransientStateFlags.CheckEthereal; return true; } // error path - go back to ethereal, start loop in CheckEthereal state State |= PhysicsState.Ethereal; TransientState |= TransientStateFlags.CheckEthereal; return false; } /// /// Sets the current frame of animation for this object /// public void set_frame(AFrame frame) { if (!frame.IsValid() && frame.IsValidExceptForHeading()) frame.Orientation = new Quaternion(0, 0, 0, 0); Position.Frame = new AFrame(frame); //Position.Frame.Origin = frame.Origin; //Position.Frame.Orientation = frame.Orientation; // custom for server: // only update part frames for objects with physics bsp if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter) && (State.HasFlag(PhysicsState.HasPhysicsBSP) || !PhysicsEngine.Instance.Server)) { PartArray.SetFrame(frame); } UpdateChildrenInternal(); } /// /// Sets the angle this object is facing /// public void set_heading(float degrees, bool sendEvent) { Position.Frame.set_heading(degrees); set_frame(Position.Frame); } /// /// Sets this object to either hidden or unhidden /// public void set_hidden(bool hidden, bool sendEvent) { if (hidden) { State |= PhysicsState.Hidden; if (PhysicsScriptTable != null) { var script = PhysicsScriptTable.GetScript(PlayScript.Hidden, 1.0f); play_script_internal(script); } if (Children != null) { for (var i = 0; i < Children.NumObjects; i++) { var childObj = Children.Objects[i]; childObj.State |= PhysicsState.NoDraw; if (childObj.PartArray != null) childObj.PartArray.SetNoDrawInternal(true); } } if (State.HasFlag(PhysicsState.ReportCollisions)) { report_collision_end(true); State &= ~PhysicsState.ReportCollisions; } State |= PhysicsState.IgnoreCollisions; if (CurCell != null) CurCell.hide_object(this); if (PartArray != null) PartArray.HandleEnterWorld(); return; } State &= ~PhysicsState.Hidden; if (PhysicsScriptTable != null) { var script = PhysicsScriptTable.GetScript(PlayScript.UnHide, 1.0f); play_script_internal(script); } if (Children != null) { for (var i = 0; i < Children.NumObjects; i++) { var childObj = Children.Objects[i]; childObj.State &= ~PhysicsState.NoDraw; if (childObj.PartArray != null) childObj.PartArray.SetNoDrawInternal(false); } } State &= ~PhysicsState.IgnoreCollisions; if (!State.HasFlag(PhysicsState.ReportCollisions)) { State |= PhysicsState.ReportCollisions; report_collision_start(); } if (PartArray != null) PartArray.HandleEnterWorld(); if (CurCell != null) CurCell.unhide_object(this); } /// /// Sets the initial frame of animation for this object /// public void set_initial_frame(AFrame frame) { Position.Frame = frame; if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetFrame(frame); UpdateChildrenInternal(); } public void set_lights(int lights_on, bool sendEvent) { // needed for server physics? } /// /// Converts the local to global velocity for this object /// public void set_local_velocity(Vector3 newVel, bool sendEvent) { var globalVec = Position.LocalToGlobalVec(newVel); set_velocity(globalVec, sendEvent); } public bool set_nodraw(bool nodraw, bool sendEvent) { // needed for server physics? return true; } public void set_object_guid(ObjectGuid guid) { ObjID = guid; ID = guid.Full; ServerObjectManager.AddServerObject(this); } /// /// Sets the angular velocity for this physics object /// public void set_omega(Vector3 omega, bool sendEvent) { Omega = omega; } /// /// Handles leaving / hitting ground in movement manager, /// and calculates acceleration /// /// Flag indicates if this object is currently standing on the ground public void set_on_walkable(bool isOnWalkable) { var prevOnWalkable = TransientState.HasFlag(TransientStateFlags.OnWalkable); if (isOnWalkable) TransientState |= TransientStateFlags.OnWalkable; else TransientState &= ~TransientStateFlags.OnWalkable; if (MovementManager != null) { if (prevOnWalkable) { if (!isOnWalkable) MovementManager.LeaveGround(); } else { if (isOnWalkable) MovementManager.HitGround(); } } calc_acceleration(); } /// /// Sets the parent object for this physics object /// /// The parent object /// The child index in the parent object /// True if success public bool set_parent(PhysicsObj obj, int idx) { if (obj == null) return false; if (!obj.add_child(this, idx)) return false; unset_parent(); leave_world(); Parent = obj; if (obj.CurCell != null) { change_cell(obj.CurCell); if (obj.Children != null) { var index = obj.Children.FindChildIndex(this); if (index != -1) { obj.UpdateChild(this, obj.Children.PartNumbers[index], obj.Children.Frames[index]); recalc_cross_cells(); } } } if (Parent.State.HasFlag(PhysicsState.Hidden)) { State |= PhysicsState.NoDraw; if (PartArray != null) PartArray.SetNoDrawInternal(true); } return true; } /// /// Sets the parent object for this physics object /// public bool set_parent(PhysicsObj obj, int partIdx, AFrame frame) { if (obj == null) return false; if (!obj.add_child(this, partIdx, frame)) return false; ExaminationObject = obj.ExaminationObject; unset_parent(); leave_world(); Parent = obj; if (obj.CurCell != null) { change_cell(obj.CurCell); obj.UpdateChild(this, partIdx, frame); recalc_cross_cells(); } return true; } private Vector3 requestCachedVelocity; /// /// Sets the requested position to the AutonomousPosition /// received from the client /// public void set_request_pos(Vector3 pos, Quaternion rotation, ObjCell cell, uint blockCellID) { RequestPos.Frame.Origin = pos; RequestPos.Frame.Orientation = rotation; if (CurCell == null) { CurCell = LScape.get_landcell(blockCellID); if (CurCell == null) return; } if (cell == null) 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) { if (PartArray == null) return; if (interrupt) PartArray.Sequence.clear_animations(); //PartArray.Sequence.append_animation(new AnimData(animID, startFrame, 0, framerate)); } public bool set_state(PhysicsState newState, bool sendEvent) { var stateDiff = State ^ newState; // lighting stuff needed for server? if (stateDiff.HasFlag(PhysicsState.LightingOn)) { if (newState.HasFlag(PhysicsState.LightingOn)) { State |= PhysicsState.LightingOn; if (PartArray != null) PartArray.InitLights(); } else { State &= ~PhysicsState.LightingOn; if (PartArray != null) PartArray.DestroyLights(); } } if (stateDiff.HasFlag(PhysicsState.NoDraw)) set_nodraw((State & PhysicsState.NoDraw) != 0, false); if (stateDiff.HasFlag(PhysicsState.Hidden)) set_hidden((State & PhysicsState.Hidden) != 0, false); return true; } public void set_target(uint contextID, uint objectID, float radius, double quantum) { if (TargetManager == null) TargetManager = new TargetManager(this); TargetManager.SetTarget(contextID, objectID, radius, quantum); } public void set_target_quantum(double new_quantum) { if (TargetManager != null) TargetManager.SetTargetQuantum(new_quantum); } /// /// Sets the global velocity for this physics object /// /// The velocity in global space /// Flag indicates if this event should send a network broadcast public void set_velocity(Vector3 velocity, bool sendEvent) { if (!velocity.Equals(Velocity)) { Velocity = velocity; if (Velocity.Length() > PhysicsGlobals.MaxVelocity) { Velocity /= Velocity.Length(); // todo: add normalize method Velocity *= PhysicsGlobals.MaxVelocity; } JumpedThisFrame = true; } if (!State.HasFlag(PhysicsState.Static)) { if (!TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; TransientState |= TransientStateFlags.Active; } } /// /// Sets the weenie for this physics object /// public void set_weenie_obj(WeenieObject wobj) { WeenieObj = wobj; if (MovementManager != null) MovementManager.SetWeenieObject(wobj); } public void stick_to_object(uint objectID) { MakePositionManager(); if (ObjMaint == null) return; var objectA = ServerObjectManager.GetObjectA(objectID); if (objectA == null) return; if (objectA.Parent != null) objectA = Parent; if (objectA.PartArray != null) PositionManager.StickTo(objectA.ID, objectA.PartArray.GetRadius(), objectA.PartArray.GetHeight()); else PositionManager.StickTo(objectA.ID, 0, 0); } public bool stop_particle_emitter(int emitterID) { if (ParticleManager == null) return false; return ParticleManager.StopParticleEmitter(emitterID); } public void store_position(Position pos) { // position ref? if ((pos.ObjCellID & 0xFFFF) < 0x100) LandDefs.AdjustToOutside(pos); if (PartArray != null && !State.HasFlag(PhysicsState.ParticleEmitter)) PartArray.SetCellID(pos.ObjCellID); set_cell_id(pos.ObjCellID); set_frame(pos.Frame); } public void teleport_hook(bool hide) { if (MovementManager != null) MovementManager.CancelMoveTo(WeenieError.ITeleported); if (PositionManager != null) { PositionManager.Unstick(); PositionManager.StopInterpolating(); PositionManager.Unconstrain(); } if (TargetManager != null) { TargetManager.ClearTarget(); TargetManager.NotifyVoyeurOfEvent(TargetStatus.Teleported); } report_collision_end(true); } public bool track_object_collision(PhysicsObj obj, bool prev_has_contact) { if (obj.State.HasFlag(PhysicsState.Static)) return report_environment_collision(prev_has_contact); if (CollisionTable == null) CollisionTable = new Dictionary(); CollisionTable[obj.ID] = new CollisionRecord(PhysicsTimer.CurrentTime, obj.State.HasFlag(PhysicsState.Ethereal)); return report_object_collision(obj, prev_has_contact); } public Transition transition(Position oldPos, Position newPos, bool adminMove) { var trans = Transition.MakeTransition(); if (trans == null) return null; var objectInfo = get_object_info(trans, adminMove); trans.InitObject(this, objectInfo.State); if (PartArray == null || PartArray.GetNumSphere() == 0) trans.InitSphere(1, PhysicsGlobals.DummySphere, 1.0f); else { var sphere = PartArray != null ? PartArray.GetSphere() : null; if (PartArray != null) trans.InitSphere(PartArray.GetNumSphere(), sphere, Scale); else trans.InitSphere(0, sphere, Scale); } trans.InitPath(CurCell, oldPos, newPos); if ((TransientState & TransientStateFlags.StationaryStuck) != 0) trans.CollisionInfo.FramesStationaryFall = 3; else if ((TransientState & TransientStateFlags.StationaryStop) != 0) trans.CollisionInfo.FramesStationaryFall = 2; else if ((TransientState & TransientStateFlags.StationaryFall) != 0) trans.CollisionInfo.FramesStationaryFall = 1; var validPos = trans.FindValidPosition(); trans.CleanupTransition(); if (!validPos) return null; return trans; } public void unpack_movement(Object addr, uint size) { if (MovementManager == null) { MovementManager = MovementManager.Create(this, WeenieObj); if (!State.HasFlag(PhysicsState.Static) && TransientState.HasFlag(TransientStateFlags.Active)) UpdateTime = PhysicsTimer.CurrentTime; } MovementManager.unpack_movement(addr, size); } public void unparent_children() { if (Children == null) return; foreach (var child in Children.Objects) child.unset_parent(); } public void unset_parent() { if (Parent == null) return; if (Parent.Children != null) Parent.Children.RemoveChild(this); if (Parent.State.HasFlag(PhysicsState.Hidden)) { State &= ~PhysicsState.Hidden; if (PartArray != null) PartArray.SetNoDrawInternal(false); } Parent = null; UpdateTime = PhysicsTimer.CurrentTime; clear_transient_states(); } public void unstick_from_object() { if (PositionManager != null) PositionManager.Unstick(); } public static float TickRate = 1.0f / 30.0f; public bool update_object() { if (Parent != null || CurCell == null || State.HasFlag(PhysicsState.Frozen)) { TransientState &= ~TransientStateFlags.Active; return false; } /*if (PlayerObject != null) { PlayerVector = PlayerObject.Position.GetOffset(Position); PlayerDistance = PlayerVector.Length(); if (PlayerDistance > 96.0f && ObjMaint.IsActive) TransientState &= ~TransientStateFlags.Active; else set_active(true); // sets UpdateTime }*/ PhysicsTimer_CurrentTime = UpdateTime; var deltaTime = PhysicsTimer.CurrentTime - UpdateTime; if (deltaTime < TickRate) return false; //Console.WriteLine("deltaTime: " + deltaTime); // commented out for debugging if (deltaTime > PhysicsGlobals.HugeQuantum) { UpdateTime = PhysicsTimer.CurrentTime; // consume time? return false; } while (deltaTime > PhysicsGlobals.MaxQuantum) { PhysicsTimer_CurrentTime += PhysicsGlobals.MaxQuantum; UpdateObjectInternal(PhysicsGlobals.MaxQuantum); deltaTime -= PhysicsGlobals.MaxQuantum; } if (deltaTime > PhysicsGlobals.MinQuantum) { PhysicsTimer_CurrentTime += deltaTime; UpdateObjectInternal(deltaTime); } UpdateTime = PhysicsTimer_CurrentTime; return true; } public bool update_animation() { if (Parent != null || State.HasFlag(PhysicsState.Frozen)) { TransientState &= ~TransientStateFlags.Active; return false; } PhysicsTimer_CurrentTime = UpdateTime; var deltaTime = PhysicsTimer.CurrentTime - UpdateTime; if (deltaTime < TickRate) return false; //Console.WriteLine("deltaTime: " + deltaTime); if (deltaTime > PhysicsGlobals.HugeQuantum) { UpdateTime = PhysicsTimer.CurrentTime; // consume time? return false; } while (deltaTime > PhysicsGlobals.MaxQuantum) { PhysicsTimer_CurrentTime += PhysicsGlobals.MaxQuantum; UpdateAnimationInternal(PhysicsGlobals.MaxQuantum); deltaTime -= PhysicsGlobals.MaxQuantum; } if (deltaTime > PhysicsGlobals.MinQuantum) { PhysicsTimer_CurrentTime += deltaTime; UpdateAnimationInternal(deltaTime); } UpdateTime = PhysicsTimer_CurrentTime; return true; } public void StartTimer(double delta = 0) { UpdateTime = PhysicsTimer.CurrentTime - delta; } public void ShowPendingMotions() { Console.WriteLine($"{Name} pending motions:"); foreach (var motion in MovementManager.MotionInterpreter.PendingMotions) Console.WriteLine($"{(MotionCommand)motion.Motion}"); } 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; 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 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; // 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) { log.Warn($"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() { if (Parent != null) return; PhysicsTimer_CurrentTime = PhysicsTimer.CurrentTime; var deltaTime = PhysicsTimer.CurrentTime - UpdateTime; if (deltaTime > PhysicsGlobals.EPSILON) { if (deltaTime < PhysicsGlobals.MinQuantum) return; /*if (deltaTime > PhysicsGlobals.MaxQuantum) // commented out for debugging { UpdateTime = Timer.CurrentTime; return; }*/ var frame = new AFrame(); UpdatePositionInternal(deltaTime, ref frame); set_frame(frame); if (ParticleManager != null) ParticleManager.UpdateParticles(); if (ScriptManager != null) ScriptManager.UpdateScripts(); UpdateTime = PhysicsTimer.CurrentTime; } else UpdateTime = PhysicsTimer.CurrentTime; } public bool IsGrounded { get => TransientState.HasFlag(TransientStateFlags.OnWalkable) && CachedVelocity.Equals(Vector3.Zero); } public bool Equals(PhysicsObj obj) { if (obj == null) return false; return ID == obj.ID; } public override int GetHashCode() { return ID.GetHashCode(); } public override string ToString() { return $"{Name} ({ID:X8})"; } } }