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; }