feat(ui): House tab ownership text — DisplayPurchaseTimeText + RuntimeHouseState
Derived the mechanism from the decomp before writing code: neither gmHouseUI::PostInit @0x004a2710 nor gmMapUI::PostInit @0x004a1c70 sends a HouseQuery, and six of gmHouseUI's seven Display* builders early-return on m_pHouseData == 0. The only text a houseless character's House tab shows is gmHouseUI::DisplayPurchaseTimeText @0x004a3110's expired branch (it doesn't gate on m_pHouseData) — the local player's PropertyInt.HousePurchaseTimestamp plus HouseSystem::HasPurchaseWaitPeriodExpired renders exactly "You may buy another house immediately." for a fresh character. Exhaustive search of the 2013 EoR decomp, ACE, and the live DAT found zero support for a second "You do not currently own a house." line the task brief described — this commit ports what the decomp actually shows. Ships: - RuntimeHouseState: a minimal (no disposal, no construction-transaction Fault() point) Runtime owner per ISSUES #413's own sizing note, wired through GameEventWiring's existing HouseData/HouseStatus delegate holes, LiveSessionEventRouter, and GameRuntime.HouseOwner. Participates in RuntimeGenerationReset (new House stage) since a fresh login must not show a stale character's house state. - HousePageController.Bindings.Lines/OnShown wired to real data; OnShown fires WorldSession.SendHouseQuery() on tab-open (AD-107: an acdream trigger, not a ported retail call site — filed in the divergence register). - Fixed a real bug found along the way: HousePageController.Bind never wired UiTemplateListBox.TemplateResolver, so no row could ever render regardless of Lines content. Now reuses the Map tab's generic hotspot resolver. Live-verified against a real local ACE server and the +Acdream character (--session-config auto-select + a UI automation script): screenshot and structural UI-tree dump both confirm the House tab renders exactly "You may buy another house immediately." Graceful logout confirmed both launches. ISSUES #413 narrowed to its one remaining piece: the six owned-house-only Display* builders (DisplayBuyPayment/RentPayment/BuyTime/RentTimes/ Location/WarningText), unexercisable without a test character that owns a house. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
eb6f3bd8c8
commit
06512f0957
13 changed files with 601 additions and 99 deletions
|
|
@ -37,6 +37,14 @@ public sealed class MapHousePanelControllerTests
|
|||
DatElementId = elementId,
|
||||
};
|
||||
|
||||
/// <summary>The House ListBox's own row template resolves to a
|
||||
/// <see cref="UiText"/> in the live DAT (<c>MapHousePanelSlotProbeTests</c>:
|
||||
/// "row template type=12" — <c>UIElement_Text</c>), unlike the Map tab's
|
||||
/// UiButton hotspots above — a fresh instance per call, matching
|
||||
/// production's real resolver.</summary>
|
||||
private static UiElement? FakeHouseRowTemplate(uint layoutId, uint elementId)
|
||||
=> new UiText { Width = 280f, Height = 28f };
|
||||
|
||||
private static MapHousePanelController.Callbacks MakeCallbacks(
|
||||
List<string>? calls = null,
|
||||
Func<DerethDateTime.Calendar>? currentCalendar = null,
|
||||
|
|
@ -54,7 +62,8 @@ public sealed class MapHousePanelControllerTests
|
|||
TemplateResolver: FakeHotspotTemplate),
|
||||
House: new HousePageController.Bindings(
|
||||
Lines: houseLines ?? (static () => Array.Empty<string>()),
|
||||
OnShown: () => calls.Add("house-shown")));
|
||||
OnShown: () => calls.Add("house-shown"),
|
||||
TemplateResolver: FakeHouseRowTemplate));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -186,4 +195,41 @@ public sealed class MapHousePanelControllerTests
|
|||
var listBox = Assert.IsType<UiTemplateListBox>(box);
|
||||
Assert.Equal(0, listBox.ContentHeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch C House-ownership-text closer (2026-08-17): the ONE
|
||||
/// decomp-verified <c>gmHouseUI::DisplayPurchaseTimeText @0x004a3110</c>
|
||||
/// line a houseless character's HouseQuery response renders — proves
|
||||
/// <see cref="HousePageController"/>'s revision-gated
|
||||
/// <see cref="MapHousePanelController.Tick"/> poll actually rebuilds the
|
||||
/// authored row template with real text end-to-end, the same way
|
||||
/// <see cref="RuntimeHouseStateTests"/> proves the text composition in
|
||||
/// isolation.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Tick_RendersHouseLinesIntoTheAuthoredRowTemplate()
|
||||
{
|
||||
ElementInfo rootInfo = FixtureLoader.LoadMapHouseHostInfos();
|
||||
ImportedLayout layout = FixtureLoader.LoadMapHouseHost();
|
||||
string[] lines = ["You may buy another house immediately."];
|
||||
MapHousePanelController? controller = MapHousePanelController.Bind(
|
||||
rootInfo, layout, MakeCallbacks(houseLines: () => lines));
|
||||
Assert.NotNull(controller);
|
||||
|
||||
controller!.Tick(0.016);
|
||||
|
||||
UiElement? box = UiElement.FindDescendant(controller.Root, HousePageController.TextBoxId);
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(box);
|
||||
// Rows land in the ListBox's internal scrollable viewport (AddChild
|
||||
// there, not directly on the ListBox itself — UiTemplateListBox's
|
||||
// own #372/#412 dormancy machinery), exposed to tests via
|
||||
// ViewportForTest.
|
||||
UiScrollablePanel viewport = Assert.IsType<UiScrollablePanel>(
|
||||
listBox.ViewportForTest);
|
||||
Assert.Single(viewport.Children);
|
||||
var row = Assert.IsType<UiText>(viewport.Children[0]);
|
||||
Assert.Equal(
|
||||
"You may buy another house immediately.",
|
||||
Assert.Single(row.LinesProvider()).Text);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue