713 lines
29 KiB
C#
713 lines
29 KiB
C#
using System.Collections.Immutable;
|
|
using System.Numerics;
|
|
using AcDream.Core.Net;
|
|
using AcDream.Core.Net.Messages;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Physics.Motion;
|
|
using AcDream.Runtime.Entities;
|
|
using AcDream.Runtime.Gameplay;
|
|
using AcDream.Runtime.Physics;
|
|
|
|
namespace AcDream.Runtime.Tests.Gameplay;
|
|
|
|
public sealed class RuntimeLocalPlayerPhysicsPublicationStateTests
|
|
{
|
|
private const uint Cell = 0xA9B40021u;
|
|
private const uint SetupId = 0x02000001u;
|
|
|
|
[Fact]
|
|
public void PreparationOwnsPrivateBodyAndMutatesNoCanonicalOrSharedState()
|
|
{
|
|
using var fixture = new Fixture();
|
|
uint fullCell = fixture.Record.FullCellId;
|
|
ulong spatial = fixture.Record.SpatialAuthorityVersion;
|
|
ulong physicsEpoch = fixture.Record.PhysicsOwnershipEpoch;
|
|
ulong clockEpoch = fixture.Record.ObjectClockEpoch;
|
|
RuntimePhysicsOwnershipSnapshot physics = fixture.Lifetime.Physics
|
|
.CaptureOwnership();
|
|
|
|
RuntimeLocalPlayerPhysicsPublicationToken token = fixture.Prepare();
|
|
|
|
Assert.Null(fixture.Record.PhysicsBody);
|
|
Assert.Null(fixture.Movement.Controller);
|
|
Assert.Equal(fullCell, fixture.Record.FullCellId);
|
|
Assert.Equal(spatial, fixture.Record.SpatialAuthorityVersion);
|
|
Assert.Equal(physicsEpoch, fixture.Record.PhysicsOwnershipEpoch);
|
|
Assert.Equal(clockEpoch, fixture.Record.ObjectClockEpoch);
|
|
Assert.Null(fixture.Record.PhysicsHost);
|
|
Assert.Equal(physics.SpatialRootCount,
|
|
fixture.Lifetime.Physics.CaptureOwnership().SpatialRootCount);
|
|
Assert.Equal(physics.RetainedShadowRegistrationCount,
|
|
fixture.Lifetime.Physics.CaptureOwnership()
|
|
.RetainedShadowRegistrationCount);
|
|
Assert.Equal(1, fixture.Owner.CaptureOwnership().CandidateCount);
|
|
Assert.True(token.IsValid);
|
|
}
|
|
|
|
[Fact]
|
|
public void CommitOwnsExactDormantBodyAndControllerWithoutWorldEdges()
|
|
{
|
|
using var fixture = new Fixture();
|
|
RuntimeLocalPlayerPhysicsPublicationToken token = fixture.Prepare();
|
|
Assert.True(fixture.Owner.TryCaptureCandidateSnapshot(
|
|
token,
|
|
out RuntimeLocalPlayerPhysicsCandidateSnapshot prepared));
|
|
uint fullCell = fixture.Record.FullCellId;
|
|
ulong spatial = fixture.Record.SpatialAuthorityVersion;
|
|
ulong bodyEpoch = fixture.Record.PhysicsOwnershipEpoch;
|
|
ulong controllerEpoch = fixture.Movement.ControllerOwnershipEpoch;
|
|
double clockPending = fixture.Record.ObjectClock.PendingSeconds;
|
|
bool clockActive = fixture.Record.ObjectClock.IsActive;
|
|
object? currentCell = fixture.Lifetime.Physics.DataCache.CellGraph.CurrCell;
|
|
|
|
RuntimeLocalPlayerPhysicsPublicationStatus status = fixture.Owner
|
|
.Commit(token);
|
|
|
|
Assert.Equal(RuntimeLocalPlayerPhysicsPublicationStatus.Committed, status);
|
|
PlayerMovementController candidate = Assert.IsType<PlayerMovementController>(
|
|
fixture.Movement.Controller);
|
|
PhysicsBody body = Assert.IsType<PhysicsBody>(fixture.Record.PhysicsBody);
|
|
Assert.Same(body, fixture.Record.PhysicsBody);
|
|
Assert.Same(candidate, fixture.Movement.Controller);
|
|
Assert.True(candidate.OwnsPhysicsBody(body));
|
|
Assert.Equal(bodyEpoch + 1UL, fixture.Record.PhysicsOwnershipEpoch);
|
|
Assert.Equal(controllerEpoch + 1UL,
|
|
fixture.Movement.ControllerOwnershipEpoch);
|
|
Assert.Equal(fullCell, fixture.Record.FullCellId);
|
|
Assert.Equal(spatial, fixture.Record.SpatialAuthorityVersion);
|
|
Assert.Same(currentCell,
|
|
fixture.Lifetime.Physics.DataCache.CellGraph.CurrCell);
|
|
Assert.Equal(clockPending, fixture.Record.ObjectClock.PendingSeconds);
|
|
Assert.Equal(clockActive, fixture.Record.ObjectClock.IsActive);
|
|
Assert.Equal(prepared.Position, body.Position);
|
|
Assert.Equal(prepared.Orientation, body.Orientation);
|
|
Assert.Equal(prepared.CellId, body.CellPosition.ObjCellId);
|
|
Assert.Equal(prepared.CellLocalPosition,
|
|
body.CellPosition.Frame.Origin);
|
|
Assert.Equal(prepared.State, body.State);
|
|
Assert.Equal(prepared.TransientState, body.TransientState);
|
|
Assert.Equal(prepared.InWorld, body.InWorld);
|
|
Assert.False(body.InWorld);
|
|
Assert.False((body.TransientState & TransientStateFlags.Active) != 0);
|
|
Assert.Null(fixture.Record.PhysicsHost);
|
|
Assert.Equal(0, fixture.Lifetime.Physics.CaptureOwnership()
|
|
.SpatialRootCount);
|
|
Assert.Equal(0, fixture.Lifetime.Physics.CaptureOwnership()
|
|
.RetainedShadowRegistrationCount);
|
|
Assert.True(fixture.Lifetime.Physics.SetPosition
|
|
.IsExactPreparedPlacementCurrent(
|
|
fixture.Record,
|
|
fixture.Placement,
|
|
fixture.Command));
|
|
Assert.Equal(0, fixture.Owner.CaptureOwnership().CandidateCount);
|
|
AssertNotLive(candidate);
|
|
Assert.Equal(prepared.Position, body.Position);
|
|
Assert.Equal(prepared.Orientation, body.Orientation);
|
|
Assert.Equal(prepared.State, body.State);
|
|
Assert.Equal(prepared.TransientState, body.TransientState);
|
|
Assert.Equal(clockPending, fixture.Record.ObjectClock.PendingSeconds);
|
|
Assert.Equal(clockActive, fixture.Record.ObjectClock.IsActive);
|
|
Assert.Same(currentCell,
|
|
fixture.Lifetime.Physics.DataCache.CellGraph.CurrCell);
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.RejectedToken,
|
|
fixture.Owner.Commit(token));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(PublicationInvalidation.SetPositionReplacement)]
|
|
[InlineData(PublicationInvalidation.Vector)]
|
|
[InlineData(PublicationInvalidation.FinalState)]
|
|
[InlineData(PublicationInvalidation.ObjectDescription)]
|
|
[InlineData(PublicationInvalidation.Create)]
|
|
[InlineData(PublicationInvalidation.Remote)]
|
|
[InlineData(PublicationInvalidation.Projectile)]
|
|
[InlineData(PublicationInvalidation.Body)]
|
|
[InlineData(PublicationInvalidation.Clock)]
|
|
[InlineData(PublicationInvalidation.Controller)]
|
|
[InlineData(PublicationInvalidation.CancelPlacement)]
|
|
public void EveryNestedAuthorityReplacementDiscardsCandidateWithoutRollback(
|
|
PublicationInvalidation invalidation)
|
|
{
|
|
using var fixture = new Fixture();
|
|
RuntimeLocalPlayerPhysicsPublicationToken token = fixture.Prepare();
|
|
PhysicsBody? replacementBody = null;
|
|
PlayerMovementController? replacementController = null;
|
|
|
|
switch (invalidation)
|
|
{
|
|
case PublicationInvalidation.SetPositionReplacement:
|
|
Assert.True(fixture.Lifetime.Physics.SetPosition
|
|
.BeginAuthoredPlacement(
|
|
fixture.Record,
|
|
fixture.Record.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.LocalAuthoritative)
|
|
.IsValid);
|
|
break;
|
|
case PublicationInvalidation.Vector:
|
|
fixture.Lifetime.Entities.AdvanceVectorAuthority(fixture.Record);
|
|
break;
|
|
case PublicationInvalidation.FinalState:
|
|
fixture.Lifetime.Entities.SetFinalPhysicsState(
|
|
fixture.Record,
|
|
fixture.Record.FinalPhysicsState | PhysicsStateFlags.Frozen);
|
|
break;
|
|
case PublicationInvalidation.ObjectDescription:
|
|
fixture.Lifetime.Entities.AdvanceObjDescAuthority(fixture.Record);
|
|
break;
|
|
case PublicationInvalidation.Create:
|
|
fixture.Lifetime.Entities.AdvanceCreateAuthority(fixture.Record);
|
|
break;
|
|
case PublicationInvalidation.Remote:
|
|
fixture.Lifetime.Entities.SetRemoteMotion(
|
|
fixture.Record,
|
|
new RemoteMotion());
|
|
break;
|
|
case PublicationInvalidation.Projectile:
|
|
fixture.Lifetime.Entities.SetProjectile(
|
|
fixture.Record,
|
|
new RuntimeProjectile(
|
|
new PhysicsBody(),
|
|
new ProjectileCollisionSphere(Vector3.Zero, 0.1f)));
|
|
break;
|
|
case PublicationInvalidation.Body:
|
|
replacementBody = new PhysicsBody();
|
|
fixture.Lifetime.Entities.SetPhysicsBody(
|
|
fixture.Record,
|
|
replacementBody);
|
|
break;
|
|
case PublicationInvalidation.Clock:
|
|
fixture.Lifetime.Entities.SuspendObjectClock(fixture.Record);
|
|
break;
|
|
case PublicationInvalidation.Controller:
|
|
replacementController = new PlayerMovementController(
|
|
new PhysicsEngine());
|
|
fixture.Movement.Controller = replacementController;
|
|
break;
|
|
case PublicationInvalidation.CancelPlacement:
|
|
Assert.True(fixture.Lifetime.Physics.SetPosition.Cancel(
|
|
fixture.Record,
|
|
publishWithdrawal: false));
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(invalidation));
|
|
}
|
|
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.RejectedAuthority,
|
|
fixture.Owner.Commit(token));
|
|
Assert.Equal(0, fixture.Owner.CaptureOwnership().CandidateCount);
|
|
if (replacementBody is not null)
|
|
Assert.Same(replacementBody, fixture.Record.PhysicsBody);
|
|
if (replacementController is not null)
|
|
Assert.Same(replacementController, fixture.Movement.Controller);
|
|
}
|
|
|
|
[Fact]
|
|
public void DeleteAndGuidReuseCannotPublishRetiredCandidate()
|
|
{
|
|
using var fixture = new Fixture();
|
|
RuntimeLocalPlayerPhysicsPublicationToken token = fixture.Prepare();
|
|
uint guid = fixture.Record.ServerGuid;
|
|
Assert.True(fixture.Lifetime.TryAcceptDelete(
|
|
new DeleteObject.Parsed(guid, fixture.Record.Incarnation),
|
|
isLocalPlayer: false,
|
|
removeRetainedObject: false,
|
|
out RuntimeEntityDeleteAcceptance acceptance));
|
|
fixture.Lifetime.CompleteAcceptedDelete(acceptance);
|
|
Assert.Null(fixture.Lifetime.RetireCanonicalOnly(fixture.Record));
|
|
RuntimeEntityRecord replacement = fixture.Lifetime.RegisterEntity(
|
|
Spawn(guid, incarnation: 2)).Canonical!;
|
|
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.RejectedAuthority,
|
|
fixture.Owner.Commit(token));
|
|
Assert.Null(replacement.PhysicsBody);
|
|
Assert.Null(fixture.Movement.Controller);
|
|
Assert.Equal(0, fixture.Owner.CaptureOwnership().CandidateCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetAndDisposeDiscardCandidatesAndConvergeOwnership()
|
|
{
|
|
var fixture = new Fixture();
|
|
_ = fixture.Prepare();
|
|
fixture.Movement.ResetSession();
|
|
Assert.Equal(0, fixture.Owner.CaptureOwnership().CandidateCount);
|
|
|
|
fixture.RepreparePlacement();
|
|
_ = fixture.Prepare();
|
|
fixture.Movement.Dispose();
|
|
Assert.True(fixture.Movement.CaptureOwnership().IsConverged);
|
|
fixture.DisposeLifetimeOnly();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(PristineViolation.Body)]
|
|
[InlineData(PristineViolation.Controller)]
|
|
[InlineData(PristineViolation.Host)]
|
|
[InlineData(PristineViolation.BodyAcquisition)]
|
|
[InlineData(PristineViolation.Remote)]
|
|
[InlineData(PristineViolation.RemoteBinding)]
|
|
[InlineData(PristineViolation.Projectile)]
|
|
[InlineData(PristineViolation.ProjectileBinding)]
|
|
[InlineData(PristineViolation.RemotePlacement)]
|
|
public void PreparationRejectsEveryNonPristineOwnershipGraph(
|
|
PristineViolation violation)
|
|
{
|
|
using var fixture = new Fixture();
|
|
switch (violation)
|
|
{
|
|
case PristineViolation.Body:
|
|
{
|
|
var activeBody = new PhysicsBody
|
|
{
|
|
TransientState = TransientStateFlags.Active,
|
|
};
|
|
fixture.Lifetime.Entities.SetPhysicsBody(
|
|
fixture.Record,
|
|
activeBody);
|
|
break;
|
|
}
|
|
case PristineViolation.Controller:
|
|
fixture.Movement.Controller = new PlayerMovementController(
|
|
new PhysicsEngine());
|
|
break;
|
|
case PristineViolation.Host:
|
|
fixture.Lifetime.Entities.SetPhysicsHost(
|
|
fixture.Record,
|
|
CreatePhysicsHost(fixture.Record.ServerGuid));
|
|
break;
|
|
case PristineViolation.BodyAcquisition:
|
|
fixture.Lifetime.Entities.SetPhysicsBodyAcquisitionInProgress(
|
|
fixture.Record,
|
|
true);
|
|
break;
|
|
case PristineViolation.Remote:
|
|
fixture.Lifetime.Entities.SetRemoteMotion(
|
|
fixture.Record,
|
|
new RemoteMotion());
|
|
break;
|
|
case PristineViolation.RemoteBinding:
|
|
fixture.Lifetime.Entities.SetRemoteMotionBindingInProgress(
|
|
fixture.Record,
|
|
true);
|
|
break;
|
|
case PristineViolation.Projectile:
|
|
fixture.Lifetime.Entities.SetProjectile(
|
|
fixture.Record,
|
|
new RuntimeProjectile(
|
|
new PhysicsBody(),
|
|
new ProjectileCollisionSphere(Vector3.Zero, 0.1f)));
|
|
break;
|
|
case PristineViolation.ProjectileBinding:
|
|
fixture.Lifetime.Entities.SetProjectileBindingInProgress(
|
|
fixture.Record,
|
|
true);
|
|
break;
|
|
case PristineViolation.RemotePlacement:
|
|
fixture.Lifetime.Entities.SetRequiresRemotePlacementRuntime(
|
|
fixture.Record,
|
|
true);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(violation));
|
|
}
|
|
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.RejectedAuthority,
|
|
fixture.Owner.Prepare(
|
|
fixture.Record,
|
|
fixture.Placement,
|
|
fixture.Command,
|
|
PlayerMovementConstructionOptions.Fallback,
|
|
out RuntimeLocalPlayerPhysicsPublicationToken token));
|
|
Assert.False(token.IsValid);
|
|
Assert.Equal(0, fixture.Owner.CaptureOwnership().CandidateCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void PreparationRejectsRecordWhichIsNotTheExactLocalIdentity()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Identity.ServerGuid = 0x70003002u;
|
|
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.RejectedAuthority,
|
|
fixture.Owner.Prepare(
|
|
fixture.Record,
|
|
fixture.Placement,
|
|
fixture.Command,
|
|
PlayerMovementConstructionOptions.Fallback,
|
|
out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void PreparationRejectsDisposedLocalIdentity()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Identity.Dispose();
|
|
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.RejectedAuthority,
|
|
fixture.Owner.Prepare(
|
|
fixture.Record,
|
|
fixture.Placement,
|
|
fixture.Command,
|
|
PlayerMovementConstructionOptions.Fallback,
|
|
out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void IdentityRevisionSwitchRejectsPreparedCandidate()
|
|
{
|
|
using var fixture = new Fixture();
|
|
RuntimeLocalPlayerPhysicsPublicationToken token = fixture.Prepare();
|
|
uint guid = fixture.Identity.ServerGuid;
|
|
fixture.Identity.ServerGuid = guid + 1u;
|
|
fixture.Identity.ServerGuid = guid;
|
|
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.RejectedAuthority,
|
|
fixture.Owner.Commit(token));
|
|
Assert.Null(fixture.Record.PhysicsBody);
|
|
Assert.Null(fixture.Movement.Controller);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(PublishedRetirement.Replacement)]
|
|
[InlineData(PublishedRetirement.Reset)]
|
|
[InlineData(PublishedRetirement.Dispose)]
|
|
public void RuntimeOwnedControllerRejectsStaleOperationsAfterRetirement(
|
|
PublishedRetirement retirement)
|
|
{
|
|
using var fixture = new Fixture();
|
|
Assert.Equal(
|
|
RuntimeLocalPlayerPhysicsPublicationStatus.Committed,
|
|
fixture.Owner.Commit(fixture.Prepare()));
|
|
PlayerMovementController stale = fixture.Movement.Controller!;
|
|
|
|
switch (retirement)
|
|
{
|
|
case PublishedRetirement.Replacement:
|
|
fixture.Movement.Controller = new PlayerMovementController(
|
|
new PhysicsEngine());
|
|
break;
|
|
case PublishedRetirement.Reset:
|
|
fixture.Movement.ResetSession();
|
|
break;
|
|
case PublishedRetirement.Dispose:
|
|
fixture.Movement.Dispose();
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(retirement));
|
|
}
|
|
|
|
AssertNotLive(stale);
|
|
}
|
|
|
|
[Fact]
|
|
public void BodyAndControllerEpochsAdvanceOnlyOnOwnershipChanges()
|
|
{
|
|
using var fixture = new Fixture(preparePlacement: false);
|
|
var firstBody = new PhysicsBody();
|
|
var secondBody = new PhysicsBody();
|
|
ulong bodyEpoch = fixture.Record.PhysicsOwnershipEpoch;
|
|
fixture.Lifetime.Entities.SetPhysicsBody(fixture.Record, firstBody);
|
|
Assert.Equal(bodyEpoch + 1UL, fixture.Record.PhysicsOwnershipEpoch);
|
|
fixture.Lifetime.Entities.SetPhysicsBody(fixture.Record, firstBody);
|
|
Assert.Equal(bodyEpoch + 1UL, fixture.Record.PhysicsOwnershipEpoch);
|
|
fixture.Lifetime.Entities.SetPhysicsBody(fixture.Record, secondBody);
|
|
Assert.Equal(bodyEpoch + 2UL, fixture.Record.PhysicsOwnershipEpoch);
|
|
fixture.Lifetime.Entities.SetPhysicsBody(fixture.Record, null);
|
|
Assert.Equal(bodyEpoch + 3UL, fixture.Record.PhysicsOwnershipEpoch);
|
|
|
|
var first = new PlayerMovementController(new PhysicsEngine());
|
|
var second = new PlayerMovementController(new PhysicsEngine());
|
|
ulong controllerEpoch = fixture.Movement.ControllerOwnershipEpoch;
|
|
fixture.Movement.Controller = first;
|
|
Assert.Equal(controllerEpoch + 1UL,
|
|
fixture.Movement.ControllerOwnershipEpoch);
|
|
fixture.Movement.Controller = first;
|
|
Assert.Equal(controllerEpoch + 1UL,
|
|
fixture.Movement.ControllerOwnershipEpoch);
|
|
fixture.Movement.Controller = second;
|
|
Assert.Equal(controllerEpoch + 2UL,
|
|
fixture.Movement.ControllerOwnershipEpoch);
|
|
fixture.Movement.Controller = null;
|
|
Assert.Equal(controllerEpoch + 3UL,
|
|
fixture.Movement.ControllerOwnershipEpoch);
|
|
}
|
|
|
|
private sealed class Fixture : IDisposable
|
|
{
|
|
private bool _lifetimeDisposed;
|
|
|
|
internal Fixture(bool preparePlacement = true)
|
|
{
|
|
Lifetime = new RuntimeEntityObjectLifetime();
|
|
Movement = new RuntimeLocalPlayerMovementState();
|
|
Identity = new RuntimeLocalPlayerIdentityState();
|
|
Owner = new RuntimeLocalPlayerPhysicsPublicationState(
|
|
Lifetime.Entities,
|
|
Lifetime.Physics,
|
|
Movement,
|
|
Identity);
|
|
Movement.AttachPhysicsPublication(Owner);
|
|
Record = Lifetime.RegisterEntity(
|
|
Spawn(0x70003001u, incarnation: 1)).Canonical!;
|
|
Identity.ServerGuid = Record.ServerGuid;
|
|
if (preparePlacement)
|
|
RepreparePlacement();
|
|
}
|
|
|
|
internal RuntimeEntityObjectLifetime Lifetime { get; }
|
|
internal RuntimeLocalPlayerMovementState Movement { get; }
|
|
internal RuntimeLocalPlayerIdentityState Identity { get; }
|
|
internal RuntimeLocalPlayerPhysicsPublicationState Owner { get; }
|
|
internal RuntimeEntityRecord Record { get; }
|
|
internal RuntimeEntityPlacementToken Placement { get; private set; }
|
|
internal RuntimeSetPositionCommand Command { get; private set; }
|
|
|
|
internal void RepreparePlacement()
|
|
{
|
|
Placement = Lifetime.Physics.SetPosition.BeginAuthoredPlacement(
|
|
Record,
|
|
Record.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.LocalAuthoritative);
|
|
Assert.True(Placement.IsValid);
|
|
var setup = new FlatSetupCollision(
|
|
ImmutableArray<FlatCollisionCylinder>.Empty,
|
|
[new FlatCollisionSphere(Vector3.Zero, 0.48f)],
|
|
height: 0f,
|
|
radius: 0f,
|
|
stepUpHeight: 0.4f,
|
|
stepDownHeight: 0.4f);
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
Lifetime.Physics.SetPosition.PrepareMover(
|
|
Placement,
|
|
new RuntimeSetPositionMoverPreparation(
|
|
RuntimeSetPositionMoverSetup.Resolved(SetupId, setup),
|
|
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
|
GameTime: 10d,
|
|
PhysicsPlacementClass.Ordinary,
|
|
PhysicsSetPositionFlags.Placement
|
|
| PhysicsSetPositionFlags.Slide),
|
|
out RuntimeSetPositionCommand command));
|
|
Command = command;
|
|
}
|
|
|
|
internal RuntimeLocalPlayerPhysicsPublicationToken Prepare()
|
|
{
|
|
Assert.Equal(RuntimeLocalPlayerPhysicsPublicationStatus.Prepared,
|
|
Owner.Prepare(
|
|
Record,
|
|
Placement,
|
|
Command,
|
|
PlayerMovementConstructionOptions.Fallback,
|
|
out RuntimeLocalPlayerPhysicsPublicationToken token));
|
|
Assert.Equal(Record.Key, token.Entity);
|
|
Assert.Equal(Placement, token.Placement);
|
|
Assert.Equal(Identity.ServerGuid, token.LocalPlayerServerGuid);
|
|
Assert.Equal(Identity.Revision, token.LocalPlayerIdentityRevision);
|
|
return token;
|
|
}
|
|
|
|
internal void DisposeLifetimeOnly()
|
|
{
|
|
if (_lifetimeDisposed)
|
|
return;
|
|
Lifetime.Dispose();
|
|
_lifetimeDisposed = true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Movement.Dispose();
|
|
Identity.Dispose();
|
|
DisposeLifetimeOnly();
|
|
}
|
|
}
|
|
|
|
private static WorldSession.EntitySpawn Spawn(uint guid, ushort incarnation)
|
|
{
|
|
var position = new CreateObject.ServerPosition(
|
|
Cell,
|
|
1f,
|
|
2f,
|
|
3f,
|
|
1f,
|
|
0f,
|
|
0f,
|
|
0f);
|
|
var timestamps = new PhysicsTimestamps(
|
|
Position: 1,
|
|
Movement: 1,
|
|
State: 1,
|
|
Vector: 1,
|
|
Teleport: 0,
|
|
ServerControlledMove: 1,
|
|
ForcePosition: 0,
|
|
ObjDesc: 1,
|
|
Instance: incarnation);
|
|
var physics = new PhysicsSpawnData(
|
|
RawState: (uint)(PhysicsStateFlags.Gravity
|
|
| PhysicsStateFlags.ReportCollisions),
|
|
Position: position,
|
|
Movement: null,
|
|
AnimationFrame: null,
|
|
SetupTableId: SetupId,
|
|
MotionTableId: 0x09000001u,
|
|
SoundTableId: null,
|
|
PhysicsScriptTableId: null,
|
|
Parent: null,
|
|
Children: null,
|
|
Scale: 1f,
|
|
Friction: null,
|
|
Elasticity: null,
|
|
Translucency: null,
|
|
Velocity: null,
|
|
Acceleration: null,
|
|
AngularVelocity: null,
|
|
DefaultScriptType: null,
|
|
DefaultScriptIntensity: null,
|
|
Timestamps: timestamps);
|
|
return new WorldSession.EntitySpawn(
|
|
Guid: guid,
|
|
Position: position,
|
|
SetupTableId: SetupId,
|
|
AnimPartChanges: Array.Empty<CreateObject.AnimPartChange>(),
|
|
TextureChanges: Array.Empty<CreateObject.TextureChange>(),
|
|
SubPalettes: Array.Empty<CreateObject.SubPaletteSwap>(),
|
|
BasePaletteId: null,
|
|
ObjScale: 1f,
|
|
Name: "local-publication-fixture",
|
|
ItemType: null,
|
|
MotionState: null,
|
|
MotionTableId: 0x09000001u,
|
|
PhysicsState: physics.RawState,
|
|
ObjectDescriptionFlags: 0x8u,
|
|
InstanceSequence: incarnation,
|
|
MovementSequence: 1,
|
|
ServerControlSequence: 1,
|
|
PositionSequence: 1,
|
|
Physics: physics);
|
|
}
|
|
|
|
public enum PublicationInvalidation
|
|
{
|
|
SetPositionReplacement,
|
|
Vector,
|
|
FinalState,
|
|
ObjectDescription,
|
|
Create,
|
|
Remote,
|
|
Projectile,
|
|
Body,
|
|
Clock,
|
|
Controller,
|
|
CancelPlacement,
|
|
}
|
|
|
|
public enum PristineViolation
|
|
{
|
|
Body,
|
|
Controller,
|
|
Host,
|
|
BodyAcquisition,
|
|
Remote,
|
|
RemoteBinding,
|
|
Projectile,
|
|
ProjectileBinding,
|
|
RemotePlacement,
|
|
}
|
|
|
|
public enum PublishedRetirement
|
|
{
|
|
Replacement,
|
|
Reset,
|
|
Dispose,
|
|
}
|
|
|
|
private static EntityPhysicsHost CreatePhysicsHost(uint id) => new(
|
|
id,
|
|
getPosition: static () => default,
|
|
getVelocity: static () => Vector3.Zero,
|
|
getRadius: static () => 0.48f,
|
|
inContact: static () => true,
|
|
minterpMaxSpeed: static () => null,
|
|
curTime: static () => 0d,
|
|
physicsTimerTime: static () => 0d,
|
|
getObjectA: static _ => null,
|
|
handleUpdateTarget: static _ => { },
|
|
interruptCurrentMovement: static () => { });
|
|
|
|
private static void AssertNotLive(PlayerMovementController controller)
|
|
{
|
|
Assert.Throws<InvalidOperationException>(() => controller.Update(
|
|
1f / 60f,
|
|
default));
|
|
Assert.Throws<InvalidOperationException>(() => controller.TickHidden(
|
|
1f / 60f));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.SuspendObjectUpdate(1f / 60f));
|
|
Assert.Throws<InvalidOperationException>(() => controller.SetPosition(
|
|
Vector3.One,
|
|
Cell,
|
|
Vector3.One));
|
|
Assert.Throws<InvalidOperationException>(() => controller.BlipPosition(
|
|
Vector3.One,
|
|
Cell,
|
|
Vector3.One));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.ApplyPhysicsState(PhysicsStateFlags.Frozen));
|
|
Assert.Throws<InvalidOperationException>(() => controller.Yaw = 1f);
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.CaptureMovementResult(mouseLookEvent: false));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.CapturePresentationResult());
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.TryGetOutboundPosition(out _));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.NoteMovementSent(1f));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.NotePositionSent(default, default, 1f));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.ShouldSendPositionEvent(default, default, 1f));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.BeginMouseLook(default));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.SubmitMouseTurnAdjustment(1f, default));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.StopMouseDrift(default));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.EndMouseLook(default));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.PrepareForAttackRequest());
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.RequestPosture(MotionCommand.Ready));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.CommitPreparedPosition());
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.PreparePositionForCommit(Vector3.One, Cell, Vector3.One));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.SetCharacterSkills(1, 1));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.SetBodyOrientation(Quaternion.Identity));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.SetLastMoveWasAutonomous(true));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.State = PlayerState.PortalSpace);
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.StepUpHeight = 1f);
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.AttachCycleVelocityAccessor(static () => Vector3.One));
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
controller.AttachAnimationRootMotionSource(static (_, _) => { }));
|
|
Assert.Throws<InvalidOperationException>(() => _ = controller.Movement);
|
|
Assert.Throws<InvalidOperationException>(() => _ = controller.MoveTo);
|
|
Assert.Throws<InvalidOperationException>(() => _ = controller.PositionManager);
|
|
Assert.Throws<InvalidOperationException>(() => _ = controller.Motion);
|
|
Assert.Throws<InvalidOperationException>(() => _ = controller.PhysicsBody);
|
|
}
|
|
}
|