using System.Numerics; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; namespace AcDream.App.Input; /// /// Serializes input-originated output from the object-phase result and the /// periodic position report from post-inbound controller state. /// public sealed class LocalPlayerOutboundController { private readonly Func _toWireRotation; private readonly Action _diagnostic; public LocalPlayerOutboundController( Func toWireRotation, Action diagnostic) { _toWireRotation = toWireRotation ?? throw new ArgumentNullException(nameof(toWireRotation)); _diagnostic = diagnostic ?? throw new ArgumentNullException(nameof(diagnostic)); } /// /// Sends input-originated movement and jump packets before inbound /// dispatch. Retail emits these from input/object processing rather than /// from CommandInterpreter::UseTime. /// public void SendPreNetworkActions( WorldSession? session, PlayerMovementController controller, MovementResult movement, bool hidden) { if (session is null || hidden) return; Quaternion wireRotation = _toWireRotation(controller.Yaw); if (!controller.TryGetOutboundPosition( wireRotation, out uint wireCellId, out Vector3 wirePosition)) { return; } if (movement.ShouldSendMovementEvent) TrySendMovement(session, controller, movement); if (movement.JumpExtent.HasValue && movement.JumpVelocity.HasValue) { uint sequence = session.NextGameActionSequence(); byte[] body = JumpAction.Build( gameActionSequence: sequence, extent: movement.JumpExtent.Value, velocity: movement.JumpVelocity.Value, cellId: wireCellId, position: wirePosition, rotation: wireRotation, instanceSequence: session.InstanceSequence, serverControlSequence: session.ServerControlSequence, teleportSequence: session.TeleportSequence, forcePositionSequence: session.ForcePositionSequence); session.SendGameAction(body); } } /// /// Ports the position-send slot in CommandInterpreter::UseTime /// (0x006B3BF0), which SmartBox calls after draining inbound /// events. The predicate and serialized frame therefore observe any /// accepted ForcePosition or teleport state from this update. /// public void SendPostNetworkPosition( WorldSession? session, PlayerMovementController controller, bool hidden) { if (session is null || hidden) return; Quaternion wireRotation = _toWireRotation(controller.Yaw); if (!controller.TryGetOutboundPosition( wireRotation, out uint wireCellId, out Vector3 wirePosition)) { return; } var position = new Position(wireCellId, wirePosition, wireRotation); if (!controller.ShouldSendPositionEvent( position, controller.ContactPlane, controller.SimTimeSeconds) || !controller.CanSendPositionEvent) { return; } MovementResult movement = controller.CapturePresentationResult(); byte contactByte = movement.IsOnGround ? (byte)1 : (byte)0; uint sequence = session.NextGameActionSequence(); byte[] body = AutonomousPosition.Build( gameActionSequence: sequence, cellId: wireCellId, position: wirePosition, rotation: wireRotation, instanceSequence: session.InstanceSequence, serverControlSequence: session.ServerControlSequence, teleportSequence: session.TeleportSequence, forcePositionSequence: session.ForcePositionSequence, lastContact: contactByte); _diagnostic( "AP", sequence, movement, wirePosition, wireCellId, contactByte); session.SendGameAction(body); controller.NotePositionSent( position, controller.ContactPlane, controller.SimTimeSeconds); } public bool TrySendMovement( WorldSession? session, PlayerMovementController? controller, MovementResult movement) { if (session is null || controller is null) return false; Quaternion wireRotation = _toWireRotation(controller.Yaw); if (!controller.TryGetOutboundPosition( wireRotation, out uint wireCellId, out Vector3 wirePosition)) { return false; } byte contactByte = movement.IsOnGround ? (byte)1 : (byte)0; RawMotionState rawMotionState = BuildRawMotionState(movement); uint sequence = session.NextGameActionSequence(); byte[] body = MoveToState.Build( gameActionSequence: sequence, rawMotionState: rawMotionState, cellId: wireCellId, position: wirePosition, rotation: wireRotation, instanceSequence: session.InstanceSequence, serverControlSequence: session.ServerControlSequence, teleportSequence: session.TeleportSequence, forcePositionSequence: session.ForcePositionSequence, contact: contactByte != 0, standingLongjump: false); _diagnostic( "MTS", sequence, movement, wirePosition, wireCellId, contactByte); session.SendGameAction(body); controller.NoteMovementSent( controller.SimTimeSeconds, movement.IsMouseLookMovementEvent); return true; } public static RawMotionState BuildRawMotionState(MovementResult movement) { HoldKey axisHoldKey = movement.IsRunning ? HoldKey.Run : HoldKey.None; return new RawMotionState { CurrentHoldKey = axisHoldKey, ForwardCommand = movement.ForwardCommand ?? RawMotionState.Default.ForwardCommand, ForwardHoldKey = movement.ForwardCommand.HasValue ? axisHoldKey : HoldKey.Invalid, ForwardSpeed = movement.ForwardSpeed ?? RawMotionState.Default.ForwardSpeed, SidestepCommand = movement.SidestepCommand ?? RawMotionState.Default.SidestepCommand, SidestepHoldKey = movement.SidestepCommand.HasValue ? movement.SidestepUsesRunHold ? HoldKey.Run : axisHoldKey : HoldKey.Invalid, SidestepSpeed = movement.SidestepSpeed ?? RawMotionState.Default.SidestepSpeed, TurnCommand = movement.TurnCommand ?? RawMotionState.Default.TurnCommand, TurnHoldKey = movement.TurnCommand.HasValue ? movement.TurnUsesRunHold ? HoldKey.Run : axisHoldKey : HoldKey.Invalid, TurnSpeed = movement.TurnSpeed ?? RawMotionState.Default.TurnSpeed, }; } }