107 lines
4.4 KiB
C#
107 lines
4.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using DRWMotionCommand = DatReaderWriter.Enums.MotionCommand;
|
|
|
|
namespace AcDream.Core.Physics;
|
|
|
|
/// <summary>
|
|
/// Reconstructs the 32-bit retail <see cref="DRWMotionCommand"/> value from
|
|
/// a 16-bit wire value broadcast in <c>InterpretedMotionState.Commands[]</c>.
|
|
///
|
|
/// <para>
|
|
/// The server serializes MotionCommands as <c>u16</c> (ACE
|
|
/// <c>InterpretedMotionState.cs:139</c>), 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 <c>Ready</c> as a SubState,
|
|
/// but there's no other 0x0003).
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// This is implemented as an eager lookup table built from all values of
|
|
/// <see cref="DRWMotionCommand"/> 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).
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Cited references:
|
|
/// <list type="bullet">
|
|
/// <item><description>
|
|
/// <c>references/ACE/Source/ACE.Server/Network/Motion/InterpretedMotionState.cs::Write</c>
|
|
/// L138-L144 — writer emits u16 for every command field.
|
|
/// </description></item>
|
|
/// <item><description>
|
|
/// <c>references/ACE/Source/ACE.Entity/Enum/CommandMasks.cs</c> — the
|
|
/// class bit assignments: 0x80=Style, 0x40=SubState, 0x20=Modifier,
|
|
/// 0x10=Action, 0x13 and 0x12=ChatEmote (with Mappable set), etc.
|
|
/// </description></item>
|
|
/// <item><description>
|
|
/// <c>docs/research/deepdives/r03-motion-animation.md</c> §3 — complete
|
|
/// command catalogue.
|
|
/// </description></item>
|
|
/// </list>
|
|
/// </para>
|
|
/// </summary>
|
|
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<ushort, uint> s_lookup = BuildLookup();
|
|
|
|
/// <summary>
|
|
/// Given a 16-bit wire value, return the full 32-bit MotionCommand
|
|
/// (class byte restored). Returns 0 if no matching enum value exists.
|
|
/// </summary>
|
|
public static uint ReconstructFullCommand(ushort wireCommand)
|
|
{
|
|
if (wireCommand == 0) return 0u;
|
|
s_lookup.TryGetValue(wireCommand, out var full);
|
|
return full;
|
|
}
|
|
|
|
private static Dictionary<ushort, uint> BuildLookup()
|
|
{
|
|
var result = new Dictionary<ushort, uint>(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;
|
|
}
|
|
}
|
|
|
|
ApplyNamedRetailOverrides(result);
|
|
return result;
|
|
}
|
|
|
|
private static void ApplyNamedRetailOverrides(Dictionary<ushort, uint> result)
|
|
{
|
|
// The generated DRW enum is shifted by three entries starting at
|
|
// AllegianceHometownRecall. The named Sept 2013 retail command_ids
|
|
// table is authoritative here:
|
|
// named-retail/acclient_2013_pseudo_c.txt lines 1017626-1017658
|
|
// and command-name table lines 1068272-1068313.
|
|
//
|
|
// These values cover recall, offhand, attack 4-6, and fast/slow punch
|
|
// actions. Without the override, wire command 0x0170 reconstructs to
|
|
// IssueSlashCommand instead of OffhandSlashHigh, so offhand swing
|
|
// animations route as UI commands and never play.
|
|
for (ushort lo = 0x016E; lo <= 0x0197; lo++)
|
|
result[lo] = 0x10000000u | lo;
|
|
}
|
|
}
|