diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 8439d051..fef89e87 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -595,7 +595,7 @@ public sealed class GameWindow : IDisposable // SpellTable.Empty if the file is missing (e.g. tooling contexts). public readonly AcDream.Core.Spells.SpellTable SpellTable = LoadSpellTable(); public readonly AcDream.Core.Spells.Spellbook SpellBook = null!; - public readonly AcDream.Core.Items.ItemRepository Items = new(); + public readonly AcDream.Core.Items.ClientObjectTable Objects = new(); /// Persisted hotbar shortcuts from the last PlayerDescription (D.5.1 toolbar source). public IReadOnlyList Shortcuts { get; private set; } = System.Array.Empty(); @@ -2000,7 +2000,7 @@ public sealed class GameWindow : IDisposable if (toolbarLayout is not null) { _toolbarController = AcDream.App.UI.Layout.ToolbarController.Bind( - toolbarLayout, Items, + toolbarLayout, Objects, () => Shortcuts, iconIds: (type, icon, under, over, effects) => iconComposer.GetIcon(type, icon, under, over, effects), useItem: guid => UseItemByGuid(guid), @@ -2411,7 +2411,7 @@ public sealed class GameWindow : IDisposable var skillTable = _dats?.Get(0x0E000004u); AcDream.Core.Net.GameEventWiring.WireAll( - _liveSession.GameEvents, Items, Combat, SpellBook, Chat, LocalPlayer, + _liveSession.GameEvents, Objects, Combat, SpellBook, Chat, LocalPlayer, TurbineChat, resolveSkillFormulaBonus: (skillId, attrCurrents) => { @@ -2636,8 +2636,8 @@ public sealed class GameWindow : IDisposable // repository so a draining/charging item re-composites its icon in real time. _liveSession.ObjectIntPropertyUpdated += u => { - if (u.Property == AcDream.Core.Items.ItemRepository.UiEffectsPropertyId) - Items.UpdateIntProperty(u.Guid, u.Property, u.Value); + if (u.Property == AcDream.Core.Items.ClientObjectTable.UiEffectsPropertyId) + Objects.UpdateIntProperty(u.Guid, u.Property, u.Value); }; } @@ -2652,7 +2652,7 @@ public sealed class GameWindow : IDisposable // with the icon/name/type its CreateObject carries, so the toolbar can render it. // D.5.1 (2026-06-17): also pass overlay/underlay ids from the extended // WeenieHeader tail so IconComposer composites all icon layers. - Items.EnrichItem(spawn.Guid, spawn.IconId, spawn.Name ?? string.Empty, + Objects.EnrichItem(spawn.Guid, spawn.IconId, spawn.Name ?? string.Empty, (AcDream.Core.Items.ItemType)(spawn.ItemType ?? 0), spawn.IconOverlayId, spawn.IconUnderlayId, spawn.UiEffects); diff --git a/src/AcDream.App/UI/Layout/ToolbarController.cs b/src/AcDream.App/UI/Layout/ToolbarController.cs index f33ddfe2..12b4f77b 100644 --- a/src/AcDream.App/UI/Layout/ToolbarController.cs +++ b/src/AcDream.App/UI/Layout/ToolbarController.cs @@ -49,7 +49,7 @@ public sealed class ToolbarController private readonly UiItemList?[] _slots = new UiItemList?[SlotIds.Length]; private readonly UiElement?[] _combatIndicators = new UiElement?[CombatIndicatorIds.Length]; - private readonly ItemRepository _repo; + private readonly ClientObjectTable _repo; private readonly Func> _shortcuts; private readonly Func _iconIds; // (itemType, icon, underlay, overlay, effects) → GL tex private readonly Action _useItem; // guid → fire UseObject @@ -68,7 +68,7 @@ public sealed class ToolbarController private ToolbarController( ImportedLayout layout, - ItemRepository repo, + ClientObjectTable repo, Func> shortcuts, Func iconIds, Action useItem, @@ -110,8 +110,8 @@ public sealed class ToolbarController combatState.CombatModeChanged += SetCombatMode; // Re-bind any deferred slot whenever the repo learns about a new/updated item. - repo.ItemAdded += _ => Populate(); - repo.ItemPropertiesUpdated += _ => Populate(); + repo.ObjectAdded += _ => Populate(); + repo.ObjectUpdated += _ => Populate(); } /// @@ -146,7 +146,7 @@ public sealed class ToolbarController /// public static ToolbarController Bind( ImportedLayout layout, - ItemRepository repo, + ClientObjectTable repo, Func> shortcuts, Func iconIds, Action useItem, @@ -165,7 +165,7 @@ public sealed class ToolbarController /// Port of gmToolbarUI::UpdateFromPlayerDesc: clear all slots, then bind /// each shortcut entry that has a resolved item in the repository. /// Entries whose item is not yet in the repo are silently skipped here; the - /// ItemAdded event re-fires this method when the item arrives + /// ObjectAdded event re-fires this method when the item arrives /// (matching retail's SetDelayedShortcutNum deferred-rebind path). /// public void Populate() @@ -180,8 +180,8 @@ public sealed class ToolbarController var list = _slots[(int)sc.Index]; if (list is null) continue; - var item = _repo.GetItem(sc.ObjectGuid); - if (item is null) continue; // deferred: ItemAdded will re-call Populate + var item = _repo.Get(sc.ObjectGuid); + if (item is null) continue; // deferred: ObjectAdded will re-call Populate uint tex = _iconIds(item.Type, item.IconId, item.IconUnderlayId, item.IconOverlayId, item.Effects); list.Cell.SetItem(sc.ObjectGuid, tex); diff --git a/src/AcDream.Core.Net/GameEventWiring.cs b/src/AcDream.Core.Net/GameEventWiring.cs index 1aeefe2a..83030988 100644 --- a/src/AcDream.Core.Net/GameEventWiring.cs +++ b/src/AcDream.Core.Net/GameEventWiring.cs @@ -11,7 +11,7 @@ namespace AcDream.Core.Net; /// /// Central registration point that wires every parsed GameEvent from /// into the appropriate Core state -/// class (, , +/// class (, , /// , ). /// /// @@ -32,7 +32,7 @@ public static class GameEventWiring { public static void WireAll( GameEventDispatcher dispatcher, - ItemRepository items, + ClientObjectTable items, CombatState combat, Spellbook spellbook, ChatLog chat, @@ -251,7 +251,7 @@ public static class GameEventWiring var p = AppraiseInfoParser.TryParse(e.Payload.Span); if (p is null || !p.Value.Success) return; // Merge parsed properties into the item if we know about it. - if (items.GetItem(p.Value.Guid) is not null) + if (items.Get(p.Value.Guid) is not null) items.UpdateProperties(p.Value.Guid, p.Value.Properties); // Spellbook from appraise: for caster items / scrolls this is // the cast-on-use list. The local player's full learned @@ -400,7 +400,7 @@ public static class GameEventWiring Console.WriteLine($"vitals: PD-ench spell={ench.SpellId} layer={ench.Layer} bucket={ench.Bucket} key={ench.StatModKey} val={ench.StatModValue}"); } - // Issue #13 — register inventory entries with ItemRepository so + // Issue #13 — register inventory entries with ClientObjectTable so // panels (inventory, paperdoll, hotbars) light up after login. // Equipped entries share the same ObjectId as inventory entries // (an equipped item is also in inventory) — register both, but @@ -408,9 +408,9 @@ public static class GameEventWiring // MoveItem so paperdoll can render. foreach (var inv in p.Value.Inventory) { - if (items.GetItem(inv.Guid) is null) + if (items.Get(inv.Guid) is null) { - items.AddOrUpdate(new ItemInstance + items.AddOrUpdate(new ClientObject { ObjectId = inv.Guid, WeenieClassId = inv.ContainerType, @@ -419,9 +419,9 @@ public static class GameEventWiring } foreach (var eq in p.Value.Equipped) { - if (items.GetItem(eq.Guid) is null) + if (items.Get(eq.Guid) is null) { - items.AddOrUpdate(new ItemInstance + items.AddOrUpdate(new ClientObject { ObjectId = eq.Guid, WeenieClassId = 0, diff --git a/src/AcDream.Core/Items/ItemInstance.cs b/src/AcDream.Core/Items/ClientObject.cs similarity index 96% rename from src/AcDream.Core/Items/ItemInstance.cs rename to src/AcDream.Core/Items/ClientObject.cs index 496958a8..773c1c4e 100644 --- a/src/AcDream.Core/Items/ItemInstance.cs +++ b/src/AcDream.Core/Items/ClientObject.cs @@ -121,11 +121,10 @@ public sealed class PropertyBundle } /// -/// Per-item live state. The server owns item identity (ObjectId); -/// acdream mirrors properties here on CreateObject and updates -/// via UpdateProperty* messages. +/// Per-object live state (the data side of every server object — items and creatures alike). +/// Retail ACCWeenieObject. /// -public sealed class ItemInstance +public sealed class ClientObject { public uint ObjectId { get; init; } public uint WeenieClassId { get; init; } // "blueprint" @@ -164,7 +163,7 @@ public sealed class Container public int Capacity { get; set; } = 102; // main inv default public int SideCapacity { get; set; } = 0; // 0 for side-pack public int BurdenLimit { get; set; } - public List Items { get; } = new(); + public List Items { get; } = new(); public List SidePacks { get; } = new(); // empty for side-pack public bool IsSidePack => SideCapacity == 0; } diff --git a/src/AcDream.Core/Items/ItemRepository.cs b/src/AcDream.Core/Items/ClientObjectTable.cs similarity index 74% rename from src/AcDream.Core/Items/ItemRepository.cs rename to src/AcDream.Core/Items/ClientObjectTable.cs index 5543c958..6e4e088e 100644 --- a/src/AcDream.Core/Items/ItemRepository.cs +++ b/src/AcDream.Core/Items/ClientObjectTable.cs @@ -5,16 +5,14 @@ using System.Collections.Generic; namespace AcDream.Core.Items; /// -/// Live item-state mirror — the client-side view of every item the -/// server has spawned for this session. Owns -/// records, tracks which container holds each item, and fires events so -/// UI panels (inventory, paperdoll, hotbars) can redraw on change. +/// The client's table of every server object (retail weenie_object_table / +/// CObjectMaint). Resolve by guid via Get. /// /// /// Retail semantics (r06): /// /// -/// Every item is a with a unique +/// Every object is a with a unique /// ObjectId. CreateObject seeds it when the server tells us /// the item exists (in our inventory, on the ground, in a /// vendor's list, etc). @@ -40,42 +38,42 @@ namespace AcDream.Core.Items; /// corrupting state. /// /// -public sealed class ItemRepository +public sealed class ClientObjectTable { - private readonly ConcurrentDictionary _items = new(); + private readonly ConcurrentDictionary _items = new(); private readonly ConcurrentDictionary _containers = new(); - /// Fires when an item is first added to the session. - public event Action? ItemAdded; + /// Fires when an object is first added to the session. + public event Action? ObjectAdded; /// - /// Fires when an item's container / slot changes (moved between + /// Fires when an object's container / slot changes (moved between /// packs, equipped, unequipped, dropped on ground). Old and new /// container ids are 0 if origin or destination is "world" / "nowhere". /// - public event Action? ItemMoved; + public event Action? ObjectMoved; - /// Fires when an item is removed from the session. - public event Action? ItemRemoved; + /// Fires when an object is removed from the session. + public event Action? ObjectRemoved; - /// Fires when an item's properties are updated (typically after Appraise). - public event Action? ItemPropertiesUpdated; + /// Fires when an object's properties are updated (typically after Appraise). + public event Action? ObjectUpdated; /// PropertyInt.UiEffects (ACE enum value 18) — the icon effect bitfield; /// the typed mirror maintains on - /// . + /// . public const uint UiEffectsPropertyId = 18u; - public int ItemCount => _items.Count; + public int ObjectCount => _items.Count; public int ContainerCount => _containers.Count; - public IEnumerable Items => _items.Values; + public IEnumerable Objects => _items.Values; public IEnumerable Containers => _containers.Values; /// - /// Look up an item by its server-assigned ObjectId. + /// Look up an object by its server-assigned ObjectId. /// - public ItemInstance? GetItem(uint objectId) => + public ClientObject? Get(uint objectId) => _items.TryGetValue(objectId, out var item) ? item : null; /// @@ -88,17 +86,17 @@ public sealed class ItemRepository _containers.TryGetValue(objectId, out var c) ? c : null; /// - /// Register / refresh an item in the repository. Called on + /// Register / refresh an object in the table. Called on /// CreateObject for item-typed weenies and on IdentifyObjectResponse /// to fill in detail properties. /// - public void AddOrUpdate(ItemInstance item) + public void AddOrUpdate(ClientObject item) { ArgumentNullException.ThrowIfNull(item); bool existed = _items.ContainsKey(item.ObjectId); _items[item.ObjectId] = item; - if (!existed) ItemAdded?.Invoke(item); - else ItemPropertiesUpdated?.Invoke(item); + if (!existed) ObjectAdded?.Invoke(item); + else ObjectUpdated?.Invoke(item); } /// @@ -114,7 +112,7 @@ public sealed class ItemRepository /// Handle a server-driven move — called from /// InventoryPutObjInContainer (0x0022) and WieldObject (0x0023) /// handlers. Updates ContainerId / ContainerSlot / CurrentlyEquippedLocation - /// and fires ItemMoved. + /// and fires ObjectMoved. /// public bool MoveItem(uint itemId, uint newContainerId, int newSlot = -1, EquipMask newEquipLocation = EquipMask.None) @@ -126,7 +124,7 @@ public sealed class ItemRepository item.ContainerSlot = newSlot; item.CurrentlyEquippedLocation = newEquipLocation; - ItemMoved?.Invoke(item, oldContainer, newContainerId); + ObjectMoved?.Invoke(item, oldContainer, newContainerId); return true; } @@ -137,16 +135,16 @@ public sealed class ItemRepository public bool Remove(uint itemId) { if (!_items.TryRemove(itemId, out var item)) return false; - ItemRemoved?.Invoke(item); + ObjectRemoved?.Invoke(item); return true; } /// - /// Enrich an already-known item (a stub created from PlayerDescription) with the + /// Enrich an already-known object (a stub created from PlayerDescription) with the /// fuller data carried by its CreateObject (icon, name, type). Returns false if the - /// item isn't tracked yet — phase 1 enriches existing items only; full + /// object isn't tracked yet — phase 1 enriches existing objects only; full /// CreateObject ingestion of newly-acquired items is the inventory phase. - /// Raises ItemPropertiesUpdated whenever the item is found (matching the + /// Raises ObjectUpdated whenever the object is found (matching the /// UpdateProperties convention — it fires on found regardless of whether a field /// actually changed) so bound widgets (the toolbar) re-render. /// @@ -168,13 +166,13 @@ public sealed class ItemRepository // 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); + ObjectUpdated?.Invoke(item); return true; } /// /// Apply a patch (e.g. from an - /// IdentifyObjectResponse) to an existing item. Individual + /// IdentifyObjectResponse) to an existing object. Individual /// keys in the incoming bundle overwrite existing values; keys not /// present are left untouched. /// @@ -188,29 +186,29 @@ public sealed class ItemRepository foreach (var kv in incoming.Strings) item.Properties.Strings[kv.Key] = kv.Value; foreach (var kv in incoming.DataIds) item.Properties.DataIds[kv.Key] = kv.Value; foreach (var kv in incoming.InstanceIds) item.Properties.InstanceIds[kv.Key] = kv.Value; - ItemPropertiesUpdated?.Invoke(item); + ObjectUpdated?.Invoke(item); return true; } /// /// 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. + /// object: store it in the bundle and, for known typed ints, mirror to the typed + /// field. Today: UiEffects (18) → . Fires + /// ObjectUpdated so bound widgets re-composite. Extensible hook for future + /// typed PropertyInts (StackSize, Structure, …). False if the object 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); + ObjectUpdated?.Invoke(item); return true; } /// - /// Flush the repository — typically called on logoff or teleport - /// that drops the session's item state. + /// Flush the table — typically called on logoff or teleport + /// that drops the session's object state. /// public void Clear() { diff --git a/tests/AcDream.App.Tests/UI/Layout/ToolbarControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/ToolbarControllerTests.cs index d8d0a6f9..9bd13b00 100644 --- a/tests/AcDream.App.Tests/UI/Layout/ToolbarControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/ToolbarControllerTests.cs @@ -47,8 +47,8 @@ public class ToolbarControllerTests public void Populate_bindsShortcutToCorrectSlot() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); - repo.AddOrUpdate(new ItemInstance { ObjectId = 0x5001u, WeenieClassId = 1u, IconId = 0x06001234u }); + var repo = new ClientObjectTable(); + repo.AddOrUpdate(new ClientObject { ObjectId = 0x5001u, WeenieClassId = 1u, IconId = 0x06001234u }); var shortcuts = new List { new(Index: 0, ObjectGuid: 0x5001u, SpellId: 0, Layer: 0) }; @@ -64,7 +64,7 @@ public class ToolbarControllerTests public void DeferredRebind_whenItemArrivesLate() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); // item NOT present yet + var repo = new ClientObjectTable(); // item NOT present yet var shortcuts = new List { new(Index: 2, ObjectGuid: 0x5002u, SpellId: 0, Layer: 0) }; @@ -72,7 +72,7 @@ public class ToolbarControllerTests iconIds: (_,_,_,_,_) => 0x88u, useItem: _ => { }); Assert.Equal(0u, slots[Row1[2]].Cell.ItemId); // not bound yet - repo.AddOrUpdate(new ItemInstance { ObjectId = 0x5002u, WeenieClassId = 1u, IconId = 0x06005678u }); + repo.AddOrUpdate(new ClientObject { ObjectId = 0x5002u, WeenieClassId = 1u, IconId = 0x06005678u }); Assert.Equal(0x5002u, slots[Row1[2]].Cell.ItemId); // rebound on ItemAdded } @@ -81,8 +81,8 @@ public class ToolbarControllerTests public void Click_emitsUseForBoundItem() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); - repo.AddOrUpdate(new ItemInstance { ObjectId = 0x5001u, WeenieClassId = 1u, IconId = 0x06001234u }); + var repo = new ClientObjectTable(); + repo.AddOrUpdate(new ClientObject { ObjectId = 0x5001u, WeenieClassId = 1u, IconId = 0x06001234u }); var shortcuts = new List { new(Index: 0, ObjectGuid: 0x5001u, SpellId: 0, Layer: 0) }; uint used = 0; @@ -106,7 +106,7 @@ public class ToolbarControllerTests public void CombatIndicator_defaultNonCombat_onlyPeaceVisible() { var (layout, _, indicators) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); ToolbarController.Bind(layout, repo, () => Array.Empty(), @@ -126,7 +126,7 @@ public class ToolbarControllerTests public void CombatIndicator_setCombatModeMelee_onlyMeleeVisible() { var (layout, _, indicators) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var ctrl = ToolbarController.Bind(layout, repo, () => Array.Empty(), @@ -147,7 +147,7 @@ public class ToolbarControllerTests public void CombatIndicator_liveSignal_updatesWhenCombatStateChanges() { var (layout, _, indicators) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var combat = new CombatState(); ToolbarController.Bind(layout, repo, @@ -187,7 +187,7 @@ public class ToolbarControllerTests public void ShortcutNumbers_afterBind_topRowHasNumbers_bottomRowEmpty() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); ToolbarController.Bind(layout, repo, () => Array.Empty(), @@ -213,7 +213,7 @@ public class ToolbarControllerTests public void ShortcutNumbers_setCombatModeWar_topRowUsesWarDigits() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var ctrl = ToolbarController.Bind(layout, repo, () => Array.Empty(), iconIds: (_,_,_,_,_) => 0u, useItem: _ => { }, @@ -240,7 +240,7 @@ public class ToolbarControllerTests public void ShortcutNumbers_backToNonCombat_restoresPeaceDigits() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var ctrl = ToolbarController.Bind(layout, repo, () => Array.Empty(), iconIds: (_,_,_,_,_) => 0u, useItem: _ => { }, @@ -261,7 +261,7 @@ public class ToolbarControllerTests public void ShortcutNumbers_digitArraysInjected() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); ToolbarController.Bind(layout, repo, () => Array.Empty(), @@ -283,7 +283,7 @@ public class ToolbarControllerTests public void ShortcutNumbers_emptyDigitArrayInjected() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); ToolbarController.Bind(layout, repo, () => Array.Empty(), @@ -304,7 +304,7 @@ public class ToolbarControllerTests public void ShortcutNumbers_nullEmptyDigits_cellsHaveNullEmptyDigits() { var (layout, slots, _) = FakeToolbar(); - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); ToolbarController.Bind(layout, repo, () => Array.Empty(), diff --git a/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs b/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs index daadaa1a..45008de9 100644 --- a/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs +++ b/tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs @@ -37,10 +37,10 @@ public sealed class GameEventWiringTests return body; } - private static (GameEventDispatcher, ItemRepository, CombatState, Spellbook, ChatLog) MakeAll() + private static (GameEventDispatcher, ClientObjectTable, CombatState, Spellbook, ChatLog) MakeAll() { var dispatcher = new GameEventDispatcher(); - var items = new ItemRepository(); + var items = new ClientObjectTable(); var combat = new CombatState(); var spellbook = new Spellbook(); var chat = new ChatLog(); @@ -101,10 +101,10 @@ public sealed class GameEventWiringTests } [Fact] - public void WireAll_WieldObject_RoutesToItemRepository() + public void WireAll_WieldObject_RoutesToClientObjectTable() { var (d, items, _, _, _) = MakeAll(); - items.AddOrUpdate(new ItemInstance { ObjectId = 0x1000, WeenieClassId = 1 }); + items.AddOrUpdate(new ClientObject { ObjectId = 0x1000, WeenieClassId = 1 }); byte[] payload = new byte[12]; BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x1000); @@ -114,7 +114,7 @@ public sealed class GameEventWiringTests var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.WieldObject, payload)); d.Dispatch(env!.Value); - var item = items.GetItem(0x1000); + var item = items.Get(0x1000); Assert.NotNull(item); Assert.Equal(EquipMask.MeleeWeapon, item!.CurrentlyEquippedLocation); Assert.Equal(0x2000u, item.ContainerId); @@ -141,7 +141,7 @@ public sealed class GameEventWiringTests // through WireAll, lands in LocalPlayerState with the right // ranks/start/current values. var dispatcher = new GameEventDispatcher(); - var items = new ItemRepository(); + var items = new ClientObjectTable(); var combat = new CombatState(); var spellbook = new Spellbook(); var chat = new ChatLog(); @@ -200,7 +200,7 @@ public sealed class GameEventWiringTests public void WireAll_PlayerDescription_FeedsSpellbook() { var dispatcher = new GameEventDispatcher(); - var items = new ItemRepository(); + var items = new ClientObjectTable(); var combat = new CombatState(); var spellbook = new Spellbook(); var chat = new ChatLog(); @@ -330,20 +330,20 @@ public sealed class GameEventWiringTests } [Fact] - public void PlayerDescription_RegistersInventoryEntries_InItemRepository() + public void PlayerDescription_RegistersInventoryEntries_InClientObjectTable() { // Issue #13 acceptance test: after a PlayerDescription with non-empty - // Inventory is dispatched through WireAll, ItemRepository.ItemCount > 0. + // Inventory is dispatched through WireAll, ClientObjectTable.ObjectCount > 0. // Wire format: strict path (no GAMEPLAY_OPTIONS bit) so inventory + // equipped follow directly after spellbook_filters. var dispatcher = new GameEventDispatcher(); - var items = new ItemRepository(); + var items = new ClientObjectTable(); var combat = new CombatState(); var spellbook = new Spellbook(); var chat = new ChatLog(); GameEventWiring.WireAll(dispatcher, items, combat, spellbook, chat); - Assert.Equal(0, items.ItemCount); // pre-condition + Assert.Equal(0, items.ObjectCount); // pre-condition var sb = new MemoryStream(); using var w = new BinaryWriter(sb); @@ -370,9 +370,9 @@ public sealed class GameEventWiringTests var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.PlayerDescription, sb.ToArray())); dispatcher.Dispatch(env!.Value); - Assert.Equal(2, items.ItemCount); - Assert.NotNull(items.GetItem(0x50000A01u)); - Assert.NotNull(items.GetItem(0x50000A02u)); + Assert.Equal(2, items.ObjectCount); + Assert.NotNull(items.Get(0x50000A01u)); + Assert.NotNull(items.Get(0x50000A02u)); } [Fact] @@ -380,14 +380,14 @@ public sealed class GameEventWiringTests { // D.5.1 Task 4: WireAll must forward parsed.Shortcuts to the onShortcuts // callback so the toolbar can read them without keeping a parser reference. - // Mirrors PlayerDescription_RegistersInventoryEntries_InItemRepository + // Mirrors PlayerDescription_RegistersInventoryEntries_InClientObjectTable // for the harness pattern; adds the Shortcut flag (0x1) + one 12-byte // entry, followed by the legacy-hotbar count (0) + spellbook_filters (0) // then empty inventory and equipped. IReadOnlyList? got = null; var dispatcher = new GameEventDispatcher(); - var items = new ItemRepository(); + var items = new ClientObjectTable(); var combat = new CombatState(); var spellbook = new Spellbook(); var chat = new ChatLog(); diff --git a/tests/AcDream.Core.Tests/Items/ItemRepositoryTests.cs b/tests/AcDream.Core.Tests/Items/ClientObjectTableTests.cs similarity index 62% rename from tests/AcDream.Core.Tests/Items/ItemRepositoryTests.cs rename to tests/AcDream.Core.Tests/Items/ClientObjectTableTests.cs index 9db4a454..e11de2b4 100644 --- a/tests/AcDream.Core.Tests/Items/ItemRepositoryTests.cs +++ b/tests/AcDream.Core.Tests/Items/ClientObjectTableTests.cs @@ -3,10 +3,10 @@ using Xunit; namespace AcDream.Core.Tests.Items; -public sealed class ItemRepositoryTests +public sealed class ClientObjectTableTests { - private static ItemInstance MakeItem(uint id, string name = "Widget") => - new ItemInstance + private static ClientObject MakeItem(uint id, string name = "Widget") => + new ClientObject { ObjectId = id, WeenieClassId = 1, @@ -20,27 +20,27 @@ public sealed class ItemRepositoryTests [Fact] public void AddOrUpdate_FiresAddedEvent() { - var repo = new ItemRepository(); - ItemInstance? added = null; - repo.ItemAdded += i => added = i; + var repo = new ClientObjectTable(); + ClientObject? added = null; + repo.ObjectAdded += i => added = i; var item = MakeItem(100); repo.AddOrUpdate(item); Assert.Same(item, added); - Assert.Equal(1, repo.ItemCount); - Assert.Same(item, repo.GetItem(100)); + Assert.Equal(1, repo.ObjectCount); + Assert.Same(item, repo.Get(100)); } [Fact] public void AddOrUpdate_ExistingItem_FiresPropertiesUpdated() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var item = MakeItem(100); repo.AddOrUpdate(item); int propUpdateCount = 0; - repo.ItemPropertiesUpdated += _ => propUpdateCount++; + repo.ObjectUpdated += _ => propUpdateCount++; repo.AddOrUpdate(item); // second call is an update Assert.Equal(1, propUpdateCount); @@ -49,12 +49,12 @@ public sealed class ItemRepositoryTests [Fact] public void MoveItem_UpdatesContainerAndFiresEvent() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var item = MakeItem(100); repo.AddOrUpdate(item); uint seenOld = 999, seenNew = 999; - repo.ItemMoved += (it, oldC, newC) => { seenOld = oldC; seenNew = newC; }; + repo.ObjectMoved += (it, oldC, newC) => { seenOld = oldC; seenNew = newC; }; repo.MoveItem(100, 42, newSlot: 3); @@ -67,29 +67,29 @@ public sealed class ItemRepositoryTests [Fact] public void MoveItem_Nonexistent_ReturnsFalse() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); Assert.False(repo.MoveItem(999, 42)); } [Fact] public void Remove_FiresEventAndRemoves() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var item = MakeItem(100); repo.AddOrUpdate(item); - ItemInstance? removed = null; - repo.ItemRemoved += i => removed = i; + ClientObject? removed = null; + repo.ObjectRemoved += i => removed = i; Assert.True(repo.Remove(100)); Assert.Same(item, removed); - Assert.Null(repo.GetItem(100)); + Assert.Null(repo.Get(100)); } [Fact] public void UpdateProperties_MergesIncomingBundle() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); var item = MakeItem(100); item.Properties.Ints[1] = 10; repo.AddOrUpdate(item); @@ -108,67 +108,67 @@ public sealed class ItemRepositoryTests [Fact] public void Clear_RemovesAllItems() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); repo.AddOrUpdate(MakeItem(1)); repo.AddOrUpdate(MakeItem(2)); repo.AddOrUpdate(MakeItem(3)); repo.Clear(); - Assert.Equal(0, repo.ItemCount); + Assert.Equal(0, repo.ObjectCount); } [Fact] public void EnrichItem_updatesIconOnExistingStub_andRaisesUpdated() { - var repo = new ItemRepository(); - repo.AddOrUpdate(new ItemInstance { ObjectId = 0x5001u, WeenieClassId = 42u }); // stub from PlayerDescription - ItemInstance? updated = null; - repo.ItemPropertiesUpdated += i => updated = i; + var repo = new ClientObjectTable(); + repo.AddOrUpdate(new ClientObject { ObjectId = 0x5001u, WeenieClassId = 42u }); // stub from PlayerDescription + ClientObject? updated = null; + repo.ObjectUpdated += i => updated = i; bool hit = repo.EnrichItem(0x5001u, iconId: 0x06001234u, name: "Mana Stone", type: ItemType.Misc); Assert.True(hit); - Assert.Equal(0x06001234u, repo.GetItem(0x5001u)!.IconId); - Assert.Equal("Mana Stone", repo.GetItem(0x5001u)!.Name); + Assert.Equal(0x06001234u, repo.Get(0x5001u)!.IconId); + Assert.Equal("Mana Stone", repo.Get(0x5001u)!.Name); Assert.NotNull(updated); } [Fact] public void EnrichItem_returnsFalse_whenItemUnknown() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); Assert.False(repo.EnrichItem(0x9999u, 0x06001234u, "x", ItemType.Misc)); } [Fact] public void EnrichItem_carriesEffects() { - var repo = new ItemRepository(); - repo.AddOrUpdate(new ItemInstance { ObjectId = 0x500000AAu }); + var repo = new ClientObjectTable(); + repo.AddOrUpdate(new ClientObject { 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); + Assert.Equal(0x1u, repo.Get(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, ItemRepository.UiEffectsPropertyId, value: 0x9); + var repo = new ClientObjectTable(); + repo.AddOrUpdate(new ClientObject { ObjectId = 0x500000ABu }); + ClientObject? fired = null; + repo.ObjectUpdated += i => fired = i; + bool ok = repo.UpdateIntProperty(0x500000ABu, ClientObjectTable.UiEffectsPropertyId, value: 0x9); Assert.True(ok); - Assert.Equal(0x9u, repo.GetItem(0x500000ABu)!.Effects); - Assert.Equal(0x9, repo.GetItem(0x500000ABu)!.Properties.Ints[ItemRepository.UiEffectsPropertyId]); + Assert.Equal(0x9u, repo.Get(0x500000ABu)!.Effects); + Assert.Equal(0x9, repo.Get(0x500000ABu)!.Properties.Ints[ClientObjectTable.UiEffectsPropertyId]); Assert.NotNull(fired); } [Fact] public void UpdateIntProperty_unknownItem_returnsFalse() { - var repo = new ItemRepository(); + var repo = new ClientObjectTable(); Assert.False(repo.UpdateIntProperty(0xDEADBEEFu, 18u, 1)); } @@ -178,12 +178,12 @@ public sealed class ItemRepositoryTests // The core "item with mana vs out of mana" promise: a draining item whose // UiEffects clears to 0 must return to its base (un-tinted) icon. Guards // against a future `if (value != 0)` regression on the unconditional assign. - var repo = new ItemRepository(); - repo.AddOrUpdate(new ItemInstance { ObjectId = 0x500000ACu, Effects = 0x1u }); - repo.UpdateIntProperty(0x500000ACu, ItemRepository.UiEffectsPropertyId, value: 0x1); - Assert.Equal(0x1u, repo.GetItem(0x500000ACu)!.Effects); - repo.UpdateIntProperty(0x500000ACu, ItemRepository.UiEffectsPropertyId, value: 0); - Assert.Equal(0u, repo.GetItem(0x500000ACu)!.Effects); + var repo = new ClientObjectTable(); + repo.AddOrUpdate(new ClientObject { ObjectId = 0x500000ACu, Effects = 0x1u }); + repo.UpdateIntProperty(0x500000ACu, ClientObjectTable.UiEffectsPropertyId, value: 0x1); + Assert.Equal(0x1u, repo.Get(0x500000ACu)!.Effects); + repo.UpdateIntProperty(0x500000ACu, ClientObjectTable.UiEffectsPropertyId, value: 0); + Assert.Equal(0u, repo.Get(0x500000ACu)!.Effects); } [Fact] @@ -191,11 +191,11 @@ public sealed class ItemRepositoryTests { // A re-spawn (CreateObject) of a now-inert item carries effects=0; it must // clear a previously-set effect (unconditional assign, not gated on != 0). - var repo = new ItemRepository(); - repo.AddOrUpdate(new ItemInstance { ObjectId = 0x500000ADu, Effects = 0x1u }); + var repo = new ClientObjectTable(); + repo.AddOrUpdate(new ClientObject { ObjectId = 0x500000ADu, Effects = 0x1u }); bool ok = repo.EnrichItem(0x500000ADu, iconId: 0x06001234u, name: "Wand", type: ItemType.Caster, effects: 0u); Assert.True(ok); - Assert.Equal(0u, repo.GetItem(0x500000ADu)!.Effects); + Assert.Equal(0u, repo.Get(0x500000ADu)!.Effects); } }