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
|
|
@ -88,6 +88,38 @@ public sealed class AttachmentUpdateOrderTests
|
|||
Assert.Equal(new Vector3(100f, 2f, 0f), renderedOrigin, new Vector3ToleranceComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestedDrawable_InheritsAncestorSuppressionWithoutMutatingOwnState()
|
||||
{
|
||||
var root = Entity(1u);
|
||||
var child = Entity(2u);
|
||||
var grandchild = Entity(3u);
|
||||
root.IsDrawVisible = false;
|
||||
|
||||
EquippedChildRenderController.ApplyParentDrawVisibility(child, root);
|
||||
EquippedChildRenderController.ApplyParentDrawVisibility(grandchild, child);
|
||||
|
||||
Assert.True(child.IsDrawVisible);
|
||||
Assert.False(child.IsAncestorDrawVisible);
|
||||
Assert.True(grandchild.IsDrawVisible);
|
||||
Assert.False(grandchild.IsAncestorDrawVisible);
|
||||
|
||||
root.IsDrawVisible = true;
|
||||
EquippedChildRenderController.ApplyParentDrawVisibility(child, root);
|
||||
EquippedChildRenderController.ApplyParentDrawVisibility(grandchild, child);
|
||||
Assert.True(child.IsAncestorDrawVisible);
|
||||
Assert.True(grandchild.IsAncestorDrawVisible);
|
||||
}
|
||||
|
||||
private static AcDream.Core.World.WorldEntity Entity(uint id) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<AcDream.Core.World.MeshRef>(),
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void AncestorTeardownCollectsEntireAttachmentSubtreePostOrder()
|
||||
{
|
||||
|
|
|
|||
65
tests/AcDream.App.Tests/Rendering/EntityPhysicsHostTests.cs
Normal file
65
tests/AcDream.App.Tests/Rendering/EntityPhysicsHostTests.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Physics.Motion;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class EntityPhysicsHostTests
|
||||
{
|
||||
[Fact]
|
||||
public void NotifyHidden_FansNonOkStatusAndClearsWatcherSubscription()
|
||||
{
|
||||
const uint watcherId = 0x50000001u;
|
||||
const uint targetId = 0x70000001u;
|
||||
var hosts = new Dictionary<uint, IPhysicsObjHost>();
|
||||
var watcherUpdates = new List<TargetInfo>();
|
||||
|
||||
EntityPhysicsHost watcher = CreateHost(
|
||||
watcherId,
|
||||
hosts,
|
||||
watcherUpdates.Add);
|
||||
EntityPhysicsHost target = CreateHost(targetId, hosts, _ => { });
|
||||
hosts.Add(watcherId, watcher);
|
||||
hosts.Add(targetId, target);
|
||||
watcher.SetTarget(0u, targetId, 1f, 0.1);
|
||||
watcherUpdates.Clear();
|
||||
|
||||
target.NotifyHidden();
|
||||
|
||||
TargetInfo hidden = Assert.Single(watcherUpdates);
|
||||
Assert.Equal(TargetStatus.ExitWorld, hidden.Status);
|
||||
Assert.Null(watcher.TargetManager.TargetInfo);
|
||||
Assert.False(target.TargetManager.VoyeurTable!.ContainsKey(watcherId));
|
||||
|
||||
watcherUpdates.Clear();
|
||||
watcher.SetTarget(0u, targetId, 1f, 0.1);
|
||||
|
||||
TargetInfo restored = Assert.Single(watcherUpdates);
|
||||
Assert.Equal(TargetStatus.Ok, restored.Status);
|
||||
Assert.Equal(targetId, restored.ObjectId);
|
||||
Assert.True(target.TargetManager.VoyeurTable.ContainsKey(watcherId));
|
||||
}
|
||||
|
||||
private static EntityPhysicsHost CreateHost(
|
||||
uint id,
|
||||
IReadOnlyDictionary<uint, IPhysicsObjHost> hosts,
|
||||
Action<TargetInfo> update)
|
||||
{
|
||||
return new EntityPhysicsHost(
|
||||
id,
|
||||
getPosition: () => new Position(
|
||||
0x01010001u,
|
||||
new Vector3(id == 0x50000001u ? 0f : 2f, 0f, 0f),
|
||||
Quaternion.Identity),
|
||||
getVelocity: () => Vector3.Zero,
|
||||
getRadius: () => 0.5f,
|
||||
inContact: () => true,
|
||||
minterpMaxSpeed: () => 1f,
|
||||
curTime: () => 1.0,
|
||||
physicsTimerTime: () => 1.0,
|
||||
getObjectA: objectId => hosts.GetValueOrDefault(objectId),
|
||||
handleUpdateTarget: update,
|
||||
interruptCurrentMovement: () => { });
|
||||
}
|
||||
}
|
||||
|
|
@ -166,6 +166,28 @@ public sealed class EntityEffectControllerTests
|
|||
Assert.DoesNotContain(fixture.Sink.Calls, call => call.EntityId == Guid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreparedOwner_DoesNotReplayPendingPacketsUntilConstructionBarrierOpens()
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
fixture.Controller.HandleDirect(new PlayPhysicsScript(Guid, DirectDid));
|
||||
WorldSession.EntitySpawn spawn = Spawn(Guid, 1);
|
||||
fixture.Runtime.RegisterLiveEntity(spawn);
|
||||
fixture.Runtime.SetEffectProfile(Guid, Fixture.LiveProfile());
|
||||
fixture.Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, Guid));
|
||||
|
||||
Assert.True(fixture.Controller.PrepareLiveEntityOwner(Guid));
|
||||
Assert.Equal(1, fixture.Controller.PendingPacketCount);
|
||||
Assert.Equal(0, fixture.Runner.ActiveScriptCount);
|
||||
|
||||
Assert.True(fixture.Controller.ReplayPendingForLiveEntity(Guid));
|
||||
Assert.Equal(0, fixture.Controller.PendingPacketCount);
|
||||
Assert.Equal(1, fixture.Runner.ActiveScriptCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadyOwnerReceivesDirectAndTypedPacketsImmediately()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue