using AcDream.App.UI; using AcDream.Core.Items; namespace AcDream.App.Tests.UI; public sealed class ItemInteractionControllerTests { private const uint Player = 0x50000001u; private const uint Pack = 0x50000010u; private const uint HealthKitUseability = 0x000A0008u; private sealed class Harness { public readonly ClientObjectTable Objects = new(); public readonly List Uses = new(); public readonly List<(uint Source, uint Target)> UseWithTarget = new(); public readonly List<(uint Item, uint Mask)> Wields = new(); public readonly List Drops = new(); public readonly List Toasts = new(); public long Now = 1_000; public Harness() { Objects.AddOrUpdate(new ClientObject { ObjectId = Player, Name = "Player", Type = ItemType.Creature, }); Objects.AddOrUpdate(new ClientObject { ObjectId = Pack, Name = "Backpack", Type = ItemType.Container, ItemsCapacity = 24, }); Objects.MoveItem(Pack, Player, 0); Controller = new ItemInteractionController( Objects, playerGuid: () => Player, sendUse: Uses.Add, sendUseWithTarget: (source, target) => UseWithTarget.Add((source, target)), sendWield: (item, mask) => Wields.Add((item, mask)), sendDrop: Drops.Add, nowMs: () => Now, toast: Toasts.Add); } public ItemInteractionController Controller { get; } public ClientObject AddContained(uint id, Action? configure = null) { var item = new ClientObject { ObjectId = id, Name = $"Item {id:X}", Type = ItemType.Misc, IconId = 0x06001234u, }; configure?.Invoke(item); Objects.AddOrUpdate(item); Objects.MoveItem(id, Pack, Objects.GetContents(Pack).Count); return item; } } [Fact] public void TargetedItem_entersTargetModeAndMarksPendingSource() { var h = new Harness(); h.AddContained(0x50000A01u, item => item.Useability = HealthKitUseability); Assert.True(h.Controller.ActivateItem(0x50000A01u)); Assert.True(h.Controller.IsTargetModeActive); Assert.True(h.Controller.IsPendingSource(0x50000A01u)); Assert.Empty(h.UseWithTarget); } [Fact] public void SelfTarget_sendsUseWithTargetAndClearsTargetMode() { var h = new Harness(); h.AddContained(0x50000A01u, item => item.Useability = HealthKitUseability); h.Controller.ActivateItem(0x50000A01u); Assert.True(h.Controller.AcquireSelfTarget()); Assert.Equal(new[] { (0x50000A01u, Player) }, h.UseWithTarget); Assert.False(h.Controller.IsTargetModeActive); } [Fact] public void ActivateTargetItemDuringTargetMode_usesClickedItemAsTarget() { var h = new Harness(); h.AddContained(0x50000A01u, item => item.Useability = 0x00080008u); h.AddContained(0x50000A02u); h.Controller.ActivateItem(0x50000A01u); Assert.True(h.Controller.ActivateItem(0x50000A02u)); Assert.Equal(new[] { (0x50000A01u, 0x50000A02u) }, h.UseWithTarget); Assert.False(h.Controller.IsTargetModeActive); } [Fact] public void DirectUseItem_sendsUse() { var h = new Harness(); h.AddContained(0x50000A03u, item => item.Useability = ItemUseability.Contained); Assert.True(h.Controller.ActivateItem(0x50000A03u)); Assert.Equal(new[] { 0x50000A03u }, h.Uses); } [Fact] public void ContainerItem_sendsUseToOpen() { var h = new Harness(); h.AddContained(0x50000A04u, item => { item.Type = ItemType.Container; item.ItemsCapacity = 12; }); Assert.True(h.Controller.ActivateItem(0x50000A04u)); Assert.Equal(new[] { 0x50000A04u }, h.Uses); } [Fact] public void EquippableItemWithFreeSlot_sendsGetAndWieldAndMovesOptimistically() { var h = new Harness(); h.AddContained(0x50000A05u, item => { item.Type = ItemType.Clothing; item.ValidLocations = EquipMask.HeadWear; }); Assert.True(h.Controller.ActivateItem(0x50000A05u)); Assert.Equal(new[] { (0x50000A05u, (uint)EquipMask.HeadWear) }, h.Wields); var equipped = h.Objects.Get(0x50000A05u)!; Assert.Equal(Player, equipped.ContainerId); Assert.Equal(EquipMask.HeadWear, equipped.CurrentlyEquippedLocation); } [Fact] public void EquippableMultiSlotItemWithFreeSlots_sendsFullCoverageMaskAndMovesOptimistically() { var h = new Harness(); const EquipMask coatMask = EquipMask.ChestWear | EquipMask.UpperArmWear | EquipMask.LowerArmWear; h.AddContained(0x50000A15u, item => { item.Type = ItemType.Clothing; item.ValidLocations = coatMask; }); Assert.True(h.Controller.ActivateItem(0x50000A15u)); Assert.Equal(new[] { (0x50000A15u, (uint)coatMask) }, h.Wields); var equipped = h.Objects.Get(0x50000A15u)!; Assert.Equal(Player, equipped.ContainerId); Assert.Equal(coatMask, equipped.CurrentlyEquippedLocation); } [Fact] public void AutoWearItemWithOverlappingSlotButDifferentPriority_sendsFullMask() { var h = new Harness(); h.Objects.AddOrUpdate(new ClientObject { ObjectId = 0x50000AF1u, Type = ItemType.Clothing, CurrentlyEquippedLocation = EquipMask.UpperArmWear, Priority = 0x00000001u, }); h.Objects.MoveItem(0x50000AF1u, Player, -1, EquipMask.UpperArmWear); const EquipMask coatMask = EquipMask.ChestWear | EquipMask.UpperArmWear | EquipMask.LowerArmWear; h.AddContained(0x50000A16u, item => { item.Type = ItemType.Clothing; item.ValidLocations = coatMask; item.Priority = 0x00000002u; }); Assert.True(h.Controller.ActivateItem(0x50000A16u)); Assert.Equal(new[] { (0x50000A16u, (uint)coatMask) }, h.Wields); Assert.Equal(coatMask, h.Objects.Get(0x50000A16u)!.CurrentlyEquippedLocation); } [Fact] public void AutoWearItemWithOverlappingSlotAndPriority_sendsNothing() { var h = new Harness(); h.Objects.AddOrUpdate(new ClientObject { ObjectId = 0x50000AF2u, Type = ItemType.Clothing, CurrentlyEquippedLocation = EquipMask.UpperArmWear, Priority = 0x00000004u, }); h.Objects.MoveItem(0x50000AF2u, Player, -1, EquipMask.UpperArmWear); const EquipMask coatMask = EquipMask.ChestWear | EquipMask.UpperArmWear | EquipMask.LowerArmWear; h.AddContained(0x50000A17u, item => { item.Type = ItemType.Clothing; item.ValidLocations = coatMask; item.Priority = 0x00000004u; }); Assert.False(h.Controller.ActivateItem(0x50000A17u)); Assert.Empty(h.Wields); Assert.Equal(Pack, h.Objects.Get(0x50000A17u)!.ContainerId); } [Fact] public void EquippableItemWithNoFreeSlot_sendsNothing() { var h = new Harness(); h.Objects.AddOrUpdate(new ClientObject { ObjectId = 0x50000AF0u, Type = ItemType.Armor, CurrentlyEquippedLocation = EquipMask.Shield, }); h.Objects.MoveItem(0x50000AF0u, Player, -1, EquipMask.Shield); h.AddContained(0x50000A06u, item => { item.Type = ItemType.Armor; item.ValidLocations = EquipMask.Shield; }); Assert.False(h.Controller.ActivateItem(0x50000A06u)); Assert.Empty(h.Wields); Assert.Equal(Pack, h.Objects.Get(0x50000A06u)!.ContainerId); } [Fact] public void InventoryDragOutsideUi_sendsDropAndMovesToWorldOptimistically() { var h = new Harness(); h.AddContained(0x50000A07u); var payload = new ItemDragPayload( 0x50000A07u, ItemDragSource.Inventory, SourceSlot: 0, SourceCell: new UiItemSlot()); Assert.True(h.Controller.DropToWorld(payload)); Assert.Equal(new[] { 0x50000A07u }, h.Drops); Assert.Equal(0u, h.Objects.Get(0x50000A07u)!.ContainerId); } [Fact] public void ToolbarShortcutDragOutsideUi_doesNotDropRealItem() { var h = new Harness(); h.AddContained(0x50000A08u); var payload = new ItemDragPayload( 0x50000A08u, ItemDragSource.ShortcutBar, SourceSlot: 0, SourceCell: new UiItemSlot()); Assert.False(h.Controller.DropToWorld(payload)); Assert.Empty(h.Drops); Assert.Equal(Pack, h.Objects.Get(0x50000A08u)!.ContainerId); } [Fact] public void ActivateItem_appliesRetailUseThrottle() { var h = new Harness(); h.AddContained(0x50000A09u, item => item.Useability = ItemUseability.Contained); h.Controller.ActivateItem(0x50000A09u); h.Now += 199; h.Controller.ActivateItem(0x50000A09u); h.Now += 1; h.Controller.ActivateItem(0x50000A09u); Assert.Equal(new[] { 0x50000A09u, 0x50000A09u }, h.Uses); } }