refactor(runtime): close canonical gameplay ownership

Unify the toolbar shortcut manager with Runtime inventory state, route retail-ordered shortcut and spellbook command effects through the canonical owners, and make retained controllers borrow those exact instances. Remove the item-interaction transaction fallback and add graphical/no-window parity plus failure-safe terminal ownership-ledger coverage.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 09:48:51 +02:00
parent ce6fae7b38
commit 89e6b207f8
36 changed files with 1433 additions and 251 deletions

View file

@ -160,6 +160,7 @@ public sealed class SelectionInteractionControllerTests
});
Items = new ItemInteractionController(
Objects,
new InventoryTransactionState(Objects),
() => Player,
sendUse: null,
sendUseWithTarget: null,

View file

@ -371,7 +371,7 @@ public sealed class CurrentGameRuntimeAdapterTests
harness.Character.Options.Replace(0x04000000u, 0x00948700u);
harness.Character.MovementSkills.Update(200, 175);
harness.Character.Spellbook.OnSpellLearned(42u);
harness.InventoryState.Shortcuts.Replace(
harness.InventoryState.Shortcuts.Load(
[
new ShortcutEntry(1, 0x80000001u, 0u),
]);
@ -506,6 +506,20 @@ public sealed class CurrentGameRuntimeAdapterTests
static entry => entry.Kind == RuntimeTraceKind.Command
&& (entry.Code >> 16)
== (int)RuntimeCommandDomain.Social);
Assert.True(harness.InventoryState.View.TryGetShortcut(
2,
out RuntimeShortcutSnapshot storedShortcut));
Assert.Equal(0x80000001u, storedShortcut.ObjectId);
Assert.True(harness.Character.View.TryGetFavorite(
0,
0,
out uint storedFavorite));
Assert.Equal(42u, storedFavorite);
Assert.Equal(0x3FFEu, harness.Character.Spellbook.SpellbookFilters);
Assert.True(harness.Character.View.TryGetDesiredComponent(
0x68000001u,
out uint storedDesired));
Assert.Equal(10u, storedDesired);
int published = harness.Commands.Published.Count;
RuntimeCommandResult stale = commands.Character.SetOptions1(
@ -515,6 +529,101 @@ public sealed class CurrentGameRuntimeAdapterTests
Assert.Equal(published, harness.Commands.Published.Count);
}
[Fact]
public void GraphicalAndNoWindowJ4CommandsProduceIdenticalCanonicalState()
{
using var directEntities = new RuntimeEntityObjectLifetime();
using var directInventory =
new RuntimeInventoryState(directEntities);
using var directCharacter = new RuntimeCharacterState();
using var harness = new Harness();
_ = harness.Runtime.Session.Start(harness.Runtime.Generation);
RuntimeGenerationToken generation = harness.Runtime.Generation;
IGameRuntimeCommands graphical = harness.Runtime;
var directOutbound = new List<string>();
var shortcut = new ShortcutEntry(2, 0x80000001u, 0u);
Assert.True(directInventory.TryAddShortcut(
shortcut,
() => directOutbound.Add("add-shortcut")));
Assert.True(directCharacter.TryAddFavorite(
0,
0,
42u,
() => directOutbound.Add("add-favorite")));
directCharacter.SetSpellbookFilter(
0x3FFEu,
() => directOutbound.Add("filter"));
Assert.True(directCharacter.TrySetDesiredComponent(
0x68000001u,
10u,
() => directOutbound.Add("desired")));
Assert.True(graphical.InventoryState.AddShortcut(
generation,
new RuntimeShortcutCommand(
shortcut.Index,
shortcut.ObjectId,
shortcut.SpellId)).Accepted);
Assert.True(graphical.Spellbook.AddFavorite(
generation,
0,
0,
42u).Accepted);
Assert.True(graphical.Spellbook.SetFilter(
generation,
0x3FFEu).Accepted);
Assert.True(graphical.Spellbook.SetDesiredComponent(
generation,
0x68000001u,
10u).Accepted);
Assert.Equal(
directInventory.View.Snapshot,
harness.InventoryState.View.Snapshot);
Assert.Equal(
directCharacter.View.Snapshot,
harness.Character.View.Snapshot);
Assert.Equal(
directInventory.Shortcuts.Items,
harness.InventoryState.Shortcuts.Items);
Assert.Equal(
directCharacter.Spellbook.GetFavorites(0),
harness.Character.Spellbook.GetFavorites(0));
Assert.Equal(
["add-shortcut", "add-favorite", "filter", "desired"],
directOutbound);
Assert.True(directInventory.TryRemoveShortcut(
2,
() => directOutbound.Add("remove-shortcut")));
Assert.True(directCharacter.TryRemoveFavorite(
0,
42u,
() => directOutbound.Add("remove-favorite")));
directCharacter.ClearDesiredComponents(
() => directOutbound.Add("clear-desired"));
Assert.True(graphical.InventoryState.RemoveShortcut(
generation,
2).Accepted);
Assert.True(graphical.Spellbook.RemoveFavorite(
generation,
0,
42u).Accepted);
Assert.True(graphical.Spellbook.ClearDesiredComponents(
generation).Accepted);
Assert.Equal(
directInventory.View.Snapshot,
harness.InventoryState.View.Snapshot);
Assert.Equal(
directCharacter.View.Snapshot,
harness.Character.View.Snapshot);
Assert.Empty(harness.InventoryState.Shortcuts.Items);
Assert.Empty(harness.Character.Spellbook.GetFavorites(0));
Assert.Empty(harness.Character.Spellbook.DesiredComponents);
}
private static ClientObject Object(WorldSession.EntitySpawn spawn) => new()
{
ObjectId = spawn.Guid,
@ -587,6 +696,7 @@ public sealed class CurrentGameRuntimeAdapterTests
_items = new ItemInteractionController(
Objects,
InventoryState.Transactions,
() => PlayerGuid,
sendUse: null,
sendUseWithTarget: null,

View file

@ -41,13 +41,32 @@ public sealed class RuntimeInventoryOwnershipTests
"InteractionRetainedUiComposition.cs");
string session = ReadSource("Net", "LiveSessionRuntimeFactory.cs");
string shutdown = ReadSource("Rendering", "GameWindowLifetime.cs");
string itemInteraction = ReadSource(
"UI",
"ItemInteractionController.cs");
Assert.Contains(
"transactions: d.Inventory.Transactions",
"d.Inventory.Transactions",
ui,
StringComparison.Ordinal);
Assert.DoesNotContain(
"new InventoryTransactionState(d.Inventory.Objects)",
ui,
StringComparison.Ordinal);
Assert.DoesNotContain(
"new InventoryTransactionState",
itemInteraction,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_ownsTransactions",
itemInteraction,
StringComparison.Ordinal);
Assert.Contains(
"OnShortcuts: _domain.Inventory.Shortcuts.Replace",
"InventoryTransactionState transactions,",
itemInteraction,
StringComparison.Ordinal);
Assert.Contains(
"OnShortcuts: _domain.Inventory.Shortcuts.Load",
session,
StringComparison.Ordinal);
Assert.Contains(
@ -64,6 +83,73 @@ public sealed class RuntimeInventoryOwnershipTests
"\"runtime entity/object lifetime\"");
}
[Fact]
public void ToolbarBorrowsRuntimeShortcutManagerWithoutAProviderOrMirror()
{
string toolbar = ReadSource(
"UI",
"Layout",
"ToolbarController.cs");
string ui = ReadSource(
"Composition",
"InteractionRetainedUiComposition.cs");
Assert.Contains(
"private readonly ShortcutStore _store;",
toolbar,
StringComparison.Ordinal);
Assert.Contains(
"_store = shortcuts ?? throw",
toolbar,
StringComparison.Ordinal);
Assert.DoesNotContain(
"new ShortcutStore()",
toolbar,
StringComparison.Ordinal);
Assert.DoesNotContain(
"Func<IReadOnlyList<ShortcutEntry>>",
toolbar,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_storeLoaded",
toolbar,
StringComparison.Ordinal);
Assert.Contains(
"d.Inventory.Shortcuts,",
ui,
StringComparison.Ordinal);
}
[Fact]
public void SpellUiDoesNotApplyASecondLocalCommandMutation()
{
string spellbook = ReadSource(
"UI",
"Layout",
"SpellbookWindowController.cs");
string spellcasting = ReadSource(
"UI",
"Layout",
"SpellcastingUiController.cs");
Assert.DoesNotContain(
"_spellbook.SetSpellbookFilters(filters);",
spellbook,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_spellbook.SetDesiredComponent(componentId, parsed);",
spellbook,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_spellbook.SetFavorite(",
spellcasting,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_spellbook.RemoveFavorite(",
spellcasting,
StringComparison.Ordinal);
}
private static string ReadSource(params string[] relative)
{
string root = FindRepositoryRoot();

View file

@ -153,6 +153,7 @@ public sealed class CursorFeedbackControllerTests
var objects = SeedTargetObjects();
var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -188,6 +189,7 @@ public sealed class CursorFeedbackControllerTests
objects.Get(Source)!.TargetType = (uint)ItemType.Misc; // a tool that targets items
var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -226,6 +228,7 @@ public sealed class CursorFeedbackControllerTests
var objects = SeedTargetObjects();
var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -254,6 +257,7 @@ public sealed class CursorFeedbackControllerTests
var objects = SeedTargetObjects();
var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,

View file

@ -31,7 +31,7 @@ public sealed class ItemInteractionControllerTests
public readonly List<CombatMode> CombatModeRequests = new();
public readonly CombatState Combat = new();
public readonly StackSplitQuantityState SplitQuantity = new();
public readonly InventoryTransactionState? SharedTransactions;
public readonly InventoryTransactionState SharedTransactions;
public uint SelectedObject;
public bool NonCombatMode;
public bool DragOnPlayerOpensSecureTrade = true;
@ -39,8 +39,7 @@ public sealed class ItemInteractionControllerTests
public long Now = 1_000;
public Harness(
Action<uint, ItemUseRequestReservation>? requestUse = null,
bool sharedTransactions = false)
Action<uint, ItemUseRequestReservation>? requestUse = null)
{
Objects.AddOrUpdate(new ClientObject
{
@ -56,12 +55,11 @@ public sealed class ItemInteractionControllerTests
ItemsCapacity = 24,
});
Objects.MoveItem(Pack, Player, 0);
SharedTransactions = sharedTransactions
? new InventoryTransactionState(Objects)
: null;
SharedTransactions = new InventoryTransactionState(Objects);
Controller = new ItemInteractionController(
Objects,
SharedTransactions,
playerGuid: () => Player,
sendUse: requestUse is null ? Uses.Add : null,
sendExamine: Examines.Add,
@ -91,8 +89,7 @@ public sealed class ItemInteractionControllerTests
},
combatState: Combat,
sendChangeCombatMode: CombatModeRequests.Add,
requestUse: requestUse,
transactions: SharedTransactions);
requestUse: requestUse);
}
public ItemInteractionController Controller { get; }
@ -321,11 +318,11 @@ public sealed class ItemInteractionControllerTests
[Fact]
public void BorrowedTransactionOwnerIsExactAndOutlivesController()
{
var h = new Harness(sharedTransactions: true);
var h = new Harness();
h.Controller.IncrementBusyCount();
Assert.Equal(1, h.SharedTransactions!.BusyCount);
Assert.Equal(1, h.SharedTransactions.BusyCount);
h.Controller.Dispose();
Assert.False(h.SharedTransactions.IsDisposed);
@ -343,12 +340,12 @@ public sealed class ItemInteractionControllerTests
Assert.Throws<ArgumentException>(() => new ItemInteractionController(
objects,
transactions,
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
sendWield: null,
sendDrop: null,
transactions: transactions));
sendDrop: null));
}
[Fact]

