diff --git a/src/AcDream.Core/Items/ItemInstance.cs b/src/AcDream.Core/Items/ItemInstance.cs index d1b5685f..496958a8 100644 --- a/src/AcDream.Core/Items/ItemInstance.cs +++ b/src/AcDream.Core/Items/ItemInstance.cs @@ -136,6 +136,13 @@ public sealed class ItemInstance public uint IconId { get; set; } // 0x06xxxxxx public uint IconUnderlayId{ get; set; } // "magic" underlay public uint IconOverlayId { get; set; } // "enchanted" overlay + /// + /// UiEffects bitfield (retail PublicWeenieDesc._effects, acclient.h:37183). + /// Drives the icon's effect-overlay recolor (Magical=0x1 … Nether=0x1000). + /// CreateObject-only (weenieFlags 0x80) + live PublicUpdatePropertyInt(0x02CE); + /// appraise never carries it. 0 = no effect. + /// + public uint Effects { get; set; } public int StackSize { get; set; } = 1; public int StackSizeMax { get; set; } = 1; public int Burden { get; set; } // per-stack total diff --git a/src/AcDream.Core/Items/ItemRepository.cs b/src/AcDream.Core/Items/ItemRepository.cs index b92dd1ce..6ffcc85c 100644 --- a/src/AcDream.Core/Items/ItemRepository.cs +++ b/src/AcDream.Core/Items/ItemRepository.cs @@ -152,7 +152,7 @@ public sealed class ItemRepository /// /// public bool EnrichItem(uint objectId, uint iconId, string name, ItemType type, - uint iconOverlayId = 0, uint iconUnderlayId = 0) + uint iconOverlayId = 0, uint iconUnderlayId = 0, uint effects = 0) { if (!_items.TryGetValue(objectId, out var item)) return false; if (iconId != 0) item.IconId = iconId; @@ -160,6 +160,9 @@ public sealed class ItemRepository if (type != default) item.Type = type; if (iconOverlayId != 0) item.IconOverlayId = iconOverlayId; if (iconUnderlayId != 0) item.IconUnderlayId = iconUnderlayId; + // D.5.2: 0 is a meaningful "no effect" state (e.g. a caster out of mana), + // so assign unconditionally — re-composition reflects the CURRENT state. + item.Effects = effects; ItemPropertiesUpdated?.Invoke(item); return true; } @@ -184,6 +187,25 @@ public sealed class ItemRepository return true; } + /// PropertyInt.UiEffects (ACE enum value 18) — the icon effect bitfield. + public const uint UiEffectsPropertyId = 18u; + + /// + /// Apply a single PropertyInt update (from PublicUpdatePropertyInt 0x02CE) to an + /// item: store it in the bundle and, for known typed ints, mirror to the typed + /// field. Today: UiEffects (18) → . Fires + /// ItemPropertiesUpdated so bound widgets re-composite. Extensible hook for future + /// typed PropertyInts (StackSize, Structure, …). False if the item is unknown. + /// + public bool UpdateIntProperty(uint itemId, uint propertyId, int value) + { + if (!_items.TryGetValue(itemId, out var item)) return false; + item.Properties.Ints[propertyId] = value; + if (propertyId == UiEffectsPropertyId) item.Effects = (uint)value; + ItemPropertiesUpdated?.Invoke(item); + return true; + } + /// /// Flush the repository — typically called on logoff or teleport /// that drops the session's item state. diff --git a/tests/AcDream.Core.Tests/Items/ItemRepositoryTests.cs b/tests/AcDream.Core.Tests/Items/ItemRepositoryTests.cs index 23cc46fe..5b39b932 100644 --- a/tests/AcDream.Core.Tests/Items/ItemRepositoryTests.cs +++ b/tests/AcDream.Core.Tests/Items/ItemRepositoryTests.cs @@ -139,4 +139,36 @@ public sealed class ItemRepositoryTests var repo = new ItemRepository(); Assert.False(repo.EnrichItem(0x9999u, 0x06001234u, "x", ItemType.Misc)); } + + [Fact] + public void EnrichItem_carriesEffects() + { + var repo = new ItemRepository(); + repo.AddOrUpdate(new ItemInstance { ObjectId = 0x500000AAu }); + bool ok = repo.EnrichItem(0x500000AAu, iconId: 0x06001234u, name: "Wand", + type: ItemType.Caster, iconOverlayId: 0, iconUnderlayId: 0, effects: 0x1u); + Assert.True(ok); + Assert.Equal(0x1u, repo.GetItem(0x500000AAu)!.Effects); + } + + [Fact] + public void UpdateIntProperty_uiEffects_setsEffectsAndFires() + { + var repo = new ItemRepository(); + repo.AddOrUpdate(new ItemInstance { ObjectId = 0x500000ABu }); + ItemInstance? fired = null; + repo.ItemPropertiesUpdated += i => fired = i; + bool ok = repo.UpdateIntProperty(0x500000ABu, 18u, value: 0x9); // 18 = UiEffects + Assert.True(ok); + Assert.Equal(0x9u, repo.GetItem(0x500000ABu)!.Effects); + Assert.Equal(0x9, repo.GetItem(0x500000ABu)!.Properties.Ints[18u]); + Assert.NotNull(fired); + } + + [Fact] + public void UpdateIntProperty_unknownItem_returnsFalse() + { + var repo = new ItemRepository(); + Assert.False(repo.UpdateIntProperty(0xDEADBEEFu, 18u, 1)); + } }