using AcDream.Core.Net;
using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
namespace AcDream.App.Physics;
///
/// Applies the remote-object half of retail
/// MovementManager::unpack_movement (0x00524440) after the
/// packet timestamp gate has accepted an UpdateMotion event.
///
///
/// The packet owner is deliberately independent of a render PartArray. A
/// normal animated object supplies a ;
/// an animation-less object supplies and
/// CMotionInterp retains the same state on its canonical physics body.
/// This keeps the interrupt/style/type-0/sticky ordering identical for both
/// object shapes instead of maintaining a second headless protocol funnel.
///
internal sealed class RemoteInboundMotionDispatcher
{
private readonly Func<
MovementManager,
uint,
WorldSession.EntityMotionUpdate,
bool> _routeServerMoveTo;
private readonly Action _stickToObject;
public RemoteInboundMotionDispatcher(
Func
routeServerMoveTo,
Action stickToObject)
{
_routeServerMoveTo = routeServerMoveTo
?? throw new ArgumentNullException(nameof(routeServerMoveTo));
_stickToObject = stickToObject
?? throw new ArgumentNullException(nameof(stickToObject));
}
public RemoteInboundMotionDispatchResult Apply(
WorldSession.EntityMotionUpdate update,
MovementManager movement,
IInterpretedMotionSink? animationSink,
IPhysicsObjHost? host,
uint cellId,
uint fallbackForwardClass,
Func? isCurrent = null)
{
ArgumentNullException.ThrowIfNull(movement);
bool Current() => isCurrent?.Invoke() ?? true;
MotionInterpreter motion = movement.Minterp;
uint previousForward = motion.InterpretedState.ForwardCommand;
RemoteInboundMotionDispatchResult Superseded() => new(
RoutedMoveTo: false,
AppliedInterpretedState: false,
PreviousForwardCommand: previousForward,
CurrentForwardCommand: motion.InterpretedState.ForwardCommand,
Superseded: true);
if (!Current())
return Superseded();
// MovementManager::unpack_movement @00524440: these two calls occur
// before the movement-type switch for every accepted packet.
motion.InterruptCurrentMovement?.Invoke();
if (!Current())
return Superseded();
motion.UnstickFromObject?.Invoke();
if (!Current())
return Superseded();
uint wireStyle = update.MotionState.Stance != 0
? 0x80000000u | update.MotionState.Stance
: 0x8000003Du;
if (motion.InterpretedState.CurrentStyle != wireStyle)
{
motion.DoMotion(
wireStyle,
new MovementParameters());
if (!Current())
return Superseded();
}
// Cases 6/7/8/9 are owned by MoveToManager and return directly.
bool routedMoveTo = _routeServerMoveTo(movement, cellId, update);
if (!Current())
return Superseded();
if (routedMoveTo)
{
return new RemoteInboundMotionDispatchResult(
RoutedMoveTo: true,
AppliedInterpretedState: false,
PreviousForwardCommand: previousForward,
CurrentForwardCommand: motion.InterpretedState.ForwardCommand);
}
// Retail's ten-way switch returns zero for every unrecognised type;
// only case 0 enters the wholesale interpreted-state funnel.
if (update.MotionState.MovementType != 0)
{
return new RemoteInboundMotionDispatchResult(
RoutedMoveTo: false,
AppliedInterpretedState: false,
PreviousForwardCommand: previousForward,
CurrentForwardCommand: motion.InterpretedState.ForwardCommand);
}
InboundInterpretedState interpreted =
InboundInterpretedMotionFactory.Create(
update.MotionState,
fallbackForwardClass);
motion.MoveToInterpretedState(interpreted, animationSink);
if (!Current())
return Superseded();
// Case-0 tail order @00524583-0052458E: stick after the wholesale
// state copy, then write standing_longjump unconditionally.
if (update.MotionState.StickyObjectGuid is { } stickyGuid
&& stickyGuid != 0)
{
_stickToObject(host, stickyGuid);
if (!Current())
return Superseded();
}
motion.StandingLongJump = update.MotionState.StandingLongJump;
return new RemoteInboundMotionDispatchResult(
RoutedMoveTo: false,
AppliedInterpretedState: true,
PreviousForwardCommand: previousForward,
CurrentForwardCommand: interpreted.ForwardCommand);
}
}
internal readonly record struct RemoteInboundMotionDispatchResult(
bool RoutedMoveTo,
bool AppliedInterpretedState,
uint PreviousForwardCommand,
uint CurrentForwardCommand,
bool Superseded = false)
{
public bool ForwardCommandChanged =>
AppliedInterpretedState
&& PreviousForwardCommand != CurrentForwardCommand;
}