using AcDream.App.Combat; using AcDream.App.Input; using AcDream.App.Update; using AcDream.Core.Rendering; namespace AcDream.App.Rendering; /// /// Owns the per-update fly/chase camera publication tail. /// /// /// The local object phase normally publishes the player before inbound /// traffic. When the player is first created by that inbound pass, the local /// frame publishes its initial root here and the spatial reconciler refreshes /// child/effect anchors before either chase camera samples it. /// internal sealed class CameraFrameController : ICameraFramePhase { private readonly CameraController _camera; private readonly IInputCaptureSource _capture; private readonly ICameraFrameInputSource _input; private readonly ILocalPlayerPresentationRuntime _player; private readonly IChaseCameraSource _chase; private readonly RetailLocalPlayerFrameController _localFrame; private readonly ILiveSpatialReconcilePhase _spatialReconciler; private readonly ICombatCameraTargetSource _combatTarget; public CameraFrameController( CameraController camera, IInputCaptureSource capture, ICameraFrameInputSource input, ILocalPlayerPresentationRuntime player, IChaseCameraSource chase, RetailLocalPlayerFrameController localFrame, ILiveSpatialReconcilePhase spatialReconciler, ICombatCameraTargetSource combatTarget) { _camera = camera ?? throw new ArgumentNullException(nameof(camera)); _capture = capture ?? throw new ArgumentNullException(nameof(capture)); _input = input ?? throw new ArgumentNullException(nameof(input)); _player = player ?? throw new ArgumentNullException(nameof(player)); _chase = chase ?? throw new ArgumentNullException(nameof(chase)); _localFrame = localFrame ?? throw new ArgumentNullException(nameof(localFrame)); _spatialReconciler = spatialReconciler ?? throw new ArgumentNullException(nameof(spatialReconciler)); _combatTarget = combatTarget ?? throw new ArgumentNullException(nameof(combatTarget)); } public void Tick(UpdateFrameTiming timing) { if (_capture.DevToolsWantCaptureKeyboard || !_input.IsAvailable) return; if (_camera.IsFlyMode) { FlyCameraInput input = _input.CaptureFly(); _camera.Fly.Update( timing.SimulationDeltaSeconds, input.Forward, input.Left, input.Backward, input.Right, input.Up, input.Down, input.Boost); return; } PlayerMovementController? controller = _player.Controller; ChaseCamera? legacy = _chase.Legacy; RetailChaseCamera? retail = _chase.Retail; if (!_player.CanPresentPlayer || controller is null || legacy is null) return; if (CameraDiagnostics.UseRetailChaseCamera && retail is not null) { ChaseCameraAdjustmentInput input = _input.CaptureChaseAdjustment(); float adjustment = CameraDiagnostics.CameraAdjustmentSpeed * timing.SimulationDeltaSecondsSingle; if (input.ZoomIn) retail.AdjustDistance(-adjustment); if (input.ZoomOut) retail.AdjustDistance(+adjustment); if (input.Raise) retail.AdjustPitch(+adjustment * 0.02f); if (input.Lower) retail.AdjustPitch(-adjustment * 0.02f); } if (!_localFrame.TryGetPresentationAfterNetwork(out var playerFrame)) return; if (!playerFrame.AdvancedBeforeNetwork) _spatialReconciler.Reconcile(); MovementResult result = playerFrame.Movement; legacy.Update( result.RenderPosition, controller.Yaw, isOnGround: result.IsOnGround, dt: timing.SimulationDeltaSecondsSingle); retail?.Update( result.RenderPosition, controller.Yaw, playerVelocity: controller.BodyVelocity, isOnGround: result.IsOnGround, contactPlaneNormal: controller.ContactPlane.Normal, dt: timing.SimulationDeltaSecondsSingle, cellId: controller.CellId, selfEntityId: controller.LocalEntityId, trackedTargetPoint: _combatTarget.GetTrackedTargetPoint()); } }