using System; using System.Linq; using AcDream.Core.Combat; using AcDream.Core.Items; using Xunit; namespace AcDream.Core.Tests.Properties; /// /// Pins the wire-adjacent enums acdream shares with the retail client to the values /// in the Sept 2013 EoR header, docs/research/named-retail/acclient.h. Each /// table below is transcribed from the cited enum block, so a hand edit that /// drifts from retail fails here. /// /// These tables are the reason the 2026-07-29 enum campaign found real bugs: /// had its four drain/restore bits rotated, and /// had a shifted craft ladder plus locally recomputed composite /// masks. Both are corrected and pinned below. /// public sealed class RetailEnumConformanceTests { /// acclient.h:3788, enum DAMAGE_TYPE. FORCE_*_32_BIT omitted. public static TheoryData RetailDamageType => new() { { "Undef", 0x0 }, { "Slash", 0x1 }, { "Pierce", 0x2 }, { "Bludgeon", 0x4 }, { "Cold", 0x8 }, { "Fire", 0x10 }, { "Acid", 0x20 }, { "Electric", 0x40 }, { "Health", 0x80 }, { "Stamina", 0x100 }, { "Mana", 0x200 }, { "Nether", 0x400 }, { "Base", 0x10000000 }, }; [Theory] [MemberData(nameof(RetailDamageType))] public void DamageTypeMatchesRetail(string name, uint value) { Assert.True(Enum.IsDefined(typeof(DamageType), name), $"DamageType.{name} is missing"); Assert.Equal(value, (uint)Enum.Parse(name)); } [Fact] public void DamageTypeDeclaresNothingRetailDoesNot() { var expected = RetailDamageType.Select(r => (string)r[0]).OrderBy(n => n, StringComparer.Ordinal); var actual = Enum.GetNames().OrderBy(n => n, StringComparer.Ordinal); Assert.Equal(expected, actual); } /// /// acclient.h:3300, enum ITEM_TYPE. TYPE_UNDEF and TYPE_SELF both sit on 0 /// in retail; acdream spells that single zero None. FORCE_*_32_BIT omitted. /// public static TheoryData RetailItemType => new() { { "None", 0x0 }, { "MeleeWeapon", 0x1 }, { "Armor", 0x2 }, { "Vestements", 0x6 }, { "Clothing", 0x4 }, { "Jewelry", 0x8 }, { "Creature", 0x10 }, { "Food", 0x20 }, { "Money", 0x40 }, { "Misc", 0x80 }, { "MissileWeapon", 0x100 }, { "Weapon", 0x101 }, { "Container", 0x200 }, { "LockableMagicTarget", 0x280 }, { "Useless", 0x400 }, { "Gem", 0x800 }, { "SpellComponents", 0x1000 }, { "Writable", 0x2000 }, { "Key", 0x4000 }, { "Caster", 0x8000 }, { "WeaponOrCaster", 0x8101 }, { "RedirectableItemEnchantmentTarget", 0x8107 }, { "Portal", 0x10000 }, { "Lockable", 0x20000 }, { "PromissoryNote", 0x40000 }, { "ManaStone", 0x80000 }, { "ItemEnchantableTarget", 0x88B8F }, { "Service", 0x100000 }, { "MagicWieldable", 0x200000 }, { "Item", 0x2DFBEF }, { "CraftCookingBase", 0x400000 }, { "VendorGrocer", 0x446220 }, { "CraftAlchemyBase", 0x800000 }, { "CraftFletchingBase", 0x1000000 }, { "CraftAlchemyIntermediate", 0x4000000 }, { "CraftFletchingIntermediate", 0x8000000 }, { "LifeStone", 0x10000000 }, { "PortalMagicTarget", 0x10010000 }, { "TinkeringTool", 0x20000000 }, { "TinkeringMaterial", 0x40000000 }, { "VendorShopkeep", 0x480467A7 }, { "Gameboard", 0x80000000 }, }; [Theory] [MemberData(nameof(RetailItemType))] public void ItemTypeMatchesRetail(string name, uint value) { Assert.True(Enum.IsDefined(typeof(ItemType), name), $"ItemType.{name} is missing"); Assert.Equal(value, (uint)Enum.Parse(name)); } [Fact] public void ItemTypeDeclaresNothingRetailDoesNot() { var expected = RetailItemType.Select(r => (string)r[0]).OrderBy(n => n, StringComparer.Ordinal); var actual = Enum.GetNames().OrderBy(n => n, StringComparer.Ordinal); Assert.Equal(expected, actual); } /// /// The craft ladder specifically: retail leaves 0x02000000 unused, and the ACE /// weenie corpus attests 0x04000000 as Craft_Alchemy_Intermediate 235 times. This /// is the pairing acdream had wrong. /// [Fact] public void CraftLadderMatchesRetailAndLeavesTheUnusedBitUnclaimed() { Assert.Equal(0x00400000u, (uint)ItemType.CraftCookingBase); Assert.Equal(0x00800000u, (uint)ItemType.CraftAlchemyBase); Assert.Equal(0x01000000u, (uint)ItemType.CraftFletchingBase); Assert.Equal(0x04000000u, (uint)ItemType.CraftAlchemyIntermediate); Assert.Equal(0x08000000u, (uint)ItemType.CraftFletchingIntermediate); Assert.DoesNotContain(Enum.GetValues(), t => (uint)t == 0x02000000u); } /// /// Weapon and WeaponOrCaster were aliases before the campaign, because /// Weapon was recomputed locally as melee|missile|caster. Retail keeps them /// distinct and only the latter includes the caster bit. /// [Fact] public void WeaponExcludesCasterAndWeaponOrCasterIncludesIt() { Assert.NotEqual(ItemType.Weapon, ItemType.WeaponOrCaster); Assert.Equal(ItemType.MeleeWeapon | ItemType.MissileWeapon, ItemType.Weapon); Assert.Equal(ItemType.Weapon | ItemType.Caster, ItemType.WeaponOrCaster); } }