using System;
using System.Numerics;
using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
namespace AcDream.App.Rendering;
///
/// R5-V2 — the App-side per entity: acdream's
/// stand-in for retail's CPhysicsObj as the movement managers see it.
/// One is built per entity (a remote RemoteMotion or the local player)
/// in GameWindow.EnsureRemoteMotionBindings / EnterPlayerModeNow
/// and registered in GameWindow._physicsHosts (guid → host), so
/// can resolve OTHER entities' hosts — the
/// cross-entity delivery path the voyeur system
/// needs.
///
/// Owns a (retail
/// CPhysicsObj::target_manager). Its set_target/clear_target/
/// add_voyeur/remove_voyeur/receive_target_update seams
/// forward to it exactly as retail's CPhysicsObj does; the movement managers'
/// target seams are repointed here, replacing the AP-79 poll adapter. The
/// per-entity accessors (position/velocity/radius/contact/clocks) and the
/// fan-out are injected by GameWindow so this
/// class stays free of GameWindow's internals (code-structure rule #1).
///
/// R5-V3: owns a too (retail
/// CPhysicsObj::position_manager — retail creates it lazily via
/// get_position_manager; acdream constructs it eagerly, which is
/// behaviorally identical because the empty facade no-ops until its first
/// StickTo/ConstrainTo). fans
/// deliveries to the injected MoveToManager fan FIRST, then the
/// PositionManager — retail CPhysicsObj::HandleUpdateTarget order
/// (0x00512bc0: MovementManager @0x00512bf0, PositionManager
/// @0x00512c1a).
///
public sealed class EntityPhysicsHost : IPhysicsObjHost
{
private readonly Func _getPosition;
private readonly Func _getVelocity;
private readonly Func _getRadius;
private readonly Func _inContact;
private readonly Func _minterpMaxSpeed;
private readonly Func _curTime;
private readonly Func _physicsTimerTime;
private readonly Func _getObjectA;
private readonly Action _handleUpdateTarget;
private readonly Action _interruptCurrentMovement;
private readonly TargetManager _targetManager;
public EntityPhysicsHost(
uint id,
Func getPosition,
Func getVelocity,
Func getRadius,
Func inContact,
Func minterpMaxSpeed,
Func curTime,
Func physicsTimerTime,
Func getObjectA,
Action handleUpdateTarget,
Action interruptCurrentMovement)
{
Id = id;
_getPosition = getPosition ?? throw new ArgumentNullException(nameof(getPosition));
_getVelocity = getVelocity ?? throw new ArgumentNullException(nameof(getVelocity));
_getRadius = getRadius ?? throw new ArgumentNullException(nameof(getRadius));
_inContact = inContact ?? throw new ArgumentNullException(nameof(inContact));
_minterpMaxSpeed = minterpMaxSpeed ?? throw new ArgumentNullException(nameof(minterpMaxSpeed));
_curTime = curTime ?? throw new ArgumentNullException(nameof(curTime));
_physicsTimerTime = physicsTimerTime ?? throw new ArgumentNullException(nameof(physicsTimerTime));
_getObjectA = getObjectA ?? throw new ArgumentNullException(nameof(getObjectA));
_handleUpdateTarget = handleUpdateTarget ?? throw new ArgumentNullException(nameof(handleUpdateTarget));
_interruptCurrentMovement = interruptCurrentMovement
?? throw new ArgumentNullException(nameof(interruptCurrentMovement));
_targetManager = new TargetManager(this);
PositionManager = new PositionManager(this);
}
// ── IPhysicsObjHost accessors ──────────────────────────────────────────
public uint Id { get; }
public Position Position => _getPosition();
public Vector3 Velocity => _getVelocity();
public float Radius => _getRadius();
public bool InContact => _inContact();
public float? MinterpMaxSpeed => _minterpMaxSpeed();
public double CurTime => _curTime();
public double PhysicsTimerTime => _physicsTimerTime();
/// The owned voyeur manager (retail
/// CPhysicsObj::target_manager).
public TargetManager TargetManager => _targetManager;
/// R5-V3 — the owned facade (retail
/// CPhysicsObj::position_manager): sticky follow + (unarmed)
/// constraint leash. Seam targets: MoveToManager.StickTo/Unstick,
/// MotionInterpreter.UnstickFromObject, the per-tick
/// AdjustOffset/UseTime drivers.
public PositionManager PositionManager { get; }
// ── IPhysicsObjHost fan-out / target-tracking seams ────────────────────
public IPhysicsObjHost? GetObjectA(uint id) => _getObjectA(id);
public void HandleUpdateTarget(TargetInfo info)
{
// Retail CPhysicsObj::HandleUpdateTarget (0x00512bc0) fan order:
// MovementManager (the injected MoveToManager fan) first, then
// PositionManager (@0x00512c1a — the R5-V3 sticky consumer).
_handleUpdateTarget(info);
PositionManager.HandleUpdateTarget(info);
}
public void InterruptCurrentMovement() => _interruptCurrentMovement();
public void SetTarget(uint contextId, uint objectId, float radius, double quantum)
=> _targetManager.SetTarget(contextId, objectId, radius, quantum);
public void ClearTarget() => _targetManager.ClearTarget();
public void ReceiveTargetUpdate(TargetInfo info) => _targetManager.ReceiveUpdate(info);
public void AddVoyeur(uint watcherId, float radius, double quantum)
=> _targetManager.AddVoyeur(watcherId, radius, quantum);
public void RemoveVoyeur(uint watcherId) => _targetManager.RemoveVoyeur(watcherId);
// ── per-tick driver + lifecycle (called by GameWindow) ─────────────────
/// Retail TargetManager::HandleTargetting — the per-tick
/// voyeur sweep (self-throttled to 0.5 s). Retail runs it unconditionally
/// for every entity in UpdateObjectInternal, BEFORE the movement
/// managers' UseTime.
public void HandleTargetting() => _targetManager.HandleTargetting();
/// Retail CPhysicsObj::exit_world's
/// TargetManager::NotifyVoyeurOfEvent(ExitWorld) — tell every
/// watcher of this entity that it left the world (they drop the
/// stick/moveto). Called on despawn before the host is removed from the
/// registry.
public void NotifyExitWorld() => _targetManager.NotifyVoyeurOfEvent(TargetStatus.ExitWorld);
/// R5-V3 (#171): retail CPhysicsObj::teleport_hook's tail
/// (0x00514ed0 @0x00514f1b-0x00514f28) — TargetManager::ClearTarget
/// (drop this entity's OWN subscription) then
/// NotifyVoyeurOfEvent(Teleported) (every entity watching THIS one
/// drops its stick/moveto — StickyManager::HandleUpdateTarget's
/// non-Ok teardown path). Called after a teleport placement.
public void NotifyTeleported()
{
_targetManager.ClearTarget();
_targetManager.NotifyVoyeurOfEvent(TargetStatus.Teleported);
}
}