feat(vfx): bind effects to live animated poses

This commit is contained in:
Erik 2026-07-14 10:56:01 +02:00
parent 96ddfdf175
commit 542dcfc384
41 changed files with 3246 additions and 741 deletions

View file

@ -70,6 +70,7 @@ public sealed class EntityEffectControllerTests
new PhysicsScriptTableResolver(
tableLoader
?? (id => _tables.TryGetValue(id, out PhysicsScriptTable? table) ? table : null)),
Poses,
childAtPart,
parentOfAttachedChild,
ownerUnregistered,
@ -88,6 +89,7 @@ public sealed class EntityEffectControllerTests
public AnimationHookRouter Router { get; } = new();
public PhysicsScriptRunner Runner { get; }
public EntityEffectController Controller { get; }
public EntityEffectPoseRegistry Poses { get; } = new();
public void AddScript(uint did, uint emitterId, double startTime = 0.0)
{
@ -246,7 +248,7 @@ public sealed class EntityEffectControllerTests
0x0202FFFFu,
new LandBlock(),
Array.Empty<WorldEntity>()));
fixture.Controller.RefreshLiveOwnerAnchors();
fixture.Controller.RefreshLiveOwnerPoses();
fixture.Runner.Tick(0.0);
Assert.Equal(0, fixture.Controller.PendingPacketCount);
@ -549,6 +551,34 @@ public sealed class EntityEffectControllerTests
Assert.False(fixture.Controller.PlayDefault(childLocalId));
}
[Fact]
public void RefreshLiveOwnerPoses_PreservesComposedAttachedRootAndUsesItAsScriptAnchor()
{
const uint childGuid = 0x70000002u;
var fixture = new Fixture();
WorldEntity child = fixture.ReadyLive(childGuid);
WorldSession.EntitySpawn spawn = Spawn(childGuid, generation: 1);
fixture.Runtime.MaterializeLiveEntity(
childGuid,
spawn.Position!.Value.LandblockId,
_ => throw new InvalidOperationException("existing projection must be reused"),
LiveEntityProjectionKind.Attached);
Matrix4x4 attachedRoot = Matrix4x4.CreateTranslation(7, 8, 9);
fixture.Poses.Publish(
child.Id,
attachedRoot,
Array.Empty<Matrix4x4>(),
spawn.Position.Value.LandblockId);
fixture.Controller.RefreshLiveOwnerPoses();
fixture.Controller.HandleDirect(new PlayPhysicsScript(childGuid, DirectDid));
fixture.Runner.Tick(0.0);
Assert.True(fixture.Poses.TryGetRootPose(child.Id, out Matrix4x4 retained));
Assert.Equal(new Vector3(7, 8, 9), retained.Translation);
Assert.Equal(new Vector3(7, 8, 9), Assert.Single(fixture.Sink.Calls).Position);
}
[Fact]
public void MissingDirectScriptReportsOwnerAndDid()
{
@ -627,7 +657,7 @@ public sealed class EntityEffectControllerTests
}
[Fact]
public void RefreshLiveOwnerAnchorsUsesCurrentWorldPositionAtDispatch()
public void RefreshLiveOwnerPosesUsesCurrentWorldPositionAtDispatch()
{
var fixture = new Fixture();
fixture.AddScript(DirectDid, 1u, startTime: 1.0);
@ -635,7 +665,7 @@ public sealed class EntityEffectControllerTests
fixture.Controller.HandleDirect(new PlayPhysicsScript(Guid, DirectDid));
entity.SetPosition(new Vector3(10, 20, 30));
fixture.Controller.RefreshLiveOwnerAnchors();
fixture.Controller.RefreshLiveOwnerPoses();
fixture.Runner.Tick(1.0);
Assert.Equal(new Vector3(10, 20, 30), Assert.Single(fixture.Sink.Calls).Position);