using AcDream.Core.Net.Packets; using AcDream.Core.Physics; namespace AcDream.Core.Net.Messages; /// /// Ports retail's RawMotionState::Pack verbatim /// (0x0051ed10, decomp lines ~293761-294013; bitfield layout /// acclient.h RawMotionState::PackBitfield, line 46474). Confirmed /// against the Ghidra decompile-by-address bridge during this slice /// (2026-06-30). /// /// /// D1 fix. Retail does NOT set a field's bit merely because the /// caller supplied a value — it compares every field against its retail /// DEFAULT () and sets the bit (and /// emits the field) only when the live value DIFFERS. This packer mirrors /// that default-difference comparison exactly, field by field, in bit /// order. /// /// /// /// Flags dword layout (bits 0-15 only; bits 16-31 are unused — retail's /// num_actions is a 5-bit field occupying bits 11-15, not "the rest /// of the dword"): /// /// /// 0x001current_holdkey != HoldKey.None /// 0x002current_style != 0x8000003D /// 0x004forward_command != 0x41000003 /// 0x008forward_holdkey != HoldKey.Invalid /// 0x010forward_speed != 1.0f /// 0x020sidestep_command != 0 /// 0x040sidestep_holdkey != HoldKey.Invalid /// 0x080sidestep_speed != 1.0f /// 0x100turn_command != 0 /// 0x200turn_holdkey != HoldKey.Invalid /// 0x400turn_speed != 1.0f /// 0xF800num_actions (bits 11-15, count not values) /// /// public static class RawMotionStatePacker { private const uint FlagCurrentHoldKey = 0x001u; private const uint FlagCurrentStyle = 0x002u; private const uint FlagForwardCommand = 0x004u; private const uint FlagForwardHoldKey = 0x008u; private const uint FlagForwardSpeed = 0x010u; private const uint FlagSidestepCommand = 0x020u; private const uint FlagSidestepHoldKey = 0x040u; private const uint FlagSidestepSpeed = 0x080u; private const uint FlagTurnCommand = 0x100u; private const uint FlagTurnHoldKey = 0x200u; private const uint FlagTurnSpeed = 0x400u; private const int NumActionsShift = 11; public static void Pack(PacketWriter w, RawMotionState state) { var defaults = RawMotionState.Default; uint flags = 0u; if (state.CurrentHoldKey != defaults.CurrentHoldKey) flags |= FlagCurrentHoldKey; if (state.CurrentStyle != defaults.CurrentStyle) flags |= FlagCurrentStyle; if (state.ForwardCommand != defaults.ForwardCommand) flags |= FlagForwardCommand; if (state.ForwardHoldKey != defaults.ForwardHoldKey) flags |= FlagForwardHoldKey; if (state.ForwardSpeed != defaults.ForwardSpeed) flags |= FlagForwardSpeed; if (state.SidestepCommand != defaults.SidestepCommand) flags |= FlagSidestepCommand; if (state.SidestepHoldKey != defaults.SidestepHoldKey) flags |= FlagSidestepHoldKey; if (state.SidestepSpeed != defaults.SidestepSpeed) flags |= FlagSidestepSpeed; if (state.TurnCommand != defaults.TurnCommand) flags |= FlagTurnCommand; if (state.TurnHoldKey != defaults.TurnHoldKey) flags |= FlagTurnHoldKey; if (state.TurnSpeed != defaults.TurnSpeed) flags |= FlagTurnSpeed; int numActions = state.Actions.Count; flags |= (uint)(numActions << NumActionsShift); w.WriteUInt32(flags); if ((flags & FlagCurrentHoldKey) != 0) w.WriteUInt32((uint)state.CurrentHoldKey); if ((flags & FlagCurrentStyle) != 0) w.WriteUInt32(state.CurrentStyle); if ((flags & FlagForwardCommand) != 0) w.WriteUInt32(state.ForwardCommand); if ((flags & FlagForwardHoldKey) != 0) w.WriteUInt32((uint)state.ForwardHoldKey); if ((flags & FlagForwardSpeed) != 0) w.WriteFloat(state.ForwardSpeed); if ((flags & FlagSidestepCommand) != 0) w.WriteUInt32(state.SidestepCommand); if ((flags & FlagSidestepHoldKey) != 0) w.WriteUInt32((uint)state.SidestepHoldKey); if ((flags & FlagSidestepSpeed) != 0) w.WriteFloat(state.SidestepSpeed); if ((flags & FlagTurnCommand) != 0) w.WriteUInt32(state.TurnCommand); if ((flags & FlagTurnHoldKey) != 0) w.WriteUInt32((uint)state.TurnHoldKey); if ((flags & FlagTurnSpeed) != 0) w.WriteFloat(state.TurnSpeed); foreach (var action in state.Actions) { w.WriteUInt16(action.Command); ushort stampWord = (ushort)((action.Stamp & 0x7FFF) | (action.Autonomous ? 0x8000 : 0)); w.WriteUInt16(stampWord); } } }