715 lines
28 KiB
C#
715 lines
28 KiB
C#
using System.Collections.Immutable;
|
|
using System.Numerics;
|
|
using AcDream.Core.Net;
|
|
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 RuntimeSetPositionMoverPreparationTests
|
|
{
|
|
private const uint Cell = 0xA9B40021u;
|
|
private const uint SetupId = 0x02000001u;
|
|
|
|
[Fact]
|
|
public void PreparedMoverPreservesCompleteAuthoredRetailInput()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
float half = MathF.Sqrt(0.5f);
|
|
var acceptedPosition = new CreateObject.ServerPosition(
|
|
Cell,
|
|
12.5f,
|
|
23.5f,
|
|
34.5f,
|
|
half,
|
|
0f,
|
|
half,
|
|
0f);
|
|
RuntimeEntityRecord record = CreateRecord(
|
|
lifetime,
|
|
objScale: 3f,
|
|
physicsScale: 2f,
|
|
objectDescriptionFlags: 0x8u | 0x20u | 0x200000u
|
|
| 0x2000000u | 0x100000u | 0x400000u,
|
|
position: acceptedPosition);
|
|
const PhysicsStateFlags state = PhysicsStateFlags.Gravity
|
|
| PhysicsStateFlags.EdgeSlide
|
|
| PhysicsStateFlags.PathClipped;
|
|
lifetime.Entities.SetFinalPhysicsState(record, state);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
ImmutableArray<FlatCollisionSphere> spheres =
|
|
[
|
|
new(new Vector3(1f, 2f, 3f), 0.1f),
|
|
new(new Vector3(4f, 5f, 6f), 0.2f),
|
|
new(new Vector3(7f, 8f, 9f), 0.3f),
|
|
];
|
|
FlatSetupCollision setup = Setup(spheres, 0.25f, -0.5f);
|
|
var portal = default(RuntimePortalPlacementAuthority);
|
|
var input = new RuntimeSetPositionMoverPreparation(
|
|
RuntimeSetPositionMoverSetup.Resolved(SetupId, setup),
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
|
GameTime: 42.25d,
|
|
PhysicsPlacementClass.Corpse,
|
|
PhysicsSetPositionFlags.Line | PhysicsSetPositionFlags.Scatter,
|
|
Line: new Vector3(3f, 4f, 5f),
|
|
ScatterRadiusX: 6f,
|
|
ScatterRadiusY: 7f,
|
|
ScatterAttempts: 8u,
|
|
ShadowWorldOffsetX: 9f,
|
|
ShadowWorldOffsetY: 10f,
|
|
Portal: portal);
|
|
|
|
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
|
.SetPosition.PrepareMover(token, input, out var command);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
|
Assert.Equal(spheres, command.Physics.Spheres);
|
|
Assert.Equal(2f, command.Physics.Scale);
|
|
Assert.Equal(0.5f, command.Physics.StepUpHeight);
|
|
Assert.Equal(-1f, command.Physics.StepDownHeight);
|
|
Assert.Equal(Cell, command.Physics.CellId);
|
|
Assert.Equal(new Vector3(12.5f, 23.5f, 34.5f),
|
|
command.Physics.CellLocalPosition);
|
|
Assert.Equal(new Vector3(
|
|
12.5f + 9f,
|
|
23.5f + 10f,
|
|
34.5f), command.Physics.Position);
|
|
Assert.Equal(new Quaternion(0f, half, 0f, half),
|
|
command.Physics.Orientation);
|
|
Assert.Equal(state, command.Physics.MoverPhysicsState);
|
|
Assert.Equal(
|
|
ObjectInfoState.IsPlayer
|
|
| ObjectInfoState.IsPK
|
|
| ObjectInfoState.IsPKLite
|
|
| ObjectInfoState.IsImpenetrable
|
|
| ObjectInfoState.CanBypassMoveRestrictions,
|
|
command.Physics.MoverFlags);
|
|
Assert.False(command.Physics.MoverFlags.HasFlag(
|
|
ObjectInfoState.EdgeSlide));
|
|
Assert.False(command.Physics.MoverFlags.HasFlag(
|
|
ObjectInfoState.PathClipped));
|
|
Assert.False(command.Physics.MoverFlags.HasFlag(
|
|
ObjectInfoState.FreeRotate));
|
|
Assert.False(command.Physics.MoverFlags.HasFlag(
|
|
ObjectInfoState.Contact));
|
|
Assert.False(command.Physics.MoverFlags.HasFlag(
|
|
ObjectInfoState.OnWalkable));
|
|
Assert.Equal(record.Key!.Value.LocalEntityId,
|
|
command.Physics.MovingEntityId);
|
|
Assert.Equal(PhysicsPlacementClass.Corpse,
|
|
command.Physics.PlacementClass);
|
|
Assert.Equal(input.Flags, command.Physics.Flags);
|
|
Assert.Equal(input.Line, command.Physics.Line);
|
|
Assert.Equal(6f, command.Physics.ScatterRadiusX);
|
|
Assert.Equal(7f, command.Physics.ScatterRadiusY);
|
|
Assert.Equal(8u, command.Physics.ScatterAttempts);
|
|
Assert.Equal(42.25d, command.GameTime);
|
|
Assert.Equal(9f, command.ShadowWorldOffsetX);
|
|
Assert.Equal(10f, command.ShadowWorldOffsetY);
|
|
Assert.Equal(portal, command.Portal);
|
|
Assert.Equal(record.VelocityAuthorityVersion,
|
|
command.ExpectedVelocityAuthorityVersion);
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalPortalKindAndAuthorityArePreservedWithoutInference()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
var portal = new RuntimePortalPlacementAuthority(
|
|
Present: true,
|
|
RevealGeneration: 17,
|
|
TeleportSequence: 3,
|
|
new RuntimeWorldHostProjectionToken(17, Cell));
|
|
RuntimeEntityPlacementToken token = lifetime.Physics.SetPosition
|
|
.BeginAuthoredPlacement(
|
|
record,
|
|
record.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
|
portal);
|
|
var input = Input(ResolvedEmptySetup()) with
|
|
{
|
|
Kind = RuntimeSetPositionOperationKind.LocalAuthoritative,
|
|
Portal = portal,
|
|
};
|
|
|
|
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
|
.SetPosition.PrepareMover(token, input, out var command);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
|
Assert.Equal(RuntimeSetPositionOperationKind.LocalAuthoritative,
|
|
command.Kind);
|
|
Assert.Equal(portal, command.Portal);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnavailableSetupRetriesWithoutInventingDummyWhileAbsentAndEmptyResolve()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime, setupTableId: null);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
RuntimeSetPositionMoverPreparationStatus unavailable = lifetime.Physics
|
|
.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Unavailable),
|
|
out RuntimeSetPositionCommand unavailableCommand);
|
|
|
|
Assert.Equal(
|
|
RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable,
|
|
unavailable);
|
|
Assert.Equal(default, unavailableCommand);
|
|
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
|
.AwaitingSetPositionPreparationCount);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
|
out RuntimeSetPositionCommand absent));
|
|
Assert.Empty(absent.Physics.Spheres);
|
|
|
|
record = CreateRecord(lifetime, guid: 0x70002002u);
|
|
token = Begin(lifetime, record);
|
|
FlatSetupCollision authoredEmpty = Setup(
|
|
ImmutableArray<FlatCollisionSphere>.Empty,
|
|
0f,
|
|
0f);
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Resolved(
|
|
SetupId,
|
|
authoredEmpty)),
|
|
out RuntimeSetPositionCommand empty));
|
|
Assert.Empty(empty.Physics.Spheres);
|
|
}
|
|
|
|
[Fact]
|
|
public void KnownSetupMustResolveTheExactCanonicalDid()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
Assert.Equal(
|
|
RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Unavailable),
|
|
out _));
|
|
Assert.Equal(
|
|
RuntimeSetPositionMoverPreparationStatus.InvalidData,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
|
out _));
|
|
Assert.Equal(
|
|
RuntimeSetPositionMoverPreparationStatus.InvalidData,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Resolved(
|
|
SetupId + 1u,
|
|
Setup(ImmutableArray<FlatCollisionSphere>.Empty, 0f, 0f))),
|
|
out _));
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(ResolvedEmptySetup()),
|
|
out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void AbsentSetupRejectsInventedCollisionPayload()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(
|
|
lifetime,
|
|
setupTableId: null);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
Assert.Equal(
|
|
RuntimeSetPositionMoverPreparationStatus.InvalidData,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Resolved(
|
|
SetupId,
|
|
Setup(ImmutableArray<FlatCollisionSphere>.Empty, 0f, 0f))),
|
|
out _));
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
|
out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void AuthoredEmptySetupStillSuppliesScaledStepHeights()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(
|
|
lifetime,
|
|
objScale: 2f);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
FlatSetupCollision authoredEmpty = Setup(
|
|
ImmutableArray<FlatCollisionSphere>.Empty,
|
|
0.25f,
|
|
-0.5f);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Resolved(
|
|
SetupId,
|
|
authoredEmpty)),
|
|
out RuntimeSetPositionCommand command));
|
|
Assert.Empty(command.Physics.Spheres);
|
|
Assert.Equal(0.5f, command.Physics.StepUpHeight);
|
|
Assert.Equal(-1f, command.Physics.StepDownHeight);
|
|
}
|
|
|
|
[Fact]
|
|
public void AuthoredTokenRejectsManualUnsealedSubmission()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
AttachBody(lifetime, record);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
RuntimeSetPositionCommand invented = new(
|
|
new PhysicsSetPositionRequest(
|
|
new Vector3(1f, 2f, 3f),
|
|
Quaternion.Identity,
|
|
Cell,
|
|
new Vector3(1f, 2f, 3f),
|
|
ImmutableArray<FlatCollisionSphere>.Empty,
|
|
1f,
|
|
0f,
|
|
0f,
|
|
record.FinalPhysicsState,
|
|
ObjectInfoState.None,
|
|
record.Key!.Value.LocalEntityId),
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
|
1d,
|
|
record.VelocityAuthorityVersion);
|
|
|
|
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
|
|
.SubmitPreparedPlacement(token, invented);
|
|
|
|
Assert.Equal(RuntimeSetPositionStatus.Rejected, outcome.Status);
|
|
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
|
|
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
|
.AwaitingSetPositionPreparationCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void RepreparationReplacesTheExactSealedCommand()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
AttachBody(lifetime, record);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(ResolvedEmptySetup()) with { GameTime = 1d },
|
|
out RuntimeSetPositionCommand first));
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(ResolvedEmptySetup()) with { GameTime = 2d },
|
|
out RuntimeSetPositionCommand second));
|
|
Assert.NotEqual(first, second);
|
|
|
|
RuntimeSetPositionOutcome stale = lifetime.Physics.SetPosition
|
|
.SubmitPreparedPlacement(token, first);
|
|
Assert.Equal(RuntimeSetPositionStatus.Rejected, stale.Status);
|
|
|
|
RuntimeSetPositionOutcome current = lifetime.Physics.SetPosition
|
|
.SubmitPreparedPlacement(token, second);
|
|
Assert.NotEqual(RuntimeSetPositionStatus.Rejected, current.Status);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null, null, 1f)]
|
|
[InlineData(0f, null, 0f)]
|
|
[InlineData(-2f, null, -2f)]
|
|
[InlineData(3f, 0f, 0f)]
|
|
[InlineData(3f, -4f, -4f)]
|
|
public void ScalePreservesAbsentZeroNegativeAndPhysicsPrecedence(
|
|
float? objScale,
|
|
float? physicsScale,
|
|
float expected)
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(
|
|
lifetime,
|
|
objScale,
|
|
physicsScale);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
|
.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Resolved(SetupId, Setup(
|
|
[new FlatCollisionSphere(Vector3.Zero, 0.5f)],
|
|
0.25f,
|
|
-0.5f))),
|
|
out RuntimeSetPositionCommand command);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
|
Assert.Equal(expected, command.Physics.Scale);
|
|
Assert.Equal(0.25f * expected, command.Physics.StepUpHeight);
|
|
Assert.Equal(-0.5f * expected, command.Physics.StepDownHeight);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(float.NaN)]
|
|
[InlineData(float.PositiveInfinity)]
|
|
[InlineData(float.NegativeInfinity)]
|
|
public void NonFiniteScaleRejectsWhenAuthoredSphereConsumesIt(float scale)
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime, scale, null);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
|
.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Resolved(
|
|
SetupId,
|
|
Setup(
|
|
[new FlatCollisionSphere(Vector3.Zero, 0.5f)],
|
|
0f,
|
|
0f))),
|
|
out RuntimeSetPositionCommand command);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.InvalidData, status);
|
|
Assert.Equal(default, command);
|
|
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
|
.AwaitingSetPositionPreparationCount);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(float.NaN)]
|
|
[InlineData(float.PositiveInfinity)]
|
|
[InlineData(float.NegativeInfinity)]
|
|
public void NonFiniteScaleIsNotConsumedByResolvedAbsentDummy(float scale)
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(
|
|
lifetime,
|
|
objScale: scale,
|
|
setupTableId: null);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
|
.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
|
out RuntimeSetPositionCommand command);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
|
Assert.Equal(scale, command.Physics.Scale);
|
|
Assert.Equal(0f, command.Physics.StepUpHeight);
|
|
Assert.Equal(0f, command.Physics.StepDownHeight);
|
|
Assert.Empty(command.Physics.Spheres);
|
|
}
|
|
|
|
[Fact]
|
|
public void PreparationDoesNotMutateCanonicalOrPresentationState()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
var body = new PhysicsBody
|
|
{
|
|
Position = new Vector3(1f, 2f, 3f),
|
|
Orientation = Quaternion.Identity,
|
|
State = PhysicsStateFlags.Gravity,
|
|
InWorld = true,
|
|
};
|
|
body.SnapToCell(0xA8B40001u, body.Position, body.Position);
|
|
lifetime.Entities.SetPhysicsBody(record, body);
|
|
lifetime.Entities.SetFullCell(record, 0xA8B40001u, 0xA8B4FFFFu);
|
|
Vector3 priorPosition = body.Position;
|
|
uint priorCell = record.FullCellId;
|
|
ulong priorSpatial = record.SpatialAuthorityVersion;
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(ResolvedEmptySetup()),
|
|
out _));
|
|
|
|
Assert.Same(body, record.PhysicsBody);
|
|
Assert.Equal(priorPosition, body.Position);
|
|
Assert.Equal(priorCell, record.FullCellId);
|
|
Assert.Equal(priorSpatial, record.SpatialAuthorityVersion);
|
|
Assert.True(body.InWorld);
|
|
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
|
|
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
|
.AwaitingSetPositionPreparationCount);
|
|
Assert.False(lifetime.Physics.SetPosition
|
|
.TryGetPreparedMoverSphereCount(record, out _));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(AuthorityReplacement.Position)]
|
|
[InlineData(AuthorityReplacement.Velocity)]
|
|
[InlineData(AuthorityReplacement.Vector)]
|
|
[InlineData(AuthorityReplacement.State)]
|
|
[InlineData(AuthorityReplacement.ObjectDescription)]
|
|
[InlineData(AuthorityReplacement.Create)]
|
|
public void AnyAuthorityReplacementRejectsPreparedSubmissionWithoutMutation(
|
|
AuthorityReplacement replacement)
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
var body = new PhysicsBody
|
|
{
|
|
Position = new Vector3(1f, 2f, 3f),
|
|
Orientation = Quaternion.Identity,
|
|
State = record.FinalPhysicsState,
|
|
InWorld = true,
|
|
};
|
|
body.SnapToCell(0xA8B40001u, body.Position, body.Position);
|
|
lifetime.Entities.SetPhysicsBody(record, body);
|
|
lifetime.Entities.SetFullCell(record, 0xA8B40001u, 0xA8B4FFFFu);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
|
lifetime.Physics.SetPosition.PrepareMover(
|
|
token,
|
|
Input(ResolvedEmptySetup()),
|
|
out RuntimeSetPositionCommand command));
|
|
Vector3 priorPosition = body.Position;
|
|
uint priorCell = record.FullCellId;
|
|
|
|
ReplaceAuthority(lifetime, record, replacement);
|
|
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
|
|
.SubmitPreparedPlacement(token, command);
|
|
|
|
Assert.Equal(RuntimeSetPositionStatus.Rejected, outcome.Status);
|
|
Assert.Equal(priorPosition, body.Position);
|
|
Assert.Equal(priorCell, record.FullCellId);
|
|
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
|
|
Assert.False(lifetime.Physics.SetPosition
|
|
.TryGetPreparedMoverSphereCount(record, out _));
|
|
|
|
RuntimeEntityPlacementToken replacementToken = Begin(lifetime, record);
|
|
Assert.True(replacementToken.IsValid);
|
|
Assert.NotEqual(token, replacementToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void ThirdAuthoredSphereIsPreservedButNotConsumedByCoreValidation()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
FlatSetupCollision setup = Setup(
|
|
[
|
|
new FlatCollisionSphere(Vector3.Zero, 0.1f),
|
|
new FlatCollisionSphere(Vector3.UnitZ, 0.2f),
|
|
new FlatCollisionSphere(
|
|
new Vector3(float.NaN, 0f, 0f),
|
|
0.3f),
|
|
], 0f, 0f);
|
|
|
|
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
|
.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.Resolved(SetupId, setup)),
|
|
out _);
|
|
|
|
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
|
}
|
|
|
|
[Fact]
|
|
public void SessionReplacementInvalidatesExactTokenAndClearsPreparationAuthority()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
|
RuntimeEntityRecord record = CreateRecord(lifetime);
|
|
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
|
|
|
_ = lifetime.BeginSessionClear();
|
|
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
|
.SetPosition.PrepareMover(
|
|
token,
|
|
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
|
out RuntimeSetPositionCommand command);
|
|
|
|
Assert.Equal(
|
|
RuntimeSetPositionMoverPreparationStatus.RejectedAuthority,
|
|
status);
|
|
Assert.Equal(default, command);
|
|
RuntimeSetPositionOwnershipSnapshot ownership = lifetime.Physics
|
|
.SetPosition.CaptureOwnership();
|
|
Assert.Equal(0, ownership.MoverPreparationAuthorityCount);
|
|
Assert.Equal(0, ownership.ActiveOperationCount);
|
|
}
|
|
|
|
private static RuntimeEntityPlacementToken Begin(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
RuntimeEntityRecord record) => lifetime.Physics.SetPosition
|
|
.BeginAuthoredPlacement(
|
|
record,
|
|
record.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative);
|
|
|
|
private static RuntimeSetPositionMoverPreparation Input(
|
|
RuntimeSetPositionMoverSetup setup) => new(
|
|
setup,
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
|
GameTime: 1d,
|
|
PhysicsPlacementClass.Ordinary,
|
|
PhysicsSetPositionFlags.Placement);
|
|
|
|
private static RuntimeSetPositionMoverSetup ResolvedEmptySetup() =>
|
|
RuntimeSetPositionMoverSetup.Resolved(
|
|
SetupId,
|
|
Setup(ImmutableArray<FlatCollisionSphere>.Empty, 0f, 0f));
|
|
|
|
private static FlatSetupCollision Setup(
|
|
ImmutableArray<FlatCollisionSphere> spheres,
|
|
float stepUp,
|
|
float stepDown) => new(
|
|
ImmutableArray<FlatCollisionCylinder>.Empty,
|
|
spheres,
|
|
height: 0f,
|
|
radius: 0f,
|
|
stepUp,
|
|
stepDown);
|
|
|
|
private static RuntimeEntityRecord CreateRecord(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
float? objScale = null,
|
|
float? physicsScale = null,
|
|
uint? objectDescriptionFlags = null,
|
|
uint guid = 0x70002001u,
|
|
uint? setupTableId = SetupId,
|
|
CreateObject.ServerPosition? position = null)
|
|
{
|
|
CreateObject.ServerPosition acceptedPosition = position ?? new(
|
|
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: 1);
|
|
var physics = new PhysicsSpawnData(
|
|
RawState: (uint)PhysicsStateFlags.Gravity,
|
|
Position: acceptedPosition,
|
|
Movement: null,
|
|
AnimationFrame: null,
|
|
SetupTableId: setupTableId,
|
|
MotionTableId: 0x09000001u,
|
|
SoundTableId: null,
|
|
PhysicsScriptTableId: null,
|
|
Parent: null,
|
|
Children: null,
|
|
Scale: physicsScale,
|
|
Friction: null,
|
|
Elasticity: null,
|
|
Translucency: null,
|
|
Velocity: null,
|
|
Acceleration: null,
|
|
AngularVelocity: null,
|
|
DefaultScriptType: null,
|
|
DefaultScriptIntensity: null,
|
|
Timestamps: timestamps);
|
|
var spawn = new WorldSession.EntitySpawn(
|
|
Guid: guid,
|
|
Position: acceptedPosition,
|
|
SetupTableId: setupTableId,
|
|
AnimPartChanges: Array.Empty<CreateObject.AnimPartChange>(),
|
|
TextureChanges: Array.Empty<CreateObject.TextureChange>(),
|
|
SubPalettes: Array.Empty<CreateObject.SubPaletteSwap>(),
|
|
BasePaletteId: null,
|
|
ObjScale: objScale,
|
|
Name: "mover-preparation-fixture",
|
|
ItemType: null,
|
|
MotionState: null,
|
|
MotionTableId: 0x09000001u,
|
|
PhysicsState: (uint)PhysicsStateFlags.Gravity,
|
|
ObjectDescriptionFlags: objectDescriptionFlags,
|
|
InstanceSequence: 1,
|
|
MovementSequence: 1,
|
|
ServerControlSequence: 1,
|
|
PositionSequence: 1,
|
|
Physics: physics);
|
|
return lifetime.RegisterEntity(spawn).Canonical!;
|
|
}
|
|
|
|
private static void AttachBody(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
RuntimeEntityRecord record)
|
|
{
|
|
var body = new PhysicsBody
|
|
{
|
|
Position = new Vector3(1f, 2f, 3f),
|
|
Orientation = Quaternion.Identity,
|
|
State = record.FinalPhysicsState,
|
|
InWorld = true,
|
|
};
|
|
body.SnapToCell(Cell, body.Position, body.Position);
|
|
lifetime.Entities.SetPhysicsBody(record, body);
|
|
lifetime.Entities.SetFullCell(record, Cell, Cell & 0xFFFF0000u);
|
|
}
|
|
|
|
private static void ReplaceAuthority(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
RuntimeEntityRecord record,
|
|
AuthorityReplacement replacement)
|
|
{
|
|
switch (replacement)
|
|
{
|
|
case AuthorityReplacement.Position:
|
|
lifetime.Entities.AdvancePositionAuthority(record);
|
|
break;
|
|
case AuthorityReplacement.Velocity:
|
|
lifetime.Entities.AdvanceMovementAuthority(record);
|
|
break;
|
|
case AuthorityReplacement.Vector:
|
|
lifetime.Entities.AdvanceVectorAuthority(record);
|
|
break;
|
|
case AuthorityReplacement.State:
|
|
_ = lifetime.Entities.ApplyRawPhysicsState(
|
|
record,
|
|
(uint)PhysicsStateFlags.Frozen);
|
|
break;
|
|
case AuthorityReplacement.ObjectDescription:
|
|
lifetime.Entities.AdvanceObjDescAuthority(record);
|
|
break;
|
|
case AuthorityReplacement.Create:
|
|
lifetime.Entities.AdvanceCreateAuthority(record);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(replacement));
|
|
}
|
|
}
|
|
|
|
public enum AuthorityReplacement
|
|
{
|
|
Position,
|
|
Velocity,
|
|
Vector,
|
|
State,
|
|
ObjectDescription,
|
|
Create,
|
|
}
|
|
}
|