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())); 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(), }); 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 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())); 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(), })!; 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(), Array.Empty(), Array.Empty(), null, null, "fixture", null, null, MotionTableId: 0x09000001u, PhysicsState: (uint)state, InstanceSequence: instanceSequence, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: 1, Physics: physics); } } }