feat(vfx): port retail hidden and teleport presentation
Preserve canonical live-object ownership across Hidden transitions and remote teleport placement so effects, collision, streaming, and targeting remain synchronized.
This commit is contained in:
parent
a51ebc66e9
commit
1e98d81448
46 changed files with 4883 additions and 127 deletions
|
|
@ -180,9 +180,10 @@ public sealed class InboundPhysicsStateControllerTests
|
|||
liveVelocity,
|
||||
out PositionTimestampDisposition normalDisposition,
|
||||
out WorldSession.EntitySpawn normal,
|
||||
out _));
|
||||
out AcceptedPhysicsTimestamps normalTimestamps));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, normalDisposition);
|
||||
Assert.Equal(liveVelocity, normal.Physics!.Value.Velocity);
|
||||
Assert.False(normalTimestamps.TeleportAdvanced);
|
||||
|
||||
Assert.True(controller.TryApplyPosition(
|
||||
new WorldSession.EntityPositionUpdate(
|
||||
|
|
@ -200,9 +201,10 @@ public sealed class InboundPhysicsStateControllerTests
|
|||
liveVelocity,
|
||||
out PositionTimestampDisposition teleportDisposition,
|
||||
out WorldSession.EntitySpawn teleported,
|
||||
out _));
|
||||
out AcceptedPhysicsTimestamps teleportTimestamps));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, teleportDisposition);
|
||||
Assert.Equal(Vector3.Zero, teleported.Physics!.Value.Velocity);
|
||||
Assert.True(teleportTimestamps.TeleportAdvanced);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,273 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
||||
public sealed class LiveEntityPresentationControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public void InitialVisibleObject_DoesNotPlayUnHide()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
|
||||
Assert.Empty(fixture.TypedPlays);
|
||||
Assert.True(fixture.Entity.IsDrawVisible);
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenThenVisible_PlaysTypedEffectsMutatesChildrenAndRestoresShadow()
|
||||
{
|
||||
Fixture fixture = new(
|
||||
PhysicsStateFlags.Hidden | PhysicsStateFlags.ReportCollisions);
|
||||
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
|
||||
Assert.Equal(
|
||||
[(fixture.Entity.Id, LiveEntityPresentationController.HiddenScriptType, 1f)],
|
||||
fixture.TypedPlays);
|
||||
Assert.Equal([(Fixture.Guid, true)], fixture.ChildNoDraw);
|
||||
Assert.Equal([Fixture.Guid], fixture.InvalidTargets);
|
||||
Assert.Equal(0, fixture.Shadows.TotalRegistered);
|
||||
Assert.False(fixture.Entity.IsDrawVisible);
|
||||
|
||||
Assert.True(fixture.Runtime.TryApplyState(
|
||||
new SetState.Parsed(Fixture.Guid, 0u, 1, 2),
|
||||
out _,
|
||||
out _));
|
||||
Assert.True(fixture.Controller.OnStateAccepted(Fixture.Guid));
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
(fixture.Entity.Id, LiveEntityPresentationController.HiddenScriptType, 1f),
|
||||
(fixture.Entity.Id, LiveEntityPresentationController.UnHideScriptType, 1f),
|
||||
],
|
||||
fixture.TypedPlays);
|
||||
Assert.Equal([(Fixture.Guid, true), (Fixture.Guid, false)], fixture.ChildNoDraw);
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
Assert.True(fixture.Entity.IsDrawVisible);
|
||||
Assert.Same(fixture.Entity, Assert.Single(fixture.Runtime.WorldEntities).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IdenticalOrStaleState_DoesNotReplayHiddenEffect()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
fixture.Controller.OnLiveEntityReady(Fixture.Guid);
|
||||
var hidden = new SetState.Parsed(
|
||||
Fixture.Guid,
|
||||
(uint)(PhysicsStateFlags.Hidden | PhysicsStateFlags.ReportCollisions),
|
||||
1,
|
||||
2);
|
||||
|
||||
Assert.True(fixture.Runtime.TryApplyState(hidden, out _, out _));
|
||||
fixture.Controller.OnStateAccepted(Fixture.Guid);
|
||||
Assert.False(fixture.Runtime.TryApplyState(hidden, out _, out _));
|
||||
fixture.Controller.OnStateAccepted(Fixture.Guid);
|
||||
|
||||
Assert.Single(fixture.TypedPlays);
|
||||
Assert.Equal(LiveEntityPresentationController.HiddenScriptType,
|
||||
fixture.TypedPlays[0].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnHideWhileLandblockPending_RestoresShadowWhenProjectionReturns()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
fixture.Controller.OnLiveEntityReady(Fixture.Guid);
|
||||
|
||||
Assert.True(fixture.Runtime.TryApplyState(
|
||||
new SetState.Parsed(
|
||||
Fixture.Guid,
|
||||
(uint)(PhysicsStateFlags.Hidden | PhysicsStateFlags.ReportCollisions),
|
||||
1,
|
||||
2),
|
||||
out _,
|
||||
out _));
|
||||
fixture.Controller.OnStateAccepted(Fixture.Guid);
|
||||
Assert.Equal(0, fixture.Shadows.TotalRegistered);
|
||||
|
||||
Assert.True(fixture.Runtime.RebucketLiveEntity(Fixture.Guid, 0x02020001u));
|
||||
Assert.False(fixture.Runtime.WorldEntities.ContainsKey(Fixture.Guid));
|
||||
|
||||
Assert.True(fixture.Runtime.TryApplyState(
|
||||
new SetState.Parsed(Fixture.Guid, 0u, 1, 3),
|
||||
out _,
|
||||
out _));
|
||||
fixture.Controller.OnStateAccepted(Fixture.Guid);
|
||||
Assert.Equal(0, fixture.Shadows.TotalRegistered);
|
||||
|
||||
fixture.Spatial.AddLandblock(new LoadedLandblock(
|
||||
0x0202FFFFu,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
|
||||
Assert.True(fixture.Runtime.WorldEntities.ContainsKey(Fixture.Guid));
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActivePlacement_IsGenerationScopedAndClearsOnTeardownAndReset()
|
||||
{
|
||||
Fixture fixture = new(PhysicsStateFlags.ReportCollisions);
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
Assert.True(fixture.Controller.BeginAuthoritativePlacement(Fixture.Guid, 1));
|
||||
Assert.True(fixture.Controller.HasActivePlacement(Fixture.Guid));
|
||||
Assert.True(fixture.Runtime.TryGetRecord(Fixture.Guid, out LiveEntityRecord oldRecord));
|
||||
|
||||
fixture.Controller.Forget(oldRecord);
|
||||
Assert.True(fixture.Runtime.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(Fixture.Guid, 1),
|
||||
isLocalPlayer: false));
|
||||
Assert.False(fixture.Controller.HasActivePlacement(Fixture.Guid));
|
||||
|
||||
fixture.Runtime.RegisterLiveEntity(Fixture.Spawn(
|
||||
PhysicsStateFlags.ReportCollisions,
|
||||
instanceSequence: 2));
|
||||
fixture.Runtime.MaterializeLiveEntity(
|
||||
Fixture.Guid,
|
||||
0x01010001u,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = Fixture.Guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(10f, 10f, 5f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
});
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Fixture.Guid));
|
||||
|
||||
Assert.False(fixture.Controller.CompleteAuthoritativePlacement(
|
||||
Fixture.Guid,
|
||||
generation: 1,
|
||||
deferShadowRestore: true));
|
||||
Assert.False(fixture.Controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
Assert.True(fixture.Controller.BeginAuthoritativePlacement(Fixture.Guid, 2));
|
||||
Assert.True(fixture.Controller.HasActivePlacement(Fixture.Guid));
|
||||
|
||||
fixture.Controller.Clear();
|
||||
|
||||
Assert.False(fixture.Controller.HasActivePlacement(Fixture.Guid));
|
||||
Assert.False(fixture.Controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
}
|
||||
|
||||
private sealed class Fixture
|
||||
{
|
||||
public const uint Guid = 0x70000051u;
|
||||
public List<(uint Owner, uint Type, float Intensity)> TypedPlays { get; } = [];
|
||||
public List<(uint Parent, bool NoDraw)> ChildNoDraw { get; } = [];
|
||||
public List<uint> InvalidTargets { get; } = [];
|
||||
public GpuWorldState Spatial { get; }
|
||||
public LiveEntityRuntime Runtime { get; }
|
||||
public ShadowObjectRegistry Shadows { get; } = new();
|
||||
public WorldEntity Entity { get; }
|
||||
public LiveEntityPresentationController Controller { get; }
|
||||
|
||||
public Fixture(PhysicsStateFlags initialState)
|
||||
{
|
||||
Spatial = new GpuWorldState();
|
||||
Spatial.AddLandblock(new LoadedLandblock(
|
||||
0x0101FFFFu,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
Runtime = new LiveEntityRuntime(
|
||||
Spatial,
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
WorldSession.EntitySpawn spawn = Spawn(initialState);
|
||||
Runtime.RegisterLiveEntity(spawn);
|
||||
Entity = Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
0x01010001u,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = Guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(10f, 10f, 5f),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
})!;
|
||||
Shadows.Register(
|
||||
Entity.Id,
|
||||
0x01000001u,
|
||||
Entity.Position,
|
||||
Entity.Rotation,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
0x01010000u,
|
||||
state: (uint)initialState,
|
||||
seedCellId: 0x01010001u);
|
||||
|
||||
Controller = new LiveEntityPresentationController(
|
||||
Runtime,
|
||||
Shadows,
|
||||
(owner, type, intensity) =>
|
||||
{
|
||||
TypedPlays.Add((owner, type, intensity));
|
||||
return true;
|
||||
},
|
||||
(parent, noDraw) => ChildNoDraw.Add((parent, noDraw)),
|
||||
InvalidTargets.Add,
|
||||
() => (1, 1));
|
||||
}
|
||||
|
||||
internal static WorldSession.EntitySpawn Spawn(
|
||||
PhysicsStateFlags state,
|
||||
ushort instanceSequence = 1)
|
||||
{
|
||||
var position = new CreateObject.ServerPosition(
|
||||
0x01010001u, 10f, 10f, 5f, 1f, 0f, 0f, 0f);
|
||||
var timestamps = new PhysicsTimestamps(1, 1, 1, 1, 0, 1, 0, 1, 1);
|
||||
var physics = new PhysicsSpawnData(
|
||||
RawState: (uint)state,
|
||||
Position: position,
|
||||
Movement: null,
|
||||
AnimationFrame: null,
|
||||
SetupTableId: 0x02000001u,
|
||||
MotionTableId: 0x09000001u,
|
||||
SoundTableId: null,
|
||||
PhysicsScriptTableId: null,
|
||||
Parent: null,
|
||||
Children: null,
|
||||
Scale: null,
|
||||
Friction: null,
|
||||
Elasticity: null,
|
||||
Translucency: null,
|
||||
Velocity: null,
|
||||
Acceleration: null,
|
||||
AngularVelocity: null,
|
||||
DefaultScriptType: null,
|
||||
DefaultScriptIntensity: null,
|
||||
Timestamps: timestamps);
|
||||
return new WorldSession.EntitySpawn(
|
||||
Guid,
|
||||
position,
|
||||
0x02000001u,
|
||||
Array.Empty<CreateObject.AnimPartChange>(),
|
||||
Array.Empty<CreateObject.TextureChange>(),
|
||||
Array.Empty<CreateObject.SubPaletteSwap>(),
|
||||
null,
|
||||
null,
|
||||
"fixture",
|
||||
null,
|
||||
null,
|
||||
MotionTableId: 0x09000001u,
|
||||
PhysicsState: (uint)state,
|
||||
InstanceSequence: instanceSequence,
|
||||
MovementSequence: 1,
|
||||
ServerControlSequence: 1,
|
||||
PositionSequence: 1,
|
||||
Physics: physics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,11 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
private sealed class EffectProfile : ILiveEntityEffectProfile { }
|
||||
|
||||
private sealed class RemoteMotionRuntime : ILiveEntityRemoteMotionRuntime
|
||||
{
|
||||
public PhysicsBody Body { get; } = new();
|
||||
}
|
||||
|
||||
private sealed class FailingRegisterResources : ILiveEntityResourceLifecycle
|
||||
{
|
||||
public int RegisterCount { get; private set; }
|
||||
|
|
@ -73,6 +78,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, spawn.Guid));
|
||||
runtime.SetAnimationRuntime(spawn.Guid, new AnimationRuntime(entity!));
|
||||
var visibilityEdges = new List<bool>();
|
||||
runtime.ProjectionVisibilityChanged += (_, visible) => visibilityEdges.Add(visible);
|
||||
|
||||
Assert.True(registered.LogicalRegistrationCreated);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
|
|
@ -90,6 +97,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Single(spatial.Entities);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.True(runtime.TryGetAnimationRuntime(entity!.Id, out _));
|
||||
Assert.Equal(new[] { false, true }, visibilityEdges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -296,6 +304,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(runtime.TryGetWorldEntity(spawn.Guid, out WorldEntity resolved));
|
||||
Assert.Same(attached, resolved);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
attached.IsAncestorDrawVisible = false;
|
||||
|
||||
WorldEntity same = runtime.MaterializeLiveEntity(
|
||||
spawn.Guid,
|
||||
|
|
@ -304,6 +313,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
LiveEntityProjectionKind.World)!;
|
||||
|
||||
Assert.Same(attached, same);
|
||||
Assert.True(same.IsAncestorDrawVisible);
|
||||
Assert.Same(attached, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.True(runtime.TryMarkWorldSpawnPublished(spawn.Guid));
|
||||
Assert.False(runtime.TryMarkWorldSpawnPublished(spawn.Guid));
|
||||
|
|
@ -500,6 +510,210 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(runtime.ShouldAdvanceRootRuntime(guid));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PhysicsStateAndRemoteBodyStaySynchronizedAcrossEitherArrivalOrder()
|
||||
{
|
||||
const uint stateBeforeBindGuid = 0x70000037u;
|
||||
const uint bindBeforeStateGuid = 0x70000038u;
|
||||
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(stateBeforeBindGuid, 1, 1, 0x01010001u));
|
||||
runtime.RegisterLiveEntity(Spawn(bindBeforeStateGuid, 1, 1, 0x01010001u));
|
||||
|
||||
PhysicsStateFlags firstState = PhysicsStateFlags.Hidden
|
||||
| PhysicsStateFlags.Gravity
|
||||
| PhysicsStateFlags.ReportCollisions;
|
||||
Assert.True(runtime.TryApplyState(
|
||||
new SetState.Parsed(stateBeforeBindGuid, (uint)firstState, 1, 2),
|
||||
out _));
|
||||
var lateBody = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(stateBeforeBindGuid, lateBody);
|
||||
|
||||
var earlyBody = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(bindBeforeStateGuid, earlyBody);
|
||||
PhysicsStateFlags secondState = PhysicsStateFlags.Static
|
||||
| PhysicsStateFlags.Ethereal
|
||||
| PhysicsStateFlags.NoDraw;
|
||||
Assert.True(runtime.TryApplyState(
|
||||
new SetState.Parsed(bindBeforeStateGuid, (uint)secondState, 1, 2),
|
||||
out _));
|
||||
|
||||
Assert.Equal((firstState & ~PhysicsStateFlags.ReportCollisions)
|
||||
| PhysicsStateFlags.IgnoreCollisions,
|
||||
lateBody.Body.State);
|
||||
Assert.Equal(secondState, earlyBody.Body.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParentDrivenChildNoDrawKeepsBoundBodyInCanonicalState()
|
||||
{
|
||||
const uint guid = 0x70000039u;
|
||||
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
|
||||
var remote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(guid, remote);
|
||||
|
||||
Assert.True(runtime.SetAttachedChildNoDraw(guid, true));
|
||||
Assert.True(remote.Body.State.HasFlag(PhysicsStateFlags.NoDraw));
|
||||
|
||||
Assert.True(runtime.SetAttachedChildNoDraw(guid, false));
|
||||
Assert.False(remote.Body.State.HasFlag(PhysicsStateFlags.NoDraw));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitiallyVisibleCreate_DoesNotManufactureUnHideTransition()
|
||||
{
|
||||
const uint guid = 0x70000040u;
|
||||
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources());
|
||||
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
|
||||
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
Assert.True(record.TryDequeueStateTransition(out RetailPhysicsStateTransition transition));
|
||||
Assert.Equal(RetailHiddenTransition.None, transition.HiddenTransition);
|
||||
Assert.Equal(PhysicsStateFlags.ReportCollisions, record.FinalPhysicsState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenCreate_SuppressesMeshInteractionButRetainsNarrowObjectTick()
|
||||
{
|
||||
const uint guid = 0x70000041u;
|
||||
var spatial = new GpuWorldState();
|
||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(
|
||||
guid,
|
||||
1,
|
||||
1,
|
||||
0x01010001u,
|
||||
PhysicsStateFlags.Hidden | PhysicsStateFlags.ReportCollisions));
|
||||
WorldEntity entity = runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid))!;
|
||||
|
||||
Assert.False(entity.IsDrawVisible);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Single(spatial.Entities);
|
||||
Assert.True(runtime.ShouldAdvanceRootRuntime(guid));
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
Assert.Equal(PhysicsStateFlags.None,
|
||||
record.FinalPhysicsState & PhysicsStateFlags.ReportCollisions);
|
||||
Assert.NotEqual(PhysicsStateFlags.None,
|
||||
record.FinalPhysicsState & PhysicsStateFlags.IgnoreCollisions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenThenVisibleState_RestoresSameMeshAndInteractionIdentity()
|
||||
{
|
||||
const uint guid = 0x70000042u;
|
||||
var spatial = new GpuWorldState();
|
||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
|
||||
WorldEntity entity = runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid))!;
|
||||
|
||||
Assert.True(runtime.TryApplyState(new SetState.Parsed(
|
||||
guid,
|
||||
(uint)(PhysicsStateFlags.Hidden | PhysicsStateFlags.ReportCollisions),
|
||||
1,
|
||||
2), out _, out RetailPhysicsStateTransition hidden));
|
||||
Assert.Equal(RetailHiddenTransition.BecameHidden, hidden.HiddenTransition);
|
||||
Assert.False(entity.IsDrawVisible);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.False(runtime.TryGetInteractionEligibleEntity(guid, out _));
|
||||
|
||||
Assert.True(runtime.TryApplyState(new SetState.Parsed(
|
||||
guid,
|
||||
0u,
|
||||
1,
|
||||
3), out _, out RetailPhysicsStateTransition visible));
|
||||
Assert.Equal(RetailHiddenTransition.BecameVisible, visible.HiddenTransition);
|
||||
Assert.True(entity.IsDrawVisible);
|
||||
Assert.True(runtime.TryGetInteractionEligibleEntity(guid, out var eligible));
|
||||
Assert.Same(entity, eligible);
|
||||
Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(entity, Assert.Single(spatial.Entities));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PositionAfterPickup_RequiresTeleportHookEvenWithEqualTeleportStamp()
|
||||
{
|
||||
const uint guid = 0x70000043u;
|
||||
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources());
|
||||
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
|
||||
runtime.RegisterLiveEntity(spawn);
|
||||
Assert.True(runtime.TryApplyPickup(
|
||||
new PickupEvent.Parsed(guid, 1, 2),
|
||||
out _));
|
||||
|
||||
var update = new WorldSession.EntityPositionUpdate(
|
||||
guid,
|
||||
new CreateObject.ServerPosition(
|
||||
0x01010002u, 12f, 13f, 5f, 1f, 0f, 0f, 0f),
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
1,
|
||||
3,
|
||||
0,
|
||||
0);
|
||||
|
||||
Assert.True(runtime.TryApplyPosition(
|
||||
update,
|
||||
isLocalPlayer: false,
|
||||
forcePositionRotation: null,
|
||||
currentLocalVelocity: null,
|
||||
out PositionTimestampDisposition disposition,
|
||||
out _,
|
||||
out AcceptedPhysicsTimestamps timestamps));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
|
||||
Assert.False(timestamps.TeleportAdvanced);
|
||||
Assert.True(timestamps.TeleportHookRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PositionFromPendingProjection_RequiresTeleportHookWithEqualTeleportStamp()
|
||||
{
|
||||
const uint guid = 0x70000044u;
|
||||
var spatial = new GpuWorldState();
|
||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
|
||||
runtime.RegisterLiveEntity(spawn);
|
||||
runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid));
|
||||
spatial.RemoveLandblock(0x0101FFFFu);
|
||||
|
||||
Assert.True(runtime.TryApplyPosition(
|
||||
new WorldSession.EntityPositionUpdate(
|
||||
guid,
|
||||
new CreateObject.ServerPosition(
|
||||
0x01010002u, 12f, 13f, 5f, 1f, 0f, 0f, 0f),
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0),
|
||||
isLocalPlayer: false,
|
||||
forcePositionRotation: null,
|
||||
currentLocalVelocity: null,
|
||||
out PositionTimestampDisposition disposition,
|
||||
out _,
|
||||
out AcceptedPhysicsTimestamps timestamps));
|
||||
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
|
||||
Assert.False(timestamps.TeleportAdvanced);
|
||||
Assert.True(timestamps.TeleportHookRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LandblockUnload_HidesTopLevelViewAndReloadRestoresSameIdentity()
|
||||
{
|
||||
|
|
@ -792,14 +1006,15 @@ public sealed class LiveEntityRuntimeTests
|
|||
uint guid,
|
||||
ushort instance,
|
||||
ushort positionSequence,
|
||||
uint cell)
|
||||
uint cell,
|
||||
PhysicsStateFlags state = PhysicsStateFlags.ReportCollisions)
|
||||
{
|
||||
var position = new CreateObject.ServerPosition(
|
||||
cell, 10f, 10f, 5f, 1f, 0f, 0f, 0f);
|
||||
var timestamps = new PhysicsTimestamps(
|
||||
positionSequence, 1, 1, 1, 0, 1, 0, 1, instance);
|
||||
var physics = new PhysicsSpawnData(
|
||||
RawState: (uint)PhysicsStateFlags.ReportCollisions,
|
||||
RawState: (uint)state,
|
||||
Position: position,
|
||||
Movement: null,
|
||||
AnimationFrame: null,
|
||||
|
|
@ -832,7 +1047,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
null,
|
||||
null,
|
||||
0x09000001u,
|
||||
PhysicsState: (uint)PhysicsStateFlags.ReportCollisions,
|
||||
PhysicsState: (uint)state,
|
||||
InstanceSequence: instance,
|
||||
MovementSequence: 1,
|
||||
ServerControlSequence: 1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue