203 lines
6.5 KiB
C#
203 lines
6.5 KiB
C#
using AcDream.Core.Physics;
|
|
|
|
namespace AcDream.Runtime.Gameplay;
|
|
|
|
public interface IRuntimeMovementInputSource
|
|
{
|
|
MovementInput Capture();
|
|
}
|
|
|
|
public interface IRuntimeLocalPlayerFrameHost
|
|
{
|
|
bool CanAdvancePlayer { get; }
|
|
PlayerMovementController? Controller { get; }
|
|
uint ResolveLocalEntityId();
|
|
void HandleTargeting();
|
|
bool IsHidden { get; }
|
|
RetailObjectClockDisposition ObjectClockDisposition { get; }
|
|
void Project(
|
|
PlayerMovementController controller,
|
|
MovementResult movement,
|
|
bool hidden);
|
|
void SendPreNetwork(
|
|
PlayerMovementController controller,
|
|
MovementResult movement,
|
|
bool hidden);
|
|
void SendPostNetwork(
|
|
PlayerMovementController controller,
|
|
bool hidden);
|
|
}
|
|
|
|
public readonly record struct RuntimeLocalPlayerPresentationFrame(
|
|
MovementResult Movement,
|
|
bool Hidden,
|
|
bool AdvancedBeforeNetwork);
|
|
|
|
/// <summary>
|
|
/// Presentation-independent owner of retail's local-player object phase,
|
|
/// inbound-network barrier, and post-network position phase. Graphical and
|
|
/// direct hosts supply projections and input but execute this exact ordering.
|
|
/// </summary>
|
|
public sealed class RuntimeLocalPlayerFrameController
|
|
{
|
|
private readonly IRuntimeLocalPlayerFrameHost _host;
|
|
private readonly IRuntimeMovementInputSource _input;
|
|
private readonly Action? _publishMovement;
|
|
private AdvancedFrame? _advancedFrame;
|
|
|
|
private readonly record struct AdvancedFrame(
|
|
PlayerMovementController Controller,
|
|
MovementResult Movement,
|
|
bool ObjectAdvanced,
|
|
bool Hidden,
|
|
bool ObjectQuantumAdvanced);
|
|
|
|
public RuntimeLocalPlayerFrameController(
|
|
IRuntimeLocalPlayerFrameHost host,
|
|
IRuntimeMovementInputSource input,
|
|
Action? publishMovement = null)
|
|
{
|
|
_host = host ?? throw new ArgumentNullException(nameof(host));
|
|
_input = input ?? throw new ArgumentNullException(nameof(input));
|
|
_publishMovement = publishMovement;
|
|
}
|
|
|
|
public bool HiddenPartPoseDirty => _advancedFrame is
|
|
{
|
|
Hidden: true,
|
|
ObjectQuantumAdvanced: true,
|
|
};
|
|
|
|
public void AdvanceBeforeNetwork(float deltaSeconds)
|
|
{
|
|
_advancedFrame = null;
|
|
PlayerMovementController? controller = _host.Controller;
|
|
if (!_host.CanAdvancePlayer || controller is null)
|
|
return;
|
|
|
|
if (!float.IsFinite(deltaSeconds) || deltaSeconds <= 0f)
|
|
{
|
|
bool rejectedHidden = _host.IsHidden;
|
|
MovementResult rejected =
|
|
controller.CapturePresentationResult();
|
|
_host.Project(controller, rejected, rejectedHidden);
|
|
_advancedFrame = new AdvancedFrame(
|
|
controller,
|
|
rejected,
|
|
ObjectAdvanced: false,
|
|
Hidden: rejectedHidden,
|
|
ObjectQuantumAdvanced: false);
|
|
return;
|
|
}
|
|
|
|
controller.LocalEntityId = _host.ResolveLocalEntityId();
|
|
|
|
bool hidden = _host.IsHidden;
|
|
if (_host.ObjectClockDisposition
|
|
is RetailObjectClockDisposition.Suspend)
|
|
{
|
|
controller.SuspendObjectUpdate(deltaSeconds);
|
|
MovementResult paused =
|
|
controller.CapturePresentationResult();
|
|
_host.Project(controller, paused, hidden);
|
|
_advancedFrame = new AdvancedFrame(
|
|
controller,
|
|
paused,
|
|
ObjectAdvanced: false,
|
|
Hidden: hidden,
|
|
ObjectQuantumAdvanced: false);
|
|
return;
|
|
}
|
|
|
|
MovementResult movement = hidden
|
|
? controller.TickHidden(
|
|
deltaSeconds,
|
|
_host.HandleTargeting)
|
|
: controller.Update(
|
|
deltaSeconds,
|
|
_input.Capture(),
|
|
_host.HandleTargeting);
|
|
|
|
_host.Project(controller, movement, hidden);
|
|
_host.SendPreNetwork(controller, movement, hidden);
|
|
_advancedFrame = new AdvancedFrame(
|
|
controller,
|
|
movement,
|
|
ObjectAdvanced: true,
|
|
Hidden: hidden,
|
|
ObjectQuantumAdvanced:
|
|
controller.AdvancedObjectQuantumLastTick);
|
|
}
|
|
|
|
public void RunPostNetworkCommandPhase()
|
|
{
|
|
PlayerMovementController? controller = _host.Controller;
|
|
if (!_host.CanAdvancePlayer || controller is null)
|
|
return;
|
|
|
|
bool hidden = _host.IsHidden;
|
|
if (_advancedFrame is { } advanced
|
|
&& ReferenceEquals(advanced.Controller, controller))
|
|
{
|
|
MovementResult movement = RefreshSpatialFields(
|
|
advanced.Movement,
|
|
controller);
|
|
_host.Project(controller, movement, hidden);
|
|
_advancedFrame = new AdvancedFrame(
|
|
controller,
|
|
movement,
|
|
advanced.ObjectAdvanced,
|
|
advanced.Hidden,
|
|
advanced.ObjectQuantumAdvanced);
|
|
|
|
if (!advanced.ObjectAdvanced)
|
|
return;
|
|
}
|
|
|
|
_host.SendPostNetwork(controller, hidden);
|
|
_publishMovement?.Invoke();
|
|
}
|
|
|
|
public bool TryGetPresentationAfterNetwork(
|
|
out RuntimeLocalPlayerPresentationFrame frame)
|
|
{
|
|
frame = default;
|
|
PlayerMovementController? controller = _host.Controller;
|
|
if (!_host.CanAdvancePlayer || controller is null)
|
|
return false;
|
|
|
|
bool hidden = _host.IsHidden;
|
|
if (_advancedFrame is { } advanced
|
|
&& ReferenceEquals(advanced.Controller, controller))
|
|
{
|
|
MovementResult movement = RefreshSpatialFields(
|
|
advanced.Movement,
|
|
controller);
|
|
frame = new RuntimeLocalPlayerPresentationFrame(
|
|
movement,
|
|
hidden,
|
|
AdvancedBeforeNetwork: advanced.ObjectAdvanced);
|
|
return true;
|
|
}
|
|
|
|
MovementResult initial =
|
|
controller.CapturePresentationResult();
|
|
_host.Project(controller, initial, hidden);
|
|
frame = new RuntimeLocalPlayerPresentationFrame(
|
|
initial,
|
|
hidden,
|
|
AdvancedBeforeNetwork: false);
|
|
return true;
|
|
}
|
|
|
|
private static MovementResult RefreshSpatialFields(
|
|
MovementResult movement,
|
|
PlayerMovementController controller) =>
|
|
movement with
|
|
{
|
|
Position = controller.Position,
|
|
RenderPosition = controller.RenderPosition,
|
|
CellId = controller.CellId,
|
|
IsOnGround = controller.CanSendPositionEvent,
|
|
};
|
|
}
|