diff --git a/src/AcDream.Core/Items/ClientObject.cs b/src/AcDream.Core/Items/ClientObject.cs index 2ec5d309..cc3695b6 100644 --- a/src/AcDream.Core/Items/ClientObject.cs +++ b/src/AcDream.Core/Items/ClientObject.cs @@ -57,46 +57,46 @@ public enum ItemType : uint } /// -/// Equipment slot bitmask. 31 slots from head to Aetheria. Paperdoll -/// widget offsets +0x604..+0x660 in the retail panel correspond -/// to these bits 1:1 (see r06 §2 and UI slice 05 paperdoll section). +/// Equipment slot bitmask — the verbatim retail INVENTORY_LOC enum +/// (docs/research/named-retail/acclient.h:3193; identical to ACE's EquipMask). +/// The wire (ValidLocations / CurrentWieldedLocation / WieldObject EquipLoc) delivers +/// these exact bits. Pinned by EquipMaskTests — do NOT renumber. /// [Flags] public enum EquipMask : uint { - None = 0, - HeadWear = 0x00000001, - ChestWear = 0x00000002, - AbdomenWear = 0x00000004, - UpperArmWear = 0x00000008, - LowerArmWear = 0x00000010, - HandWear = 0x00000020, - UpperLegWear = 0x00000040, - LowerLegWear = 0x00000080, - FootWear = 0x00000100, - ChestArmor = 0x00000200, - AbdomenArmor = 0x00000400, - UpperArmArmor = 0x00000800, - LowerArmArmor = 0x00001000, - HandArmor = 0x00002000, - UpperLegArmor = 0x00004000, - LowerLegArmor = 0x00008000, - FootArmor = 0x00010000, - Necklace = 0x00020000, - LeftBracelet = 0x00040000, - RightBracelet = 0x00080000, - LeftRing = 0x00100000, - RightRing = 0x00200000, - MeleeWeapon = 0x00400000, - Shield = 0x00800000, - MissileWeapon = 0x01000000, - Held = 0x02000000, // lit torch, book in hand - MissileAmmo = 0x04000000, - Cloak = 0x08000000, - TrinketOne = 0x10000000, - AetheriaRed = 0x20000000, - AetheriaYellow= 0x40000000, - AetheriaBlue = 0x80000000u, + None = 0x00000000, + HeadWear = 0x00000001, + ChestWear = 0x00000002, + AbdomenWear = 0x00000004, + UpperArmWear = 0x00000008, + LowerArmWear = 0x00000010, + HandWear = 0x00000020, + UpperLegWear = 0x00000040, + LowerLegWear = 0x00000080, + FootWear = 0x00000100, + ChestArmor = 0x00000200, + AbdomenArmor = 0x00000400, + UpperArmArmor = 0x00000800, + LowerArmArmor = 0x00001000, + UpperLegArmor = 0x00002000, + LowerLegArmor = 0x00004000, + NeckWear = 0x00008000, + WristWearLeft = 0x00010000, + WristWearRight = 0x00020000, + FingerWearLeft = 0x00040000, + FingerWearRight= 0x00080000, + MeleeWeapon = 0x00100000, + Shield = 0x00200000, + MissileWeapon = 0x00400000, + MissileAmmo = 0x00800000, + Held = 0x01000000, + TwoHanded = 0x02000000, + TrinketOne = 0x04000000, + Cloak = 0x08000000, + SigilOne = 0x10000000, + SigilTwo = 0x20000000, + SigilThree = 0x40000000, } /// diff --git a/tests/AcDream.Core.Tests/Items/EquipMaskTests.cs b/tests/AcDream.Core.Tests/Items/EquipMaskTests.cs new file mode 100644 index 00000000..d3576603 --- /dev/null +++ b/tests/AcDream.Core.Tests/Items/EquipMaskTests.cs @@ -0,0 +1,54 @@ +using AcDream.Core.Items; +using Xunit; + +namespace AcDream.Core.Tests.Items; + +/// +/// Pins every EquipMask member to the verbatim retail INVENTORY_LOC value +/// (docs/research/named-retail/acclient.h:3193). The wire delivers these exact bits +/// (ACE EquipMask == retail INVENTORY_LOC); any drift desyncs the paperdoll element-id→mask +/// map and the GetAndWieldItem wire. This test is the anti-regression lock. +/// +public sealed class EquipMaskTests +{ + [Theory] + [InlineData(0x00000000u, EquipMask.None)] + [InlineData(0x00000001u, EquipMask.HeadWear)] + [InlineData(0x00000002u, EquipMask.ChestWear)] + [InlineData(0x00000004u, EquipMask.AbdomenWear)] + [InlineData(0x00000008u, EquipMask.UpperArmWear)] + [InlineData(0x00000010u, EquipMask.LowerArmWear)] + [InlineData(0x00000020u, EquipMask.HandWear)] + [InlineData(0x00000040u, EquipMask.UpperLegWear)] + [InlineData(0x00000080u, EquipMask.LowerLegWear)] + [InlineData(0x00000100u, EquipMask.FootWear)] + [InlineData(0x00000200u, EquipMask.ChestArmor)] + [InlineData(0x00000400u, EquipMask.AbdomenArmor)] + [InlineData(0x00000800u, EquipMask.UpperArmArmor)] + [InlineData(0x00001000u, EquipMask.LowerArmArmor)] + [InlineData(0x00002000u, EquipMask.UpperLegArmor)] + [InlineData(0x00004000u, EquipMask.LowerLegArmor)] + [InlineData(0x00008000u, EquipMask.NeckWear)] + [InlineData(0x00010000u, EquipMask.WristWearLeft)] + [InlineData(0x00020000u, EquipMask.WristWearRight)] + [InlineData(0x00040000u, EquipMask.FingerWearLeft)] + [InlineData(0x00080000u, EquipMask.FingerWearRight)] + [InlineData(0x00100000u, EquipMask.MeleeWeapon)] + [InlineData(0x00200000u, EquipMask.Shield)] + [InlineData(0x00400000u, EquipMask.MissileWeapon)] + [InlineData(0x00800000u, EquipMask.MissileAmmo)] + [InlineData(0x01000000u, EquipMask.Held)] + [InlineData(0x02000000u, EquipMask.TwoHanded)] + [InlineData(0x04000000u, EquipMask.TrinketOne)] + [InlineData(0x08000000u, EquipMask.Cloak)] + [InlineData(0x10000000u, EquipMask.SigilOne)] + [InlineData(0x20000000u, EquipMask.SigilTwo)] + [InlineData(0x40000000u, EquipMask.SigilThree)] + public void Member_has_canonical_retail_value(uint expected, EquipMask member) + => Assert.Equal(expected, (uint)member); + + [Fact] + public void Weapon_ready_slot_composite_is_0x3500000() // acclient.h:3235 WEAPON_READY_SLOT_LOC + => Assert.Equal(0x3500000u, + (uint)(EquipMask.MeleeWeapon | EquipMask.MissileWeapon | EquipMask.Held | EquipMask.TwoHanded)); +}