using System; using System.Numerics; namespace AcDream.Core.Physics; /// /// Chooses the visible locomotion cycle for server-controlled remotes whose /// UpdateMotion packet is a MoveToObject/MoveToPosition union rather than an /// InterpretedMotionState. /// /// Retail references: /// /// /// MovementManager::PerformMovement (0x00524440) dispatches movement /// types 6/7 into MoveToManager::MoveToObject/MoveToPosition instead /// of unpacking an InterpretedMotionState. /// /// /// MovementParameters::UnPackNet (0x0052AC50) shows MoveTo packets /// carry movement params + run rate, not a ForwardCommand field. /// /// /// ACE MovementData.Write uses the same movement type union; holtburger /// documents the matching MovementType::MoveToPosition = 7. /// /// /// public static class ServerControlledLocomotion { public const float StopSpeed = 0.20f; public const float RunThreshold = 1.25f; public const float MinSpeedMod = 0.25f; public const float MaxSpeedMod = 3.00f; // Retail MoveToManager::BeginMoveForward -> MovementParameters::get_command // (0x0052AA00) seeds forward motion before the next position update. public static LocomotionCycle PlanMoveToStart( float moveToSpeed = 1f, float runRate = 1f, bool canRun = true) { moveToSpeed = SanitizePositive(moveToSpeed); runRate = SanitizePositive(runRate); if (!canRun) return new LocomotionCycle(MotionCommand.WalkForward, moveToSpeed, true); return new LocomotionCycle( MotionCommand.RunForward, moveToSpeed * runRate, true); } public static LocomotionCycle PlanFromVelocity(Vector3 worldVelocity) { float horizontalSpeed = MathF.Sqrt( worldVelocity.X * worldVelocity.X + worldVelocity.Y * worldVelocity.Y); if (horizontalSpeed < StopSpeed) return new LocomotionCycle(MotionCommand.Ready, 1f, false); if (horizontalSpeed < RunThreshold) { float speedMod = Math.Clamp( horizontalSpeed / MotionInterpreter.WalkAnimSpeed, MinSpeedMod, MaxSpeedMod); return new LocomotionCycle(MotionCommand.WalkForward, speedMod, true); } return new LocomotionCycle( MotionCommand.RunForward, Math.Clamp(horizontalSpeed / MotionInterpreter.RunAnimSpeed, MinSpeedMod, MaxSpeedMod), true); } public readonly record struct LocomotionCycle( uint Motion, float SpeedMod, bool IsMoving); private static float SanitizePositive(float value) { return float.IsFinite(value) && value > 0f ? value : 1f; } }