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);
}
}