using System;
namespace AcDream.Core.Physics.Motion;
///
/// R5 — port of retail's PositionManager facade (acclient.h:30952,
/// struct #3468; decomp 0x00555160-0x005553d0,
/// r5-positionmanager-sticky-decomp.md). A thin fan-out over three
/// sub-managers: Interpolation, Sticky, Constraint. Owned 1:1 by the entity's
/// (retail CPhysicsObj::position_manager,
/// lazily created).
///
/// Interpolation note: retail's adjust_offset chains
/// Interpolation → Sticky → Constraint. acdream's interpolation stage lives in
/// (the R5-renamed remote-motion combiner,
/// formerly the misnamed Physics.PositionManager) and is NOT chained
/// here in V1 — this facade owns only the two R5 targets (Sticky retires TS-39;
/// Constraint is structural — see ). Folding the
/// combiner in as the interp stage is a wiring-slice cleanup.
///
public sealed class PositionManager
{
private readonly IPhysicsObjHost _host;
// Lazily created (retail: sticky on first StickTo, constraint on first
// ConstrainTo — 0x00555230 / 0x00555280).
private StickyManager? _sticky;
private ConstraintManager? _constraint;
public PositionManager(IPhysicsObjHost host)
=> _host = host ?? throw new ArgumentNullException(nameof(host));
/// Exposed for wiring/tests — the lazily-created sub-managers
/// (null until first use).
public StickyManager? Sticky => _sticky;
public ConstraintManager? Constraint => _constraint;
/// Retail PositionManager::StickTo (0x00555230) — lazily
/// create the and begin following
/// .
public void StickTo(uint objectId, float radius, float height)
{
_sticky ??= new StickyManager(_host);
_sticky.StickTo(objectId, radius, height);
}
/// Retail PositionManager::UnStick (0x005551e0) — forward
/// to the sticky sub-manager if it exists.
public void UnStick() => _sticky?.UnStick();
/// Retail PositionManager::GetStickyObjectID
/// (0x00555270).
public uint GetStickyObjectId() => _sticky?.TargetId ?? 0u;
/// Retail PositionManager::ConstrainTo (0x00555280) —
/// lazily create the and arm the leash.
/// (Unused in acdream — no arming call site; see
/// .)
public void ConstrainTo(Position anchor, float startDistance, float maxDistance)
{
_constraint ??= new ConstraintManager(_host);
_constraint.ConstrainTo(anchor, startDistance, maxDistance);
}
/// Retail PositionManager::UnConstrain
/// (0x005552b0).
public void UnConstrain() => _constraint?.UnConstrain();
/// Retail PositionManager::IsFullyConstrained
/// (0x005552c0) — false when no constraint sub-manager exists.
public bool IsFullyConstrained() => _constraint?.IsFullyConstrained() ?? false;
///
/// Retail PositionManager::HandleUpdateTarget (0x005553d0) — only
/// the sticky sub-manager cares about live target positions (interpolation
/// and constraint don't). Fanned out from
/// CPhysicsObj::HandleUpdateTarget.
///
public void HandleUpdateTarget(TargetInfo info) => _sticky?.HandleUpdateTarget(info);
///
/// Retail PositionManager::adjust_offset (0x00555190) — chains the
/// sub-managers' contributions into the SAME
/// accumulator, in retail order. Retail runs Interpolation → Sticky →
/// Constraint; acdream's interpolation stays in the separate remote-motion
/// combiner (see class note), so this chains Sticky → Constraint only.
/// Constraint is LAST because it clamps the already-composed displacement.
///
public void AdjustOffset(MotionDeltaFrame offset, double quantum)
{
_sticky?.AdjustOffset(offset, quantum);
_constraint?.AdjustOffset(offset, quantum);
}
///
/// Retail PositionManager::UseTime (0x00555160) — per-tick pump.
/// Retail order Interpolation → Constraint → Sticky; acdream runs the two
/// owned managers (constraint's UseTime is a retail no-op, so effectively
/// just the sticky 1 s watchdog).
///
public void UseTime() => _sticky?.UseTime();
}