156 lines
5.7 KiB
C#
156 lines
5.7 KiB
C#
using System.Collections.Immutable;
|
|
using System.Numerics;
|
|
using AcDream.Core.Net.Messages;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Runtime.Entities;
|
|
|
|
namespace AcDream.Runtime.Physics;
|
|
|
|
internal enum RuntimeSetPositionMoverPreparationStatus
|
|
{
|
|
Prepared,
|
|
RetrySetupUnavailable,
|
|
RejectedAuthority,
|
|
InvalidData,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Distinguishes a Setup payload which has not arrived yet from a completed
|
|
/// lookup whose result is absent. Retail supplies its dummy placement sphere
|
|
/// only inside <c>CPhysicsObj::SetPosition</c>; an unavailable lookup must not
|
|
/// manufacture that fallback while asynchronous preparation is still live.
|
|
/// </summary>
|
|
internal readonly record struct RuntimeSetPositionMoverSetup(
|
|
bool IsResolved,
|
|
uint SetupTableId,
|
|
FlatSetupCollision? Collision)
|
|
{
|
|
internal static RuntimeSetPositionMoverSetup Unavailable => default;
|
|
|
|
internal static RuntimeSetPositionMoverSetup ResolvedAbsent =>
|
|
new(true, 0u, null);
|
|
|
|
internal static RuntimeSetPositionMoverSetup Resolved(
|
|
uint setupTableId,
|
|
FlatSetupCollision collision) =>
|
|
new(
|
|
true,
|
|
setupTableId != 0u
|
|
? setupTableId
|
|
: throw new ArgumentOutOfRangeException(nameof(setupTableId)),
|
|
collision ?? throw new ArgumentNullException(nameof(collision)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explicit, immutable inputs surrounding retail's authored mover shape.
|
|
/// None of these values are inferred from presentation state.
|
|
/// </summary>
|
|
internal readonly record struct RuntimeSetPositionMoverPreparation(
|
|
RuntimeSetPositionMoverSetup Setup,
|
|
RuntimeSetPositionOperationKind Kind,
|
|
double GameTime,
|
|
PhysicsPlacementClass PlacementClass,
|
|
PhysicsSetPositionFlags Flags,
|
|
Vector3 Line = default,
|
|
float ScatterRadiusX = 0f,
|
|
float ScatterRadiusY = 0f,
|
|
uint ScatterAttempts = 0u,
|
|
float ShadowWorldOffsetX = 0f,
|
|
float ShadowWorldOffsetY = 0f,
|
|
RuntimePortalPlacementAuthority Portal = default);
|
|
|
|
/// <summary>
|
|
/// Pure preparation port of the mover inputs consumed by retail
|
|
/// <c>CPhysicsObj::SetPosition</c> (0x005160C0). It preserves the complete DAT
|
|
/// sphere order; Core's SPHEREPATH port applies retail's two-sphere cap later.
|
|
/// </summary>
|
|
internal static class RuntimeSetPositionMoverPreparer
|
|
{
|
|
internal static bool TryBuild(
|
|
RuntimeEntityRecord record,
|
|
in CreateObject.ServerPosition acceptedPosition,
|
|
uint canonicalSetupTableId,
|
|
RuntimeSetPositionOperationKind acceptedKind,
|
|
RuntimePortalPlacementAuthority acceptedPortal,
|
|
ulong velocityAuthorityVersion,
|
|
in RuntimeSetPositionMoverPreparation preparation,
|
|
out RuntimeSetPositionCommand command)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(record);
|
|
command = default;
|
|
if (!preparation.Setup.IsResolved
|
|
|| preparation.Kind != acceptedKind
|
|
|| preparation.Portal != acceptedPortal
|
|
|| (canonicalSetupTableId == 0u
|
|
? preparation.Setup.SetupTableId != 0u
|
|
|| preparation.Setup.Collision is not null
|
|
: preparation.Setup.SetupTableId != canonicalSetupTableId
|
|
|| preparation.Setup.Collision is null))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CreateObject.ServerPosition position = acceptedPosition;
|
|
Vector3 cellLocal = new(
|
|
position.PositionX,
|
|
position.PositionY,
|
|
position.PositionZ);
|
|
Vector3 world = new(
|
|
cellLocal.X + preparation.ShadowWorldOffsetX,
|
|
cellLocal.Y + preparation.ShadowWorldOffsetY,
|
|
cellLocal.Z);
|
|
Quaternion orientation = new(
|
|
position.RotationX,
|
|
position.RotationY,
|
|
position.RotationZ,
|
|
position.RotationW);
|
|
|
|
float scale = record.Snapshot.Physics?.Scale
|
|
?? record.Snapshot.ObjScale
|
|
?? 1f;
|
|
FlatSetupCollision? setup = preparation.Setup.Collision;
|
|
ImmutableArray<FlatCollisionSphere> spheres = setup?.Spheres
|
|
?? ImmutableArray<FlatCollisionSphere>.Empty;
|
|
// CPartArray::GetStepUpHeight/GetStepDownHeight are Setup properties,
|
|
// not sphere properties. An authored Setup with no collision spheres
|
|
// still supplies both values; only a genuinely absent Setup takes the
|
|
// retail dummy path with exact zero steps and no scale multiplication.
|
|
float stepUp = setup is not null ? setup.StepUpHeight * scale : 0f;
|
|
float stepDown = setup is not null ? setup.StepDownHeight * scale : 0f;
|
|
|
|
EntityCollisionFlags collisionFlags =
|
|
EntityCollisionFlagsExt.FromPwdBitfield(
|
|
record.Snapshot.ObjectDescriptionFlags ?? 0u);
|
|
ObjectInfoState moverFlags = collisionFlags.ToMoverState();
|
|
if (collisionFlags.HasFlag(EntityCollisionFlags.IsPlayer))
|
|
moverFlags |= ObjectInfoState.IsPlayer;
|
|
|
|
var request = new PhysicsSetPositionRequest(
|
|
world,
|
|
orientation,
|
|
position.LandblockId,
|
|
cellLocal,
|
|
spheres,
|
|
scale,
|
|
stepUp,
|
|
stepDown,
|
|
record.FinalPhysicsState,
|
|
moverFlags,
|
|
record.Key?.LocalEntityId ?? 0u,
|
|
preparation.PlacementClass,
|
|
preparation.Flags,
|
|
preparation.Line,
|
|
preparation.ScatterRadiusX,
|
|
preparation.ScatterRadiusY,
|
|
preparation.ScatterAttempts);
|
|
command = new RuntimeSetPositionCommand(
|
|
request,
|
|
preparation.Kind,
|
|
preparation.GameTime,
|
|
velocityAuthorityVersion,
|
|
preparation.ShadowWorldOffsetX,
|
|
preparation.ShadowWorldOffsetY,
|
|
preparation.Portal);
|
|
return true;
|
|
}
|
|
}
|