using System;
using System.Buffers.Binary;
namespace AcDream.Core.Net.Messages;
///
/// Inbound SetState GameMessage (opcode 0xF74B). The server
/// broadcasts this whenever a previously-spawned entity's
/// PhysicsState bitmask changes after CreateObject — chiefly
/// when a door opens / closes (server flips ETHEREAL_PS = 0x4) or a
/// spell projectile becomes ethereal post-impact.
///
///
/// Wire layout (per
/// references/holtburger/crates/holtburger-protocol/src/messages/object/messages/properties.rs:117-122,
/// matched by every other acdream parser):
///
///
/// - u32 opcode — 0xF74B
/// - u32 objectGuid
/// - u32 physicsState — bitmask (acclient.h:2815 / 2819)
/// - u16 instanceSequence — stale-packet rejection
/// - u16 stateSequence — stale-packet rejection
///
///
///
/// Total body size: 16 bytes (4-byte opcode + 12-byte payload).
///
///
///
/// Server-side reference:
/// references/ACE/Source/ACE.Server/Network/GameMessages/Messages/GameMessageSetState.cs:8-15
/// — ACE writes the same field order using its UShortSequence.CurrentBytes
/// helper (which calls BitConverter.GetBytes((ushort)value) = 2 bytes per
/// sequence field), so its wire output matches holtburger's 12-byte payload.
/// A one-shot ACDREAM_PROBE_BUILDING hex-dump in
/// 's dispatcher (added in the same commit) emits
/// the first SetState body bytes for runtime confirmation.
///
///
///
/// Named-retail anchor: CPhysicsObj::set_state at
/// docs/research/named-retail/acclient_2013_pseudo_c.txt:283044
/// describes the runtime state-store on the in-memory object
/// (this->state = arg2). The wire format for this opcode is
/// confirmed by holtburger's SetStateData struct; the named-retail
/// decomp does not cover the deserialization path for this opcode.
///
///
public static class SetState
{
public const uint Opcode = 0xF74Bu;
public readonly record struct Parsed(
uint Guid,
uint PhysicsState,
ushort InstanceSequence,
ushort StateSequence);
///
/// Parse a 0xF74B body. must start with the
/// 4-byte opcode (matches the convention used by VectorUpdate /
/// UpdateMotion / UpdatePosition). Returns null on truncation or
/// opcode mismatch.
///
public static Parsed? TryParse(ReadOnlySpan body)
{
if (body.Length < 16) return null;
try
{
uint opcode = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(0, 4));
if (opcode != Opcode) return null;
uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(4, 4));
uint state = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(8, 4));
ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(12, 2));
ushort stateSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(14, 2));
return new Parsed(guid, state, instSeq, stateSeq);
}
catch
{
return null;
}
}
}