using System; using System.Collections.Generic; using DRWMotionCommand = DatReaderWriter.Enums.MotionCommand; namespace AcDream.Core.Physics; /// /// Reconstructs the 32-bit retail value from /// a 16-bit wire value broadcast in InterpretedMotionState.Commands[]. /// /// /// The server serializes MotionCommands as u16 (ACE /// InterpretedMotionState.cs:139), truncating the class byte (Style / /// SubState / Modifier / Action / ChatEmote / UI / Toggle / Mappable / /// Command — see r03 §3.1). The client must re-attach the class byte before /// routing the command into the motion table, because the same low 16 bits /// can map to different classes (e.g. 0x0003 is Ready as a SubState, /// but there's no other 0x0003). /// /// /// /// This is implemented as an eager lookup table built from all values of /// via reflection. If the wire value matches /// more than one enum value (different class bits), we prefer the /// lowest-class-numbered variant that has a non-zero class byte — roughly /// matching retail priority (Action < Modifier < SubState < Style). /// /// /// /// Cited references: /// /// /// references/ACE/Source/ACE.Server/Network/Motion/InterpretedMotionState.cs::Write /// L138-L144 — writer emits u16 for every command field. /// /// /// references/ACE/Source/ACE.Entity/Enum/CommandMasks.cs — the /// class bit assignments: 0x80=Style, 0x40=SubState, 0x20=Modifier, /// 0x10=Action, 0x13 and 0x12=ChatEmote (with Mappable set), etc. /// /// /// docs/research/deepdives/r03-motion-animation.md §3 — complete /// command catalogue. /// /// /// /// public static class MotionCommandResolver { // Lookup table built eagerly at type-init. Sparse: only values that // appear in the DRW enum (which came from the generated protocol XML) // are present. ~450 entries typical. private static readonly Dictionary s_lookup = BuildLookup(); /// /// Given a 16-bit wire value, return the full 32-bit MotionCommand /// (class byte restored). Returns 0 if no matching enum value exists. /// public static uint ReconstructFullCommand(ushort wireCommand) { if (wireCommand == 0) return 0u; s_lookup.TryGetValue(wireCommand, out var full); return full; } private static Dictionary BuildLookup() { var result = new Dictionary(512); var values = Enum.GetValues(typeof(DRWMotionCommand)); foreach (DRWMotionCommand v in values) { uint full = (uint)v; ushort lo = (ushort)(full & 0xFFFFu); if (lo == 0) continue; // Invalid / unmappable // If a value with this low-16-bit already exists, keep the one // with the lower class byte (Action=0x10 beats SubState=0x41 // beats Style=0x80). This matches retail: the server tends to // emit Actions and ChatEmotes far more often than Styles, so // the Action-class reconstruction is the common case. if (!result.TryGetValue(lo, out var existing) || (full >> 24) < (existing >> 24)) { result[lo] = full; } } return result; } }