feat(ui): Map/House panel — slices 2+3, panel shell + Map tab
Mounts host 0x2100006E slot 0x1000018C (RetailPanelCatalog.MapHouse = 16) as a two-tab UiTabPanel (Map default, House second) through the OP3/FA3 recipe (LayoutImporter.Build -> Bind -> ActivateTabBehavior). Toolbar button 0x1000019A un-ghosts (added to both RetailPanelCatalog.Mounted and .Toolbar). Combined into one commit because MapHousePanelController.Bind depends on both MapPageController and HousePageController existing — splitting them would mean landing dead code first. Map tab (gmMapUI, MapPageController): - Calendar formatter matching gmMapUI::Update's "Date: %s\nTime: %s" shape, reusing WorldTimeService.CurrentCalendar (new Func<DerethDateTime.Calendar> dependency threaded through InteractionRetainedUiDependencies/GameWindow — a stable long-lived service, not routed through the deferred-binding machinery Radar's per-session state needs). MonthName enum values already match retail display text; HourName's "AndHalf" suffix is rewritten to "-and-Half". - Coordinate math + marker placement reuse RadarCoordinates/ LandDefs.GidToLcoord verbatim (both already byte-exact ports of CPlayerSystem::InqPlayerCoords/LandDefs::gid_to_lcoord) — no re-port. PlaceMarkerOnMap's centering math (m_x0 + x - w/2) ported from gmMapUI::PlaceMarkerOnMap @0x004a18b0. Indoor gating clears the coordinate text and hides the player marker, matching gmMapUI::Update's else branch. - 53-town s_rgLocations table ported verbatim into MapLocations.cs. Markers built once at bind time via the panel's own RowTemplateResolver against m_pMap's authored hotspot-template attrs (0x47/0x48), with literal-string tooltips through AuthoredTooltipText/Enabled (RetailTooltipPresenter) — closes divergence-register row TS-85's last item, gmMapUI::AddMapNote @0x004A1C51. - Structural finding: m_pMap (0x100001EC) is itself authored as a Type-1 BUTTON (the GM click-to-teleport hook at gmMapUI::ListenToElementMessage), and the player/house icons (0x100001ED/EE) are its own NESTED children, not siblings — UiButton.ConsumesDatChildren swallows them from the normally-built tree. Both are re-resolved standalone through the same template resolver the town hotspots use and reattached under m_pMap. House tab (gmHouseUI, HousePageController): mounts the ListBox (0x100001E6) with its authored row template, wired to an empty Lines() source by default — genuinely empty until Slice 4's wire lands, matching retail's own PostInit (no Update call, no static content). 21 new tests (7 MapHousePanelControllerTests, 14 MapPageControllerTests): tab table pairing, close button, town-hotspot count/tooltips, calendar formatter golden values (Frostfell 27/119 P.Y., every HourName incl. AndHalf), player/house marker placement and indoor-gating reproduced against the real fixture via already-tested RadarCoordinates (no re-derivation). Fixture map_house_2100006E_1000018C.json captured via the shared RetailLayoutFixtureGenerator (other 34 fixtures deliberately NOT regenerated — out of scope for this batch, would touch unrelated panels' schema drift). Full solution builds clean; App suite 5391/0 failed/71 skipped (non-live; one earlier flaky streaming failure unrelated to this change, confirmed pre-existing on the branch before these commits). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
04841b6807
commit
6b0fa4ff0d
15 changed files with 5865 additions and 3 deletions
79
src/AcDream.App/UI/Layout/MapLocations.cs
Normal file
79
src/AcDream.App/UI/Layout/MapLocations.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// One retail map hotspot rect + rollover name. Mirrors
|
||||
/// <c>gmMapUI::LocationRolloverInfo</c> (<c>acclient.h:55686</c>): X/Y/Width/
|
||||
/// Height are direct pixel offsets within <c>m_pMap</c> (no coordinate
|
||||
/// transform — <c>gmMapUI::AddMapNote @0x004a1bb0</c> passes them straight to
|
||||
/// <c>MoveTo</c>/<c>ResizeTo</c>), and Name is the literal tooltip text
|
||||
/// (<c>StringInfo::SetLiteralValue</c>, not a DAT string-table lookup).
|
||||
/// </summary>
|
||||
public readonly record struct MapLocation(int X, int Y, int Width, int Height, string Name);
|
||||
|
||||
/// <summary>
|
||||
/// Verbatim port of retail's static <c>s_rgLocations[0x35]</c> table
|
||||
/// (<c>docs/research/named-retail/acclient_2013_pseudo_c.txt:977225-977651</c>),
|
||||
/// consumed by <c>gmMapUI::PostInit @0x004a1c70</c> to place the 53 town
|
||||
/// hotspots on the Map tab. Ported verbatim, in original index order —
|
||||
/// do not resort or "clean up"; the order is not semantically meaningful
|
||||
/// but the values must match byte-for-byte.
|
||||
/// </summary>
|
||||
public static class MapLocations
|
||||
{
|
||||
public static readonly MapLocation[] All =
|
||||
{
|
||||
new(0xb2, 0x14, 0xb, 0xc, "Aerlinthe Island"),
|
||||
new(0x12, 0x4a, 0x5, 0x5, "Ahurenga"),
|
||||
new(0x8d, 0xa6, 0x7, 0x6, "Al-Arqas"),
|
||||
new(0x81, 0x79, 0x7, 0x6, "Al-Jalima"),
|
||||
new(0xbe, 0x58, 0x9, 0x8, "Arwic"),
|
||||
new(0x13, 0xc9, 0x7, 0x6, "Ayan Baqur"),
|
||||
new(0xc8, 0xbe, 0x7, 0x6, "Baishi"),
|
||||
new(0xb8, 0x35, 0x5, 0x5, "Bandit Castle"),
|
||||
new(0x22, 0x54, 0x5, 0x5, "Bluespire"),
|
||||
new(0x2c, 0xeb, 0x5, 0x5, "Candeth Keep"),
|
||||
new(0xb4, 0x61, 0x9, 0x8, "Cragstone"),
|
||||
new(0x5b, 0x66, 0x5, 0x5, "Danby's Outpost"),
|
||||
new(0xd3, 0x8a, 0x9, 0x8, "Dryreach"),
|
||||
new(0xc7, 0x6a, 0x9, 0x8, "Eastham"),
|
||||
new(0x38, 0xd, 0x5, 0x5, "Fiun Outpost"),
|
||||
new(0x25, 0x7f, 0x9, 0x8, "Fort Tethana"),
|
||||
new(0x9c, 0x5e, 0x9, 0x8, "Glenden Wood"),
|
||||
new(0x2b, 0x4f, 0x5, 0x5, "Greenspire"),
|
||||
new(0xe0, 0xb1, 0x7, 0x6, "Hebian-to"),
|
||||
new(0xa4, 0x4d, 0x9, 0x8, "Holtburg"),
|
||||
new(0xb6, 0xe6, 0x7, 0x6, "Kara"),
|
||||
new(0x9b, 0xb9, 0x7, 0x6, "Khayyaban"),
|
||||
new(0xe0, 0xda, 0x7, 0x6, "Kryst"),
|
||||
new(0xd4, 0xc3, 0x7, 0x6, "Lin"),
|
||||
new(0x9f, 0xe0, 0x5, 0x5, "Linvak Tukal"),
|
||||
new(0xb9, 0x7e, 0x9, 0x8, "Lytelthorpe"),
|
||||
new(0xeb, 0xdc, 0x7, 0x6, "MacNiall's Freehold"),
|
||||
new(0xdf, 0xcb, 0x7, 0x6, "Mayoi"),
|
||||
new(0x8d, 0x35, 0x5, 0x5, "Mt Esper-Crater Village"),
|
||||
new(0xe0, 0xbf, 0x7, 0x6, "Nanto"),
|
||||
new(0x8e, 0x2e, 0x5, 0x5, "Neydisa"),
|
||||
new(0xf0, 0x80, 0x5, 0x5, "Oolutanga's Refuge"),
|
||||
new(0x4a, 0x4f, 0x5, 0x5, "Plateau Village"),
|
||||
new(0x94, 0xda, 0x7, 0x6, "Qalaba'r"),
|
||||
new(0x1a, 0x53, 0x5, 0x5, "Redspire"),
|
||||
new(0xc1, 0x72, 0x9, 0x8, "Rithwic"),
|
||||
new(0x92, 0x85, 0x7, 0x6, "Samsur"),
|
||||
new(0x32, 0x2a, 0x5, 0x5, "Sanamar"),
|
||||
new(0xc3, 0xa3, 0x7, 0x6, "Sawato"),
|
||||
new(0xd5, 0xab, 0x7, 0x6, "Shoushi"),
|
||||
new(0x29, 0x19, 0x5, 0x5, "Silyun"),
|
||||
new(0x6, 0xef, 0xf, 0x10, "Singularity Caul Island"),
|
||||
new(0x64, 0x30, 0x5, 0x5, "Stonehold"),
|
||||
new(0x20, 0x4c, 0x5, 0x5, "Timaru"),
|
||||
new(0xef, 0xa3, 0x7, 0x6, "Tou-Tou"),
|
||||
new(0x83, 0x94, 0x7, 0x6, "Tufa"),
|
||||
new(0x70, 0xf4, 0x5, 0x5, "Ulgrim's Island"),
|
||||
new(0x9f, 0xa0, 0x7, 0x6, "Uziz"),
|
||||
new(0x3f, 0xcb, 0x7, 0x6, "Wai Jhou"),
|
||||
new(0x90, 0xb5, 0x7, 0x6, "Xarabydun"),
|
||||
new(0xaf, 0x91, 0x7, 0x6, "Yanshi"),
|
||||
new(0x79, 0x9c, 0x7, 0x6, "Yaraq"),
|
||||
new(0x7b, 0x70, 0x7, 0x6, "Zaikhal"),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue