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
|
|
@ -1,3 +1,5 @@
|
|||
using System.Diagnostics;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.Runtime.Gameplay;
|
||||
|
|
@ -26,6 +28,8 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
private PlayerMovementController? _controller;
|
||||
private PlayerMovementController? _preparingMotionOwner;
|
||||
private bool _autoRunActive;
|
||||
private bool _hasCommandInput;
|
||||
private MovementInput _commandInput;
|
||||
private bool _disposed;
|
||||
private long _revision;
|
||||
|
||||
|
|
@ -43,6 +47,8 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
}
|
||||
|
||||
public bool AutoRunActive => _autoRunActive;
|
||||
public bool HasCommandInput => _hasCommandInput;
|
||||
public MovementInput CommandInput => _commandInput;
|
||||
public long Revision => Interlocked.Read(ref _revision);
|
||||
public IRuntimeMovementView View => this;
|
||||
|
||||
|
|
@ -63,7 +69,9 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
false,
|
||||
0d,
|
||||
Revision,
|
||||
_autoRunActive)
|
||||
_autoRunActive,
|
||||
_hasCommandInput,
|
||||
_commandInput)
|
||||
: new RuntimeMovementSnapshot(
|
||||
true,
|
||||
controller.LocalEntityId,
|
||||
|
|
@ -72,7 +80,9 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
controller.IsAirborne,
|
||||
controller.SimTimeSeconds,
|
||||
Revision,
|
||||
_autoRunActive);
|
||||
_autoRunActive,
|
||||
_hasCommandInput,
|
||||
_commandInput);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +119,27 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
return true;
|
||||
case RuntimeMovementCommand.Stop:
|
||||
CancelAutoRun();
|
||||
ClearCommandInput();
|
||||
return true;
|
||||
case RuntimeMovementCommand.Ready:
|
||||
case RuntimeMovementCommand.Sit:
|
||||
case RuntimeMovementCommand.Crouch:
|
||||
case RuntimeMovementCommand.Sleep:
|
||||
CancelAutoRun();
|
||||
ClearCommandInput();
|
||||
uint motion = command switch
|
||||
{
|
||||
RuntimeMovementCommand.Ready =>
|
||||
MotionCommand.Ready,
|
||||
RuntimeMovementCommand.Sit =>
|
||||
MotionCommand.Sitting,
|
||||
RuntimeMovementCommand.Crouch =>
|
||||
MotionCommand.Crouch,
|
||||
RuntimeMovementCommand.Sleep =>
|
||||
MotionCommand.Sleeping,
|
||||
_ => throw new UnreachableException(),
|
||||
};
|
||||
return _controller?.RequestPosture(motion) == true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
@ -125,12 +155,75 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs the persistent semantic movement level sampled by either host.
|
||||
/// The graphical input adapter and direct host both consume this exact
|
||||
/// owner; no bot-only movement latch exists.
|
||||
/// </summary>
|
||||
public void SetCommandInput(in MovementInput input)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_hasCommandInput && _commandInput == input)
|
||||
return;
|
||||
_commandInput = input;
|
||||
_hasCommandInput = true;
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
|
||||
public bool ClearCommandInput()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (!_hasCommandInput)
|
||||
return false;
|
||||
_commandInput = default;
|
||||
_hasCommandInput = false;
|
||||
Interlocked.Increment(ref _revision);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct-host projection of the same combat readiness query used by the
|
||||
/// graphical attack adapter. A host without a constructed local movement
|
||||
/// owner is not ready; Slice K's content host must hydrate that owner
|
||||
/// before combat packets can leave.
|
||||
/// </summary>
|
||||
public bool IsReadyForAttack(CombatMode mode)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_controller is not { } controller)
|
||||
return false;
|
||||
var motion = controller.Motion.InterpretedState;
|
||||
return CombatInputPlanner.PlayerInReadyPositionForAttack(
|
||||
mode,
|
||||
motion.CurrentStyle,
|
||||
motion.ForwardCommand);
|
||||
}
|
||||
|
||||
public bool IsDualWield
|
||||
{
|
||||
get
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _controller?.Motion.InterpretedState.CurrentStyle
|
||||
== CombatInputPlanner.DualWieldCombatStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PrepareForAttackRequest()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
CancelAutoRun();
|
||||
return _controller?.PrepareForAttackRequest() == true;
|
||||
}
|
||||
|
||||
public void ResetInputIntent()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (!_autoRunActive)
|
||||
if (!_autoRunActive && !_hasCommandInput)
|
||||
return;
|
||||
_autoRunActive = false;
|
||||
_hasCommandInput = false;
|
||||
_commandInput = default;
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
|
||||
|
|
@ -145,9 +238,12 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
bool changed =
|
||||
_autoRunActive
|
||||
|| _hasCommandInput
|
||||
|| _controller is not null
|
||||
|| _preparingMotionOwner is not null;
|
||||
_autoRunActive = false;
|
||||
_hasCommandInput = false;
|
||||
_commandInput = default;
|
||||
_controller = null;
|
||||
_preparingMotionOwner = null;
|
||||
if (changed)
|
||||
|
|
@ -160,6 +256,7 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
_controller is not null,
|
||||
_preparingMotionOwner is not null,
|
||||
_autoRunActive,
|
||||
_hasCommandInput,
|
||||
Revision);
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -167,6 +264,8 @@ public sealed class RuntimeLocalPlayerMovementState
|
|||
if (_disposed)
|
||||
return;
|
||||
_autoRunActive = false;
|
||||
_hasCommandInput = false;
|
||||
_commandInput = default;
|
||||
_controller = null;
|
||||
_preparingMotionOwner = null;
|
||||
Interlocked.Increment(ref _revision);
|
||||
|
|
@ -210,11 +309,13 @@ public readonly record struct RuntimeLocalMovementOwnershipSnapshot(
|
|||
bool HasController,
|
||||
bool HasPreparingMotionOwner,
|
||||
bool AutoRunActive,
|
||||
bool HasCommandInput,
|
||||
long Revision)
|
||||
{
|
||||
public bool IsConverged =>
|
||||
IsDisposed
|
||||
&& !HasController
|
||||
&& !HasPreparingMotionOwner
|
||||
&& !AutoRunActive;
|
||||
&& !AutoRunActive
|
||||
&& !HasCommandInput;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue