fix(ui): port retail partial stack transfers
Route the selected stack quantity through retail GetObjectSplitSize semantics, send exact split-to-container and split-to-ground actions, and keep the original object in place until the server publishes the newly guided stack. Cover selection scoping, wire bytes, container placement, and world drops with conformance tests. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
6005c51c4d
commit
ea72c395c9
14 changed files with 280 additions and 19 deletions
|
|
@ -18,7 +18,10 @@ public sealed class ItemInteractionControllerTests
|
|||
public readonly List<(uint Source, uint Target)> UseWithTarget = new();
|
||||
public readonly List<(uint Item, uint Mask)> Wields = new();
|
||||
public readonly List<uint> Drops = new();
|
||||
public readonly List<(uint Item, uint Amount)> SplitDrops = new();
|
||||
public readonly List<string> Toasts = new();
|
||||
public readonly StackSplitQuantityState SplitQuantity = new();
|
||||
public uint SelectedObject;
|
||||
public long Now = 1_000;
|
||||
|
||||
public Harness()
|
||||
|
|
@ -46,7 +49,10 @@ public sealed class ItemInteractionControllerTests
|
|||
sendWield: (item, mask) => Wields.Add((item, mask)),
|
||||
sendDrop: Drops.Add,
|
||||
nowMs: () => Now,
|
||||
toast: Toasts.Add);
|
||||
toast: Toasts.Add,
|
||||
sendSplitToWorld: (item, amount) => SplitDrops.Add((item, amount)),
|
||||
selectedObjectId: () => SelectedObject,
|
||||
stackSplitQuantity: SplitQuantity);
|
||||
}
|
||||
|
||||
public ItemInteractionController Controller { get; }
|
||||
|
|
@ -389,6 +395,28 @@ public sealed class ItemInteractionControllerTests
|
|||
Assert.Equal(0u, h.Objects.Get(0x50000A07u)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedPartialStackDragOutsideUi_splitsWithoutMovingOriginal()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.AddContained(0x50000A70u, item => item.StackSize = 10);
|
||||
h.SelectedObject = 0x50000A70u;
|
||||
h.SplitQuantity.Reset(10u);
|
||||
h.SplitQuantity.SetValue(1u);
|
||||
var payload = new ItemDragPayload(
|
||||
0x50000A70u,
|
||||
ItemDragSource.Inventory,
|
||||
SourceSlot: 0,
|
||||
SourceCell: new UiItemSlot());
|
||||
|
||||
Assert.True(h.Controller.DropToWorld(payload));
|
||||
|
||||
Assert.Equal(new[] { (0x50000A70u, 1u) }, h.SplitDrops);
|
||||
Assert.Empty(h.Drops);
|
||||
Assert.Equal(Pack, h.Objects.Get(0x50000A70u)!.ContainerId);
|
||||
Assert.Equal(10, h.Objects.Get(0x50000A70u)!.StackSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolbarShortcutDragOutsideUi_doesNotDropRealItem()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public class InventoryControllerTests
|
|||
private static InventoryController Bind(ImportedLayout layout, ClientObjectTable objects,
|
||||
int? strength = 100, List<uint>? uses = null, List<uint>? closes = null,
|
||||
List<(uint item, uint container, int placement)>? puts = null,
|
||||
List<(uint item, uint container, uint placement, uint amount)>? splits = null,
|
||||
List<(uint source, uint target, uint amount)>? merges = null,
|
||||
List<(uint source, uint target)>? mergeNotices = null,
|
||||
string? ownerName = null,
|
||||
|
|
@ -69,6 +70,9 @@ public class InventoryControllerTests
|
|||
sendUse: uses is null ? null : g => uses.Add(g),
|
||||
sendNoLongerViewing: closes is null ? null : g => closes.Add(g),
|
||||
sendPutItemInContainer: puts is null ? null : (i, c, p) => puts.Add((i, c, p)),
|
||||
sendStackableSplitToContainer: splits is null
|
||||
? null
|
||||
: (i, c, p, a) => splits.Add((i, c, p, a)),
|
||||
sendStackableMerge: merges is null ? null : (s, t, a) => merges.Add((s, t, a)),
|
||||
notifyMergeAttempt: mergeNotices is null ? null : (s, t) => mergeNotices.Add((s, t)),
|
||||
onClose: onClose,
|
||||
|
|
@ -570,6 +574,62 @@ public class InventoryControllerTests
|
|||
Assert.Contains((0xFFFFu, Player, 1), puts); // placement = occupied count (1) = first empty
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Drop_selectedPartialStack_splitsIntoContainer_withoutMovingOriginal()
|
||||
{
|
||||
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0xAu,
|
||||
StackSize = 10,
|
||||
StackSizeMax = 100,
|
||||
});
|
||||
objects.MoveItem(0xAu, Player, 0);
|
||||
var selection = new SelectionState();
|
||||
selection.Select(0xAu, SelectionChangeSource.Inventory);
|
||||
var splitQuantity = new StackSplitQuantityState();
|
||||
splitQuantity.Reset(10u);
|
||||
splitQuantity.SetValue(1u);
|
||||
var splits = new List<(uint item, uint container, uint placement, uint amount)>();
|
||||
var puts = new List<(uint item, uint container, int placement)>();
|
||||
var ctrl = Bind(layout, objects, puts: puts, splits: splits,
|
||||
selection: selection, stackSplitQuantity: splitQuantity);
|
||||
|
||||
ctrl.HandleDropRelease(grid, grid.GetItem(5)!, Payload(0xAu));
|
||||
|
||||
Assert.Equal(new[] { (0xAu, Player, 1u, 1u) }, splits);
|
||||
Assert.Empty(puts);
|
||||
Assert.Equal(Player, objects.Get(0xAu)!.ContainerId);
|
||||
Assert.Equal(0, objects.Get(0xAu)!.ContainerSlot);
|
||||
Assert.Equal(10, objects.Get(0xAu)!.StackSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Drop_unselectedStack_ignoresOtherSelectionSplitQuantity_andMovesWholeStack()
|
||||
{
|
||||
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 0xAu, StackSize = 10 });
|
||||
objects.MoveItem(0xAu, Player, 0);
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 0xBu, StackSize = 20 });
|
||||
objects.MoveItem(0xBu, Player, 1);
|
||||
var selection = new SelectionState();
|
||||
selection.Select(0xBu, SelectionChangeSource.Inventory);
|
||||
var splitQuantity = new StackSplitQuantityState();
|
||||
splitQuantity.Reset(20u);
|
||||
splitQuantity.SetValue(1u);
|
||||
var splits = new List<(uint item, uint container, uint placement, uint amount)>();
|
||||
var puts = new List<(uint item, uint container, int placement)>();
|
||||
var ctrl = Bind(layout, objects, puts: puts, splits: splits,
|
||||
selection: selection, stackSplitQuantity: splitQuantity);
|
||||
|
||||
ctrl.HandleDropRelease(grid, grid.GetItem(4)!, Payload(0xAu));
|
||||
|
||||
Assert.Empty(splits);
|
||||
Assert.Equal(new[] { (0xAu, Player, 2) }, puts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Drop_matchingStackOnOccupiedStack_mergesBeforeMoveAndBroadcastsToolbarNotice()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue