using System.Buffers.Binary; namespace AcDream.Core.Net.Messages; /// /// Retail Sound game message (0xF750): the server tells the client /// to play one SoundType slot from an object's SoundTable. /// /// /// Wire layout, 16 bytes, S→C — three oracles agree: /// u32 opcode | u32 guid | u32 SoundType | f32 volume. Retail dispatch is /// CM_Physics::DispatchSB_SoundEvent @ 0x006AC760 (which reads /// buf+4, buf+8, buf+0xC); ACE writes exactly those fields /// in GameMessageSound.cs with a declared length of 16; holtburger's /// PlaySoundData parses the same triple. /// /// /// /// This message carries every server-driven cue: melee hits and wounds, wield /// and unwield, pickup and drop, lockpicking, lifestone bind, spell resist, /// trap triggers, item mana depletion. It was unhandled by acdream until /// Campaign A slice A3, which is why all of those were silent. /// /// public readonly record struct SoundEvent(uint Guid, uint SoundType, float Volume) { public const uint Opcode = 0xF750u; public const int WireSize = 16; public static SoundEvent? TryParse(ReadOnlySpan body) { if (body.Length < WireSize || BinaryPrimitives.ReadUInt32LittleEndian(body) != Opcode) return null; return new SoundEvent( BinaryPrimitives.ReadUInt32LittleEndian(body[4..]), BinaryPrimitives.ReadUInt32LittleEndian(body[8..]), BinaryPrimitives.ReadSingleLittleEndian(body[12..])); } }