feat(ui): D.2b-B — InventoryController bind + grid population (loose/side-bag partition)
Task 4: InventoryController.Bind + Populate: find-by-id bind for the 7 inventory element ids, configure grid (6 cols x 32px) + container list, partition GetContents(player) into loose items (contentsGrid) vs side bags (containerList), populate main-pack cell in topContainer, subscribe ObjectAdded/Moved/Removed/Updated for live rebuilds. Handles both Ingest-indexed (production) and AddOrUpdate-direct (test) paths via fallback scan. Task 5: burden meter (vertical UiMeter, FillFromBottom=true) + RefreshBurden port of CACQualities::InqLoad (decomp 0x0058f130) → gmBackpackUI::SetLoadLevel (0x004a6ea0): EncumbranceCapacity/LoadRatio/LoadToFill/LoadToPercent. Three AttachCaption overlays (Burden, Contents of Backpack, %text). 5 tests all green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5e75d2ac76
commit
89c640a54d
2 changed files with 368 additions and 0 deletions
138
tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs
Normal file
138
tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
using System.Collections.Generic;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Items;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public class InventoryControllerTests
|
||||
{
|
||||
private const uint Player = 0x50000001u;
|
||||
|
||||
// UiElement is abstract — a concrete, parameterless stand-in for the root + caption hosts
|
||||
// (the real captions are Type-0 → UiDatElement; in tests we only need a host that holds children).
|
||||
private sealed class TestElement : UiElement { }
|
||||
|
||||
// Element ids (spec §1).
|
||||
private const uint ContentsGrid = 0x100001C6u;
|
||||
private const uint ContainerList = 0x100001CAu;
|
||||
private const uint TopContainer = 0x100001C9u;
|
||||
private const uint BurdenMeter = 0x100001D9u;
|
||||
private const uint BurdenText = 0x100001D8u;
|
||||
private const uint BurdenCaption = 0x100001D7u;
|
||||
private const uint ContentsCaption = 0x100001C5u;
|
||||
|
||||
private static (ImportedLayout layout, UiItemList grid, UiItemList containers,
|
||||
UiItemList top, UiMeter meter, UiElement burdenText,
|
||||
UiElement burdenCap, UiElement contentsCap) BuildLayout()
|
||||
{
|
||||
var grid = new UiItemList { Width = 192, Height = 96 };
|
||||
var containers = new UiItemList { Width = 36, Height = 252 };
|
||||
var top = new UiItemList { Width = 36, Height = 36 };
|
||||
var meter = new UiMeter { Width = 11, Height = 58 };
|
||||
var burdenText = new TestElement { Width = 36, Height = 15 };
|
||||
var burdenCap = new TestElement { Width = 36, Height = 15 };
|
||||
var contentsCap = new TestElement { Width = 192, Height = 15 };
|
||||
var root = new TestElement { Width = 300, Height = 362 };
|
||||
root.AddChild(grid); root.AddChild(containers); root.AddChild(top);
|
||||
root.AddChild(meter); root.AddChild(burdenText); root.AddChild(burdenCap);
|
||||
root.AddChild(contentsCap);
|
||||
var byId = new Dictionary<uint, UiElement>
|
||||
{
|
||||
[ContentsGrid] = grid, [ContainerList] = containers, [TopContainer] = top,
|
||||
[BurdenMeter] = meter, [BurdenText] = burdenText, [BurdenCaption] = burdenCap,
|
||||
[ContentsCaption] = contentsCap,
|
||||
};
|
||||
return (new ImportedLayout(root, byId), grid, containers, top, meter,
|
||||
burdenText, burdenCap, contentsCap);
|
||||
}
|
||||
|
||||
private static InventoryController Bind(ImportedLayout layout, ClientObjectTable objects,
|
||||
int? strength = 100)
|
||||
=> InventoryController.Bind(layout, objects, () => Player,
|
||||
iconIds: (_, _, _, _, _) => 0u, // no real GL upload in tests
|
||||
strength: () => strength, datFont: null);
|
||||
|
||||
[Fact]
|
||||
public void Populate_fills_contents_grid_with_loose_items_only()
|
||||
{
|
||||
var (layout, grid, containers, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 0xA, ContainerId = Player,
|
||||
ContainerSlot = 0, Burden = 10 }); // loose
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 0xB, ContainerId = Player,
|
||||
ContainerSlot = 1, Burden = 20 }); // loose
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 0xC, ContainerId = Player,
|
||||
ContainerSlot = 2, Type = ItemType.Container }); // side bag
|
||||
|
||||
Bind(layout, objects);
|
||||
|
||||
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(0xCu, containers.GetItem(0)!.ItemId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grid_is_six_columns_thirtytwo_px()
|
||||
{
|
||||
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||
Bind(layout, new ClientObjectTable());
|
||||
Assert.Equal(6, grid.Columns);
|
||||
Assert.Equal(32f, grid.CellWidth);
|
||||
Assert.Equal(32f, grid.CellHeight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectAdded_for_player_item_rebuilds_grid()
|
||||
{
|
||||
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
Bind(layout, objects);
|
||||
Assert.Equal(0, grid.GetNumUIItems());
|
||||
|
||||
objects.Ingest(new WeenieData(0xA, "Sword", ItemType.MeleeWeapon, 1, 0, 0, 0, 0,
|
||||
null, null, null, 5, Player, null, null, null, null, null, null, null, null, null));
|
||||
|
||||
Assert.Equal(1, grid.GetNumUIItems());
|
||||
Assert.Equal(0xAu, grid.GetItem(0)!.ItemId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Burden_meter_fill_and_percent_from_load()
|
||||
{
|
||||
var (layout, _, _, _, meter, burdenText, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
// Str 100 → capacity 15000. Carried 7500 → load 0.5 → fill 0.1667, "50%".
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 0xA, ContainerId = Player, Burden = 7500 });
|
||||
Bind(layout, objects, strength: 100);
|
||||
|
||||
Assert.Equal(0.16667f, meter.Fill() ?? -1f, 3);
|
||||
Assert.True(meter.Vertical);
|
||||
// The % text caption child reads "50%".
|
||||
Assert.Contains("50%", CaptionText(burdenText));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Captions_render_known_strings()
|
||||
{
|
||||
var (layout, _, _, _, _, _, burdenCap, contentsCap) = BuildLayout();
|
||||
Bind(layout, new ClientObjectTable());
|
||||
Assert.Contains("Burden", CaptionText(burdenCap));
|
||||
Assert.Contains("Contents of Backpack", CaptionText(contentsCap));
|
||||
}
|
||||
|
||||
// Reads the text of the UiText caption child attached by the controller.
|
||||
private static string CaptionText(UiElement host)
|
||||
{
|
||||
foreach (var c in host.Children)
|
||||
if (c is UiText t)
|
||||
{
|
||||
var lines = t.LinesProvider();
|
||||
if (lines.Count > 0) return lines[0].Text;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue