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:
parent
ce6fae7b38
commit
89e6b207f8
36 changed files with 1433 additions and 251 deletions
|
|
@ -160,6 +160,7 @@ public sealed class SelectionInteractionControllerTests
|
|||
});
|
||||
Items = new ItemInteractionController(
|
||||
Objects,
|
||||
new InventoryTransactionState(Objects),
|
||||
() => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -858,6 +858,7 @@ public sealed class AppraisalUiControllerTests
|
|||
List<uint> sent)
|
||||
=> new(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
playerGuid: () => 0x50000002u,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ public sealed class ExternalContainerControllerTests
|
|||
|
||||
Interaction = new ItemInteractionController(
|
||||
Objects,
|
||||
new InventoryTransactionState(Objects),
|
||||
playerGuid: () => Player,
|
||||
sendUse: Uses.Add,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public class PaperdollControllerTests
|
|||
{
|
||||
var itemInteraction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
() => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -41,4 +41,41 @@ public class ShortcutStoreTests
|
|||
store.SetItem(18, 1u); // no-op, no throw
|
||||
store.Remove(-1); // no-op, no throw
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RevisionObserversAndDisposalTrackTheOneCanonicalSlotMap()
|
||||
{
|
||||
var store = new ShortcutStore();
|
||||
int observed = 0;
|
||||
store.Changed += () => throw new InvalidOperationException("observer");
|
||||
store.Changed += () => observed++;
|
||||
|
||||
store.Load([new ShortcutEntry(2, 0x5001u, 0u)]);
|
||||
|
||||
Assert.Equal(1, observed);
|
||||
Assert.Equal(1, store.Revision);
|
||||
Assert.Equal(1, store.DispatchFailureCount);
|
||||
Assert.Equal(2, store.SubscriberCount);
|
||||
Assert.Equal(1, store.Count);
|
||||
|
||||
store.Set(new ShortcutEntry(2, 0x5001u, 0u));
|
||||
Assert.Equal(1, store.Revision);
|
||||
Assert.Equal(1, observed);
|
||||
|
||||
store.Set(new ShortcutEntry(2, 0x5002u, 0u));
|
||||
Assert.Equal(2, store.Revision);
|
||||
Assert.Equal(2, observed);
|
||||
|
||||
store.Dispose();
|
||||
store.Dispose();
|
||||
|
||||
Assert.True(store.IsDisposed);
|
||||
Assert.Empty(store.Items);
|
||||
Assert.Equal(0, store.SubscriberCount);
|
||||
Assert.Equal(3, observed);
|
||||
Assert.Equal(3, store.Revision);
|
||||
Assert.Equal(3, store.DispatchFailureCount);
|
||||
Assert.Throws<ObjectDisposedException>(
|
||||
() => store.SetItem(1, 1u));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ public sealed class RuntimeCharacterStateTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void DisposalRetriesObserverFailuresAndClearsBothOwners()
|
||||
public void DisposalReportsObserverFailuresAfterTerminalConvergence()
|
||||
{
|
||||
var state = new RuntimeCharacterState();
|
||||
state.Spellbook.OnSpellLearned(7u);
|
||||
|
|
@ -143,13 +143,13 @@ public sealed class RuntimeCharacterStateTests
|
|||
state.Dispose);
|
||||
|
||||
Assert.Equal(2, error.InnerExceptions.Count);
|
||||
Assert.False(state.IsDisposed);
|
||||
Assert.True(state.IsDisposed);
|
||||
Assert.False(state.Spellbook.Knows(7u));
|
||||
Assert.Null(state.LocalPlayer.Get(
|
||||
AcDream.Core.Player.LocalPlayerState.VitalKind.Health));
|
||||
Assert.True(state.CaptureOwnership().IsConverged);
|
||||
|
||||
state.Dispose();
|
||||
state.Dispose();
|
||||
|
||||
Assert.True(state.IsDisposed);
|
||||
}
|
||||
|
|
@ -254,4 +254,77 @@ public sealed class RuntimeCharacterStateTests
|
|||
Assert.True(first.View.Snapshot.SpellbookRevision > 0);
|
||||
Assert.Equal(0, second.View.Snapshot.SpellbookRevision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpellbookCommandsFollowRetailLocalAndOutboundOrder()
|
||||
{
|
||||
using var state = new RuntimeCharacterState();
|
||||
var order = new List<string>();
|
||||
state.Spellbook.SpellbookChanged += () => order.Add("local");
|
||||
state.Spellbook.DesiredComponentsChanged += () => order.Add("local");
|
||||
|
||||
Assert.True(state.TryAddFavorite(
|
||||
0,
|
||||
0,
|
||||
42u,
|
||||
() => order.Add("send")));
|
||||
Assert.Equal(["local", "send"], order);
|
||||
Assert.Equal([42u], state.Spellbook.GetFavorites(0));
|
||||
|
||||
order.Clear();
|
||||
state.SetSpellbookFilter(0x3FFEu, () => order.Add("send"));
|
||||
Assert.Equal(["local", "send"], order);
|
||||
Assert.Equal(0x3FFEu, state.Spellbook.SpellbookFilters);
|
||||
|
||||
order.Clear();
|
||||
Assert.True(state.TrySetDesiredComponent(
|
||||
0x68000001u,
|
||||
0u,
|
||||
() => order.Add("send")));
|
||||
Assert.Equal(["send", "local"], order);
|
||||
Assert.True(state.Spellbook.DesiredComponents.ContainsKey(
|
||||
0x68000001u));
|
||||
Assert.Equal(0u, state.Spellbook.DesiredComponents[0x68000001u]);
|
||||
|
||||
order.Clear();
|
||||
state.ClearDesiredComponents(() => order.Add("send"));
|
||||
Assert.Equal(["send", "local"], order);
|
||||
Assert.Empty(state.Spellbook.DesiredComponents);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => state.TrySetDesiredComponent(
|
||||
0x68000002u,
|
||||
10u,
|
||||
() => throw new InvalidOperationException("transport")));
|
||||
Assert.Equal(10u, state.Spellbook.DesiredComponents[0x68000002u]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidSpellbookCommandsDoNotPublishOrMutate()
|
||||
{
|
||||
using var state = new RuntimeCharacterState();
|
||||
int sends = 0;
|
||||
|
||||
Assert.False(state.TryAddFavorite(
|
||||
8,
|
||||
0,
|
||||
42u,
|
||||
() => sends++));
|
||||
Assert.False(state.TryRemoveFavorite(
|
||||
-1,
|
||||
42u,
|
||||
() => sends++));
|
||||
Assert.False(state.TrySetDesiredComponent(
|
||||
0u,
|
||||
1u,
|
||||
() => sends++));
|
||||
Assert.False(state.TrySetDesiredComponent(
|
||||
1u,
|
||||
5001u,
|
||||
() => sends++));
|
||||
|
||||
Assert.Equal(0, sends);
|
||||
Assert.Empty(state.Spellbook.GetFavorites(0));
|
||||
Assert.Empty(state.Spellbook.DesiredComponents);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Social;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.Runtime.Tests.Gameplay;
|
||||
|
||||
public sealed class RuntimeGameplayOwnershipTests
|
||||
{
|
||||
[Fact]
|
||||
public void CombinedLedgerConvergesEveryJ4OwnerAndSubscription()
|
||||
{
|
||||
using var entities = new RuntimeEntityObjectLifetime();
|
||||
var inventory = new RuntimeInventoryState(entities);
|
||||
var character = new RuntimeCharacterState();
|
||||
var communication = new RuntimeCommunicationState();
|
||||
inventory.Shortcuts.Changed += static () => { };
|
||||
inventory.Shortcuts.Load([new ShortcutEntry(1, 2u, 3u)]);
|
||||
inventory.ItemMana.OnQueryItemManaResponse(2u, 0.5f, true);
|
||||
inventory.ExternalContainers.RequestOpen(3u);
|
||||
inventory.ExternalContainers.ApplyViewContents(3u);
|
||||
inventory.Transactions.IncrementBusyCount();
|
||||
|
||||
character.Spellbook.OnSpellLearned(4u);
|
||||
character.Spellbook.SetFavorite(0, 0, 4u);
|
||||
character.Spellbook.SetDesiredComponent(5u, 6u);
|
||||
character.LocalPlayer.OnVitalUpdate(7u, 1u, 2u, 3u, 4u);
|
||||
character.LocalPlayer.OnAttributeUpdate(1u, 2u, 3u, 4u);
|
||||
character.LocalPlayer.OnSkillUpdate(
|
||||
6u, 1u, 2u, 3u, 4u, 5u, 6d, 7u);
|
||||
character.Options.Replace(1u, 2u);
|
||||
character.MovementSkills.Update(3, 4);
|
||||
|
||||
communication.Chat.OnTellReceived(
|
||||
"Sender",
|
||||
"hello",
|
||||
0x50000001u);
|
||||
communication.Chat.OnSelfSent(
|
||||
ChatKind.Tell,
|
||||
"reply",
|
||||
"Recipient");
|
||||
communication.TurbineChat.OnChannelsReceived(
|
||||
1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u);
|
||||
communication.Friends.Apply(new FriendsUpdate(
|
||||
FriendsUpdateType.Full,
|
||||
[new FriendEntry(1u, "Friend", true, false, [], [])]));
|
||||
communication.Squelch.Replace(new SquelchDatabase(
|
||||
new Dictionary<string, uint> { ["account"] = 1u },
|
||||
new Dictionary<uint, SquelchInfo>
|
||||
{
|
||||
[2u] = new SquelchInfo(
|
||||
"Character",
|
||||
false,
|
||||
new HashSet<uint> { 3u }),
|
||||
},
|
||||
new SquelchInfo(
|
||||
string.Empty,
|
||||
false,
|
||||
new HashSet<uint> { 4u })));
|
||||
IDisposable subscription =
|
||||
communication.Events.Subscribe(new NoopObserver());
|
||||
|
||||
RuntimeGameplayOwnershipSnapshot populated =
|
||||
RuntimeGameplayOwnership.Capture(
|
||||
inventory,
|
||||
character,
|
||||
communication);
|
||||
|
||||
Assert.False(populated.IsConverged);
|
||||
Assert.Equal(1, populated.Inventory.ShortcutCount);
|
||||
Assert.Equal(1, populated.Character.LearnedSpellCount);
|
||||
Assert.Equal(1, populated.Communication.StreamSubscriberCount);
|
||||
Assert.True(populated.Communication.HasReplyTarget);
|
||||
Assert.True(populated.Communication.HasRetellTarget);
|
||||
|
||||
communication.Dispose();
|
||||
character.Dispose();
|
||||
inventory.Dispose();
|
||||
subscription.Dispose();
|
||||
|
||||
RuntimeGameplayOwnershipSnapshot retired =
|
||||
RuntimeGameplayOwnership.Capture(
|
||||
inventory,
|
||||
character,
|
||||
communication);
|
||||
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.Equal(0, retired.Inventory.ShortcutSubscriberCount);
|
||||
Assert.False(retired.Character.InternalSubscriptionsAttached);
|
||||
Assert.Equal(0, retired.Communication.StreamSubscriberCount);
|
||||
Assert.Equal(0, retired.Communication.PendingDispatchCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BorrowerFailuresCannotStrandAnyJ4OwnerDuringTerminalDisposal()
|
||||
{
|
||||
using var entities = new RuntimeEntityObjectLifetime();
|
||||
var inventory = new RuntimeInventoryState(entities);
|
||||
var character = new RuntimeCharacterState();
|
||||
var communication = new RuntimeCommunicationState();
|
||||
|
||||
inventory.ExternalContainers.RequestOpen(0x70000001u);
|
||||
inventory.ExternalContainers.ApplyViewContents(0x70000001u);
|
||||
inventory.ExternalContainers.Changed += static _ =>
|
||||
throw new InvalidOperationException("inventory observer");
|
||||
|
||||
character.Spellbook.OnSpellLearned(42u);
|
||||
character.LocalPlayer.OnVitalUpdate(7u, 1u, 2u, 3u, 4u);
|
||||
character.Spellbook.SpellbookChanged += static () =>
|
||||
throw new InvalidOperationException("spellbook observer");
|
||||
character.LocalPlayer.CharacterChanged += static () =>
|
||||
throw new InvalidOperationException("character observer");
|
||||
|
||||
_ = communication.Events.Subscribe(new ThrowingObserver());
|
||||
communication.Chat.OnSystemMessage("failure-isolated event", 0u);
|
||||
Assert.Equal(1, communication.DispatchFailureCount);
|
||||
|
||||
Assert.Throws<AggregateException>(inventory.Dispose);
|
||||
Assert.Throws<AggregateException>(character.Dispose);
|
||||
communication.Dispose();
|
||||
|
||||
RuntimeGameplayOwnershipSnapshot retired =
|
||||
RuntimeGameplayOwnership.Capture(
|
||||
inventory,
|
||||
character,
|
||||
communication);
|
||||
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.Equal(1, retired.Communication.DispatchFailureCount);
|
||||
}
|
||||
|
||||
private sealed class NoopObserver : IRuntimeCommunicationObserver
|
||||
{
|
||||
public void OnChat(in RuntimeCommunicationEvent delta) { }
|
||||
}
|
||||
|
||||
private sealed class ThrowingObserver : IRuntimeCommunicationObserver
|
||||
{
|
||||
public void OnChat(in RuntimeCommunicationEvent delta) =>
|
||||
throw new InvalidOperationException("communication observer");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ public sealed class RuntimeInventoryStateTests
|
|||
{
|
||||
using var entities = new RuntimeEntityObjectLifetime();
|
||||
using var inventory = new RuntimeInventoryState(entities);
|
||||
inventory.Shortcuts.Replace(
|
||||
inventory.Shortcuts.Load(
|
||||
[
|
||||
new AcDream.Core.Items.ShortcutEntry(3, 0x80000001u, 0u),
|
||||
new AcDream.Core.Items.ShortcutEntry(4, 0u, 42u),
|
||||
|
|
@ -55,12 +55,12 @@ public sealed class RuntimeInventoryStateTests
|
|||
inventory.ExternalContainers.RequestOpen(0x70000001u);
|
||||
inventory.ExternalContainers.ApplyViewContents(0x70000001u);
|
||||
inventory.ItemMana.OnQueryItemManaResponse(0x50000001u, 0.75f, true);
|
||||
inventory.Shortcuts.Replace(shortcuts);
|
||||
inventory.Shortcuts.Load(shortcuts);
|
||||
|
||||
Assert.Same(entities.Objects, inventory.Objects);
|
||||
Assert.Equal(0x70000001u, inventory.ExternalContainers.CurrentContainerId);
|
||||
Assert.Equal(0.75f, inventory.ItemMana.GetManaPercent(0x50000001u));
|
||||
Assert.Same(shortcuts, inventory.Shortcuts.Items);
|
||||
Assert.Equal(shortcuts, inventory.Shortcuts.Items);
|
||||
Assert.Equal(1, inventory.Shortcuts.Revision);
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ public sealed class RuntimeInventoryStateTests
|
|||
inventory.ExternalContainers.RequestOpen(0x70000001u);
|
||||
inventory.ExternalContainers.ApplyViewContents(0x70000001u);
|
||||
inventory.ItemMana.OnQueryItemManaResponse(0x50000001u, 0.75f, true);
|
||||
inventory.Shortcuts.Replace([new ShortcutEntry(1, 2u, 3u)]);
|
||||
inventory.Shortcuts.Load([new ShortcutEntry(1, 2u, 3u)]);
|
||||
inventory.Transactions.IncrementBusyCount();
|
||||
|
||||
inventory.ResetExternalContainer();
|
||||
|
|
@ -98,7 +98,7 @@ public sealed class RuntimeInventoryStateTests
|
|||
using var first = new RuntimeInventoryState(firstEntities);
|
||||
using var second = new RuntimeInventoryState(secondEntities);
|
||||
|
||||
first.Shortcuts.Replace([new ShortcutEntry(1, 2u, 3u)]);
|
||||
first.Shortcuts.Load([new ShortcutEntry(1, 2u, 3u)]);
|
||||
first.Transactions.IncrementBusyCount();
|
||||
|
||||
Assert.NotSame(first.Objects, second.Objects);
|
||||
|
|
@ -116,7 +116,7 @@ public sealed class RuntimeInventoryStateTests
|
|||
inventory.ExternalContainers.RequestOpen(0x70000001u);
|
||||
inventory.ExternalContainers.ApplyViewContents(0x70000001u);
|
||||
inventory.ItemMana.OnQueryItemManaResponse(0x50000001u, 0.5f, true);
|
||||
inventory.Shortcuts.Replace([new ShortcutEntry(1, 2u, 3u)]);
|
||||
inventory.Shortcuts.Load([new ShortcutEntry(1, 2u, 3u)]);
|
||||
|
||||
inventory.Dispose();
|
||||
inventory.Dispose();
|
||||
|
|
@ -129,7 +129,7 @@ public sealed class RuntimeInventoryStateTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void DisposalRetriesATransientOwnerObserverFailureToConvergence()
|
||||
public void DisposalFailureIsReportedAfterTerminalOwnerConvergence()
|
||||
{
|
||||
using var entities = new RuntimeEntityObjectLifetime();
|
||||
var inventory = new RuntimeInventoryState(entities);
|
||||
|
|
@ -146,12 +146,72 @@ public sealed class RuntimeInventoryStateTests
|
|||
};
|
||||
|
||||
Assert.Throws<AggregateException>(inventory.Dispose);
|
||||
Assert.False(inventory.IsDisposed);
|
||||
Assert.True(inventory.IsDisposed);
|
||||
Assert.True(inventory.Transactions.IsDisposed);
|
||||
inventory.Dispose();
|
||||
|
||||
Assert.True(inventory.CaptureOwnership().IsConverged);
|
||||
Assert.Equal(0u, inventory.ExternalContainers.CurrentContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShortcutCommandsUseRetailSendThenLocalOrderOnTheExactOwner()
|
||||
{
|
||||
using var entities = new RuntimeEntityObjectLifetime();
|
||||
using var inventory = new RuntimeInventoryState(entities);
|
||||
var order = new List<string>();
|
||||
inventory.Shortcuts.Changed += () => order.Add("local");
|
||||
var entry = new ShortcutEntry(3, 0x80000001u, 0u);
|
||||
|
||||
Assert.True(inventory.TryAddShortcut(
|
||||
entry,
|
||||
() =>
|
||||
{
|
||||
Assert.Null(inventory.Shortcuts.GetEntry(3));
|
||||
order.Add("send");
|
||||
}));
|
||||
Assert.Equal(["send", "local"], order);
|
||||
Assert.Equal(entry, inventory.Shortcuts.GetEntry(3));
|
||||
|
||||
order.Clear();
|
||||
Assert.True(inventory.TryRemoveShortcut(
|
||||
3,
|
||||
() =>
|
||||
{
|
||||
Assert.Equal(entry, inventory.Shortcuts.GetEntry(3));
|
||||
order.Add("send");
|
||||
}));
|
||||
Assert.Equal(["send", "local"], order);
|
||||
Assert.Null(inventory.Shortcuts.GetEntry(3));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => inventory.TryAddShortcut(
|
||||
entry,
|
||||
() => throw new InvalidOperationException("transport")));
|
||||
Assert.Equal(entry, inventory.Shortcuts.GetEntry(3));
|
||||
|
||||
int sends = 0;
|
||||
Assert.False(inventory.TryAddShortcut(
|
||||
new ShortcutEntry(ShortcutStore.SlotCount, 1u, 0u),
|
||||
() => sends++));
|
||||
Assert.False(inventory.TryRemoveShortcut(-1, () => sends++));
|
||||
Assert.Equal(0, sends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisposalRetiresShortcutObserversAndTransactionSubscriptions()
|
||||
{
|
||||
using var entities = new RuntimeEntityObjectLifetime();
|
||||
var inventory = new RuntimeInventoryState(entities);
|
||||
inventory.Shortcuts.Changed += static () => { };
|
||||
inventory.Shortcuts.Load([new ShortcutEntry(1, 2u, 3u)]);
|
||||
|
||||
inventory.Dispose();
|
||||
|
||||
Assert.True(inventory.IsDisposed);
|
||||
Assert.Equal(0u, inventory.ExternalContainers.CurrentContainerId);
|
||||
Assert.True(inventory.Shortcuts.IsDisposed);
|
||||
Assert.True(inventory.Transactions.IsDisposed);
|
||||
Assert.Equal(0, inventory.Shortcuts.SubscriberCount);
|
||||
Assert.Empty(inventory.Shortcuts.Items);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue