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