Outbound GameAction message builders for player movement: - MoveToState (0xF61C): sent on motion state changes (start/stop walking, turn, speed change). Carries RawMotionState (flag-driven variable fields) + WorldPosition + sequence numbers. - AutonomousPosition (0xF753): periodic position heartbeat sent every ~200ms while moving. No RawMotionState — just WorldPosition + sequences + contact byte. Both follow the GameAction envelope pattern (0xF7B1 + sequence + action type) established by GameActionLoginComplete. Wire format ported from references/holtburger movement protocol — field order and alignment match exactly (contact byte + pad_to_4). Also: - Adds WriteFloat to PacketWriter (needed by both builders) - Adds SendGameAction + NextGameActionSequence to WorldSession (public wrappers for PlayerMovementController in Task 2) 11 new tests, 265 total, all green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
88 lines
3.4 KiB
C#
88 lines
3.4 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Net.Packets;
|
|
|
|
namespace AcDream.Core.Net.Messages;
|
|
|
|
/// <summary>
|
|
/// Outbound <c>GameAction(AutonomousPosition)</c> message — opcode
|
|
/// <c>0xF753</c>. Sent roughly every 200ms while the player is moving to
|
|
/// give the server a periodic ground-truth position heartbeat. Simpler than
|
|
/// <see cref="MoveToState"/>: no RawMotionState, just the GameAction
|
|
/// envelope, a WorldPosition, sequence numbers, a contact byte, and 4-byte
|
|
/// alignment padding.
|
|
///
|
|
/// <para>
|
|
/// Wire layout (ported from
|
|
/// <c>references/holtburger/crates/holtburger-protocol/src/messages/movement/actions.rs</c>
|
|
/// <c>AutonomousPositionActionData::pack</c>):
|
|
/// </para>
|
|
/// <list type="bullet">
|
|
/// <item><b>GameAction envelope</b>: u32 0xF7B1, u32 sequence, u32 0xF753</item>
|
|
/// <item><b>WorldPosition</b>: u32 cellId, f32 x, f32 y, f32 z,
|
|
/// f32 rotW, f32 rotX, f32 rotY, f32 rotZ</item>
|
|
/// <item><b>Sequences</b>: u16 instance, u16 serverControl,
|
|
/// u16 teleport, u16 forcePosition</item>
|
|
/// <item><b>Contact byte</b>: u8 (1 = on ground, 0 = airborne)</item>
|
|
/// <item><b>Align to 4 bytes</b></item>
|
|
/// </list>
|
|
/// </summary>
|
|
public static class AutonomousPosition
|
|
{
|
|
public const uint GameActionOpcode = 0xF7B1u;
|
|
public const uint AutonomousPositionAction = 0xF753u;
|
|
|
|
/// <summary>
|
|
/// Build an AutonomousPosition GameAction body.
|
|
/// </summary>
|
|
/// <param name="gameActionSequence">Monotonically increasing counter from
|
|
/// <see cref="WorldSession.NextGameActionSequence"/>.</param>
|
|
/// <param name="cellId">Landblock cell ID (u32).</param>
|
|
/// <param name="position">World-space position relative to the landblock.</param>
|
|
/// <param name="rotation">Rotation quaternion. AC wire order is W, X, Y, Z.</param>
|
|
/// <param name="instanceSequence">Instance sequence number from the server.</param>
|
|
/// <param name="serverControlSequence">Server-control sequence number.</param>
|
|
/// <param name="teleportSequence">Teleport sequence number.</param>
|
|
/// <param name="forcePositionSequence">Force-position sequence number.</param>
|
|
/// <param name="lastContact">1 if the character was last on the ground, 0 if airborne.</param>
|
|
public static byte[] Build(
|
|
uint gameActionSequence,
|
|
uint cellId,
|
|
Vector3 position,
|
|
Quaternion rotation,
|
|
ushort instanceSequence,
|
|
ushort serverControlSequence,
|
|
ushort teleportSequence,
|
|
ushort forcePositionSequence,
|
|
byte lastContact = 1)
|
|
{
|
|
var w = new PacketWriter(64);
|
|
|
|
// --- GameAction envelope ---
|
|
w.WriteUInt32(GameActionOpcode);
|
|
w.WriteUInt32(gameActionSequence);
|
|
w.WriteUInt32(AutonomousPositionAction);
|
|
|
|
// --- WorldPosition (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);
|
|
|
|
// --- Contact byte + 4-byte align ---
|
|
w.WriteByte(lastContact);
|
|
w.AlignTo4();
|
|
|
|
return w.ToArray();
|
|
}
|
|
}
|