feat(headless): complete deterministic bot command parity
This commit is contained in:
parent
7e8acb74dd
commit
38e83640d9
37 changed files with 2805 additions and 295 deletions
|
|
@ -0,0 +1,203 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue