using System;
using System.Linq;
using AcDream.Core.Combat;
using AcDream.Core.Items;
using AcDream.Core.Physics;
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);
}
///
/// acclient.h:3193, enum INVENTORY_LOC — the composite slot groups only. The
/// 32 primitive slots were already correct and are pinned by EquipMaskTests.
///
public static TheoryData RetailEquipMaskComposites => new()
{
{ "Clothing", 0x080001FF },
{ "Armor", 0x00007E00 },
{ "Jewelry", 0x7C0F8000 },
{ "WristWear", 0x00030000 },
{ "FingerWear", 0x000C0000 },
{ "Sigil", 0x70000000 },
{ "ReadySlot", 0x03F00000 },
{ "Weapon", 0x02500000 },
{ "WeaponReadySlot", 0x03500000 },
{ "All", 0x7FFFFFFF },
{ "CanGoInReadySlot", 0x7FFFFFFF },
};
[Theory]
[MemberData(nameof(RetailEquipMaskComposites))]
public void EquipMaskCompositeMatchesRetail(string name, uint value)
{
Assert.True(Enum.IsDefined(typeof(EquipMask), name), $"EquipMask.{name} is missing");
Assert.Equal(value, (uint)Enum.Parse(name));
}
///
/// Retail's CLOTHING_LOC is the nine wear slots plus the cloak slot (bit 27) — it is
/// NOT the union of the wear slots alone. A type remark here used to claim the extra
/// bit was 31 and unnamed; it is 27 and it is .
///
[Fact]
public void ClothingCompositeIsTheWearSlotsPlusCloak()
{
EquipMask wearSlots =
EquipMask.HeadWear | EquipMask.ChestWear | EquipMask.AbdomenWear
| EquipMask.UpperArmWear | EquipMask.LowerArmWear | EquipMask.HandWear
| EquipMask.UpperLegWear | EquipMask.LowerLegWear | EquipMask.FootWear;
Assert.Equal(0x000001FFu, (uint)wearSlots);
Assert.Equal(EquipMask.Clothing, wearSlots | EquipMask.Cloak);
Assert.NotEqual(EquipMask.Clothing, wearSlots);
Assert.Equal(0u, (uint)EquipMask.Clothing & 0x80000000u);
}
/// acclient.h:3688, enum TransientState.
public static TheoryData RetailTransientState => new()
{
{ "Contact", 0x1 },
{ "OnWalkable", 0x2 },
{ "Sliding", 0x4 },
{ "WaterContact", 0x8 },
{ "StationaryFall", 0x10 },
{ "StationaryStop", 0x20 },
{ "StationaryStuck", 0x40 },
{ "Active", 0x80 },
{ "CheckEthereal", 0x100 },
};
[Theory]
[MemberData(nameof(RetailTransientState))]
public void TransientStateFlagsMatchesRetail(string name, uint value)
{
Assert.True(Enum.IsDefined(typeof(TransientStateFlags), name),
$"TransientStateFlags.{name} is missing");
Assert.Equal(value, (uint)Enum.Parse(name));
}
/// acclient.h:2815, enum PhysicsState, including the two bits retail itself reserves.
public static TheoryData RetailPhysicsState => new()
{
{ "Static", 0x1 },
{ "ReservedUnused1", 0x2 },
{ "Ethereal", 0x4 },
{ "ReportCollisions", 0x8 },
{ "IgnoreCollisions", 0x10 },
{ "NoDraw", 0x20 },
{ "Missile", 0x40 },
{ "Pushable", 0x80 },
{ "AlignPath", 0x100 },
{ "PathClipped", 0x200 },
{ "Gravity", 0x400 },
{ "Lighting", 0x800 },
{ "ParticleEmitter", 0x1000 },
{ "ReservedUnused2", 0x2000 },
{ "Hidden", 0x4000 },
{ "ScriptedCollision", 0x8000 },
{ "HasPhysicsBsp", 0x10000 },
{ "Inelastic", 0x20000 },
{ "HasDefaultAnim", 0x40000 },
{ "HasDefaultScript", 0x80000 },
{ "Cloaked", 0x100000 },
{ "ReportAsEnvironment", 0x200000 },
{ "EdgeSlide", 0x400000 },
{ "Sledding", 0x800000 },
{ "Frozen", 0x1000000 },
};
[Theory]
[MemberData(nameof(RetailPhysicsState))]
public void PhysicsStateFlagsMatchesRetail(string name, uint value)
{
Assert.True(Enum.IsDefined(typeof(PhysicsStateFlags), name),
$"PhysicsStateFlags.{name} is missing");
Assert.Equal(value, (uint)Enum.Parse(name));
}
/// acclient.h:4371, enum ATTACK_HEIGHT. NUM_ATTACK_HEIGHTS is a count, not a height.
[Theory]
[InlineData("Undef", 0)]
[InlineData("High", 1)]
[InlineData("Medium", 2)]
[InlineData("Low", 3)]
public void AttackHeightMatchesRetail(string name, int value)
{
Assert.True(Enum.IsDefined(typeof(AttackHeight), name), $"AttackHeight.{name} is missing");
Assert.Equal(value, (int)Enum.Parse(name));
}
///
/// acclient.h:3807, enum AttackType. The two composites are the interesting
/// part: retail spells Unarmed 0x19 and MultiStrike 0x79E0, and acdream derives both
/// from its primitives. This asserts the derivation lands on retail's literal.
///
[Fact]
public void AttackTypeCompositesMatchRetailLiterals()
{
Assert.Equal(0x19u, (uint)AttackType.Unarmed);
Assert.Equal(0x79E0u, (uint)AttackType.MultiStrike);
}
}