Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates. Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean. Co-authored-by: OpenAI Codex <codex@openai.com>
591 lines
22 KiB
C#
591 lines
22 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.World;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.World;
|
|
|
|
namespace AcDream.App.Physics;
|
|
|
|
/// <summary>
|
|
/// Owns the SetPosition half of retail remote
|
|
/// <c>CPhysicsObj::MoveOrTeleport</c> (0x00516330). It resolves the
|
|
/// destination through the placement transition, commits that transition to
|
|
/// the existing body, and synchronizes the remote movement/contact state.
|
|
/// Logical identity, target-hook actions, rebucketing callbacks, and render
|
|
/// presentation remain with their existing owners.
|
|
/// </summary>
|
|
internal sealed class RemoteTeleportController : IDisposable
|
|
{
|
|
internal delegate ResolveResult PlacementResolver(
|
|
Vector3 position,
|
|
uint cellId,
|
|
float radius,
|
|
float height,
|
|
ObjectInfoState moverFlags,
|
|
uint movingEntityId);
|
|
|
|
private readonly PhysicsEngine _physics;
|
|
private readonly LiveEntityRuntime _liveEntities;
|
|
private readonly Func<uint, WorldEntity, (float Radius, float Height)> _getSetupCylinder;
|
|
private readonly Func<Vector3, uint, Vector3> _cellLocalForSeed;
|
|
private readonly Action<WorldEntity, PhysicsBody, uint> _syncResolvedShadow;
|
|
private readonly Action<uint, ushort, bool> _completeAuthoritativePlacement;
|
|
private readonly Action<uint, ushort> _beginAuthoritativePlacement;
|
|
private readonly PlacementResolver _resolvePlacement;
|
|
private readonly Dictionary<uint, PendingPlacement> _pending = new();
|
|
|
|
internal RemoteTeleportController(
|
|
PhysicsEngine physics,
|
|
LiveEntityRuntime liveEntities,
|
|
Func<uint, WorldEntity, (float Radius, float Height)> getSetupCylinder,
|
|
Func<Vector3, uint, Vector3> cellLocalForSeed,
|
|
Action<WorldEntity, PhysicsBody, uint> syncResolvedShadow,
|
|
Action<uint, ushort, bool> completeAuthoritativePlacement,
|
|
Action<uint, ushort> beginAuthoritativePlacement,
|
|
PlacementResolver? resolvePlacement = null)
|
|
{
|
|
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
|
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
|
|
_getSetupCylinder = getSetupCylinder
|
|
?? throw new ArgumentNullException(nameof(getSetupCylinder));
|
|
_cellLocalForSeed = cellLocalForSeed
|
|
?? throw new ArgumentNullException(nameof(cellLocalForSeed));
|
|
_syncResolvedShadow = syncResolvedShadow
|
|
?? throw new ArgumentNullException(nameof(syncResolvedShadow));
|
|
_completeAuthoritativePlacement = completeAuthoritativePlacement
|
|
?? throw new ArgumentNullException(nameof(completeAuthoritativePlacement));
|
|
_beginAuthoritativePlacement = beginAuthoritativePlacement
|
|
?? throw new ArgumentNullException(nameof(beginAuthoritativePlacement));
|
|
_resolvePlacement = resolvePlacement ?? ResolvePlacement;
|
|
_liveEntities.ProjectionVisibilityChanged += OnProjectionVisibilityChanged;
|
|
}
|
|
|
|
internal readonly record struct Result(
|
|
bool Applied,
|
|
bool ContactResolved,
|
|
Vector3 Position,
|
|
uint CellId,
|
|
Quaternion Orientation,
|
|
bool Superseded = false);
|
|
|
|
private readonly record struct PendingPlacement(
|
|
LiveEntityRecord Record,
|
|
ulong PositionAuthorityVersion,
|
|
ulong VelocityAuthorityVersion,
|
|
ILiveEntityRemotePlacementRuntime Remote,
|
|
PhysicsBody Body,
|
|
WorldEntity Entity,
|
|
Vector3 RequestedWorldPosition,
|
|
uint RequestedCellId,
|
|
Quaternion RequestedOrientation,
|
|
double GameTime,
|
|
ushort Generation,
|
|
ushort PositionSequence,
|
|
bool WasInContact,
|
|
bool WasOnWalkable,
|
|
RollbackPlacement Rollback);
|
|
|
|
private readonly record struct RollbackPlacement(
|
|
Vector3 Position,
|
|
uint CellId,
|
|
Vector3 CellLocalPosition,
|
|
Quaternion Orientation,
|
|
TransientStateFlags TransientState,
|
|
Plane ContactPlane,
|
|
bool ContactPlaneValid,
|
|
uint ContactPlaneCellId,
|
|
bool ContactPlaneIsWater,
|
|
double LastUpdateTime,
|
|
bool Airborne,
|
|
Vector3 LastServerPosition,
|
|
double LastServerPositionTime,
|
|
Vector3 LastShadowSyncPosition,
|
|
Quaternion LastShadowSyncOrientation);
|
|
|
|
/// <summary>
|
|
/// Applies a placement to whichever exact incarnation is current at this
|
|
/// call boundary. Packet handlers should use the token-bearing overload;
|
|
/// this form serves non-wire orchestration and retains the same callback
|
|
/// revalidation once admitted.
|
|
/// </summary>
|
|
internal Result TryApply(
|
|
ILiveEntityRemotePlacementRuntime remote,
|
|
WorldEntity entity,
|
|
Vector3 requestedWorldPosition,
|
|
uint requestedCellId,
|
|
Vector3 requestedCellLocalPosition,
|
|
Quaternion requestedOrientation,
|
|
double gameTime,
|
|
bool destinationProjectionVisible,
|
|
ushort generation,
|
|
ushort positionSequence)
|
|
{
|
|
if (!_liveEntities.TryGetRecord(entity.ServerGuid, out LiveEntityRecord record))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Remote placement for 0x{entity.ServerGuid:X8} requires a live incarnation.");
|
|
}
|
|
|
|
return TryApply(
|
|
record,
|
|
record.PositionAuthorityVersion,
|
|
record.VelocityAuthorityVersion,
|
|
remote,
|
|
entity,
|
|
requestedWorldPosition,
|
|
requestedCellId,
|
|
requestedCellLocalPosition,
|
|
requestedOrientation,
|
|
gameTime,
|
|
destinationProjectionVisible,
|
|
generation,
|
|
positionSequence);
|
|
}
|
|
|
|
internal Result TryApply(
|
|
LiveEntityRecord expectedRecord,
|
|
ulong expectedPositionAuthorityVersion,
|
|
ulong expectedVelocityAuthorityVersion,
|
|
ILiveEntityRemotePlacementRuntime remote,
|
|
WorldEntity entity,
|
|
Vector3 requestedWorldPosition,
|
|
uint requestedCellId,
|
|
Vector3 requestedCellLocalPosition,
|
|
Quaternion requestedOrientation,
|
|
double gameTime,
|
|
bool destinationProjectionVisible,
|
|
ushort generation,
|
|
ushort positionSequence)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(remote);
|
|
ArgumentNullException.ThrowIfNull(entity);
|
|
ArgumentNullException.ThrowIfNull(expectedRecord);
|
|
if (!_liveEntities.TryGetRecord(entity.ServerGuid, out LiveEntityRecord liveRecord)
|
|
|| !ReferenceEquals(liveRecord, expectedRecord)
|
|
|| !_liveEntities.IsCurrentPositionAuthority(
|
|
expectedRecord,
|
|
expectedPositionAuthorityVersion)
|
|
|| liveRecord.Generation != generation
|
|
|| !ReferenceEquals(liveRecord.WorldEntity, entity)
|
|
|| !ReferenceEquals(liveRecord.RemoteMotionRuntime, remote)
|
|
|| liveRecord.PhysicsBody is null
|
|
|| !ReferenceEquals(liveRecord.PhysicsBody, remote.Body))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Remote placement for 0x{entity.ServerGuid:X8} must use its incarnation's canonical physics body.");
|
|
}
|
|
|
|
bool wasInContact = liveRecord.PhysicsBody.InContact;
|
|
bool wasOnWalkable = liveRecord.PhysicsBody.OnWalkable;
|
|
RollbackPlacement rollback = CaptureRollback(remote, liveRecord.PhysicsBody);
|
|
if (_pending.TryGetValue(entity.ServerGuid, out PendingPlacement prior)
|
|
&& ReferenceEquals(prior.Record, liveRecord)
|
|
&& prior.Generation == generation)
|
|
{
|
|
wasInContact = prior.WasInContact;
|
|
wasOnWalkable = prior.WasOnWalkable;
|
|
rollback = prior.Rollback;
|
|
}
|
|
|
|
PendingPlacement request = new(
|
|
liveRecord,
|
|
expectedPositionAuthorityVersion,
|
|
expectedVelocityAuthorityVersion,
|
|
remote,
|
|
liveRecord.PhysicsBody,
|
|
entity,
|
|
requestedWorldPosition,
|
|
requestedCellId,
|
|
requestedOrientation,
|
|
gameTime,
|
|
generation,
|
|
positionSequence,
|
|
wasInContact,
|
|
wasOnWalkable,
|
|
rollback);
|
|
if (!destinationProjectionVisible)
|
|
{
|
|
if (!ParkPending(request, requestedCellLocalPosition))
|
|
return Superseded(request);
|
|
return new Result(
|
|
true,
|
|
false,
|
|
requestedWorldPosition,
|
|
requestedCellId,
|
|
requestedOrientation);
|
|
}
|
|
ResolveResult placement = Resolve(request);
|
|
if (!IsCurrent(request))
|
|
return Superseded(request);
|
|
if (!placement.Ok)
|
|
{
|
|
_pending.Remove(entity.ServerGuid);
|
|
if (!RollBackDeferredPlacement(request))
|
|
return Superseded(request);
|
|
return new Result(
|
|
false,
|
|
false,
|
|
request.Body.Position,
|
|
remote.CellId,
|
|
request.Body.Orientation);
|
|
}
|
|
|
|
_pending.Remove(entity.ServerGuid);
|
|
return CommitResolved(request, placement);
|
|
}
|
|
|
|
internal void Forget(uint serverGuid)
|
|
{
|
|
_pending.Remove(serverGuid);
|
|
}
|
|
|
|
internal void Forget(LiveEntityRecord record)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(record);
|
|
if (_pending.TryGetValue(record.ServerGuid, out PendingPlacement pending)
|
|
&& pending.Generation == record.Generation
|
|
&& (record.WorldEntity is null
|
|
|| ReferenceEquals(pending.Entity, record.WorldEntity)))
|
|
{
|
|
_pending.Remove(record.ServerGuid);
|
|
}
|
|
}
|
|
|
|
internal void Clear()
|
|
{
|
|
_pending.Clear();
|
|
}
|
|
|
|
internal bool HasPending(uint serverGuid) => _pending.ContainsKey(serverGuid);
|
|
internal int PendingPlacementCount => _pending.Count;
|
|
|
|
/// <summary>
|
|
/// Transfers any older deferred shadow restoration before the accepted
|
|
/// destination is rebucketed and can publish a visibility edge.
|
|
/// </summary>
|
|
internal void BeginPlacement(uint serverGuid, ushort generation) =>
|
|
_beginAuthoritativePlacement(serverGuid, generation);
|
|
|
|
public void Dispose()
|
|
{
|
|
_liveEntities.ProjectionVisibilityChanged -= OnProjectionVisibilityChanged;
|
|
Clear();
|
|
}
|
|
|
|
private ResolveResult Resolve(PendingPlacement request)
|
|
{
|
|
var (radius, height) = _getSetupCylinder(
|
|
request.Entity.ServerGuid,
|
|
request.Entity);
|
|
if (radius < 0.05f)
|
|
{
|
|
radius = 0.48f;
|
|
height = 1.835f;
|
|
}
|
|
|
|
return _resolvePlacement(
|
|
request.RequestedWorldPosition,
|
|
request.RequestedCellId,
|
|
radius,
|
|
height,
|
|
IsPlayerGuid(request.Entity.ServerGuid)
|
|
? ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide
|
|
: ObjectInfoState.EdgeSlide,
|
|
request.Entity.Id);
|
|
}
|
|
|
|
private ResolveResult ResolvePlacement(
|
|
Vector3 position,
|
|
uint cellId,
|
|
float radius,
|
|
float height,
|
|
ObjectInfoState moverFlags,
|
|
uint movingEntityId) =>
|
|
_physics.ResolvePlacement(
|
|
position,
|
|
cellId,
|
|
radius,
|
|
height,
|
|
stepUpHeight: 0.4f,
|
|
stepDownHeight: 0.4f,
|
|
moverFlags: moverFlags,
|
|
movingEntityId: movingEntityId);
|
|
|
|
private Result CommitResolved(PendingPlacement request, ResolveResult placement)
|
|
{
|
|
if (!IsCurrent(request))
|
|
return Superseded(request);
|
|
if (!RemoteTeleportPlacement.Apply(
|
|
request.Remote,
|
|
request.Body,
|
|
placement,
|
|
_cellLocalForSeed(placement.Position, placement.CellId),
|
|
request.RequestedOrientation,
|
|
request.GameTime,
|
|
request.WasInContact,
|
|
request.WasOnWalkable,
|
|
() => IsCurrent(request),
|
|
() => IsVelocityCurrent(request)))
|
|
{
|
|
return Superseded(request);
|
|
}
|
|
if (!IsCurrent(request))
|
|
return Superseded(request);
|
|
if (request.Remote.CellId != placement.CellId)
|
|
request.Remote.CellId = placement.CellId;
|
|
if (!IsCurrent(request))
|
|
return Superseded(request);
|
|
request.Remote.LastServerPosition = placement.Position;
|
|
request.Remote.LastServerPositionTime = request.GameTime;
|
|
request.Remote.LastShadowSyncPosition = Vector3.Zero;
|
|
request.Remote.LastShadowSyncOrientation = Quaternion.Zero;
|
|
request.Entity.SetPosition(request.Body.Position);
|
|
request.Entity.ParentCellId = placement.CellId;
|
|
request.Entity.Rotation = request.Body.Orientation;
|
|
if (!IsCurrent(request))
|
|
return Superseded(request);
|
|
if (_liveEntities.TryGetRecord(request.Entity.ServerGuid, out LiveEntityRecord record)
|
|
&& ReferenceEquals(record, request.Record))
|
|
{
|
|
bool deferShadowRestore =
|
|
record.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden)
|
|
|| !record.IsSpatiallyVisible;
|
|
_completeAuthoritativePlacement(
|
|
request.Entity.ServerGuid,
|
|
request.Generation,
|
|
deferShadowRestore);
|
|
if (!IsCurrent(request))
|
|
return Superseded(request);
|
|
if (!deferShadowRestore)
|
|
{
|
|
request.Remote.LastShadowSyncPosition = request.Body.Position;
|
|
request.Remote.LastShadowSyncOrientation = request.Body.Orientation;
|
|
_syncResolvedShadow(request.Entity, request.Body, placement.CellId);
|
|
if (!IsCurrent(request))
|
|
return Superseded(request);
|
|
}
|
|
}
|
|
return new Result(
|
|
true,
|
|
true,
|
|
request.Body.Position,
|
|
placement.CellId,
|
|
request.Body.Orientation);
|
|
}
|
|
|
|
private bool ParkPending(PendingPlacement request, Vector3 requestedCellLocalPosition)
|
|
{
|
|
if (!IsCurrent(request))
|
|
return false;
|
|
request.Body.Orientation = request.RequestedOrientation;
|
|
request.Body.SnapToCell(
|
|
request.RequestedCellId,
|
|
request.RequestedWorldPosition,
|
|
requestedCellLocalPosition);
|
|
request.Body.LastUpdateTime = request.GameTime;
|
|
request.Body.ContactPlaneValid = false;
|
|
request.Body.ContactPlaneCellId = 0u;
|
|
request.Body.ContactPlaneIsWater = false;
|
|
PhysicsObjUpdate.ApplySetPositionContact(
|
|
request.Body,
|
|
inContact: false,
|
|
onWalkable: false);
|
|
request.Remote.Airborne = true;
|
|
if (request.Remote.CellId != request.RequestedCellId)
|
|
request.Remote.CellId = request.RequestedCellId;
|
|
if (!IsCurrent(request))
|
|
return false;
|
|
request.Remote.LastServerPosition = request.RequestedWorldPosition;
|
|
request.Remote.LastServerPositionTime = request.GameTime;
|
|
request.Remote.LastShadowSyncPosition = Vector3.Zero;
|
|
request.Remote.LastShadowSyncOrientation = Quaternion.Zero;
|
|
request.Entity.SetPosition(request.RequestedWorldPosition);
|
|
request.Entity.ParentCellId = request.RequestedCellId;
|
|
request.Entity.Rotation = request.RequestedOrientation;
|
|
if (!IsCurrent(request))
|
|
return false;
|
|
_pending[request.Entity.ServerGuid] = request;
|
|
return true;
|
|
}
|
|
|
|
private void OnProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
|
|
{
|
|
if (!visible)
|
|
return;
|
|
|
|
if (_pending.TryGetValue(record.ServerGuid, out PendingPlacement pending))
|
|
{
|
|
if (record.WorldEntity is null
|
|
|| !ReferenceEquals(record, pending.Record)
|
|
|| !ReferenceEquals(record.WorldEntity, pending.Entity)
|
|
|| record.Generation != pending.Generation)
|
|
{
|
|
_pending.Remove(record.ServerGuid);
|
|
return;
|
|
}
|
|
if (record.RemoteMotionRuntime is not ILiveEntityRemotePlacementRuntime currentRemote
|
|
|| record.PhysicsBody is null
|
|
|| !ReferenceEquals(record.PhysicsBody, pending.Body)
|
|
|| !ReferenceEquals(currentRemote.Body, record.PhysicsBody))
|
|
{
|
|
_pending.Remove(record.ServerGuid);
|
|
if (record.RemoteMotionRuntime is not null
|
|
&& (record.PhysicsBody is null
|
|
|| !ReferenceEquals(record.RemoteMotionRuntime.Body, record.PhysicsBody)))
|
|
{
|
|
_liveEntities.ClearRemoteMotionRuntime(record.ServerGuid);
|
|
}
|
|
RollBackDeferredPlacement(pending);
|
|
return;
|
|
}
|
|
if (!ReferenceEquals(currentRemote, pending.Remote))
|
|
{
|
|
pending = pending with { Remote = currentRemote };
|
|
_pending[record.ServerGuid] = pending;
|
|
}
|
|
if (!_liveEntities.IsCurrentPositionAuthority(
|
|
pending.Record,
|
|
pending.PositionAuthorityVersion)
|
|
|| record.Snapshot.PositionSequence != pending.PositionSequence)
|
|
{
|
|
// A newer accepted UpdatePosition rebucketed the projection
|
|
// before its TryApply call could replace this request. Keep
|
|
// the original rollback alive for that synchronous call.
|
|
return;
|
|
}
|
|
|
|
_pending.Remove(record.ServerGuid);
|
|
ResolveResult placement = Resolve(pending);
|
|
if (!IsCurrent(pending))
|
|
return;
|
|
if (!placement.Ok)
|
|
RollBackDeferredPlacement(pending);
|
|
else
|
|
CommitResolved(pending, placement);
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
private static RollbackPlacement CaptureRollback(
|
|
ILiveEntityRemotePlacementRuntime remote,
|
|
PhysicsBody body)
|
|
{
|
|
return new RollbackPlacement(
|
|
body.Position,
|
|
body.CellPosition.ObjCellId,
|
|
body.CellPosition.Frame.Origin,
|
|
body.Orientation,
|
|
body.TransientState,
|
|
body.ContactPlane,
|
|
body.ContactPlaneValid,
|
|
body.ContactPlaneCellId,
|
|
body.ContactPlaneIsWater,
|
|
body.LastUpdateTime,
|
|
remote.Airborne,
|
|
remote.LastServerPosition,
|
|
remote.LastServerPositionTime,
|
|
remote.LastShadowSyncPosition,
|
|
remote.LastShadowSyncOrientation);
|
|
}
|
|
|
|
private bool RollBackDeferredPlacement(PendingPlacement pending)
|
|
{
|
|
if (!IsCurrentAuthority(pending))
|
|
return false;
|
|
RollbackPlacement rollback = pending.Rollback;
|
|
PhysicsBody body = pending.Body;
|
|
body.Orientation = rollback.Orientation;
|
|
body.SnapToCell(
|
|
rollback.CellId,
|
|
rollback.Position,
|
|
rollback.CellLocalPosition);
|
|
body.TransientState = rollback.TransientState;
|
|
body.ContactPlane = rollback.ContactPlane;
|
|
body.ContactPlaneValid = rollback.ContactPlaneValid;
|
|
body.ContactPlaneCellId = rollback.ContactPlaneCellId;
|
|
body.ContactPlaneIsWater = rollback.ContactPlaneIsWater;
|
|
body.LastUpdateTime = rollback.LastUpdateTime;
|
|
body.calc_acceleration();
|
|
pending.Remote.Airborne = rollback.Airborne;
|
|
pending.Remote.CellId = rollback.CellId;
|
|
pending.Remote.LastServerPosition = rollback.LastServerPosition;
|
|
pending.Remote.LastServerPositionTime = rollback.LastServerPositionTime;
|
|
pending.Remote.LastShadowSyncPosition = rollback.LastShadowSyncPosition;
|
|
pending.Remote.LastShadowSyncOrientation = rollback.LastShadowSyncOrientation;
|
|
pending.Entity.SetPosition(rollback.Position);
|
|
pending.Entity.ParentCellId = rollback.CellId;
|
|
pending.Entity.Rotation = rollback.Orientation;
|
|
if (!IsCurrentAuthority(pending))
|
|
return false;
|
|
|
|
if (rollback.CellId == 0)
|
|
_liveEntities.WithdrawLiveEntityProjectionToCellless(pending.Entity.ServerGuid);
|
|
else
|
|
_liveEntities.RebucketLiveEntity(pending.Entity.ServerGuid, rollback.CellId);
|
|
|
|
if (!IsCurrentAuthority(pending)
|
|
|| !_liveEntities.TryGetRecord(
|
|
pending.Entity.ServerGuid,
|
|
out LiveEntityRecord restored)
|
|
|| !ReferenceEquals(restored, pending.Record)
|
|
|| restored.Generation != pending.Generation)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (rollback.CellId == 0)
|
|
{
|
|
_completeAuthoritativePlacement(
|
|
pending.Entity.ServerGuid,
|
|
pending.Generation,
|
|
false);
|
|
return IsCurrentAuthority(pending);
|
|
}
|
|
|
|
bool deferShadowRestore =
|
|
restored.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden)
|
|
|| !restored.IsSpatiallyVisible;
|
|
_completeAuthoritativePlacement(
|
|
pending.Entity.ServerGuid,
|
|
pending.Generation,
|
|
deferShadowRestore);
|
|
if (!IsCurrentAuthority(pending))
|
|
return false;
|
|
if (deferShadowRestore)
|
|
return true;
|
|
|
|
pending.Remote.LastShadowSyncPosition = Vector3.Zero;
|
|
pending.Remote.LastShadowSyncOrientation = Quaternion.Zero;
|
|
_syncResolvedShadow(pending.Entity, pending.Body, rollback.CellId);
|
|
return IsCurrentAuthority(pending);
|
|
}
|
|
|
|
private bool IsCurrentAuthority(PendingPlacement request) =>
|
|
_liveEntities.IsCurrentPositionAuthority(
|
|
request.Record,
|
|
request.PositionAuthorityVersion)
|
|
&& request.Record.Generation == request.Generation
|
|
&& ReferenceEquals(request.Record.WorldEntity, request.Entity)
|
|
&& ReferenceEquals(request.Record.PhysicsBody, request.Body);
|
|
|
|
private bool IsCurrent(PendingPlacement request) =>
|
|
IsCurrentAuthority(request)
|
|
&& ReferenceEquals(request.Record.RemoteMotionRuntime, request.Remote);
|
|
|
|
private bool IsVelocityCurrent(PendingPlacement request) =>
|
|
_liveEntities.IsCurrentVelocityAuthority(
|
|
request.Record,
|
|
request.VelocityAuthorityVersion);
|
|
|
|
private static Result Superseded(PendingPlacement request) => new(
|
|
Applied: false,
|
|
ContactResolved: false,
|
|
Position: request.Body.Position,
|
|
CellId: request.Remote.CellId,
|
|
Orientation: request.Body.Orientation,
|
|
Superseded: true);
|
|
|
|
private static bool IsPlayerGuid(uint guid) =>
|
|
(guid & 0xFF000000u) == 0x50000000u;
|
|
}
|