Campaign FA slice FA3: retail's four-tab social panel (LayoutDesc
0x2100006E slot 0x1000018F, RetailPanelCatalog id 12), built on the OP3
OptionsPanelController recipe -- Type-8 tab host, ActivateTabBehavior,
per-page scoped controllers.
- SocialPanelController mounts the tab host and wires the close button;
F3 (ToggleAllegiancePanel) and F4 (ToggleFellowshipPanel) open the
panel and switch to their own tab, sharing the same gmPanelUI
one-active-panel exclusivity every sibling main panel already has.
- The live-DAT tab table CORRECTS the coordinator addendum's x-order
guess: button 0x1000028C ("Allegiance") pairs with page 0x10000291 and
is the authored DEFAULT entry, not Friends -- each button's own page
id and its own P0x57 (matching the F3/F4 ActionMap ids on the
Allegiance/Fellowship pages specifically) both corroborate the real
pairing. See SocialPanelController's class doc for the full table.
- SocialFellowshipPageController swaps the two authored empty/full
frames (0x1000026B/0x10000275) on RuntimeFellowshipState's
IsInFellowship -- both frames' full containment (name box, create
button, checkboxes vs. roster list, six buttons) was confirmed by the
live-mount probe, so a single Visible toggle per frame is the whole
swap (closes lane-A unknown U6).
- SocialAllegiancePageController hides the monarch/patron blocks and
blanks their name text to a literal space when
RuntimeAllegianceState.Snapshot.HasProfile is false, using SCOPED
FindDescendant lookups (the panel authors 0x10000492 twice, once per
block).
- SocialFriendsPageController/SocialSquelchPageController bind their
ListBoxes read-only to RuntimeCommunicationState's existing J4.1
Friends/Squelch owners (names only), rebuilding on revision change.
Their action buttons are honest INERT (D1) -- register row AD-79.
- UiTemplateListBox gains Flush() (lane A/D's "Gap found" prerequisite)
so a poll-and-rebuild list can shrink between refreshes.
- RetailPanelCatalog.SocialPanel = 12, byte-verified from the live slot's
own P0x10000029; listed in Mounted only (no toolbar button -- lane A
§6.1: the open path is keyboard-only).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
96 lines
3.7 KiB
C#
96 lines
3.7 KiB
C#
namespace AcDream.App.UI;
|
|
|
|
/// <summary>
|
|
/// Retail <c>gmPanelUI</c> panel ids carried by DAT property <c>0x10000029</c>.
|
|
/// The toolbar exposes only a subset of these ids; Helpful/Harmful effects use
|
|
/// the authored <c>gmUIElement_EffectsIndicator</c> buttons instead.
|
|
/// </summary>
|
|
public static class RetailPanelCatalog
|
|
{
|
|
public const uint CharacterInformation = 3u;
|
|
public const uint PositiveEffects = 4u;
|
|
public const uint NegativeEffects = 5u;
|
|
public const uint Inventory = 7u;
|
|
public const uint LinkStatus = 8u;
|
|
public const uint MiniGame = 9u;
|
|
public const uint Character = 11u;
|
|
public const uint Magic = 13u;
|
|
public const uint Vitae = 15u;
|
|
|
|
/// <summary>
|
|
/// The retail Options panel's <c>gmPanelUI</c> slot key — byte-verified
|
|
/// from the toolbar options button's OWN authored property
|
|
/// <c>0x10000029 = 10</c> (fixture <c>toolbar_21000016.json</c>,
|
|
/// element <c>0x1000019B</c>) and independently corroborated by
|
|
/// <c>docs/research/2026-08-10-options-panel-structure.md</c> §1.4's
|
|
/// decompiled slot-key table (host <c>0x2100006E</c>, slot
|
|
/// <c>0x1000018D</c> → key 10). Campaign OP slice OP3.
|
|
/// </summary>
|
|
public const uint Options = 10u;
|
|
|
|
/// <summary>
|
|
/// Campaign FA slice FA3: the four-tab Friends/Allegiance/Fellowship/
|
|
/// Squelch panel's <c>gmPanelUI</c> slot key — byte-verified from the
|
|
/// live installed DATs (host <c>0x2100006E</c> slot <c>0x1000018F</c>'s
|
|
/// own authored <c>0x10000029 = 12</c>, <c>FaPanelSlotProbeTests</c>).
|
|
/// No toolbar button authors this id (lane A §6.1: the open path is
|
|
/// keyboard-only, F3/F4) — <see cref="SocialPanel"/> is therefore in
|
|
/// <see cref="Mounted"/> only, not <see cref="Toolbar"/>, the same
|
|
/// convention <see cref="PositiveEffects"/>/<see cref="NegativeEffects"/>/
|
|
/// <see cref="LinkStatus"/>/<see cref="MiniGame"/>/<see cref="Vitae"/>
|
|
/// already use for their own non-toolbar open paths.
|
|
/// </summary>
|
|
public const uint SocialPanel = 12u;
|
|
|
|
private static readonly (uint PanelId, string WindowName)[] Mounted =
|
|
{
|
|
(CharacterInformation, WindowNames.CharacterInformation),
|
|
(PositiveEffects, WindowNames.PositiveEffects),
|
|
(NegativeEffects, WindowNames.NegativeEffects),
|
|
(Inventory, WindowNames.Inventory),
|
|
(LinkStatus, WindowNames.LinkStatus),
|
|
(MiniGame, WindowNames.MiniGame),
|
|
(Character, WindowNames.Character),
|
|
(Magic, WindowNames.Spellbook),
|
|
(Vitae, WindowNames.Vitae),
|
|
(Options, WindowNames.Options),
|
|
(SocialPanel, WindowNames.SocialPanel),
|
|
};
|
|
|
|
private static readonly (uint PanelId, string WindowName)[] Toolbar =
|
|
{
|
|
(Inventory, WindowNames.Inventory),
|
|
(Character, WindowNames.Character),
|
|
(Magic, WindowNames.Spellbook),
|
|
(Options, WindowNames.Options),
|
|
};
|
|
|
|
public static IReadOnlyList<(uint PanelId, string WindowName)> MountedPanels => Mounted;
|
|
public static IReadOnlyList<(uint PanelId, string WindowName)> ToolbarPanels => Toolbar;
|
|
|
|
public static bool TryGetWindowName(uint panelId, out string windowName)
|
|
{
|
|
foreach (var entry in Mounted)
|
|
{
|
|
if (entry.PanelId != panelId) continue;
|
|
windowName = entry.WindowName;
|
|
return true;
|
|
}
|
|
|
|
windowName = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
public static bool TryGetPanelId(string windowName, out uint panelId)
|
|
{
|
|
foreach (var entry in Mounted)
|
|
{
|
|
if (!string.Equals(entry.WindowName, windowName, StringComparison.Ordinal)) continue;
|
|
panelId = entry.PanelId;
|
|
return true;
|
|
}
|
|
|
|
panelId = 0;
|
|
return false;
|
|
}
|
|
}
|