refactor(runtime): extract local teleport and player mode
Move local teleport, player-mode, animation, shadow, and sealed-dungeon lifetimes out of GameWindow. Make player-mode entry transactional and isolate approach completions by controller lifetime and approach generation so stale callbacks cannot affect replacements. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
c557038353
commit
eeb0f6b45c
29 changed files with 3311 additions and 1073 deletions
117
src/AcDream.App/Physics/LocalPlayerShadowSynchronizer.cs
Normal file
117
src/AcDream.App/Physics/LocalPlayerShadowSynchronizer.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Owns publication and suspension of the local player's collision shadow.
|
||||
/// Identity/incarnation checks stay beside the cached shadow pose so player
|
||||
/// mode, projection, and teleport placement cannot publish different owners.
|
||||
/// </summary>
|
||||
internal sealed class LocalPlayerShadowSynchronizer
|
||||
{
|
||||
private readonly PhysicsEngine _physics;
|
||||
private readonly LiveEntityRuntime _liveEntities;
|
||||
private readonly ILocalPlayerIdentitySource _identity;
|
||||
private readonly LiveWorldOriginState _origin;
|
||||
private readonly LocalPlayerShadowState _state;
|
||||
|
||||
public LocalPlayerShadowSynchronizer(
|
||||
PhysicsEngine physics,
|
||||
LiveEntityRuntime liveEntities,
|
||||
ILocalPlayerIdentitySource identity,
|
||||
LiveWorldOriginState origin,
|
||||
LocalPlayerShadowState state)
|
||||
{
|
||||
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
||||
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
|
||||
_state = state ?? throw new ArgumentNullException(nameof(state));
|
||||
}
|
||||
|
||||
public void Sync(WorldEntity playerEntity, uint cellId, bool force = false)
|
||||
=> SyncPose(
|
||||
playerEntity,
|
||||
playerEntity.Position,
|
||||
playerEntity.Rotation,
|
||||
cellId,
|
||||
force);
|
||||
|
||||
public LocalPlayerShadowState.Snapshot? Capture() => _state.Current;
|
||||
|
||||
public void SyncPose(
|
||||
WorldEntity playerEntity,
|
||||
System.Numerics.Vector3 position,
|
||||
System.Numerics.Quaternion orientation,
|
||||
uint cellId,
|
||||
bool force = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(playerEntity);
|
||||
if (_liveEntities.IsHidden(_identity.ServerGuid)
|
||||
|| cellId == 0
|
||||
|| !IsCurrentVisibleProjection(playerEntity))
|
||||
{
|
||||
Suspend(playerEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force
|
||||
&& _state.Current is { } last
|
||||
&& last.CellId == cellId
|
||||
&& System.Numerics.Vector3.DistanceSquared(
|
||||
last.Position,
|
||||
position) <= 1e-4f
|
||||
&& MathF.Abs(System.Numerics.Quaternion.Dot(
|
||||
last.Orientation,
|
||||
orientation)) >= 0.99999f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ShadowPositionSynchronizer.Sync(
|
||||
_physics.ShadowObjects,
|
||||
playerEntity.Id,
|
||||
position,
|
||||
orientation,
|
||||
cellId,
|
||||
_origin.CenterX,
|
||||
_origin.CenterY);
|
||||
_state.Set(position, orientation, cellId);
|
||||
}
|
||||
|
||||
public void Restore(
|
||||
WorldEntity playerEntity,
|
||||
LocalPlayerShadowState.Snapshot? snapshot)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(playerEntity);
|
||||
if (snapshot is not { } prior || !IsCurrentVisibleProjection(playerEntity))
|
||||
{
|
||||
Suspend(playerEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
SyncPose(
|
||||
playerEntity,
|
||||
prior.Position,
|
||||
prior.Orientation,
|
||||
prior.CellId,
|
||||
force: true);
|
||||
}
|
||||
|
||||
public bool IsCurrentVisibleProjection(WorldEntity playerEntity) =>
|
||||
_liveEntities.TryGetRecord(playerEntity.ServerGuid, out LiveEntityRecord record)
|
||||
&& ReferenceEquals(record.WorldEntity, playerEntity)
|
||||
&& _liveEntities.IsCurrentSpatialRootObject(record);
|
||||
|
||||
public void Suspend(WorldEntity playerEntity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(playerEntity);
|
||||
_physics.ShadowObjects.Suspend(playerEntity.Id);
|
||||
_state.Clear();
|
||||
}
|
||||
|
||||
public void ResetSession() => _state.Clear();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue