feat(headless): hydrate isolated collision worlds
This commit is contained in:
parent
9569dadb57
commit
b6547ff38c
20 changed files with 1960 additions and 101 deletions
|
|
@ -972,7 +972,7 @@ public sealed class PlayerMovementController
|
|||
/// Install a complete authoritative frame rotation (spawn, teleport, or
|
||||
/// server correction) without collapsing it through the yaw projection.
|
||||
/// </summary>
|
||||
internal void SetBodyOrientation(Quaternion orientation)
|
||||
public void SetBodyOrientation(Quaternion orientation)
|
||||
{
|
||||
_body.Orientation = AcDream.Core.Physics.Motion.FrameOps.SetRotate(
|
||||
_body.Position,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
namespace AcDream.Runtime.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Applies the exact server-owned run/jump snapshot to either host's one local
|
||||
/// movement controller. This lives beside the canonical skill owner so
|
||||
/// graphical and no-window construction cannot drift.
|
||||
/// </summary>
|
||||
public static class RuntimeMovementSkillProjection
|
||||
{
|
||||
public static bool ApplyTo(
|
||||
RuntimeMovementSkillState skills,
|
||||
PlayerMovementController? controller)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(skills);
|
||||
RuntimeMovementSkillSnapshot snapshot = skills.Snapshot;
|
||||
if (controller is null || !snapshot.IsComplete)
|
||||
return false;
|
||||
|
||||
controller.SetCharacterSkills(
|
||||
snapshot.RunSkill,
|
||||
snapshot.JumpSkill);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,23 @@ using AcDream.Runtime.World;
|
|||
|
||||
namespace AcDream.Runtime.Session;
|
||||
|
||||
public interface IRuntimeDirectWorldProjection
|
||||
{
|
||||
void ProjectSpawn(
|
||||
RuntimeEntityRecord record,
|
||||
bool isLocalPlayer);
|
||||
|
||||
void ProjectPosition(
|
||||
RuntimeEntityRecord record,
|
||||
bool isLocalPlayer);
|
||||
|
||||
void BeginTeleport();
|
||||
|
||||
RuntimeDestinationReadiness PrepareDestination(
|
||||
long revealGeneration,
|
||||
RuntimeTeleportDestination destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presentation-free inbound entity route for a direct Runtime host. It
|
||||
/// applies the same canonical identity, timestamp, object-table, and transit
|
||||
|
|
@ -18,15 +35,18 @@ public sealed class RuntimeLiveEntitySessionController
|
|||
private readonly GameRuntime _runtime;
|
||||
private readonly WorldSession _session;
|
||||
private readonly Action<string> _log;
|
||||
private readonly IRuntimeDirectWorldProjection? _worldProjection;
|
||||
|
||||
public RuntimeLiveEntitySessionController(
|
||||
GameRuntime runtime,
|
||||
WorldSession session,
|
||||
Action<string>? log = null)
|
||||
Action<string>? log = null,
|
||||
IRuntimeDirectWorldProjection? worldProjection = null)
|
||||
{
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_log = log ?? (_ => { });
|
||||
_worldProjection = worldProjection;
|
||||
}
|
||||
|
||||
public LiveEntitySessionSink CreateSink() => new(
|
||||
|
|
@ -54,13 +74,20 @@ public sealed class RuntimeLiveEntitySessionController
|
|||
return;
|
||||
|
||||
ulong integrationVersion = canonical.CreateIntegrationVersion;
|
||||
_ = Entities.ApplyAcceptedSpawn(
|
||||
bool applied = Entities.ApplyAcceptedSpawn(
|
||||
canonical,
|
||||
integrationVersion,
|
||||
canonical.Snapshot,
|
||||
replaceGeneration:
|
||||
registration.Inbound.Disposition
|
||||
is CreateObjectTimestampDisposition.NewGeneration);
|
||||
if (applied)
|
||||
{
|
||||
_worldProjection?.ProjectSpawn(
|
||||
canonical,
|
||||
canonical.ServerGuid
|
||||
== _runtime.PlayerIdentity.ServerGuid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeleted(DeleteObject.Parsed delete)
|
||||
|
|
@ -146,6 +173,14 @@ public sealed class RuntimeLiveEntitySessionController
|
|||
_runtime.TransitOwner.OfferTeleportDestination(
|
||||
destination,
|
||||
timestamps.TeleportAdvanced);
|
||||
if (Entities.Entities.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord record))
|
||||
{
|
||||
_worldProjection?.ProjectPosition(
|
||||
record,
|
||||
isLocalPlayer: true);
|
||||
}
|
||||
TryCompletePortal();
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +209,7 @@ public sealed class RuntimeLiveEntitySessionController
|
|||
RuntimeWorldTransitState transit = _runtime.TransitOwner;
|
||||
if (!transit.TryQueueTeleportStart(sequence))
|
||||
return;
|
||||
_worldProjection?.BeginTeleport();
|
||||
if (!transit.ActivateQueuedTeleport())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
|
|
@ -216,16 +252,21 @@ public sealed class RuntimeLiveEntitySessionController
|
|||
RuntimeWorldHostAcknowledgementStage.ProjectionRegistered);
|
||||
|
||||
bool indoor = (destination.CellId & 0xFFFFu) >= 0x0100u;
|
||||
RuntimeDestinationReadiness readiness =
|
||||
_worldProjection?.PrepareDestination(
|
||||
generation,
|
||||
destination)
|
||||
?? new RuntimeDestinationReadiness(
|
||||
generation,
|
||||
destination.CellId,
|
||||
indoor,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: indoor ? 0 : 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true);
|
||||
if (!transit.AcknowledgeDestinationReadiness(
|
||||
new RuntimeDestinationReadiness(
|
||||
generation,
|
||||
destination.CellId,
|
||||
indoor,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: indoor ? 0 : 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true)))
|
||||
readiness))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Runtime rejected headless destination readiness.");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue