using AcDream.Core.Physics; using AcDream.App.Physics; namespace AcDream.App.Input; internal interface ILocalPlayerIdentitySource { uint ServerGuid { get; } } /// /// Session-scoped identity slot shared by focused update owners. /// internal sealed class LocalPlayerIdentityState : ILocalPlayerIdentitySource { public uint ServerGuid { get; set; } } internal interface ILocalPlayerControllerSource { PlayerMovementController? Controller { get; } } internal interface ILocalPlayerMotionSource { MotionInterpreter? Motion { get; } } /// /// The one mutable local movement-controller slot. Player-mode lifecycle owns /// assignment; update and presentation owners receive the read-only seam. /// internal sealed class LocalPlayerControllerSlot : ILocalPlayerControllerSource, ILocalPlayerMotionSource { private PlayerMovementController? _preparingMotionOwner; public PlayerMovementController? Controller { get; set; } /// /// The motion owner visible to the local PartArray completion relay. /// During player-mode construction this is the fully animation-bound /// candidate controller; all other consumers continue to see only the /// committed . /// MotionInterpreter? ILocalPlayerMotionSource.Motion => _preparingMotionOwner?.Motion ?? Controller?.Motion; /// /// Opens the narrow construction-time ownership seam required by retail's /// CPhysicsObj/PartArray lifetime. Initial placement synchronously queues /// and completes StopCompletely, so its MotionDone relay must reach the /// candidate interpreter before the complete controller is published. /// public IDisposable BeginMotionPreparation(PlayerMovementController controller) { ArgumentNullException.ThrowIfNull(controller); if (_preparingMotionOwner is not null) { throw new InvalidOperationException( "A local player motion owner is already being prepared."); } _preparingMotionOwner = controller; return new MotionPreparation(this, controller); } private void EndMotionPreparation(PlayerMovementController controller) { if (!ReferenceEquals(_preparingMotionOwner, controller)) { throw new InvalidOperationException( "The local player motion preparation owner changed unexpectedly."); } _preparingMotionOwner = null; } private sealed class MotionPreparation( LocalPlayerControllerSlot owner, PlayerMovementController controller) : IDisposable { private LocalPlayerControllerSlot? _owner = owner; public void Dispose() { LocalPlayerControllerSlot? current = Interlocked.Exchange(ref _owner, null); current?.EndMotionPreparation(controller); } } } internal interface ILocalPlayerPhysicsHostSource { EntityPhysicsHost? Host { get; } } /// /// The one mutable local physics-host slot. Player-mode lifecycle owns /// assignment; teleport and update owners receive the read-only seam. /// internal sealed class LocalPlayerPhysicsHostSlot : ILocalPlayerPhysicsHostSource { public EntityPhysicsHost? Host { get; set; } } internal interface ILocalPlayerModeSource { bool IsPlayerMode { get; } bool ChaseModeEverEntered { get; } } /// /// The one mutable local presentation-mode slot. Checkpoint F transfers write /// ownership to PlayerModeController; streaming and camera owners only read it. /// internal sealed class LocalPlayerModeState : ILocalPlayerModeSource { public bool IsPlayerMode { get; set; } public bool ChaseModeEverEntered { get; set; } public void ResetSession() { IsPlayerMode = false; ChaseModeEverEntered = false; } } /// /// Session-scoped server-authoritative movement skills. Player-description /// delivery and player-mode construction share this owner so rebuilding the /// local physics controller cannot fall back to stale defaults. /// internal sealed class LocalPlayerSkillState { public int RunSkill { get; private set; } = -1; public int JumpSkill { get; private set; } = -1; public bool IsComplete => RunSkill >= 0 && JumpSkill >= 0; public void Update( int runSkill, int jumpSkill, PlayerMovementController? controller) { if (runSkill >= 0) RunSkill = runSkill; if (jumpSkill >= 0) JumpSkill = jumpSkill; ApplyTo(controller); } public bool ApplyTo(PlayerMovementController? controller) { if (controller is null || !IsComplete) return false; controller.SetCharacterSkills(RunSkill, JumpSkill); return true; } public void ResetSession() { RunSkill = -1; JumpSkill = -1; } } internal interface IViewportAspectSource { float Aspect { get; } } /// Framebuffer aspect published by the window host. internal sealed class ViewportAspectState : IViewportAspectSource { public float Aspect { get; private set; } = 16f / 9f; public void Update(int width, int height) { if (width > 0 && height > 0) Aspect = width / (float)height; } }