View file

@ -858,6 +858,7 @@ public sealed class AppraisalUiControllerTests
List<uint> sent)
=> new(
objects,
new InventoryTransactionState(objects),
playerGuid: () => 0x50000002u,
sendUse: null,
sendUseWithTarget: null,

View file

@ -106,6 +106,7 @@ public sealed class ExternalContainerControllerTests
Interaction = new ItemInteractionController(
Objects,
new InventoryTransactionState(Objects),
playerGuid: () => Player,
sendUse: Uses.Add,
sendUseWithTarget: null,

View file

@ -508,6 +508,7 @@ public class InventoryControllerTests
var appraisals = new List<uint>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -544,6 +545,7 @@ public class InventoryControllerTests
objects.Get(0xA)!.Useability = 0x000A0008u;
var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -712,6 +714,7 @@ public class InventoryControllerTests
var pickups = new List<(uint item, uint container, int placement)>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -756,6 +759,7 @@ public class InventoryControllerTests
var eventOrder = new List<(string Kind, uint Item, ulong Token)>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -807,6 +811,7 @@ public class InventoryControllerTests
var eventOrder = new List<(string Kind, uint Item)>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -864,6 +869,7 @@ public class InventoryControllerTests
var messages = new List<string>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -932,6 +938,7 @@ public class InventoryControllerTests
var messages = new List<string>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -994,6 +1001,7 @@ public class InventoryControllerTests
var merges = new List<(uint Source, uint Target, uint Amount)>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -1053,6 +1061,7 @@ public class InventoryControllerTests
var splits = new List<(uint Item, uint Container, uint Placement, uint Amount)>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -1099,6 +1108,7 @@ public class InventoryControllerTests
var puts = new List<(uint Item, uint Container, int Placement)>();
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -1138,6 +1148,7 @@ public class InventoryControllerTests
SeedContained(objects, loot, chest, slot: 0, type: ItemType.Misc);
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,
@ -1176,6 +1187,7 @@ public class InventoryControllerTests
SeedContained(objects, loot, chest, slot: 0, type: ItemType.Misc);
using var interaction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: null,
sendUseWithTarget: null,

View file

@ -52,6 +52,7 @@ public class PaperdollControllerTests
{
var itemInteraction = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
() => Player,
sendUse: null,
sendUseWithTarget: null,

View file

@ -413,10 +413,18 @@ public sealed class SpellbookWindowControllerTests
spellId => spellId == 103u ? 8 : 1,
selectObject ?? (_ => { }),
addFavorite ?? (_ => { }),
sendFilter ?? (_ => { }),
filters =>
{
spellbook.SetSpellbookFilters(filters);
sendFilter?.Invoke(filters);
},
removeSpell ?? (_ => { }),
showConfirmation ?? ((_, _) => { }),
setDesiredComponent ?? ((_, _) => { }),
(componentId, amount) =>
{
spellbook.SetDesiredComponent(componentId, amount);
setDesiredComponent?.Invoke(componentId, amount);
},
close ?? (() => { }),
new ComponentBookTemplateFactory(
FixtureLoader.LoadComponentCategoryTemplateInfos(),

View file

@ -338,8 +338,12 @@ public sealed class SpellcastingUiControllerTests
item => item.ObjectId,
useItem,
selection ?? new SelectionState(),
addFavorite ?? ((_, _, _) => { }),
(_, _) => { },
(tab, position, spellId) =>
{
spellbook.SetFavorite(tab, position, spellId);
addFavorite?.Invoke(tab, position, spellId);
},
(tab, spellId) => spellbook.RemoveFavorite(tab, spellId),
shortcutDigits,
emptySlotSprite,
examineSpell);

View file

@ -34,6 +34,14 @@ public class ToolbarControllerTests
(InventoryButtonId, RetailPanelCatalog.Inventory),
};
private static ShortcutStore Store(
IEnumerable<ShortcutEntry> shortcuts)
{
var store = new ShortcutStore();
store.Load(shortcuts);
return store;
}
private static (ImportedLayout layout, Dictionary<uint, UiItemList> slots,
Dictionary<uint, UiElement> indicators) FakeToolbar()
{
@ -108,7 +116,7 @@ public class ToolbarControllerTests
var shortcuts = new List<ShortcutEntry>
{ new(Index: 0, ObjectId: 0x5001u, SpellId: 0) };
ToolbarController.Bind(layout, repo, () => shortcuts,
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { });
Assert.Equal(0x5001u, slots[Row1[0]].Cell.ItemId);
@ -134,7 +142,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(
layout,
repo,
() => shortcuts,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 0x77u,
useItem: _ => { });
Assert.Equal(0x5001u, slots[Row1[0]].Cell.ItemId);
@ -152,7 +160,7 @@ public class ToolbarControllerTests
var shortcuts = new List<ShortcutEntry>
{ new(Index: 2, ObjectId: 0x5002u, SpellId: 0) };
ToolbarController.Bind(layout, repo, () => shortcuts,
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x88u, useItem: _ => { });
Assert.Equal(0u, slots[Row1[2]].Cell.ItemId); // not bound yet
@ -168,7 +176,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
var shortcuts = new List<ShortcutEntry>
{ new(Index: 2, ObjectId: 0x5002u, SpellId: 0) };
var controller = ToolbarController.Bind(layout, repo, () => shortcuts,
var controller = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x88u, useItem: _ => { });
controller.Dispose();
@ -194,7 +202,7 @@ public class ToolbarControllerTests
uint used = 0;
var selection = new SelectionState();
ToolbarController.Bind(layout, repo, () => shortcuts,
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u,
useItem: g => used = g,
selection: selection);
@ -217,7 +225,7 @@ public class ToolbarControllerTests
var toggles = new List<uint>();
var ctrl = ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
ctrl.BindPanelButtons(
panelId => panelId is RetailPanelCatalog.Inventory or RetailPanelCatalog.Character,
@ -257,6 +265,7 @@ public class ToolbarControllerTests
var useWithTarget = new List<(uint Source, uint Target)>();
var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
playerGuid: () => player,
sendUse: null,
sendUseWithTarget: (source, target) => useWithTarget.Add((source, target)),
@ -266,7 +275,7 @@ public class ToolbarControllerTests
int inventoryClicks = 0;
var ctrl = ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u,
useItem: _ => { },
itemInteraction: interaction);
@ -293,7 +302,7 @@ public class ToolbarControllerTests
repo.AddOrUpdate(new ClientObject { ObjectId = item, Type = ItemType.Misc });
var puts = new List<(uint Item, uint Container, int Placement)>();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
playerGuid: () => player,
@ -322,6 +331,7 @@ public class ToolbarControllerTests
var directPuts = new List<(uint Item, uint Container, int Placement)>();
using var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
playerGuid: () => player,
sendUse: null,
sendUseWithTarget: null,
@ -331,7 +341,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: static (_, _, _, _, _) => 0u,
useItem: static _ => { },
itemInteraction: interaction,
@ -370,6 +380,7 @@ public class ToolbarControllerTests
var messages = new List<string>();
using var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
playerGuid: () => player,
sendUse: null,
sendUseWithTarget: null,
@ -379,7 +390,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: static (_, _, _, _, _) => 0u,
useItem: static _ => { },
itemInteraction: interaction,
@ -411,7 +422,7 @@ public class ToolbarControllerTests
repo.AddOrUpdate(new ClientObject { ObjectId = item, Type = ItemType.Misc });
var puts = new List<(uint Item, uint Container, int Placement)>();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
playerGuid: () => player,
@ -432,7 +443,7 @@ public class ToolbarControllerTests
var (layout, _, _) = FakeToolbar();
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
var inventoryButton = (UiButton)layout.FindElement(InventoryButtonId)!;
var characterButton = (UiButton)layout.FindElement(CharacterButtonId)!;
@ -498,6 +509,7 @@ public class ToolbarControllerTests
uint selected = item;
var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
() => player,
sendUse: uses.Add,
sendUseWithTarget: null,
@ -507,7 +519,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
itemInteraction: interaction,
@ -555,6 +567,7 @@ public class ToolbarControllerTests
var selection = new SelectionState();
using var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
() => player,
sendUse: null,
sendUseWithTarget: null,
@ -563,7 +576,7 @@ public class ToolbarControllerTests
using var controller = ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
itemInteraction: interaction,
@ -619,6 +632,7 @@ public class ToolbarControllerTests
var wields = new List<(uint Item, uint Location)>();
using var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
() => player,
sendUse: null,
sendUseWithTarget: null,
@ -627,7 +641,7 @@ public class ToolbarControllerTests
using var controller = ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
itemInteraction: interaction,
@ -670,7 +684,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
playerGuid: () => player);
@ -709,7 +723,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
playerGuid: () => player);
@ -731,7 +745,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
useItem: _ => { },
toggleCombat: () => toggles++);
@ -758,7 +772,7 @@ public class ToolbarControllerTests
var controller = ToolbarController.Bind(
layout,
repo,
() => shortcuts,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
useItem: id => used = id,
selectItem: id => selected = id);
@ -784,6 +798,7 @@ public class ToolbarControllerTests
var appraisals = new List<uint>();
using var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
playerGuid: () => player,
sendUse: null,
sendUseWithTarget: null,
@ -793,7 +808,7 @@ public class ToolbarControllerTests
using var controller = ToolbarController.Bind(
layout,
repo,
() => shortcuts,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
useItem: _ => { },
itemInteraction: interaction,
@ -832,6 +847,7 @@ public class ToolbarControllerTests
uint sentTarget = 0;
var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
() => player,
sendUse: null,
sendUseWithTarget: (s, t) => (sentSource, sentTarget) = (s, t),
@ -845,7 +861,7 @@ public class ToolbarControllerTests
var controller = ToolbarController.Bind(
layout,
repo,
() => shortcuts,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
useItem: _ => { },
itemInteraction: interaction);
@ -873,6 +889,7 @@ public class ToolbarControllerTests
repo.MoveItem(item, pack, 0);
var interaction = new ItemInteractionController(
repo,
new InventoryTransactionState(repo),
() => player,
sendUse: null,
sendUseWithTarget: null,
@ -882,7 +899,7 @@ public class ToolbarControllerTests
var controller = ToolbarController.Bind(
layout,
repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 1u,
useItem: _ => { },
itemInteraction: interaction,
@ -908,7 +925,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) =>0u, useItem: _ => { });
// Only peace indicator (index 0 = 0x10000192) is visible.
@ -928,7 +945,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) =>0u, useItem: _ => { });
ctrl.SetCombatMode(CombatMode.Melee);
@ -950,7 +967,7 @@ public class ToolbarControllerTests
var combat = new CombatState();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) =>0u, useItem: _ => { },
combatState: combat);
@ -989,7 +1006,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
@ -1018,7 +1035,7 @@ public class ToolbarControllerTests
var (layout, slots, _) = FakeToolbar();
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
@ -1046,7 +1063,7 @@ public class ToolbarControllerTests
var (layout, slots, _) = FakeToolbar();
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
@ -1078,7 +1095,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
@ -1100,7 +1117,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted, emptyDigits: FakeEmpty);
@ -1121,7 +1138,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted, emptyDigits: null);
@ -1147,7 +1164,7 @@ public class ToolbarControllerTests
{ new(Index: 0, ObjectId: 0x5001u, SpellId: 0) };
int iconCallCount = 0;
ToolbarController.Bind(layout, repo, () => shortcuts,
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => { iconCallCount++; return 0x77u; }, useItem: _ => { });
int callsAfterBind = iconCallCount; // 1 call from initial Populate
@ -1172,7 +1189,7 @@ public class ToolbarControllerTests
{ new(Index: 1, ObjectId: 0x5003u, SpellId: 0) };
int iconCallCount = 0;
ToolbarController.Bind(layout, repo, () => shortcuts,
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => { iconCallCount++; return 0x99u; }, useItem: _ => { });
Assert.Equal(0, iconCallCount); // not called — item absent during initial Populate
@ -1198,7 +1215,7 @@ public class ToolbarControllerTests
var shortcuts = new List<ShortcutEntry>
{ new(Index: 3, ObjectId: 0x5004u, SpellId: 0) };
ToolbarController.Bind(layout, repo, () => shortcuts,
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0xAAu, useItem: _ => { });
Assert.Equal(0x5004u, slots[Row1[3]].Cell.ItemId); // bound
@ -1225,7 +1242,7 @@ public class ToolbarControllerTests
{ new(Index: 4, ObjectId: 0x5005u, SpellId: 0) };
int iconCallCount = 0;
ToolbarController.Bind(layout, repo, () => shortcuts,
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => { iconCallCount++; return 0xBBu; }, useItem: _ => { });
int callsAfterBind = iconCallCount; // 1 call for the shortcut item
@ -1247,7 +1264,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
for (int i = 0; i < Row1.Length; i++)
@ -1272,7 +1289,7 @@ public class ToolbarControllerTests
{
var (layout, slots, _) = FakeToolbar();
var ctrl = ToolbarController.Bind(layout, new ClientObjectTable(),
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
var list = slots[Row1[0]];
@ -1287,7 +1304,7 @@ public class ToolbarControllerTests
{
var (layout, slots, _) = FakeToolbar();
var ctrl = ToolbarController.Bind(layout, new ClientObjectTable(),
() => Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
var list = slots[Row1[0]];
var payload = new ItemDragPayload(0u, ItemDragSource.Inventory, 0, new UiItemSlot());
@ -1316,7 +1333,7 @@ public class ToolbarControllerTests
var (adds, removes) = NewSpies(out var add, out var rem);
uint selected = 0;
var ctrl = ToolbarController.Bind(layout, repo, () => shortcuts,
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem,
selectItem: item => selected = item, selectedObjectId: () => selected);
@ -1341,7 +1358,7 @@ public class ToolbarControllerTests
{ new(Index: 3, ObjectId: 0x5001u, SpellId: 0),
new(Index: 5, ObjectId: 0x5002u, SpellId: 0) };
var (adds, removes) = NewSpies(out var add, out var rem);
var ctrl = ToolbarController.Bind(layout, repo, () => shortcuts,
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem);
@ -1370,7 +1387,7 @@ public class ToolbarControllerTests
};
var (adds, _) = NewSpies(out var add, out var remove);
var controller = ToolbarController.Bind(
layout, repo, () => shortcuts,
layout, repo, Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
useItem: _ => { },
sendAddShortcut: add,
@ -1393,7 +1410,7 @@ public class ToolbarControllerTests
var shortcuts = new System.Collections.Generic.List<ShortcutEntry>
{ new(Index: 3, ObjectId: 0x5001u, SpellId: 0) };
var (adds, removes) = NewSpies(out var add, out var rem);
var ctrl = ToolbarController.Bind(layout, repo, () => shortcuts,
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem);
@ -1419,7 +1436,7 @@ public class ToolbarControllerTests
new ShortcutEntry(5, 0x5002u, 0xA5C31234u),
};
var wire = new List<string>();
var ctrl = ToolbarController.Bind(layout, repo, () => shortcuts,
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
useItem: _ => { },
sendAddShortcut: entry => wire.Add($"add:{entry.Index}:{entry.ObjectId:X8}:{entry.SpellId:X8}"),
@ -1452,7 +1469,7 @@ public class ToolbarControllerTests
new ShortcutEntry(4, 0x5001u, 0x11223344u),
};
var wire = new List<string>();
var ctrl = ToolbarController.Bind(layout, repo, () => shortcuts,
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
useItem: _ => { },
sendAddShortcut: entry => wire.Add($"add:{entry.Index}:{entry.ObjectId:X8}:{entry.SpellId:X8}"),
@ -1477,7 +1494,7 @@ public class ToolbarControllerTests
var shortcuts = new System.Collections.Generic.List<ShortcutEntry>
{ new(Index: 3, ObjectId: 0x5001u, SpellId: 0) };
var (adds, removes) = NewSpies(out var add, out var rem);
var ctrl = ToolbarController.Bind(layout, repo, () => shortcuts,
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem);
@ -1496,7 +1513,7 @@ public class ToolbarControllerTests
{
var (layout, slots, _) = FakeToolbar();
ToolbarController.Bind(layout, new ClientObjectTable(),
() => System.Array.Empty<ShortcutEntry>(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
Assert.Equal(0x060011FAu, slots[Row1[0]].Cell.DragAcceptSprite); // green cross, not the ring F9
}

View file

@ -18,6 +18,7 @@ public sealed class RetailItemConfirmationControllerTests
var uses = new List<uint>();
var items = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: uses.Add,
sendUseWithTarget: null,
@ -52,6 +53,7 @@ public sealed class RetailItemConfirmationControllerTests
var uses = new List<uint>();
var items = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: uses.Add,
sendUseWithTarget: null,
@ -80,6 +82,7 @@ public sealed class RetailItemConfirmationControllerTests
var messages = new List<string>();
var items = new ItemInteractionController(
objects,
new InventoryTransactionState(objects),
playerGuid: () => Player,
sendUse: uses.Add,
sendUseWithTarget: null,

View file

@ -163,6 +163,7 @@ public sealed class RetailUiInteractionFlowTests
{
var interaction = new ItemInteractionController(
Objects,
new InventoryTransactionState(Objects),
playerGuid: () => Player,
sendUse: Uses.Add,
sendUseWithTarget: (source, target) => UseWithTarget.Add((source, target)),
@ -197,6 +198,7 @@ public sealed class RetailUiInteractionFlowTests
{
itemInteraction ??= new ItemInteractionController(
Objects,
new InventoryTransactionState(Objects),
() => Player,
sendUse: null,
sendUseWithTarget: null,