feat(runtime): classify retail authoritative position routes
This commit is contained in:
parent
0fbc7a1fb7
commit
3f800a4aec
2 changed files with 1107 additions and 0 deletions
|
|
@ -0,0 +1,554 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.Runtime.Physics;
|
||||
|
||||
internal enum RuntimePositionEntityKind : byte
|
||||
{
|
||||
Unknown,
|
||||
LocalPlayer,
|
||||
Remote,
|
||||
Projectile,
|
||||
}
|
||||
|
||||
internal enum RuntimeCreateResidenceKind : byte
|
||||
{
|
||||
Unknown,
|
||||
TopLevel,
|
||||
Parented,
|
||||
PickedUp,
|
||||
}
|
||||
|
||||
internal enum RuntimeAcceptedPositionSource : byte
|
||||
{
|
||||
Unknown,
|
||||
PositionEvent,
|
||||
SameIncarnationCreate,
|
||||
}
|
||||
|
||||
internal enum RuntimeLeaveWorldCause : byte
|
||||
{
|
||||
Unknown,
|
||||
Pickup,
|
||||
Parent,
|
||||
}
|
||||
|
||||
internal enum RuntimeAuthoritativePositionDisposition : byte
|
||||
{
|
||||
RejectedAuthority,
|
||||
RejectedData,
|
||||
AwaitFreshPosition,
|
||||
NoPositionOperation,
|
||||
Interpolate,
|
||||
SetPosition,
|
||||
SetPositionSimple,
|
||||
}
|
||||
|
||||
internal enum RuntimeTeleportHookPhase : byte
|
||||
{
|
||||
None,
|
||||
BeforePositionOperation,
|
||||
AfterPositionOperation,
|
||||
AfterEnterWorld,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact accepted-wire authority for one presentation-independent position
|
||||
/// route. <see cref="RuntimeEntityKey"/> identifies the incarnation while
|
||||
/// <see cref="PositionAuthorityVersion"/> rejects a superseded accepted pose.
|
||||
/// The previous and accepted TELEPORT_TS values remain explicit so the pure
|
||||
/// classifier can apply retail's wrap-safe freshness rule without consulting
|
||||
/// App state.
|
||||
/// </summary>
|
||||
internal readonly record struct RuntimeAuthoritativePositionAuthority(
|
||||
RuntimeGenerationToken Generation,
|
||||
RuntimeEntityKey Entity,
|
||||
ulong PositionAuthorityVersion,
|
||||
ushort AcceptedPositionSequence,
|
||||
ushort PreviousTeleportSequence,
|
||||
ushort AcceptedTeleportSequence,
|
||||
PositionTimestampDisposition TimestampDisposition)
|
||||
{
|
||||
internal bool IsStructurallyValid => Generation.Value != 0UL
|
||||
&& Entity.LocalEntityId != 0u
|
||||
&& PositionAuthorityVersion != 0UL
|
||||
&& TimestampDisposition is PositionTimestampDisposition.Apply
|
||||
or PositionTimestampDisposition.ForcePosition;
|
||||
|
||||
internal bool TeleportAdvanced =>
|
||||
TimestampDisposition is PositionTimestampDisposition.Apply
|
||||
&& PhysicsTimestampGate.IsNewer(
|
||||
PreviousTeleportSequence,
|
||||
AcceptedTeleportSequence);
|
||||
|
||||
internal bool TeleportRegressed =>
|
||||
PhysicsTimestampGate.IsNewer(
|
||||
AcceptedTeleportSequence,
|
||||
PreviousTeleportSequence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Placement facts which must not be collapsed into presentation visibility.
|
||||
/// Hidden and NoDraw objects still run retail SetPosition. Hidden suppresses
|
||||
/// the complete collision batch after its retail state rewrite; NoDraw is
|
||||
/// render-only. Source/peer ReportCollisions and IgnoreCollisions decisions
|
||||
/// remain downstream in RuntimeCollisionReportingState.
|
||||
/// </summary>
|
||||
internal readonly record struct RuntimePositionPlacementFacts(
|
||||
PhysicsStateFlags PhysicsState,
|
||||
bool HasAuthoredMoverShape)
|
||||
{
|
||||
internal bool CollisionBatchEligible =>
|
||||
(PhysicsState & PhysicsStateFlags.Hidden) == 0;
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeCreatePositionRouteRequest(
|
||||
RuntimeAuthoritativePositionAuthority Authority,
|
||||
RuntimePositionEntityKind EntityKind,
|
||||
RuntimeCreateResidenceKind Residence,
|
||||
CreateObject.ServerPosition? AcceptedWirePosition,
|
||||
RuntimePositionPlacementFacts PlacementFacts);
|
||||
|
||||
internal readonly record struct RuntimeAcceptedPositionRouteRequest(
|
||||
RuntimeAuthoritativePositionAuthority Authority,
|
||||
RuntimePositionEntityKind EntityKind,
|
||||
RuntimeAcceptedPositionSource Source,
|
||||
CreateObject.ServerPosition AcceptedWirePosition,
|
||||
uint? PlacementFrame,
|
||||
Vector3? PositionPackVelocity,
|
||||
uint? CommittedCellId,
|
||||
bool HasContact,
|
||||
float PlayerDistance,
|
||||
bool UsePositionFromServer,
|
||||
bool HasAnimations,
|
||||
RuntimePositionPlacementFacts PlacementFacts);
|
||||
|
||||
internal readonly record struct RuntimeLeaveWorldRouteRequest(
|
||||
RuntimeAuthoritativePositionAuthority Authority,
|
||||
RuntimePositionEntityKind EntityKind,
|
||||
RuntimeLeaveWorldCause Cause,
|
||||
RuntimePositionPlacementFacts PlacementFacts);
|
||||
|
||||
/// <summary>
|
||||
/// Immutable action plan for retail HandleReceivedPosition/MoveOrTeleport.
|
||||
/// It deliberately contains no renderer, world entity, UI, or host callback.
|
||||
/// Graphical and headless hosts will consume the same plan during Group 2.
|
||||
/// </summary>
|
||||
internal readonly record struct RuntimeAuthoritativePositionRoute(
|
||||
RuntimeAuthoritativePositionAuthority Authority,
|
||||
RuntimeAuthoritativePositionDisposition Disposition,
|
||||
RuntimeSetPositionOperationKind OperationKind,
|
||||
PhysicsSetPositionFlags SetPositionFlags,
|
||||
uint PlacementFrame,
|
||||
bool UnparentBeforeRouting,
|
||||
bool ApplyPlacementFrameBeforeRouting,
|
||||
bool LeaveWorld,
|
||||
RuntimeTeleportHookPhase TeleportHookPhase,
|
||||
bool StopInterpolating,
|
||||
bool ConstrainAfterRouting,
|
||||
bool PreserveHeading,
|
||||
bool ZeroVelocity,
|
||||
bool SendPositionImmediately,
|
||||
bool CollisionBatchEligible)
|
||||
{
|
||||
internal bool Accepted => Disposition is not
|
||||
RuntimeAuthoritativePositionDisposition.RejectedAuthority
|
||||
and not RuntimeAuthoritativePositionDisposition.RejectedData;
|
||||
|
||||
internal bool PerformsSetPosition => Disposition is
|
||||
RuntimeAuthoritativePositionDisposition.SetPosition
|
||||
or RuntimeAuthoritativePositionDisposition.SetPositionSimple;
|
||||
|
||||
internal bool RunsTeleportHook =>
|
||||
TeleportHookPhase is not RuntimeTeleportHookPhase.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pure retail route classifier for new CreateObject residence,
|
||||
/// SmartBox::HandleReceivedPosition (0x00453FD0), and
|
||||
/// CPhysicsObj::MoveOrTeleport (0x00516330).
|
||||
/// </summary>
|
||||
internal static class RuntimeAuthoritativePositionRouteClassifier
|
||||
{
|
||||
private const float MaxPhysicsDistance = 96f;
|
||||
private const PhysicsSetPositionFlags InitialCreateFlags =
|
||||
PhysicsSetPositionFlags.Placement | PhysicsSetPositionFlags.Slide;
|
||||
private const PhysicsSetPositionFlags AuthoritativeTeleportFlags =
|
||||
PhysicsSetPositionFlags.Teleport
|
||||
| PhysicsSetPositionFlags.Slide
|
||||
| PhysicsSetPositionFlags.SendPositionEvent;
|
||||
|
||||
internal static RuntimeAuthoritativePositionRoute ClassifyCreate(
|
||||
in RuntimeCreatePositionRouteRequest request)
|
||||
{
|
||||
if (!ValidCreateAuthority(request.Authority)
|
||||
|| !ValidEntityKind(request.EntityKind)
|
||||
|| request.Residence is RuntimeCreateResidenceKind.Unknown)
|
||||
{
|
||||
return RejectedAuthority(request.Authority);
|
||||
}
|
||||
|
||||
RuntimeSetPositionOperationKind operation = OperationKind(
|
||||
request.EntityKind,
|
||||
initialCreate: true);
|
||||
bool reporting = request.PlacementFacts.CollisionBatchEligible;
|
||||
if (request.Residence is RuntimeCreateResidenceKind.Parented
|
||||
or RuntimeCreateResidenceKind.PickedUp)
|
||||
{
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
|
||||
operation,
|
||||
PhysicsSetPositionFlags.None,
|
||||
0u,
|
||||
UnparentBeforeRouting: false,
|
||||
ApplyPlacementFrameBeforeRouting: false,
|
||||
LeaveWorld: true,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: false,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
if (request.AcceptedWirePosition is not { } position
|
||||
|| !ValidPosition(position))
|
||||
{
|
||||
return RejectedData(request.Authority, operation, reporting);
|
||||
}
|
||||
|
||||
// New CreateObject enters through CPhysicsObj::enter_world(Position*)
|
||||
// -> enter_world(1), which uses 0x11 for every top-level object.
|
||||
// Static state and an authored-shapeless Setup do not suppress the
|
||||
// SetPosition transaction; Core supplies retail's placement dummy
|
||||
// sphere when required.
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
operation,
|
||||
InitialCreateFlags,
|
||||
0u,
|
||||
UnparentBeforeRouting: false,
|
||||
ApplyPlacementFrameBeforeRouting: false,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: request.EntityKind
|
||||
is RuntimePositionEntityKind.LocalPlayer
|
||||
? RuntimeTeleportHookPhase.AfterEnterWorld
|
||||
: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: false,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
internal static RuntimeAuthoritativePositionRoute ClassifyAcceptedPosition(
|
||||
in RuntimeAcceptedPositionRouteRequest request)
|
||||
{
|
||||
RuntimeSetPositionOperationKind operation = OperationKind(
|
||||
request.EntityKind,
|
||||
initialCreate: false);
|
||||
bool reporting = request.PlacementFacts.CollisionBatchEligible;
|
||||
if (!ValidAcceptedAuthority(request.Authority, request.EntityKind)
|
||||
|| request.Source is RuntimeAcceptedPositionSource.Unknown
|
||||
|| !ValidEntityKind(request.EntityKind))
|
||||
{
|
||||
return RejectedAuthority(request.Authority, operation, reporting);
|
||||
}
|
||||
if (!ValidPosition(request.AcceptedWirePosition))
|
||||
return RejectedData(request.Authority, operation, reporting);
|
||||
|
||||
bool force = request.Authority.TimestampDisposition
|
||||
is PositionTimestampDisposition.ForcePosition;
|
||||
if (force)
|
||||
{
|
||||
// The FORCE_POSITION branch precedes unset_parent and
|
||||
// SetPlacementFrame in HandleReceivedPosition.
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
||||
operation,
|
||||
AuthoritativeTeleportFlags,
|
||||
request.PlacementFrame ?? 0u,
|
||||
UnparentBeforeRouting: false,
|
||||
ApplyPlacementFrameBeforeRouting: false,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: false,
|
||||
PreserveHeading: true,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: true,
|
||||
reporting);
|
||||
}
|
||||
|
||||
uint placement = request.PlacementFrame ?? 0u;
|
||||
if (request.EntityKind is RuntimePositionEntityKind.LocalPlayer)
|
||||
{
|
||||
if (request.Authority.TeleportAdvanced)
|
||||
{
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
operation,
|
||||
AuthoritativeTeleportFlags,
|
||||
placement,
|
||||
UnparentBeforeRouting: true,
|
||||
ApplyPlacementFrameBeforeRouting: !request.HasAnimations,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.AfterPositionOperation,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: true,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: true,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
bool interpolate = request.UsePositionFromServer
|
||||
&& request.HasContact;
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
interpolate
|
||||
? RuntimeAuthoritativePositionDisposition.Interpolate
|
||||
: RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
||||
operation,
|
||||
PhysicsSetPositionFlags.None,
|
||||
placement,
|
||||
UnparentBeforeRouting: true,
|
||||
ApplyPlacementFrameBeforeRouting: !request.HasAnimations,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: true,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
// Accepted wire position is intentionally distinct from committed
|
||||
// residence. A target frame with a nonzero cell does not make a
|
||||
// cellless canonical body resident; only a later Runtime SetPosition
|
||||
// or simulation commit may change FullCellId.
|
||||
bool cellless = !request.CommittedCellId.HasValue
|
||||
|| request.CommittedCellId.Value == 0u;
|
||||
if (request.Authority.TeleportAdvanced || cellless)
|
||||
{
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
operation,
|
||||
AuthoritativeTeleportFlags,
|
||||
placement,
|
||||
UnparentBeforeRouting: true,
|
||||
ApplyPlacementFrameBeforeRouting: !request.HasAnimations,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.BeforePositionOperation,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: true,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
// Same-incarnation CreateObject passes arg5=true directly to
|
||||
// HandleReceivedPosition; it never consults the body's current
|
||||
// contact bit for MoveOrTeleport classification.
|
||||
bool effectiveContact = request.Source
|
||||
is RuntimeAcceptedPositionSource.SameIncarnationCreate
|
||||
|| request.HasContact;
|
||||
if (!effectiveContact)
|
||||
{
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
||||
operation,
|
||||
PhysicsSetPositionFlags.None,
|
||||
placement,
|
||||
UnparentBeforeRouting: true,
|
||||
ApplyPlacementFrameBeforeRouting: !request.HasAnimations,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: false,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
// Retail has no explicit NaN policy here. Reject a nonfinite derived
|
||||
// distance at the modern authority boundary rather than recreating an
|
||||
// x87 unordered-compare accident as gameplay behavior.
|
||||
if (!float.IsFinite(request.PlayerDistance)
|
||||
|| request.PlayerDistance < 0f)
|
||||
{
|
||||
return RejectedData(request.Authority, operation, reporting);
|
||||
}
|
||||
|
||||
bool nearby = request.PlayerDistance < MaxPhysicsDistance;
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
nearby
|
||||
? RuntimeAuthoritativePositionDisposition.Interpolate
|
||||
: RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
||||
operation,
|
||||
nearby ? PhysicsSetPositionFlags.None : AuthoritativeTeleportFlags,
|
||||
placement,
|
||||
UnparentBeforeRouting: true,
|
||||
ApplyPlacementFrameBeforeRouting: !request.HasAnimations,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: !nearby,
|
||||
ConstrainAfterRouting: true,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
internal static RuntimeAuthoritativePositionRoute ClassifyLeaveWorld(
|
||||
in RuntimeLeaveWorldRouteRequest request)
|
||||
{
|
||||
RuntimeSetPositionOperationKind operation = OperationKind(
|
||||
request.EntityKind,
|
||||
initialCreate: false);
|
||||
bool reporting = request.PlacementFacts.CollisionBatchEligible;
|
||||
if (!ValidCreateAuthority(request.Authority)
|
||||
|| !ValidEntityKind(request.EntityKind)
|
||||
|| request.Cause is RuntimeLeaveWorldCause.Unknown)
|
||||
{
|
||||
return RejectedAuthority(request.Authority, operation, reporting);
|
||||
}
|
||||
|
||||
return new RuntimeAuthoritativePositionRoute(
|
||||
request.Authority,
|
||||
RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
|
||||
operation,
|
||||
PhysicsSetPositionFlags.None,
|
||||
0u,
|
||||
UnparentBeforeRouting: false,
|
||||
ApplyPlacementFrameBeforeRouting: false,
|
||||
LeaveWorld: true,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: false,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
||||
private static bool ValidCreateAuthority(
|
||||
in RuntimeAuthoritativePositionAuthority authority) =>
|
||||
authority.IsStructurallyValid
|
||||
&& authority.TimestampDisposition is PositionTimestampDisposition.Apply
|
||||
&& authority.PreviousTeleportSequence == authority.AcceptedTeleportSequence;
|
||||
|
||||
private static bool ValidAcceptedAuthority(
|
||||
in RuntimeAuthoritativePositionAuthority authority,
|
||||
RuntimePositionEntityKind kind)
|
||||
{
|
||||
if (!authority.IsStructurallyValid || authority.TeleportRegressed)
|
||||
return false;
|
||||
return authority.TimestampDisposition switch
|
||||
{
|
||||
PositionTimestampDisposition.Apply => true,
|
||||
PositionTimestampDisposition.ForcePosition =>
|
||||
kind is RuntimePositionEntityKind.LocalPlayer
|
||||
&& authority.PreviousTeleportSequence
|
||||
== authority.AcceptedTeleportSequence,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool ValidEntityKind(RuntimePositionEntityKind kind) =>
|
||||
kind is RuntimePositionEntityKind.LocalPlayer
|
||||
or RuntimePositionEntityKind.Remote
|
||||
or RuntimePositionEntityKind.Projectile;
|
||||
|
||||
private static bool ValidPosition(in CreateObject.ServerPosition position)
|
||||
{
|
||||
var origin = new Vector3(
|
||||
position.PositionX,
|
||||
position.PositionY,
|
||||
position.PositionZ);
|
||||
var orientation = new Quaternion(
|
||||
position.RotationX,
|
||||
position.RotationY,
|
||||
position.RotationZ,
|
||||
position.RotationW);
|
||||
return float.IsFinite(origin.X)
|
||||
&& float.IsFinite(origin.Y)
|
||||
&& float.IsFinite(origin.Z)
|
||||
&& float.IsFinite(orientation.X)
|
||||
&& float.IsFinite(orientation.Y)
|
||||
&& float.IsFinite(orientation.Z)
|
||||
&& float.IsFinite(orientation.W)
|
||||
&& PositionFrameValidation.IsValid(
|
||||
position.LandblockId,
|
||||
origin,
|
||||
orientation);
|
||||
}
|
||||
|
||||
private static RuntimeSetPositionOperationKind OperationKind(
|
||||
RuntimePositionEntityKind kind,
|
||||
bool initialCreate) => kind switch
|
||||
{
|
||||
RuntimePositionEntityKind.LocalPlayer when initialCreate =>
|
||||
RuntimeSetPositionOperationKind.InitialLogin,
|
||||
RuntimePositionEntityKind.LocalPlayer =>
|
||||
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
||||
RuntimePositionEntityKind.Projectile =>
|
||||
RuntimeSetPositionOperationKind.ProjectileAuthoritative,
|
||||
_ => RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
};
|
||||
|
||||
private static RuntimeAuthoritativePositionRoute RejectedAuthority(
|
||||
in RuntimeAuthoritativePositionAuthority authority,
|
||||
RuntimeSetPositionOperationKind operation =
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
bool reporting = false) => new(
|
||||
authority,
|
||||
RuntimeAuthoritativePositionDisposition.RejectedAuthority,
|
||||
operation,
|
||||
PhysicsSetPositionFlags.None,
|
||||
0u,
|
||||
UnparentBeforeRouting: false,
|
||||
ApplyPlacementFrameBeforeRouting: false,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: false,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
|
||||
private static RuntimeAuthoritativePositionRoute RejectedData(
|
||||
in RuntimeAuthoritativePositionAuthority authority,
|
||||
RuntimeSetPositionOperationKind operation,
|
||||
bool reporting) => new(
|
||||
authority,
|
||||
RuntimeAuthoritativePositionDisposition.RejectedData,
|
||||
operation,
|
||||
PhysicsSetPositionFlags.None,
|
||||
0u,
|
||||
UnparentBeforeRouting: false,
|
||||
ApplyPlacementFrameBeforeRouting: false,
|
||||
LeaveWorld: false,
|
||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||
StopInterpolating: false,
|
||||
ConstrainAfterRouting: false,
|
||||
PreserveHeading: false,
|
||||
ZeroVelocity: false,
|
||||
SendPositionImmediately: false,
|
||||
reporting);
|
||||
}
|
||||
|
|
@ -0,0 +1,553 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Physics;
|
||||
|
||||
namespace AcDream.Runtime.Tests.Physics;
|
||||
|
||||
public sealed class RuntimeAuthoritativePositionRouteClassifierTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(RuntimePositionEntityKind.LocalPlayer,
|
||||
RuntimeSetPositionOperationKind.InitialLogin,
|
||||
RuntimeTeleportHookPhase.AfterEnterWorld)]
|
||||
[InlineData(RuntimePositionEntityKind.Remote,
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
RuntimeTeleportHookPhase.None)]
|
||||
[InlineData(RuntimePositionEntityKind.Projectile,
|
||||
RuntimeSetPositionOperationKind.ProjectileAuthoritative,
|
||||
RuntimeTeleportHookPhase.None)]
|
||||
internal void TopLevelCreate_AlwaysUsesRetailEnterWorldFlags(
|
||||
RuntimePositionEntityKind kind,
|
||||
RuntimeSetPositionOperationKind expectedKind,
|
||||
RuntimeTeleportHookPhase expectedHookPhase)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||
new RuntimeCreatePositionRouteRequest(
|
||||
Authority(),
|
||||
kind,
|
||||
RuntimeCreateResidenceKind.TopLevel,
|
||||
Position(),
|
||||
new RuntimePositionPlacementFacts(
|
||||
PhysicsStateFlags.Static
|
||||
| PhysicsStateFlags.Hidden
|
||||
| PhysicsStateFlags.NoDraw,
|
||||
HasAuthoredMoverShape: false)));
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
route.Disposition);
|
||||
Assert.Equal(expectedKind, route.OperationKind);
|
||||
Assert.Equal(0x11u, (uint)route.SetPositionFlags);
|
||||
Assert.Equal(expectedHookPhase, route.TeleportHookPhase);
|
||||
Assert.True(route.PerformsSetPosition);
|
||||
Assert.False(route.CollisionBatchEligible);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RuntimeCreateResidenceKind.Parented)]
|
||||
[InlineData(RuntimeCreateResidenceKind.PickedUp)]
|
||||
internal void NewParentedOrPickedUpCreate_LeavesWorldAndAwaitsPosition(
|
||||
RuntimeCreateResidenceKind residence)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||
new RuntimeCreatePositionRouteRequest(
|
||||
Authority(),
|
||||
RuntimePositionEntityKind.Remote,
|
||||
residence,
|
||||
AcceptedWirePosition: null,
|
||||
default));
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
|
||||
route.Disposition);
|
||||
Assert.True(route.LeaveWorld);
|
||||
Assert.False(route.PerformsSetPosition);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidPositions))]
|
||||
internal void CreateAndPositionRejectMalformedAuthoritativeFrames(
|
||||
CreateObject.ServerPosition invalid)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute create =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||
new RuntimeCreatePositionRouteRequest(
|
||||
Authority(),
|
||||
RuntimePositionEntityKind.Remote,
|
||||
RuntimeCreateResidenceKind.TopLevel,
|
||||
invalid,
|
||||
default));
|
||||
RuntimeAuthoritativePositionRoute update = ClassifyRemote(
|
||||
position: invalid,
|
||||
committedCellId: Cell,
|
||||
hasContact: true,
|
||||
playerDistance: 1f);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.RejectedData,
|
||||
create.Disposition);
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.RejectedData,
|
||||
update.Disposition);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RuntimeAcceptedPositionSource.PositionEvent)]
|
||||
[InlineData(RuntimeAcceptedPositionSource.SameIncarnationCreate)]
|
||||
internal void AcceptedPosition_UnparentsAndAppliesDefaultZeroPlacementBeforeRoute(
|
||||
RuntimeAcceptedPositionSource source)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
source: source,
|
||||
placementFrame: null,
|
||||
committedCellId: Cell,
|
||||
hasContact: true,
|
||||
playerDistance: 10f);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.Interpolate,
|
||||
route.Disposition);
|
||||
Assert.True(route.UnparentBeforeRouting);
|
||||
Assert.True(route.ApplyPlacementFrameBeforeRouting);
|
||||
Assert.Equal(0u, route.PlacementFrame);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnimatedAcceptedPosition_UnparentsButSkipsPlacementFrameReset()
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
committedCellId: Cell,
|
||||
hasContact: true,
|
||||
playerDistance: 10f,
|
||||
hasAnimations: true);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.Interpolate,
|
||||
route.Disposition);
|
||||
Assert.True(route.UnparentBeforeRouting);
|
||||
Assert.False(route.ApplyPlacementFrameBeforeRouting);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(95.999f, RuntimeAuthoritativePositionDisposition.Interpolate)]
|
||||
[InlineData(96f, RuntimeAuthoritativePositionDisposition.SetPositionSimple)]
|
||||
internal void SameIncarnationCreate_ForcesContactRoute(
|
||||
float distance,
|
||||
RuntimeAuthoritativePositionDisposition expected)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
source: RuntimeAcceptedPositionSource.SameIncarnationCreate,
|
||||
committedCellId: Cell,
|
||||
hasContact: false,
|
||||
playerDistance: distance);
|
||||
|
||||
Assert.Equal(expected, route.Disposition);
|
||||
Assert.True(route.ConstrainAfterRouting);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ushort.MaxValue, 0)]
|
||||
[InlineData(0x8000, 0)]
|
||||
internal void RemoteFreshTeleport_UsesWrapSafeTimestampAndRetailHook(
|
||||
ushort previousTeleport,
|
||||
ushort acceptedTeleport)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
authority: Authority(previousTeleport, acceptedTeleport),
|
||||
committedCellId: Cell,
|
||||
hasContact: false,
|
||||
playerDistance: float.NaN);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
route.Disposition);
|
||||
Assert.Equal(0x1012u, (uint)route.SetPositionFlags);
|
||||
Assert.Equal(RuntimeTeleportHookPhase.BeforePositionOperation,
|
||||
route.TeleportHookPhase);
|
||||
Assert.True(route.ConstrainAfterRouting);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoteCellless_UsesCommittedResidenceNotAcceptedWireCell()
|
||||
{
|
||||
CreateObject.ServerPosition accepted = Position(Cell, 200f);
|
||||
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
position: accepted,
|
||||
committedCellId: null,
|
||||
hasContact: true,
|
||||
playerDistance: 1f);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
route.Disposition);
|
||||
Assert.Equal(RuntimeTeleportHookPhase.BeforePositionOperation,
|
||||
route.TeleportHookPhase);
|
||||
Assert.Equal(Cell, accepted.LandblockId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoteNoContact_PerformsNoPositionOperationOrConstraint()
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
committedCellId: Cell,
|
||||
hasContact: false,
|
||||
playerDistance: float.NaN);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
||||
route.Disposition);
|
||||
Assert.False(route.ConstrainAfterRouting);
|
||||
Assert.False(route.PerformsSetPosition);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0f, RuntimeAuthoritativePositionDisposition.Interpolate, false)]
|
||||
[InlineData(95.999f, RuntimeAuthoritativePositionDisposition.Interpolate, false)]
|
||||
[InlineData(96f, RuntimeAuthoritativePositionDisposition.SetPositionSimple, true)]
|
||||
[InlineData(500f, RuntimeAuthoritativePositionDisposition.SetPositionSimple, true)]
|
||||
internal void RemoteContact_UsesExactNinetySixBoundary(
|
||||
float distance,
|
||||
RuntimeAuthoritativePositionDisposition expected,
|
||||
bool stopInterpolating)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
committedCellId: Cell,
|
||||
hasContact: true,
|
||||
playerDistance: distance);
|
||||
|
||||
Assert.Equal(expected, route.Disposition);
|
||||
Assert.Equal(stopInterpolating, route.StopInterpolating);
|
||||
Assert.True(route.ConstrainAfterRouting);
|
||||
Assert.Equal(stopInterpolating ? 0x1012u : 0u,
|
||||
(uint)route.SetPositionFlags);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(float.NaN)]
|
||||
[InlineData(float.PositiveInfinity)]
|
||||
[InlineData(float.NegativeInfinity)]
|
||||
[InlineData(-0.001f)]
|
||||
internal void RemoteContact_RejectsMalformedDerivedDistance(float distance)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyRemote(
|
||||
committedCellId: Cell,
|
||||
hasContact: true,
|
||||
playerDistance: distance);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.RejectedData,
|
||||
route.Disposition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemotePositionPackVelocity_IsDeadRoutingInput()
|
||||
{
|
||||
RuntimeAcceptedPositionRouteRequest request = RemoteRequest(
|
||||
committedCellId: Cell,
|
||||
hasContact: true,
|
||||
playerDistance: 10f) with
|
||||
{
|
||||
PositionPackVelocity = new Vector3(float.NaN, float.PositiveInfinity, 7f),
|
||||
};
|
||||
|
||||
RuntimeAuthoritativePositionRoute route =
|
||||
RuntimeAuthoritativePositionRouteClassifier
|
||||
.ClassifyAcceptedPosition(request);
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.Interpolate,
|
||||
route.Disposition);
|
||||
Assert.True(route.Accepted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LocalFreshForce_PreservesHeadingBlipsAndAcknowledgesImmediately()
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyLocal(
|
||||
Authority(
|
||||
previousTeleport: 10,
|
||||
acceptedTeleport: 10,
|
||||
disposition: PositionTimestampDisposition.ForcePosition));
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
||||
route.Disposition);
|
||||
Assert.Equal(0x1012u, (uint)route.SetPositionFlags);
|
||||
Assert.True(route.PreserveHeading);
|
||||
Assert.True(route.SendPositionImmediately);
|
||||
Assert.False(route.UnparentBeforeRouting);
|
||||
Assert.False(route.ApplyPlacementFrameBeforeRouting);
|
||||
Assert.False(route.ConstrainAfterRouting);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForcePosition_IsRejectedForRemoteOrUnequalTeleport()
|
||||
{
|
||||
RuntimeAuthoritativePositionAuthority force = Authority(
|
||||
10, 10, PositionTimestampDisposition.ForcePosition);
|
||||
RuntimeAuthoritativePositionRoute remote = ClassifyRemote(authority: force);
|
||||
RuntimeAuthoritativePositionRoute unequal = ClassifyLocal(
|
||||
Authority(10, 11, PositionTimestampDisposition.ForcePosition));
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.RejectedAuthority,
|
||||
remote.Disposition);
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.RejectedAuthority,
|
||||
unequal.Disposition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LocalFreshTeleport_SetsPositionConstrainsAndZerosVelocity()
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyLocal(
|
||||
Authority(ushort.MaxValue, 0));
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
route.Disposition);
|
||||
Assert.Equal(0x1012u, (uint)route.SetPositionFlags);
|
||||
Assert.True(route.UnparentBeforeRouting);
|
||||
Assert.True(route.ApplyPlacementFrameBeforeRouting);
|
||||
Assert.True(route.ConstrainAfterRouting);
|
||||
Assert.True(route.ZeroVelocity);
|
||||
Assert.Equal(RuntimeTeleportHookPhase.AfterPositionOperation,
|
||||
route.TeleportHookPhase);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false, RuntimeAuthoritativePositionDisposition.NoPositionOperation)]
|
||||
[InlineData(false, true, true, RuntimeAuthoritativePositionDisposition.NoPositionOperation)]
|
||||
[InlineData(true, false, true, RuntimeAuthoritativePositionDisposition.NoPositionOperation)]
|
||||
[InlineData(true, true, false, RuntimeAuthoritativePositionDisposition.Interpolate)]
|
||||
[InlineData(true, true, true, RuntimeAuthoritativePositionDisposition.Interpolate)]
|
||||
internal void LocalOrdinary_AlwaysConstrainsAndInterpolatesOnlyByOptionAndContact(
|
||||
bool usePositionFromServer,
|
||||
bool hasContact,
|
||||
bool hasVelocity,
|
||||
RuntimeAuthoritativePositionDisposition expected)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route = ClassifyLocal(
|
||||
Authority(),
|
||||
usePositionFromServer,
|
||||
hasContact,
|
||||
hasVelocity
|
||||
? new Vector3(float.NaN, float.PositiveInfinity, 1f)
|
||||
: null);
|
||||
|
||||
Assert.Equal(expected, route.Disposition);
|
||||
Assert.True(route.ConstrainAfterRouting);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RuntimeLeaveWorldCause.Pickup)]
|
||||
[InlineData(RuntimeLeaveWorldCause.Parent)]
|
||||
internal void PickupAndParent_LeaveWorldAndAwaitLaterFreshPosition(
|
||||
RuntimeLeaveWorldCause cause)
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute route =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyLeaveWorld(
|
||||
new RuntimeLeaveWorldRouteRequest(
|
||||
Authority(),
|
||||
RuntimePositionEntityKind.Projectile,
|
||||
cause,
|
||||
default));
|
||||
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
|
||||
route.Disposition);
|
||||
Assert.Equal(RuntimeSetPositionOperationKind.ProjectileAuthoritative,
|
||||
route.OperationKind);
|
||||
Assert.True(route.LeaveWorld);
|
||||
Assert.False(route.PerformsSetPosition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenAndNoDrawDoNotGatePlacement_ButReportingRemainsSeparate()
|
||||
{
|
||||
RuntimePositionPlacementFacts visible = new(
|
||||
PhysicsStateFlags.None,
|
||||
HasAuthoredMoverShape: true);
|
||||
RuntimePositionPlacementFacts noDraw = visible with
|
||||
{
|
||||
PhysicsState = PhysicsStateFlags.NoDraw,
|
||||
};
|
||||
RuntimePositionPlacementFacts hidden = visible with
|
||||
{
|
||||
PhysicsState = PhysicsStateFlags.Hidden,
|
||||
};
|
||||
RuntimePositionPlacementFacts ignored = visible with
|
||||
{
|
||||
PhysicsState = PhysicsStateFlags.IgnoreCollisions,
|
||||
};
|
||||
|
||||
RuntimeAuthoritativePositionRoute visibleRoute =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||
new RuntimeCreatePositionRouteRequest(
|
||||
Authority(),
|
||||
RuntimePositionEntityKind.Remote,
|
||||
RuntimeCreateResidenceKind.TopLevel,
|
||||
Position(),
|
||||
visible));
|
||||
RuntimeAuthoritativePositionRoute noDrawRoute =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||
new RuntimeCreatePositionRouteRequest(
|
||||
Authority(),
|
||||
RuntimePositionEntityKind.Remote,
|
||||
RuntimeCreateResidenceKind.TopLevel,
|
||||
Position(),
|
||||
noDraw));
|
||||
RuntimeAuthoritativePositionRoute hiddenRoute =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||
new RuntimeCreatePositionRouteRequest(
|
||||
Authority(),
|
||||
RuntimePositionEntityKind.Remote,
|
||||
RuntimeCreateResidenceKind.TopLevel,
|
||||
Position(),
|
||||
hidden));
|
||||
RuntimeAuthoritativePositionRoute ignoredRoute =
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||
new RuntimeCreatePositionRouteRequest(
|
||||
Authority(),
|
||||
RuntimePositionEntityKind.Remote,
|
||||
RuntimeCreateResidenceKind.TopLevel,
|
||||
Position(),
|
||||
ignored));
|
||||
|
||||
Assert.All([visibleRoute, noDrawRoute, hiddenRoute, ignoredRoute], route =>
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition,
|
||||
route.Disposition));
|
||||
Assert.True(visibleRoute.CollisionBatchEligible);
|
||||
Assert.True(noDrawRoute.CollisionBatchEligible);
|
||||
Assert.False(hiddenRoute.CollisionBatchEligible);
|
||||
Assert.True(ignoredRoute.CollisionBatchEligible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectilePosition_UsesRemoteMoveOrTeleportClassification()
|
||||
{
|
||||
RuntimeAcceptedPositionRouteRequest request = RemoteRequest(
|
||||
committedCellId: Cell,
|
||||
hasContact: true,
|
||||
playerDistance: 96f) with
|
||||
{
|
||||
EntityKind = RuntimePositionEntityKind.Projectile,
|
||||
};
|
||||
|
||||
RuntimeAuthoritativePositionRoute route =
|
||||
RuntimeAuthoritativePositionRouteClassifier
|
||||
.ClassifyAcceptedPosition(request);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionOperationKind.ProjectileAuthoritative,
|
||||
route.OperationKind);
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
||||
route.Disposition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaleOrMalformedAuthorityIsRejectedWithoutRouting()
|
||||
{
|
||||
RuntimeAuthoritativePositionRoute stale = ClassifyRemote(
|
||||
authority: Authority(previousTeleport: 0, acceptedTeleport: ushort.MaxValue));
|
||||
RuntimeAuthoritativePositionRoute wrongGeneration = ClassifyRemote(
|
||||
authority: Authority() with { Generation = default });
|
||||
RuntimeAuthoritativePositionRoute wrongEntity = ClassifyRemote(
|
||||
authority: Authority() with { Entity = default });
|
||||
RuntimeAuthoritativePositionRoute wrongVersion = ClassifyRemote(
|
||||
authority: Authority() with { PositionAuthorityVersion = 0UL });
|
||||
|
||||
Assert.All([stale, wrongGeneration, wrongEntity, wrongVersion], route =>
|
||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.RejectedAuthority,
|
||||
route.Disposition));
|
||||
}
|
||||
|
||||
public static TheoryData<CreateObject.ServerPosition> InvalidPositions => new()
|
||||
{
|
||||
Position(cell: 0u),
|
||||
Position(x: float.NaN),
|
||||
Position(x: float.PositiveInfinity),
|
||||
Position(rotationW: 0f),
|
||||
Position(rotationW: float.NaN),
|
||||
};
|
||||
|
||||
private const uint Cell = 0x0101FFFFu;
|
||||
|
||||
private static RuntimeAuthoritativePositionAuthority Authority(
|
||||
ushort previousTeleport = 10,
|
||||
ushort acceptedTeleport = 10,
|
||||
PositionTimestampDisposition disposition = PositionTimestampDisposition.Apply) =>
|
||||
new(
|
||||
new RuntimeGenerationToken(7),
|
||||
new RuntimeEntityKey(0x70000001u, 3),
|
||||
PositionAuthorityVersion: 11UL,
|
||||
AcceptedPositionSequence: 20,
|
||||
previousTeleport,
|
||||
acceptedTeleport,
|
||||
disposition);
|
||||
|
||||
private static RuntimeAuthoritativePositionRoute ClassifyRemote(
|
||||
RuntimeAuthoritativePositionAuthority? authority = null,
|
||||
RuntimeAcceptedPositionSource source = RuntimeAcceptedPositionSource.PositionEvent,
|
||||
CreateObject.ServerPosition? position = null,
|
||||
uint? placementFrame = 0u,
|
||||
uint? committedCellId = Cell,
|
||||
bool hasContact = true,
|
||||
float playerDistance = 10f,
|
||||
bool hasAnimations = false) =>
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition(
|
||||
RemoteRequest(
|
||||
authority,
|
||||
source,
|
||||
position,
|
||||
placementFrame,
|
||||
committedCellId,
|
||||
hasContact,
|
||||
playerDistance,
|
||||
hasAnimations));
|
||||
|
||||
private static RuntimeAcceptedPositionRouteRequest RemoteRequest(
|
||||
RuntimeAuthoritativePositionAuthority? authority = null,
|
||||
RuntimeAcceptedPositionSource source = RuntimeAcceptedPositionSource.PositionEvent,
|
||||
CreateObject.ServerPosition? position = null,
|
||||
uint? placementFrame = 0u,
|
||||
uint? committedCellId = Cell,
|
||||
bool hasContact = true,
|
||||
float playerDistance = 10f,
|
||||
bool hasAnimations = false) =>
|
||||
new(
|
||||
authority ?? Authority(),
|
||||
RuntimePositionEntityKind.Remote,
|
||||
source,
|
||||
position ?? Position(),
|
||||
placementFrame,
|
||||
PositionPackVelocity: Vector3.One,
|
||||
committedCellId,
|
||||
hasContact,
|
||||
playerDistance,
|
||||
UsePositionFromServer: false,
|
||||
hasAnimations,
|
||||
default);
|
||||
|
||||
private static RuntimeAuthoritativePositionRoute ClassifyLocal(
|
||||
RuntimeAuthoritativePositionAuthority authority,
|
||||
bool usePositionFromServer = false,
|
||||
bool hasContact = true,
|
||||
Vector3? velocity = null) =>
|
||||
RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition(
|
||||
new RuntimeAcceptedPositionRouteRequest(
|
||||
authority,
|
||||
RuntimePositionEntityKind.LocalPlayer,
|
||||
RuntimeAcceptedPositionSource.PositionEvent,
|
||||
Position(),
|
||||
PlacementFrame: null,
|
||||
PositionPackVelocity: velocity,
|
||||
CommittedCellId: Cell,
|
||||
hasContact,
|
||||
PlayerDistance: 0f,
|
||||
usePositionFromServer,
|
||||
HasAnimations: false,
|
||||
default));
|
||||
|
||||
private static CreateObject.ServerPosition Position(
|
||||
uint cell = Cell,
|
||||
float x = 10f,
|
||||
float rotationW = 1f) =>
|
||||
new(
|
||||
cell,
|
||||
x,
|
||||
20f,
|
||||
30f,
|
||||
rotationW,
|
||||
0f,
|
||||
0f,
|
||||
0f);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue