fix(D.2b): gapless insert on optimistic move — stop the inventory reshuffle
Visual gate: moving an item within a bag reshuffled every item ("the order is
not set"). Root cause: insert-before set ONLY the dragged item's ContainerSlot
to N, colliding with the item already at N; the sort-by-slot tie then reordered
the whole grid on every repaint. Fix: MoveItemOptimistic now does a proper
index-based INSERT (shift the others, renumber 0..N-1 gapless) like retail's
ItemList_InsertItem, and HandleDropRelease uses the target's GRID INDEX
(SlotIndex) as the placement rather than its raw ContainerSlot. Regression test
pins the shifted, gapless order. Full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
975f89c870
commit
82ab0e045a
3 changed files with 52 additions and 2 deletions
|
|
@ -333,7 +333,7 @@ public sealed class InventoryController : IItemListDragHandler
|
|||
{
|
||||
container = EffectiveOpen();
|
||||
placement = targetCell.ItemId != 0
|
||||
? (_objects.Get(targetCell.ItemId)?.ContainerSlot ?? targetCell.SlotIndex) // insert-before
|
||||
? targetCell.SlotIndex // insert-before = the target's GRID INDEX (gapless), not its raw ContainerSlot
|
||||
: _objects.GetContents(container).Count; // first empty = append
|
||||
}
|
||||
else if (targetList == _containerList || targetList == _topContainer)
|
||||
|
|
|
|||
|
|
@ -152,7 +152,40 @@ public sealed class ClientObjectTable
|
|||
_pendingMoves[itemId] = (p.container, p.slot, p.outstanding + 1);
|
||||
else
|
||||
_pendingMoves[itemId] = (item.ContainerId, item.ContainerSlot, 1);
|
||||
return MoveItem(itemId, newContainerId, newSlot);
|
||||
|
||||
// Gapless INSERT — treat newSlot as the insert INDEX, shift the other items, and renumber both
|
||||
// containers' slots 0..N-1. A raw MoveItem sets ONLY this item's slot, which then collides with
|
||||
// the item already at that slot; the sort-by-slot tie reshuffles the whole grid on every repaint
|
||||
// (the "items change position when I move one" bug). Retail's ItemList_InsertItem shifts the list.
|
||||
uint oldContainer = item.ContainerId;
|
||||
item.ContainerId = newContainerId;
|
||||
item.CurrentlyEquippedLocation = EquipMask.None;
|
||||
|
||||
if (oldContainer != 0 && oldContainer != newContainerId
|
||||
&& _containerIndex.TryGetValue(oldContainer, out var srcList))
|
||||
{ srcList.Remove(itemId); RenumberContainer(srcList); } // keep the source gapless too
|
||||
|
||||
if (newContainerId != 0)
|
||||
{
|
||||
if (!_containerIndex.TryGetValue(newContainerId, out var dstList))
|
||||
_containerIndex[newContainerId] = dstList = new List<uint>();
|
||||
dstList.Remove(itemId); // same-container move: pull out first
|
||||
int idx = (newSlot < 0 || newSlot > dstList.Count) ? dstList.Count : newSlot;
|
||||
dstList.Insert(idx, itemId);
|
||||
RenumberContainer(dstList); // sequential ContainerSlots → no ties
|
||||
}
|
||||
else item.ContainerSlot = newSlot;
|
||||
|
||||
ObjectMoved?.Invoke(item, oldContainer, newContainerId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Assign each item in <paramref name="list"/> a sequential ContainerSlot (0..N-1) matching
|
||||
/// its list position — keeps a container gapless + collision-free so the grid order is stable.</summary>
|
||||
private void RenumberContainer(List<uint> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
if (_objects.TryGetValue(list[i], out var o)) o.ContainerSlot = i;
|
||||
}
|
||||
|
||||
/// <summary>The server confirmed a move (InventoryPutObjInContainer 0x0022 echo) — decrement the
|
||||
|
|
|
|||
|
|
@ -422,6 +422,23 @@ public sealed class ClientObjectTableTests
|
|||
Assert.Equal(0xC5u, table.Get(0x911u)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveItemOptimistic_insertBefore_shiftsOthers_keepsGaplessOrder() // the "items change position" bug
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.Ingest(FullWeenie(0x920u, container: 0xC0u)); table.MoveItem(0x920u, 0xC0u, 0);
|
||||
table.Ingest(FullWeenie(0x921u, container: 0xC0u)); table.MoveItem(0x921u, 0xC0u, 1);
|
||||
table.Ingest(FullWeenie(0x922u, container: 0xC0u)); table.MoveItem(0x922u, 0xC0u, 2);
|
||||
|
||||
// Move 0x922 (index 2) to index 0 → order [0x922, 0x920, 0x921], slots renumbered 0,1,2 (no ties,
|
||||
// no reshuffle). A raw slot-set would leave 0x922 + 0x920 both at slot 0 → unstable sort.
|
||||
table.MoveItemOptimistic(0x922u, 0xC0u, 0);
|
||||
Assert.Equal(new[] { 0x922u, 0x920u, 0x921u }, table.GetContents(0xC0u));
|
||||
Assert.Equal(0, table.Get(0x922u)!.ContainerSlot);
|
||||
Assert.Equal(1, table.Get(0x920u)!.ContainerSlot);
|
||||
Assert.Equal(2, table.Get(0x921u)!.ContainerSlot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainerIndex_SlotChange_ResortsInPlace() // guards the Reindex same-container early-out
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue