fix(client): restore retail interaction parity
Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
This commit is contained in:
parent
0c699240e0
commit
f6fe0f2a4f
151 changed files with 10162 additions and 1211 deletions
|
|
@ -494,6 +494,28 @@ public class InventoryControllerTests
|
|||
Assert.True(containers.GetItem(0)!.Selected); // square — the bag is also the selected item
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoubleClickOwnedBag_opensOnceOnFirstPress_andNeverRunsGenericUse()
|
||||
{
|
||||
var (layout, _, containers, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
SeedBag(objects, 0xCu, slot: 0);
|
||||
var uses = new List<uint>();
|
||||
Bind(layout, objects, uses: uses);
|
||||
|
||||
UiItemSlot bag = containers.GetItem(0)!;
|
||||
bag.OnEvent(new UiEvent(0u, bag, UiEventType.MouseDown));
|
||||
bag.OnEvent(new UiEvent(0u, bag, UiEventType.Click));
|
||||
bag.OnEvent(new UiEvent(0u, bag, UiEventType.MouseDown));
|
||||
bag.OnEvent(new UiEvent(0u, bag, UiEventType.Click));
|
||||
bag.OnEvent(new UiEvent(0u, bag, UiEventType.DoubleClick));
|
||||
|
||||
Assert.Equal(new[] { 0xCu }, uses);
|
||||
Assert.Null(bag.DoubleClicked);
|
||||
Assert.True(containers.GetItem(0)!.IsOpenContainer);
|
||||
Assert.Equal(0xCu, objects.Get(0xCu)!.ObjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MouseDownGridItem_movesSquareImmediately_noWire_keepsOpenContainer()
|
||||
{
|
||||
|
|
@ -684,7 +706,7 @@ public class InventoryControllerTests
|
|||
Workmanship: null);
|
||||
|
||||
[Fact]
|
||||
public void Drop_onOccupiedGridCell_insertsBefore_andMovesLocally()
|
||||
public void Drop_onOccupiedGridCell_insertsBefore_andWaitsForServer()
|
||||
{
|
||||
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
|
|
@ -699,7 +721,7 @@ public class InventoryControllerTests
|
|||
((IItemListDragHandler)ctrl).HandleDropRelease(grid, bCell, Payload(0xFFFFu));
|
||||
|
||||
Assert.Contains((0xFFFFu, Player, 1), puts); // insert-before slot 1, into the open container
|
||||
Assert.Equal(Player, objects.Get(0xFFFFu)!.ContainerId); // moved locally (instant)
|
||||
Assert.Equal(0u, objects.Get(0xFFFFu)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1316,11 +1338,12 @@ public class InventoryControllerTests
|
|||
StackSizeMax = 100,
|
||||
});
|
||||
objects.MoveItem(0xAu, Player, 0);
|
||||
SeedBag(objects, 0xCu, slot: 1);
|
||||
var selection = new SelectionState();
|
||||
selection.Select(0xAu, SelectionChangeSource.Inventory);
|
||||
var splitQuantity = new StackSplitQuantityState();
|
||||
splitQuantity.Reset(10u);
|
||||
splitQuantity.SetValue(1u);
|
||||
splitQuantity.SetValue(2u);
|
||||
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,
|
||||
|
|
@ -1328,7 +1351,9 @@ public class InventoryControllerTests
|
|||
|
||||
ctrl.HandleDropRelease(grid, grid.GetItem(5)!, Payload(0xAu));
|
||||
|
||||
Assert.Equal(new[] { (0xAu, Player, 1u, 1u) }, splits);
|
||||
// Placement counts the main pack's visible loose-item list only;
|
||||
// side bags occupy the separate selector list and must not shift it.
|
||||
Assert.Equal(new[] { (0xAu, Player, 1u, 2u) }, splits);
|
||||
Assert.Empty(puts);
|
||||
Assert.Equal(Player, objects.Get(0xAu)!.ContainerId);
|
||||
Assert.Equal(0, objects.Get(0xAu)!.ContainerSlot);
|
||||
|
|
@ -1464,7 +1489,7 @@ public class InventoryControllerTests
|
|||
((IItemListDragHandler)ctrl).HandleDropRelease(containers, bagCell, Payload(0xFFFFu));
|
||||
|
||||
Assert.Contains((0xFFFFu, 0xCu, 0), puts); // into the bag, append (placement 0)
|
||||
Assert.Equal(0xCu, objects.Get(0xFFFFu)!.ContainerId);
|
||||
Assert.Equal(0u, objects.Get(0xFFFFu)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1483,6 +1508,34 @@ public class InventoryControllerTests
|
|||
ctrl.OnDragOver(grid, grid.GetItem(0)!, Payload(0xFFFFu))); // grid → green
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MainPackFullness_countsLooseItems_notSideBags_afterAFreeSlotAppears()
|
||||
{
|
||||
var (layout, _, _, top, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = Player,
|
||||
Type = ItemType.Creature,
|
||||
ItemsCapacity = 2,
|
||||
});
|
||||
SeedContained(objects, 0xA0u, Player, slot: 0);
|
||||
SeedContained(objects, 0xA1u, Player, slot: 1);
|
||||
SeedBag(objects, 0xC0u, slot: 0);
|
||||
SeedContained(objects, 0xB0u, 0xC0u, slot: 0);
|
||||
var controller = (IItemListDragHandler)Bind(layout, objects);
|
||||
UiItemSlot mainPack = top.GetItem(0)!;
|
||||
|
||||
Assert.Equal(ItemDragAcceptance.Reject,
|
||||
controller.OnDragOver(top, mainPack, Payload(0xB0u)));
|
||||
|
||||
Assert.True(objects.Remove(0xA1u));
|
||||
|
||||
Assert.Equal(ItemDragAcceptance.Accept,
|
||||
controller.OnDragOver(top, top.GetItem(0)!, Payload(0xB0u)));
|
||||
Assert.Equal(0.5f, top.GetItem(0)!.CapacityFill);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GroundPack_rejectsContentsGrid_butEmptyPackSlotAcceptsAndPicksUpAtThatSlot()
|
||||
{
|
||||
|
|
@ -1617,7 +1670,7 @@ public class InventoryControllerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Drop_thenServerRollback_revertsTheMove() // optimistic + InventoryServerSaveFailed snap-back
|
||||
public void Drop_thenServerReject_keepsCanonicalPlacement()
|
||||
{
|
||||
var (layout, _, containers, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
|
|
@ -1626,11 +1679,10 @@ public class InventoryControllerTests
|
|||
var ctrl = Bind(layout, objects);
|
||||
|
||||
((IItemListDragHandler)ctrl).HandleDropRelease(containers, containers.GetItem(0)!, Payload(0xAu));
|
||||
Assert.Equal(0xCu, objects.Get(0xAu)!.ContainerId); // moved into the bag optimistically (instant)
|
||||
|
||||
objects.RollbackMove(0xAu); // server rejected (InventoryServerSaveFailed)
|
||||
Assert.Equal(Player, objects.Get(0xAu)!.ContainerId); // snapped back to the main pack
|
||||
Assert.Equal(3, objects.Get(0xAu)!.ContainerSlot); // and the original slot
|
||||
Assert.Equal(Player, objects.Get(0xAu)!.ContainerId);
|
||||
Assert.False(objects.RejectMove(0xAu, 0x426u));
|
||||
Assert.Equal(Player, objects.Get(0xAu)!.ContainerId);
|
||||
Assert.Equal(3, objects.Get(0xAu)!.ContainerSlot);
|
||||
}
|
||||
|
||||
// Reads the text of the UiText caption child attached by the controller.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue