using System.Numerics;
using AcDream.Core.Net.Packets;
using AcDream.Core.Physics;
namespace AcDream.Core.Net.Messages;
///
/// Outbound GameAction(MoveToState) message — opcode 0xF61C.
/// Sent whenever the client's motion state changes: starting or stopping
/// walking, switching direction, changing speed, entering or leaving combat
/// stance. The server uses this to update the player's authoritative motion
/// state and to drive interpolated position for other nearby clients.
///
///
/// Wire layout — ports retail's MoveToStatePack::Pack verbatim
/// (0x005168f0, decomp lines ~284694-284722). Confirmed against the
/// Ghidra decompile-by-address bridge during this slice (2026-06-30):
///
///
/// - GameAction envelope: u32 0xF7B1, u32 sequence, u32 0xF61C
/// - RawMotionState::Pack: see
/// — default-difference flags dword + conditional fields + actions.
/// - Position::Pack: u32 cellId, f32 x, f32 y, f32 z,
/// f32 qw, f32 qx, f32 qy, f32 qz (32 bytes)
/// - Sequences: u16 instance, u16 serverControl,
/// u16 teleport, u16 forcePosition
/// - Trailing byte: (standingLongjump ? 0x02 : 0) |
/// (contact ? 0x01 : 0) — MoveToStatePack::Pack trailing
/// byte expression (longjump_mode == 0) - 1U & 2 |
/// contact != 0.
/// - Align to 4 bytes
///
///
public static class MoveToState
{
public const uint GameActionOpcode = 0xF7B1u;
public const uint MoveToStateAction = 0xF61Cu;
///
/// Build a MoveToState GameAction body.
///
/// Monotonically increasing counter from
/// .
/// Complete raw-motion snapshot, matching
/// retail's CPhysicsObj::InqRawMotionState(). Fields equal to
/// are omitted from the wire
/// (see ).
/// Landblock cell ID (u32).
/// World-space position relative to the landblock.
/// Rotation quaternion. AC wire order is W, X, Y, Z.
/// Instance sequence number from the server.
/// Server-control sequence number.
/// Teleport sequence number.
/// Force-position sequence number.
/// True if the character is on the ground.
/// True during a standing (charged,
/// stationary) longjump wind-up. Not yet implemented in acdream —
/// callers pass false honestly until that feature lands.
public static byte[] Build(
uint gameActionSequence,
RawMotionState rawMotionState,
uint cellId,
Vector3 position,
Quaternion rotation,
ushort instanceSequence,
ushort serverControlSequence,
ushort teleportSequence,
ushort forcePositionSequence,
bool contact = true,
bool standingLongjump = false)
{
var w = new PacketWriter(128);
// --- GameAction envelope ---
w.WriteUInt32(GameActionOpcode);
w.WriteUInt32(gameActionSequence);
w.WriteUInt32(MoveToStateAction);
// --- RawMotionState::Pack (0x0051ed10) ---
RawMotionStatePacker.Pack(w, rawMotionState);
// --- Position::Pack (0x005a9640): cellId + Frame::Pack (32 bytes) ---
w.WriteUInt32(cellId);
w.WriteFloat(position.X);
w.WriteFloat(position.Y);
w.WriteFloat(position.Z);
// Quaternion wire order: W, X, Y, Z
w.WriteFloat(rotation.W);
w.WriteFloat(rotation.X);
w.WriteFloat(rotation.Y);
w.WriteFloat(rotation.Z);
// --- Sequence numbers ---
w.WriteUInt16(instanceSequence);
w.WriteUInt16(serverControlSequence);
w.WriteUInt16(teleportSequence);
w.WriteUInt16(forcePositionSequence);
// --- Trailing byte (MoveToStatePack::Pack, 0x005168f0): ---
// ((longjump_mode != 0) ? 0x02 : 0) | (contact != 0 ? 0x01 : 0)
byte trailing = (byte)((standingLongjump ? 0x02 : 0) | (contact ? 0x01 : 0));
w.WriteByte(trailing);
w.AlignTo4();
return w.ToArray();
}
}