using AcDream.Core.Items; using Xunit; namespace AcDream.Core.Tests.Items; public sealed class ClientObjectTableTests { private static ClientObject MakeItem(uint id, string name = "Widget") => new ClientObject { ObjectId = id, WeenieClassId = 1, Name = name, Type = ItemType.Misc, StackSize = 1, Burden = 10, Value = 5, }; [Fact] public void AddOrUpdate_FiresAddedEvent() { 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.ObjectCount); Assert.Same(item, repo.Get(100)); } [Fact] public void AddOrUpdate_ExistingItem_FiresPropertiesUpdated() { var repo = new ClientObjectTable(); var item = MakeItem(100); repo.AddOrUpdate(item); int propUpdateCount = 0; repo.ObjectUpdated += _ => propUpdateCount++; repo.AddOrUpdate(item); // second call is an update Assert.Equal(1, propUpdateCount); } [Fact] public void MoveItem_UpdatesContainerAndFiresEvent() { var repo = new ClientObjectTable(); var item = MakeItem(100); repo.AddOrUpdate(item); uint seenOld = 999, seenNew = 999; repo.ObjectMoved += (it, oldC, newC) => { seenOld = oldC; seenNew = newC; }; repo.MoveItem(100, 42, newSlot: 3); Assert.Equal(0u, seenOld); // was not in any container initially Assert.Equal(42u, seenNew); Assert.Equal(42u, item.ContainerId); Assert.Equal(3, item.ContainerSlot); } [Fact] public void MoveItem_Nonexistent_ReturnsFalse() { var repo = new ClientObjectTable(); Assert.False(repo.MoveItem(999, 42)); } [Fact] public void Remove_FiresEventAndRemoves() { var repo = new ClientObjectTable(); var item = MakeItem(100); repo.AddOrUpdate(item); ClientObject? removed = null; repo.ObjectRemoved += i => removed = i; Assert.True(repo.Remove(100)); Assert.Same(item, removed); Assert.Null(repo.Get(100)); } [Fact] public void UpdateProperties_MergesIncomingBundle() { var repo = new ClientObjectTable(); var item = MakeItem(100); item.Properties.Ints[1] = 10; repo.AddOrUpdate(item); var patch = new PropertyBundle(); patch.Ints[2] = 20; // new patch.Ints[1] = 15; // overrides patch.Strings[100] = "desc"; repo.UpdateProperties(100, patch); Assert.Equal(15, item.Properties.Ints[1]); Assert.Equal(20, item.Properties.Ints[2]); Assert.Equal("desc", item.Properties.Strings[100]); } [Fact] public void Clear_RemovesAllItems() { var repo = new ClientObjectTable(); repo.AddOrUpdate(MakeItem(1)); repo.AddOrUpdate(MakeItem(2)); repo.AddOrUpdate(MakeItem(3)); repo.Clear(); Assert.Equal(0, repo.ObjectCount); } [Fact] public void EnrichItem_updatesIconOnExistingStub_andRaisesUpdated() { 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.Get(0x5001u)!.IconId); Assert.Equal("Mana Stone", repo.Get(0x5001u)!.Name); Assert.NotNull(updated); } [Fact] public void EnrichItem_returnsFalse_whenItemUnknown() { var repo = new ClientObjectTable(); Assert.False(repo.EnrichItem(0x9999u, 0x06001234u, "x", ItemType.Misc)); } [Fact] public void EnrichItem_carriesEffects() { 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.Get(0x500000AAu)!.Effects); } [Fact] public void UpdateIntProperty_uiEffects_setsEffectsAndFires() { 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.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 ClientObjectTable(); Assert.False(repo.UpdateIntProperty(0xDEADBEEFu, 18u, 1)); } [Fact] public void UpdateIntProperty_uiEffectsClearedToZero_clearsEffects() { // 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 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] public void EnrichItem_effectsZero_clearsPriorEffects() { // 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 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.Get(0x500000ADu)!.Effects); } [Fact] public void ClientObject_NewFields_DefaultAndSettable() { var o = new ClientObject { ObjectId = 1, WielderId = 0x42u, ItemsCapacity = 24, ContainersCapacity = 7, Priority = 8u, Structure = 5, MaxStructure = 10, Workmanship = 7.5f, }; o.WeenieClassId = 0xABCDu; // now settable Assert.Equal(0x42u, o.WielderId); Assert.Equal(24, o.ItemsCapacity); Assert.Equal(7, o.ContainersCapacity); Assert.Equal(8u, o.Priority); Assert.Equal(5, o.Structure); Assert.Equal(10, o.MaxStructure); Assert.Equal(7.5f, o.Workmanship); Assert.Equal(0xABCDu, o.WeenieClassId); } [Fact] public void WeenieData_Construct() { var d = new WeenieData(Guid: 1, Name: "x", Type: ItemType.Misc, WeenieClassId: 2, IconId: 0x06001234u, IconOverlayId: 0, IconUnderlayId: 0, Effects: 0, Value: 5, StackSize: 1, StackSizeMax: 1, Burden: 10, ContainerId: 0x99u, WielderId: null, ValidLocations: null, CurrentWieldedLocation: null, Priority: null, ItemsCapacity: null, ContainersCapacity: null, Structure: null, MaxStructure: null, Workmanship: null); Assert.Equal(0x99u, d.ContainerId); } }