diff --git a/src/AcDream.App/UI/Layout/InventoryController.cs b/src/AcDream.App/UI/Layout/InventoryController.cs index 9aa6ac45..01de0aea 100644 --- a/src/AcDream.App/UI/Layout/InventoryController.cs +++ b/src/AcDream.App/UI/Layout/InventoryController.cs @@ -33,7 +33,9 @@ public sealed class InventoryController // 3D-items grid: 192x96 → 6 cols x 3 rows of the 32x32 UIItem cell (template 0x21000037). private const int ContentsColumns = 6; - private const float CellPx = 32f; + private const float ContentsCellPx = 32f; // gm3DItemsUI grid (192x96 = 6x3 of 32px) + private const float BackpackCellPx = 36f; // gmBackpackUI column cells (0x100001C9/CA = 36px) + private const int SideBagSlots = 7; // 0x100001CA is 36x252 = 7 slots private readonly ClientObjectTable _objects; private readonly Func _playerGuid; @@ -73,8 +75,8 @@ public sealed class InventoryController if (_contentsGrid is not null) { _contentsGrid.Columns = ContentsColumns; - _contentsGrid.CellWidth = CellPx; - _contentsGrid.CellHeight = CellPx; + _contentsGrid.CellWidth = ContentsCellPx; + _contentsGrid.CellHeight = ContentsCellPx; } // Bind the gutter scrollbar to the contents grid's scroll model (the factory built @@ -94,8 +96,8 @@ public sealed class InventoryController if (_containerList is not null) { _containerList.Columns = 1; - _containerList.CellWidth = CellPx; - _containerList.CellHeight = CellPx; + _containerList.CellWidth = BackpackCellPx; + _containerList.CellHeight = BackpackCellPx; } // Burden meter: vertical 11×58 bar (gmBackpackUI m_burdenMeter, retail direction 4). @@ -177,6 +179,21 @@ public sealed class InventoryController list.AddItem(cell); } + // Side-bag column: pad with empty slot frames up to the player's container capacity + // (clamped to the 7-slot column) so it reads like retail (bags on top, empty frames + // below) rather than one lone cell. Falls back to the full 7 slots when capacity is + // unknown (divergence AP-52). + if (_containerList is not null) + { + int capacity = _objects.Get(p)?.ContainersCapacity ?? 0; + int bags = _containerList.GetNumUIItems(); + int slots = capacity > 0 ? capacity : SideBagSlots; + slots = Math.Max(slots, bags); + slots = Math.Min(slots, SideBagSlots); + while (_containerList.GetNumUIItems() < slots) + _containerList.AddItem(new UiItemSlot { SpriteResolve = _containerList.SpriteResolve }); + } + // Main-pack cell: the player's own container. Icon = placeholder until a backpack // RenderSurface DID is pinned (spec §3 / divergence row). Bind the guid so a future // click can select it. diff --git a/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs index 53844cd5..0fa5d270 100644 --- a/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs @@ -83,8 +83,9 @@ public class InventoryControllerTests Assert.Equal(2, grid.GetNumUIItems()); // 2 loose Assert.Equal(0xAu, grid.GetItem(0)!.ItemId); Assert.Equal(0xBu, grid.GetItem(1)!.ItemId); - Assert.Equal(1, containers.GetNumUIItems()); // 1 side bag + Assert.Equal(7, containers.GetNumUIItems()); // 1 side bag + 6 empty (no capacity → 7-slot column) Assert.Equal(0xCu, containers.GetItem(0)!.ItemId); + Assert.Equal(0u, containers.GetItem(1)!.ItemId); // padded empty frame } [Fact] @@ -102,7 +103,8 @@ public class InventoryControllerTests Assert.Equal(1, grid.GetNumUIItems()); // only the loose item Assert.Equal(0xAu, grid.GetItem(0)!.ItemId); - Assert.Equal(0, containers.GetNumUIItems()); // equipped item is not a side bag either + Assert.Equal(7, containers.GetNumUIItems()); // 7 empty slots (no bags; the equipped item isn't here) + Assert.Equal(0u, containers.GetItem(0)!.ItemId); } [Fact] @@ -203,6 +205,22 @@ public class InventoryControllerTests Assert.Same(grid.Scroll, bar.Model); // the bar drives the grid's scroll } + [Fact] + public void Side_bag_column_pads_empty_slots_up_to_capacity() + { + var (layout, _, containers, _, _, _, _, _) = BuildLayout(); + var objects = new ClientObjectTable(); + objects.AddOrUpdate(new ClientObject { ObjectId = Player, ContainersCapacity = 3 }); + SeedContained(objects, 0xC, Player, slot: 0, type: ItemType.Container); // one side bag + + Bind(layout, objects); + + Assert.Equal(3, containers.GetNumUIItems()); // 1 bag + 2 empty = capacity 3 + Assert.Equal(0xCu, containers.GetItem(0)!.ItemId); // the bag + Assert.Equal(0u, containers.GetItem(1)!.ItemId); // empty frame + Assert.Equal(0u, containers.GetItem(2)!.ItemId); // empty frame + } + // Reads the text of the UiText caption child attached by the controller. private static string CaptionText(UiElement host) {