refactor(runtime): close simulation ownership
Move remote-motion construction, CreateObject vector initialization, final simulation-component retirement, and the combined J5 ownership ledger into Runtime. Delete App compatibility views and moved-state reconstruction while preserving the existing graphical projection and retail update order.
This commit is contained in:
parent
c30a3efeb0
commit
cdee7a4b49
57 changed files with 1013 additions and 410 deletions
|
|
@ -96,7 +96,7 @@ public sealed class LiveCombatAttackOperationsTests
|
|||
var combat = new CombatState();
|
||||
var targets = new FakeTargets();
|
||||
var settings = new FakeSettings();
|
||||
var player = new LocalPlayerControllerSlot();
|
||||
var player = new RuntimeLocalPlayerMovementState();
|
||||
var live = new FakeLiveSource { IsInWorld = inWorld };
|
||||
var feedback = new FakeFeedback();
|
||||
var outbound = new LocalPlayerOutboundController(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,4 @@
|
|||
global using AcDream.Runtime.Gameplay;
|
||||
global using AcDream.Runtime.Physics;
|
||||
global using ILiveEntityRemoteMotionRuntime =
|
||||
AcDream.Runtime.Physics.IRuntimeRemoteMotion;
|
||||
global using ILiveEntityPhysicsHostConsumer =
|
||||
AcDream.Runtime.Physics.IRuntimePhysicsHostConsumer;
|
||||
global using ILiveEntityCanonicalRuntimeConsumer =
|
||||
AcDream.Runtime.Physics.IRuntimeCanonicalPhysicsConsumer;
|
||||
global using ILiveEntityCanonicalCellConsumer =
|
||||
AcDream.Runtime.Physics.IRuntimeCanonicalCellConsumer;
|
||||
global using ILiveEntityRemotePlacementRuntime =
|
||||
AcDream.Runtime.Physics.IRuntimeRemotePlacement;
|
||||
global using LocalPlayerControllerSlot =
|
||||
AcDream.Runtime.Gameplay.RuntimeLocalPlayerMovementState;
|
||||
global using ILocalPlayerControllerSource =
|
||||
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerControllerSource;
|
||||
global using ILocalPlayerMotionSource =
|
||||
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerMotionSource;
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ public sealed class MouseLookControllerTests
|
|||
{
|
||||
PlayerMovementController player = CreatePlayer();
|
||||
var mode = new LocalPlayerModeState();
|
||||
var slot = new LocalPlayerControllerSlot { Controller = player };
|
||||
var slot = new RuntimeLocalPlayerMovementState { Controller = player };
|
||||
var camera = new CameraController(new OrbitCamera(), new FlyCamera());
|
||||
var chase = new ChaseCameraInputState();
|
||||
var mouse = new FakeMouse();
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ using AcDream.Core.Physics;
|
|||
|
||||
namespace AcDream.App.Tests.Input;
|
||||
|
||||
public sealed class LocalPlayerControllerSlotTests
|
||||
public sealed class RuntimeLocalPlayerMovementStateTests
|
||||
{
|
||||
[Fact]
|
||||
public void MotionPreparation_RoutesCompletionWithoutPublishingController()
|
||||
{
|
||||
var slot = new LocalPlayerControllerSlot();
|
||||
var slot = new RuntimeLocalPlayerMovementState();
|
||||
ILocalPlayerMotionSource source = slot;
|
||||
var controller = new PlayerMovementController(new PhysicsEngine());
|
||||
controller.Motion.AddToQueue(0, MotionCommand.Ready, 0);
|
||||
|
|
@ -29,7 +29,7 @@ public sealed class LocalPlayerControllerSlotTests
|
|||
[Fact]
|
||||
public void MotionPreparation_HandsOffToCommittedControllerOnDispose()
|
||||
{
|
||||
var slot = new LocalPlayerControllerSlot();
|
||||
var slot = new RuntimeLocalPlayerMovementState();
|
||||
ILocalPlayerMotionSource source = slot;
|
||||
var controller = new PlayerMovementController(new PhysicsEngine());
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ public sealed class LocalPlayerControllerSlotTests
|
|||
[Fact]
|
||||
public void MotionPreparation_DrainsPriorAnimationBeforePublishingCandidate()
|
||||
{
|
||||
var slot = new LocalPlayerControllerSlot();
|
||||
var slot = new RuntimeLocalPlayerMovementState();
|
||||
ILocalPlayerMotionSource source = slot;
|
||||
var prior = new PlayerMovementController(new PhysicsEngine());
|
||||
var candidate = new PlayerMovementController(new PhysicsEngine());
|
||||
|
|
@ -71,7 +71,7 @@ public sealed class LocalPlayerControllerSlotTests
|
|||
[Fact]
|
||||
public void MotionPreparation_RejectsConcurrentCandidate()
|
||||
{
|
||||
var slot = new LocalPlayerControllerSlot();
|
||||
var slot = new RuntimeLocalPlayerMovementState();
|
||||
var first = new PlayerMovementController(new PhysicsEngine());
|
||||
var second = new PlayerMovementController(new PhysicsEngine());
|
||||
|
||||
|
|
@ -105,11 +105,10 @@ public sealed class ProjectileControllerTests
|
|||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||
WorldEntity entity = record.WorldEntity!;
|
||||
Assert.Null(record.AnimationRuntime);
|
||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
||||
var remote =
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||
remote.Body.Position = entity.Position;
|
||||
remote.Body.Orientation = entity.Rotation;
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
||||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1));
|
||||
Assert.Same(remote.Body, record.ProjectileRuntime!.Body);
|
||||
|
||||
|
|
@ -686,7 +685,9 @@ public sealed class ProjectileControllerTests
|
|||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
||||
PhysicsBody body = record.PhysicsBody!;
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, new AcDream.Runtime.Physics.RemoteMotion(body));
|
||||
Assert.Same(
|
||||
body,
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid).Body);
|
||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||
record, record.FinalPhysicsState, 2.0, 1, 1));
|
||||
|
|
@ -710,7 +711,9 @@ public sealed class ProjectileControllerTests
|
|||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
||||
PhysicsBody body = record.PhysicsBody!;
|
||||
Vector3 position = body.Position;
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, new AcDream.Runtime.Physics.RemoteMotion(body));
|
||||
Assert.Same(
|
||||
body,
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid).Body);
|
||||
|
||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||
|
|
@ -949,9 +952,8 @@ public sealed class ProjectileControllerTests
|
|||
var fixture = new Fixture();
|
||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
||||
var remote =
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||
|
||||
PhysicsStateFlags ordinaryState =
|
||||
PhysicsStateFlags.Gravity | PhysicsStateFlags.IgnoreCollisions;
|
||||
|
|
@ -1001,9 +1003,8 @@ public sealed class ProjectileControllerTests
|
|||
var fixture = new Fixture();
|
||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
||||
var remote =
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||
|
||||
record.FinalPhysicsState = MissileState;
|
||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||
|
|
@ -1073,15 +1074,14 @@ public sealed class ProjectileControllerTests
|
|||
seedCellId: startCell,
|
||||
isStatic: false);
|
||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
||||
var remote =
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||
Assert.Equal(new Vector3(40f, 0f, 0f), remote.Body.Velocity);
|
||||
Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega);
|
||||
remote.Body.set_velocity(new Vector3(8f, 0f, 0f));
|
||||
remote.Body.Omega = new Vector3(0f, 0f, 3f);
|
||||
remote.Body.Position = entity.Position;
|
||||
remote.Body.State = record.FinalPhysicsState;
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
||||
PhysicsBody body = remote.Body;
|
||||
|
||||
record.FinalPhysicsState = MissileState;
|
||||
|
|
@ -1139,11 +1139,10 @@ public sealed class ProjectileControllerTests
|
|||
velocity: new Vector3(float.NaN, 0f, 0f));
|
||||
WorldEntity entity = record.WorldEntity!;
|
||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
||||
var remote =
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||
remote.Body.Position = entity.Position;
|
||||
remote.Body.Orientation = entity.Rotation;
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
||||
|
||||
Assert.True(float.IsNaN(record.Snapshot.Physics!.Value.Velocity!.Value.X));
|
||||
Assert.Equal(Vector3.Zero, remote.Body.Velocity);
|
||||
|
|
@ -1165,12 +1164,11 @@ public sealed class ProjectileControllerTests
|
|||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||
WorldEntity entity = record.WorldEntity!;
|
||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
||||
var remote =
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||
remote.Body.Position = entity.Position;
|
||||
remote.Body.Orientation = entity.Rotation;
|
||||
remote.Body.Velocity = new Vector3(float.PositiveInfinity, 0f, 0f);
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
||||
|
||||
record.FinalPhysicsState = MissileState;
|
||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||
|
|
@ -1189,8 +1187,9 @@ public sealed class ProjectileControllerTests
|
|||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
||||
PhysicsBody body = record.PhysicsBody!;
|
||||
var remote = new AcDream.Runtime.Physics.RemoteMotion(body);
|
||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
||||
var remote =
|
||||
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||
Assert.Same(body, remote.Body);
|
||||
|
||||
fixture.Live.RebucketLiveEntity(Guid, CellB);
|
||||
Assert.Equal(CellB, remote.CellId);
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ public sealed class RemoteTeleportControllerTests
|
|||
public void Unregister(WorldEntity entity) { }
|
||||
}
|
||||
|
||||
private sealed class BodyOnlyRemote(PhysicsBody body) : ILiveEntityRemoteMotionRuntime
|
||||
private sealed class BodyOnlyRemote(PhysicsBody body) : IRuntimeRemoteMotion
|
||||
{
|
||||
public PhysicsBody Body { get; } = body;
|
||||
}
|
||||
|
||||
private sealed class MutablePlacementRemote(PhysicsBody body) : ILiveEntityRemotePlacementRuntime
|
||||
private sealed class MutablePlacementRemote(PhysicsBody body) : IRuntimeRemotePlacement
|
||||
{
|
||||
private uint _cellId;
|
||||
private Func<uint>? _readCell;
|
||||
|
|
@ -56,7 +56,7 @@ public sealed class RemoteTeleportControllerTests
|
|||
|
||||
private sealed class AlternatingBodyRemote(
|
||||
PhysicsBody first,
|
||||
PhysicsBody later) : ILiveEntityRemotePlacementRuntime
|
||||
PhysicsBody later) : IRuntimeRemotePlacement
|
||||
{
|
||||
private int _reads;
|
||||
public PhysicsBody Body => _reads++ == 0 ? first : later;
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ public sealed class WorldRenderFrameBuilderTests
|
|||
cells,
|
||||
mode,
|
||||
chase,
|
||||
new LocalPlayerControllerSlot(),
|
||||
new RuntimeLocalPlayerMovementState(),
|
||||
origin);
|
||||
var camera = new FlyCamera { Position = new Vector3(5f, 6f, 7f) };
|
||||
WorldCameraFrame cameraFrame = CameraFrame(camera);
|
||||
|
|
@ -227,7 +227,7 @@ public sealed class WorldRenderFrameBuilderTests
|
|||
new CellVisibility(),
|
||||
new LocalPlayerModeState(),
|
||||
new ChaseCameraInputState(),
|
||||
new LocalPlayerControllerSlot(),
|
||||
new RuntimeLocalPlayerMovementState(),
|
||||
new LiveWorldOriginState());
|
||||
WorldCameraFrame camera = CameraFrame(new FlyCamera());
|
||||
|
||||
|
|
|
|||
|
|
@ -827,7 +827,6 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
Host,
|
||||
Commands,
|
||||
Identity,
|
||||
Entities,
|
||||
EntityObjects,
|
||||
InventoryState,
|
||||
Character,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,13 @@ public sealed class RuntimePhysicsOwnershipTests
|
|||
runtimeRoot,
|
||||
"Physics",
|
||||
"RuntimePhysicsState.cs"));
|
||||
string app = string.Join(
|
||||
"\n",
|
||||
Directory.EnumerateFiles(
|
||||
appRoot,
|
||||
"*.cs",
|
||||
SearchOption.AllDirectories)
|
||||
.Select(File.ReadAllText));
|
||||
|
||||
Assert.DoesNotContain(
|
||||
"ResolveWithTransition(",
|
||||
|
|
@ -131,6 +138,67 @@ public sealed class RuntimePhysicsOwnershipTests
|
|||
"public bool TryGetPhysicsHost(",
|
||||
runtimePhysics,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"GetOrCreateRemoteMotion(",
|
||||
runtimePhysics,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_physics.TryCommitAuthoritativeVector(",
|
||||
liveRuntime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"TryActivateOrdinaryObject(",
|
||||
liveRuntime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_entityObjects.CompleteProjectionRetirement(",
|
||||
liveRuntime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"record.RemoteMotionRuntime =",
|
||||
liveRuntime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"record.PhysicsBody =",
|
||||
liveRuntime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"new RemoteMotion(",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"LiveEntityRemoteMotionRuntimeView",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"ILiveEntityRemoteMotionRuntime",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"ILiveEntityRemotePlacementRuntime",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"LocalPlayerControllerSlot",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"ILocalPlayerControllerSource",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.False(File.Exists(Path.Combine(
|
||||
appRoot,
|
||||
"Physics",
|
||||
"RemotePhysicsBodyInitializer.cs")));
|
||||
Assert.False(typeof(AcDream.App.World.LiveEntityRecord)
|
||||
.GetProperty(nameof(AcDream.App.World.LiveEntityRecord.PhysicsBody))!
|
||||
.CanWrite);
|
||||
Assert.False(typeof(AcDream.App.World.LiveEntityRecord)
|
||||
.GetProperty(nameof(AcDream.App.World.LiveEntityRecord.RemoteMotionRuntime))!
|
||||
.CanWrite);
|
||||
Assert.False(typeof(AcDream.App.World.LiveEntityRecord)
|
||||
.GetProperty(nameof(AcDream.App.World.LiveEntityRecord.PhysicsHost))!
|
||||
.CanWrite);
|
||||
}
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
})!;
|
||||
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
|
||||
var controllerSlot = new LocalPlayerControllerSlot
|
||||
var controllerSlot = new RuntimeLocalPlayerMovementState
|
||||
{
|
||||
Controller = new PlayerMovementController(new PhysicsEngine()),
|
||||
};
|
||||
|
|
@ -494,7 +494,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
})!;
|
||||
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
|
||||
var controllerSlot = new LocalPlayerControllerSlot
|
||||
var controllerSlot = new RuntimeLocalPlayerMovementState
|
||||
{
|
||||
Controller = new PlayerMovementController(new PhysicsEngine()),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -422,7 +422,7 @@ public sealed class StreamingFrameControllerTests
|
|||
}
|
||||
|
||||
internal LocalPlayerModeState Mode { get; } = new();
|
||||
internal LocalPlayerControllerSlot Player { get; } = new();
|
||||
internal RuntimeLocalPlayerMovementState Player { get; } = new();
|
||||
internal SessionSource Session { get; } = new();
|
||||
internal LiveWorldOriginState Origin { get; } = new();
|
||||
internal PlayerLandblockSource PlayerLandblock { get; } = new();
|
||||
|
|
|
|||
|
|
@ -144,12 +144,7 @@ public sealed class GameWindowLiveEntityCompositionTests
|
|||
typeof(LiveEntityAnimationRuntimeView<LiveEntityAnimationState>)
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.Name == "_runtime");
|
||||
FieldInfo remoteRuntime = Assert.Single(
|
||||
typeof(LiveEntityRemoteMotionRuntimeView<RemoteMotion>)
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.Name == "_runtime");
|
||||
Assert.Equal(typeof(ILiveEntityRuntimeSource), animationRuntime.FieldType);
|
||||
Assert.Equal(typeof(ILiveEntityRuntimeSource), remoteRuntime.FieldType);
|
||||
|
||||
FieldInfo[] projectileFields = typeof(ProjectileController).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
|
@ -192,7 +187,7 @@ public sealed class GameWindowLiveEntityCompositionTests
|
|||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"new LiveEntityRemoteMotionRuntimeView<RemoteMotion>(() =>",
|
||||
"LiveEntityRemoteMotionRuntimeView",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("new DatProjectileSetupResolver", livePresentation);
|
||||
|
|
|
|||
|
|
@ -371,7 +371,11 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
LiveEntityRecord record = fixture.Record;
|
||||
WorldEntity entity = record.WorldEntity!;
|
||||
var body = new PhysicsBody();
|
||||
record.PhysicsBody = body;
|
||||
Assert.Same(
|
||||
body,
|
||||
fixture.Runtime.GetOrCreatePhysicsBody(
|
||||
record.ServerGuid,
|
||||
_ => body));
|
||||
fixture.Relationships.OnUnparentAction = _ =>
|
||||
fixture.Runtime.WithdrawLiveEntityProjection(record)
|
||||
? ChildUnparentDisposition.Completed
|
||||
|
|
|
|||
|
|
@ -874,7 +874,7 @@ public sealed class LiveEntityLifecycleStressTests
|
|||
_animations.TryGetValue(id, out Animation? animation) ? animation : null;
|
||||
}
|
||||
|
||||
private sealed class TestRemoteMotion : ILiveEntityRemotePlacementRuntime
|
||||
private sealed class TestRemoteMotion : IRuntimeRemotePlacement
|
||||
{
|
||||
private uint _cellId;
|
||||
private Func<uint>? _readCell;
|
||||
|
|
|
|||
|
|
@ -710,8 +710,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
interruptCurrentMovement);
|
||||
|
||||
private sealed class HostConsumerMotion :
|
||||
ILiveEntityRemoteMotionRuntime,
|
||||
ILiveEntityPhysicsHostConsumer
|
||||
IRuntimeRemoteMotion,
|
||||
IRuntimePhysicsHostConsumer
|
||||
{
|
||||
private Func<IPhysicsObjHost?>? _read;
|
||||
public PhysicsBody Body { get; } = new();
|
||||
|
|
@ -725,8 +725,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
}
|
||||
|
||||
private sealed class ThrowingCellConsumerMotion :
|
||||
ILiveEntityRemoteMotionRuntime,
|
||||
ILiveEntityCanonicalRuntimeConsumer
|
||||
IRuntimeRemoteMotion,
|
||||
IRuntimeCanonicalPhysicsConsumer
|
||||
{
|
||||
private bool _failOnce = true;
|
||||
public PhysicsBody Body { get; } = new();
|
||||
|
|
@ -748,14 +748,14 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
|
||||
private sealed class AlternatingBodyMotion(
|
||||
PhysicsBody first,
|
||||
PhysicsBody second) : ILiveEntityRemoteMotionRuntime
|
||||
PhysicsBody second) : IRuntimeRemoteMotion
|
||||
{
|
||||
private int _reads;
|
||||
public PhysicsBody Body => _reads++ == 0 ? first : second;
|
||||
}
|
||||
|
||||
private sealed class SequenceBodyMotion(params PhysicsBody[] bodies) :
|
||||
ILiveEntityRemoteMotionRuntime
|
||||
IRuntimeRemoteMotion
|
||||
{
|
||||
public int ReadCount { get; private set; }
|
||||
|
||||
|
|
@ -771,8 +771,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
}
|
||||
|
||||
private sealed class CallbackCanonicalMotion(Action callback) :
|
||||
ILiveEntityRemoteMotionRuntime,
|
||||
ILiveEntityCanonicalRuntimeConsumer
|
||||
IRuntimeRemoteMotion,
|
||||
IRuntimeCanonicalPhysicsConsumer
|
||||
{
|
||||
public PhysicsBody Body { get; } = new();
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
private sealed class EffectProfile : ILiveEntityEffectProfile { }
|
||||
|
||||
private sealed class RemoteMotionRuntime : ILiveEntityRemoteMotionRuntime
|
||||
private sealed class RemoteMotionRuntime : IRuntimeRemoteMotion
|
||||
{
|
||||
public PhysicsBody Body { get; } = new();
|
||||
}
|
||||
|
|
@ -1156,43 +1156,6 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.False(remote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovementActivation_ReactivatesExactIncarnation_ButStaticIsNoOp()
|
||||
{
|
||||
const uint ordinaryGuid = 0x70000050u;
|
||||
const uint staticGuid = 0x70000051u;
|
||||
var spatial = new GpuWorldState();
|
||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
|
||||
|
||||
LiveEntityRecord ordinary = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(ordinaryGuid, 1, 1, 0x01010001u));
|
||||
var ordinaryRemote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(ordinaryGuid, ordinaryRemote);
|
||||
ordinary.ObjectClock.Deactivate();
|
||||
ordinaryRemote.Body.TransientState &= ~TransientStateFlags.Active;
|
||||
|
||||
Assert.True(runtime.TryActivateOrdinaryObject(ordinaryGuid, ordinaryRemote));
|
||||
Assert.True(ordinary.ObjectClock.IsActive);
|
||||
Assert.True(ordinaryRemote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
||||
|
||||
LiveEntityRecord staticRecord = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(
|
||||
staticGuid,
|
||||
1,
|
||||
1,
|
||||
0x01010001u,
|
||||
PhysicsStateFlags.Static));
|
||||
var staticRemote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(staticGuid, staticRemote);
|
||||
|
||||
Assert.False(runtime.TryActivateOrdinaryObject(staticGuid, staticRemote));
|
||||
Assert.False(staticRecord.ObjectClock.IsActive);
|
||||
Assert.False(staticRemote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthoritativeVector_WakesOrdinaryClockButPreservesStaticActivity()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -244,6 +244,10 @@ public sealed class RuntimeEntityOwnershipTests
|
|||
Assert.Contains(
|
||||
viewFields,
|
||||
field => field.FieldType == typeof(IRuntimeInventoryView));
|
||||
Assert.DoesNotContain(
|
||||
viewFields,
|
||||
field => field.FieldType == typeof(LiveEntityRuntime)
|
||||
|| field.FieldType == typeof(ClientObjectTable));
|
||||
Assert.DoesNotContain(
|
||||
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
|
||||
BindingFlags.NonPublic),
|
||||
|
|
@ -271,6 +275,21 @@ public sealed class RuntimeEntityOwnershipTests
|
|||
Assert.DoesNotContain("_entityObjects.PublishEntity", liveSource);
|
||||
Assert.DoesNotContain("_directory.TryApply", liveSource);
|
||||
Assert.DoesNotContain("_directory.RemoveActive", liveSource);
|
||||
|
||||
string viewSource = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Runtime",
|
||||
"CurrentGameRuntimeViewAdapter.cs"));
|
||||
Assert.Contains(
|
||||
"_entityView.MaterializedCount",
|
||||
viewSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"_entities.MaterializedCount",
|
||||
viewSource,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static bool IsPresentationType(Type type)
|
||||
|
|
|
|||
|
|
@ -1123,7 +1123,10 @@ public sealed class RuntimeEntityObjectLifetimeTests
|
|||
lifetime.Dispose();
|
||||
|
||||
Assert.Equal(
|
||||
default(RuntimeEntityObjectOwnershipSnapshot),
|
||||
default(RuntimeEntityObjectOwnershipSnapshot) with
|
||||
{
|
||||
IsDisposed = true,
|
||||
},
|
||||
lifetime.CaptureOwnership());
|
||||
}
|
||||
|
||||
|
|
@ -1158,7 +1161,10 @@ public sealed class RuntimeEntityObjectLifetimeTests
|
|||
lifetime.Dispose();
|
||||
|
||||
Assert.Equal(
|
||||
default(RuntimeEntityObjectOwnershipSnapshot),
|
||||
default(RuntimeEntityObjectOwnershipSnapshot) with
|
||||
{
|
||||
IsDisposed = true,
|
||||
},
|
||||
lifetime.CaptureOwnership());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,60 @@ using AcDream.Core.Items;
|
|||
using AcDream.Core.Social;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime;
|
||||
|
||||
namespace AcDream.Runtime.Tests.Gameplay;
|
||||
|
||||
public sealed class RuntimeGameplayOwnershipTests
|
||||
{
|
||||
[Fact]
|
||||
public void J5LedgerConvergesEntityPhysicsAndGameplayAsOneLifetime()
|
||||
{
|
||||
var entities = new RuntimeEntityObjectLifetime();
|
||||
var inventory = new RuntimeInventoryState(entities);
|
||||
var character = new RuntimeCharacterState();
|
||||
var communication = new RuntimeCommunicationState();
|
||||
var actions = RuntimeActionTestFactory.Create(inventory.Transactions);
|
||||
var movement = new RuntimeLocalPlayerMovementState();
|
||||
|
||||
movement.Execute(RuntimeMovementCommand.ToggleRunLock);
|
||||
inventory.Transactions.IncrementBusyCount();
|
||||
actions.Selection.Select(
|
||||
0x70000001u,
|
||||
AcDream.Core.Selection.SelectionChangeSource.System);
|
||||
character.Spellbook.OnSpellLearned(42u);
|
||||
communication.Chat.OnSystemMessage("runtime-only", 0u);
|
||||
|
||||
RuntimeSimulationOwnershipSnapshot populated =
|
||||
RuntimeSimulationOwnership.Capture(
|
||||
entities,
|
||||
inventory,
|
||||
character,
|
||||
communication,
|
||||
actions,
|
||||
movement);
|
||||
Assert.False(populated.IsConverged);
|
||||
|
||||
movement.Dispose();
|
||||
actions.Dispose();
|
||||
communication.Dispose();
|
||||
character.Dispose();
|
||||
inventory.Dispose();
|
||||
entities.Dispose();
|
||||
|
||||
RuntimeSimulationOwnershipSnapshot retired =
|
||||
RuntimeSimulationOwnership.Capture(
|
||||
entities,
|
||||
inventory,
|
||||
character,
|
||||
communication,
|
||||
actions,
|
||||
movement);
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.True(retired.EntityObjects.IsDisposed);
|
||||
Assert.True(retired.Physics.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CombinedLedgerConvergesEveryGameplayOwnerAndSubscription()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -340,6 +340,49 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.Equal(0, first.Physics.Engine.LandblockCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TerminalDisposalClearsLandblocksShadowsAndWorksets()
|
||||
{
|
||||
var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record =
|
||||
lifetime.Entities.AddActive(Spawn(0x70000041u, 1));
|
||||
lifetime.Physics.AcknowledgeSpatialProjection(
|
||||
record,
|
||||
spatial: true);
|
||||
|
||||
RuntimeCollisionAdmission admission =
|
||||
lifetime.Physics.BeginCollisionAdmission(0x0101FFFFu);
|
||||
lifetime.Physics.AdmitCollisionAssets(
|
||||
admission,
|
||||
CollisionAssets(0x0101FFFFu));
|
||||
_ = lifetime.Physics.CompleteCollisionAdmission(admission);
|
||||
lifetime.Physics.Engine.ShadowObjects.Register(
|
||||
entityId: record.LocalEntityId!.Value,
|
||||
gfxObjId: 0x01000001u,
|
||||
worldPos: new Vector3(10f, 20f, 5f),
|
||||
rotation: Quaternion.Identity,
|
||||
radius: 0.5f,
|
||||
worldOffsetX: 0f,
|
||||
worldOffsetY: 0f,
|
||||
landblockId: 0x0101FFFFu,
|
||||
seedCellId: 0x01010001u,
|
||||
isStatic: false);
|
||||
|
||||
Assert.False(lifetime.Physics.CaptureOwnership().IsConverged);
|
||||
Assert.True(
|
||||
lifetime.Physics.Engine.ShadowObjects
|
||||
.RetainedRegistrationCount > 0);
|
||||
|
||||
lifetime.Dispose();
|
||||
|
||||
RuntimePhysicsOwnershipSnapshot retired =
|
||||
lifetime.Physics.CaptureOwnership();
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.Equal(0, retired.LandblockCount);
|
||||
Assert.Equal(0, retired.RetainedShadowRegistrationCount);
|
||||
Assert.Equal(0, retired.SpatialRootCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoteBindingOwnsCanonicalBodyCellAndTypedCellPublication()
|
||||
{
|
||||
|
|
@ -368,6 +411,66 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.Equal(0, lifetime.Physics.SpatialRemoteCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuiltInRemoteConstructionInstallsCreateVectorsOnlyOnce()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = lifetime.Entities.AddActive(
|
||||
Spawn(
|
||||
0x70000042u,
|
||||
1,
|
||||
velocity: new Vector3(40f, 0f, 0f),
|
||||
angularVelocity: new Vector3(0f, 0f, 2f)));
|
||||
|
||||
RemoteMotion remote =
|
||||
lifetime.Physics.GetOrCreateRemoteMotion(record);
|
||||
|
||||
Assert.Equal(new Vector3(40f, 0f, 0f), remote.Body.Velocity);
|
||||
Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega);
|
||||
remote.Body.set_velocity(new Vector3(8f, 0f, 0f));
|
||||
remote.Body.Omega = new Vector3(0f, 0f, 3f);
|
||||
|
||||
Assert.Same(
|
||||
remote,
|
||||
lifetime.Physics.GetOrCreateRemoteMotion(record));
|
||||
Assert.Equal(new Vector3(8f, 0f, 0f), remote.Body.Velocity);
|
||||
Assert.Equal(new Vector3(0f, 0f, 3f), remote.Body.Omega);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuiltInMovementActivationUsesExactRuntimeIncarnation()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord ordinary =
|
||||
lifetime.Entities.AddActive(Spawn(0x70000043u, 1));
|
||||
RemoteMotion ordinaryRemote =
|
||||
lifetime.Physics.GetOrCreateRemoteMotion(ordinary);
|
||||
ordinary.ObjectClock.Deactivate();
|
||||
ordinaryRemote.Body.TransientState &=
|
||||
~TransientStateFlags.Active;
|
||||
|
||||
ordinaryRemote.Movement.ActivatePhysicsObject!();
|
||||
|
||||
Assert.True(ordinary.ObjectClock.IsActive);
|
||||
Assert.True(ordinaryRemote.Body.IsActive);
|
||||
|
||||
RuntimeEntityRecord staticRecord =
|
||||
lifetime.Entities.AddActive(Spawn(0x70000044u, 1));
|
||||
lifetime.Entities.SetFinalPhysicsState(
|
||||
staticRecord,
|
||||
PhysicsStateFlags.Static);
|
||||
RemoteMotion staticRemote =
|
||||
lifetime.Physics.GetOrCreateRemoteMotion(staticRecord);
|
||||
staticRecord.ObjectClock.Deactivate();
|
||||
staticRemote.Body.TransientState &=
|
||||
~TransientStateFlags.Active;
|
||||
|
||||
staticRemote.Movement.ActivatePhysicsObject!();
|
||||
|
||||
Assert.False(staticRecord.ObjectClock.IsActive);
|
||||
Assert.False(staticRemote.Body.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemotePhysicsTickUsesCanonicalRuntimeAndPublishesPoseSnapshot()
|
||||
{
|
||||
|
|
@ -523,7 +626,11 @@ public sealed class RuntimePhysicsStateTests
|
|||
0u);
|
||||
}
|
||||
|
||||
private static WorldSession.EntitySpawn Spawn(uint guid, ushort instance)
|
||||
private static WorldSession.EntitySpawn Spawn(
|
||||
uint guid,
|
||||
ushort instance,
|
||||
Vector3? velocity = null,
|
||||
Vector3? angularVelocity = null)
|
||||
{
|
||||
var position = new CreateObject.ServerPosition(
|
||||
0x0101FFFFu,
|
||||
|
|
@ -559,9 +666,9 @@ public sealed class RuntimePhysicsStateTests
|
|||
Friction: null,
|
||||
Elasticity: null,
|
||||
Translucency: null,
|
||||
Velocity: null,
|
||||
Velocity: velocity,
|
||||
Acceleration: null,
|
||||
AngularVelocity: null,
|
||||
AngularVelocity: angularVelocity,
|
||||
DefaultScriptType: null,
|
||||
DefaultScriptIntensity: null,
|
||||
Timestamps: timestamps);
|
||||
|
|
|
|||
384
tests/AcDream.Runtime.Tests/RuntimeSimulationFixtureHostTests.cs
Normal file
384
tests/AcDream.Runtime.Tests/RuntimeSimulationFixtureHostTests.cs
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Spells;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Physics;
|
||||
|
||||
namespace AcDream.Runtime.Tests;
|
||||
|
||||
public sealed class RuntimeSimulationFixtureHostTests
|
||||
{
|
||||
[Fact]
|
||||
public void RuntimeOnlyHostDrivesCompleteJ5SimulationWithoutPresentation()
|
||||
{
|
||||
var host = new RuntimeOnlySimulationFixtureHost();
|
||||
|
||||
host.Move();
|
||||
host.Use(0x70000011u);
|
||||
host.Attack();
|
||||
host.Cast();
|
||||
host.AdvanceProjectile(failProjection: false);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
"movement:run",
|
||||
"use:70000011",
|
||||
"attack:prepare",
|
||||
"attack:Medium:0.5",
|
||||
"cast:stop",
|
||||
"cast:1",
|
||||
"cast:busy",
|
||||
"projectile:ack",
|
||||
],
|
||||
host.Trace);
|
||||
|
||||
string[] loaded = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Select(static assembly =>
|
||||
assembly.GetName().Name ?? string.Empty)
|
||||
.ToArray();
|
||||
Assert.DoesNotContain(loaded, static name =>
|
||||
name.StartsWith(
|
||||
"AcDream.App",
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
|| name.StartsWith(
|
||||
"AcDream.UI.",
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
|| name.StartsWith(
|
||||
"Silk.NET",
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
|| name.StartsWith(
|
||||
"OpenAL",
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
|| name.StartsWith(
|
||||
"ImGui",
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
host.Dispose();
|
||||
Assert.True(host.CaptureOwnership().IsConverged);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectionAcknowledgementFailureStillAllowsTerminalConvergence()
|
||||
{
|
||||
var host = new RuntimeOnlySimulationFixtureHost();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
host.AdvanceProjectile(failProjection: true));
|
||||
|
||||
host.Dispose();
|
||||
|
||||
RuntimeSimulationOwnershipSnapshot ownership =
|
||||
host.CaptureOwnership();
|
||||
Assert.True(ownership.IsConverged);
|
||||
Assert.Equal(0, ownership.Physics.SpatialProjectileCount);
|
||||
Assert.Equal(0, ownership.EntityObjects.ActiveEntityCount);
|
||||
}
|
||||
|
||||
private sealed class RuntimeOnlySimulationFixtureHost :
|
||||
IRuntimeInteractionTransport,
|
||||
IRuntimeCombatAttackOperations,
|
||||
IRuntimeCombatTargetOperations,
|
||||
IRuntimeCombatModeOperations,
|
||||
IRuntimeSpellCastOperations,
|
||||
IDisposable
|
||||
{
|
||||
private const uint ProjectileGuid = 0x70000021u;
|
||||
private double _now = 10d;
|
||||
private uint _sequence;
|
||||
private bool _disposed;
|
||||
|
||||
public RuntimeOnlySimulationFixtureHost()
|
||||
{
|
||||
EntityObjects = new RuntimeEntityObjectLifetime();
|
||||
Inventory = new RuntimeInventoryState(EntityObjects);
|
||||
Character = new RuntimeCharacterState();
|
||||
Character.Spellbook.InstallMetadata(SpellMetadata());
|
||||
Character.Spellbook.OnSpellLearned(1u);
|
||||
Communication = new RuntimeCommunicationState();
|
||||
Actions = new RuntimeActionState(
|
||||
Inventory.Transactions,
|
||||
Character.Spellbook,
|
||||
this,
|
||||
this,
|
||||
this,
|
||||
this,
|
||||
now: () => _now);
|
||||
Movement = new RuntimeLocalPlayerMovementState();
|
||||
}
|
||||
|
||||
public RuntimeEntityObjectLifetime EntityObjects { get; }
|
||||
public RuntimeInventoryState Inventory { get; }
|
||||
public RuntimeCharacterState Character { get; }
|
||||
public RuntimeCommunicationState Communication { get; }
|
||||
public RuntimeActionState Actions { get; }
|
||||
public RuntimeLocalPlayerMovementState Movement { get; }
|
||||
public List<string> Trace { get; } = [];
|
||||
|
||||
public void Move()
|
||||
{
|
||||
Assert.True(Movement.Execute(
|
||||
RuntimeMovementCommand.ToggleRunLock));
|
||||
Assert.True(Movement.AutoRunActive);
|
||||
Trace.Add("movement:run");
|
||||
}
|
||||
|
||||
public void Use(uint serverGuid)
|
||||
{
|
||||
ItemUseRequestReservation reservation =
|
||||
Actions.Transactions.BeginUseRequestReservation();
|
||||
RuntimeInteractionDispatchResult result =
|
||||
Actions.Transactions.TryDispatchUse(
|
||||
serverGuid,
|
||||
ownedByPlayer: false,
|
||||
useable: true,
|
||||
reservation,
|
||||
this,
|
||||
out _);
|
||||
Assert.Equal(
|
||||
RuntimeInteractionDispatchResult.Dispatched,
|
||||
result);
|
||||
Actions.Transactions.CompleteUse(0u);
|
||||
}
|
||||
|
||||
public void Attack()
|
||||
{
|
||||
Actions.Combat.SetCombatMode(CombatMode.Melee);
|
||||
Actions.CombatAttack.SetDesiredPower(0.5f);
|
||||
Actions.CombatAttack.PressAttack(AttackHeight.Medium);
|
||||
_now += 0.5d;
|
||||
Actions.CombatAttack.ReleaseAttack();
|
||||
}
|
||||
|
||||
public void Cast() =>
|
||||
Assert.Equal(
|
||||
CastRequestResult.Sent,
|
||||
Actions.SpellCast.Cast(1u));
|
||||
|
||||
public void AdvanceProjectile(bool failProjection)
|
||||
{
|
||||
RuntimeEntityRecord record =
|
||||
EntityObjects.RegisterEntity(
|
||||
Spawn(ProjectileGuid, 1)).Canonical!;
|
||||
PhysicsBody body =
|
||||
EntityObjects.Physics.GetOrCreatePhysicsBody(
|
||||
record,
|
||||
static canonical =>
|
||||
{
|
||||
var created = new PhysicsBody
|
||||
{
|
||||
Position = new Vector3(10f, 20f, 5f),
|
||||
Orientation = Quaternion.Identity,
|
||||
InWorld = true,
|
||||
TransientState =
|
||||
TransientStateFlags.Active,
|
||||
};
|
||||
created.SnapToCell(
|
||||
canonical.FullCellId,
|
||||
created.Position,
|
||||
created.Position);
|
||||
return created;
|
||||
});
|
||||
_ = EntityObjects.Physics.BindProjectile(
|
||||
record,
|
||||
body,
|
||||
new ProjectileCollisionSphere(
|
||||
Vector3.Zero,
|
||||
0.25f));
|
||||
EntityObjects.Physics.AcknowledgeSpatialProjection(
|
||||
record,
|
||||
spatial: true);
|
||||
|
||||
var updater =
|
||||
new RuntimeProjectilePhysicsUpdater(
|
||||
EntityObjects.Physics);
|
||||
Assert.True(updater.TryBegin(
|
||||
record,
|
||||
quantum: 0.05f,
|
||||
record.ObjectClockEpoch,
|
||||
externalOwnerValid: null,
|
||||
out RuntimeProjectilePhysicsCommit commit));
|
||||
_ = updater.Complete(
|
||||
commit,
|
||||
liveCenterX: 1,
|
||||
liveCenterY: 1,
|
||||
_ =>
|
||||
{
|
||||
if (failProjection)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"projection acknowledgement");
|
||||
}
|
||||
Trace.Add("projectile:ack");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public RuntimeSimulationOwnershipSnapshot CaptureOwnership() =>
|
||||
RuntimeSimulationOwnership.Capture(
|
||||
EntityObjects,
|
||||
Inventory,
|
||||
Character,
|
||||
Communication,
|
||||
Actions,
|
||||
Movement);
|
||||
|
||||
public bool IsInWorld => true;
|
||||
public uint LocalPlayerId => 0x50000001u;
|
||||
public bool CanSend => true;
|
||||
public bool IsDualWield => false;
|
||||
public bool PlayerReadyForAttack => true;
|
||||
public bool AutoRepeatAttack => false;
|
||||
public bool AutoTarget => false;
|
||||
|
||||
public bool TrySendUse(uint serverGuid, out uint sequence)
|
||||
{
|
||||
sequence = ++_sequence;
|
||||
Trace.Add($"use:{serverGuid:X8}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySendPickup(
|
||||
uint itemGuid,
|
||||
uint destinationContainerId,
|
||||
int placement,
|
||||
out uint sequence)
|
||||
{
|
||||
sequence = ++_sequence;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanStartAttack() => true;
|
||||
public void PrepareAttackRequest() =>
|
||||
Trace.Add("attack:prepare");
|
||||
|
||||
public bool SendAttack(AttackHeight height, float power)
|
||||
{
|
||||
Trace.Add($"attack:{height}:{power:0.0}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SendCancelAttack() { }
|
||||
public uint? SelectClosestTarget() => null;
|
||||
public IReadOnlyList<ClientObject> GetOrderedEquipment() => [];
|
||||
public void NotifyExplicitCombatModeRequest() { }
|
||||
public void SendChangeCombatMode(CombatMode mode) { }
|
||||
public bool HasRequiredComponents(uint spellId) => true;
|
||||
|
||||
public bool IsTargetCompatible(
|
||||
uint targetId,
|
||||
SpellMetadata spell,
|
||||
bool showMessage) => true;
|
||||
|
||||
public void StopCompletely() =>
|
||||
Trace.Add("cast:stop");
|
||||
|
||||
public void SendUntargeted(uint spellId) =>
|
||||
Trace.Add($"cast:{spellId}");
|
||||
|
||||
public void SendTargeted(uint targetId, uint spellId) =>
|
||||
Trace.Add($"cast:{targetId}:{spellId}");
|
||||
|
||||
public void DisplayMessage(string message) =>
|
||||
Trace.Add($"message:{message}");
|
||||
|
||||
public void IncrementBusy()
|
||||
{
|
||||
Inventory.Transactions.IncrementBusyCount();
|
||||
Trace.Add("cast:busy");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
Movement.Dispose();
|
||||
Actions.Dispose();
|
||||
Communication.Dispose();
|
||||
Character.Dispose();
|
||||
Inventory.Dispose();
|
||||
EntityObjects.Dispose();
|
||||
}
|
||||
|
||||
private static SpellTable SpellMetadata()
|
||||
{
|
||||
const string header =
|
||||
"Spell ID,Name,Flags [Hex],IsUntargetted,TargetMask [Hex]";
|
||||
const string row = "1,Runtime Fixture,0x0,true,0x0";
|
||||
return SpellTable.LoadFromReader(
|
||||
new System.IO.StringReader($"{header}\n{row}"));
|
||||
}
|
||||
|
||||
private static WorldSession.EntitySpawn Spawn(
|
||||
uint guid,
|
||||
ushort instance)
|
||||
{
|
||||
var position = new CreateObject.ServerPosition(
|
||||
0x01010001u,
|
||||
10f,
|
||||
20f,
|
||||
5f,
|
||||
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: instance);
|
||||
var physics = new PhysicsSpawnData(
|
||||
RawState: (uint)(
|
||||
PhysicsStateFlags.ReportCollisions
|
||||
| PhysicsStateFlags.Missile),
|
||||
Position: position,
|
||||
Movement: null,
|
||||
AnimationFrame: null,
|
||||
SetupTableId: 0x02000001u,
|
||||
MotionTableId: null,
|
||||
SoundTableId: null,
|
||||
PhysicsScriptTableId: null,
|
||||
Parent: null,
|
||||
Children: null,
|
||||
Scale: null,
|
||||
Friction: null,
|
||||
Elasticity: null,
|
||||
Translucency: null,
|
||||
Velocity: new Vector3(10f, 0f, 0f),
|
||||
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,
|
||||
"runtime projectile",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
PhysicsState: physics.RawState,
|
||||
InstanceSequence: instance,
|
||||
MovementSequence: 1,
|
||||
ServerControlSequence: 1,
|
||||
PositionSequence: 1,
|
||||
Physics: physics);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue