feat(ui): share indicator detail panels
Port the authored Link Status, Vitae, and Mini Game detail roots and register every indicator page with retail's one-active gmPanelUI owner. Helpful/Harmful and the new pages now replace Inventory, Character, or Magic at one canonical window position while preserving the DAT restore-previous flag. Correct the retail ping wire to its payload-free request/response, publish measured RTT, and port Vitae recovery XP from the live modifier and player properties. Keep transport packet-loss averaging and mini-game gameplay explicitly tracked under AP-110. Release build and all 5,814 tests pass with five intentional skips. Connected visual gate pending. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
52c529be86
commit
a96767ba6d
28 changed files with 6539 additions and 32 deletions
|
|
@ -81,6 +81,7 @@ public sealed record IndicatorRuntimeBindings(
|
|||
Func<int?> Strength,
|
||||
Func<LinkStatusSnapshot> LinkStatus,
|
||||
Func<double> CurrentTime,
|
||||
Action RequestLinkStatusPing,
|
||||
Action EndCharacterSession);
|
||||
|
||||
public sealed record ToolbarRuntimeBindings(
|
||||
|
|
@ -196,6 +197,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
MountCombat();
|
||||
MountSpellbook();
|
||||
MountEffects();
|
||||
MountIndicatorDetailPanels();
|
||||
MountIndicators();
|
||||
MountJumpPowerbar();
|
||||
MountDialogFactory();
|
||||
|
|
@ -245,6 +247,9 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
public SpellbookWindowController? SpellbookWindowController { get; private set; }
|
||||
public EffectsUiController? PositiveEffectsController { get; private set; }
|
||||
public EffectsUiController? NegativeEffectsController { get; private set; }
|
||||
public LinkStatusUiController? LinkStatusUiController { get; private set; }
|
||||
public VitaeUiController? VitaeUiController { get; private set; }
|
||||
public MiniGameUiController? MiniGameUiController { get; private set; }
|
||||
public IndicatorBarController? IndicatorBarController { get; private set; }
|
||||
public JumpPowerbarController? JumpPowerbarController { get; private set; }
|
||||
public RetailFpsController? FpsController { get; private set; }
|
||||
|
|
@ -274,6 +279,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
SpellcastingUiController?.Tick();
|
||||
PositiveEffectsController?.Tick();
|
||||
NegativeEffectsController?.Tick();
|
||||
LinkStatusUiController?.Tick();
|
||||
IndicatorBarController?.Tick();
|
||||
JumpPowerbarController?.Tick();
|
||||
SelectedObjectController?.Tick(deltaSeconds);
|
||||
|
|
@ -880,7 +886,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
if (positive) PositiveEffectsController = controller;
|
||||
else NegativeEffectsController = controller;
|
||||
UiElement root = layout.Root;
|
||||
RetailWindowFrame.Mount(
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
Host.Root,
|
||||
root,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
|
|
@ -898,10 +904,184 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
ContentClickThrough = false,
|
||||
Controller = controller,
|
||||
});
|
||||
_panelUi.Register(
|
||||
_panelUi.RegisterMainPanel(
|
||||
positive ? RetailPanelCatalog.PositiveEffects : RetailPanelCatalog.NegativeEffects,
|
||||
positive ? WindowNames.PositiveEffects : WindowNames.NegativeEffects,
|
||||
rootInfo);
|
||||
handle,
|
||||
rootInfo.TryGetEffectiveBool(
|
||||
RetailPanelUiController.RestorePreviousPropertyId,
|
||||
out bool restorePrevious)
|
||||
&& restorePrevious);
|
||||
}
|
||||
|
||||
private void MountIndicatorDetailPanels()
|
||||
{
|
||||
MountLinkStatusPanel();
|
||||
MountVitaePanel();
|
||||
MountMiniGamePanel();
|
||||
}
|
||||
|
||||
private void MountLinkStatusPanel()
|
||||
{
|
||||
ElementInfo? rootInfo;
|
||||
ImportedLayout? layout;
|
||||
LinkStatusStrings strings;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
rootInfo = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats,
|
||||
LinkStatusUiController.LayoutId,
|
||||
LinkStatusUiController.RootId);
|
||||
var resolver = new DatStringResolver(_bindings.Assets.Dats);
|
||||
layout = rootInfo is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
rootInfo,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
resolver.Resolve);
|
||||
LinkStatusStrings fallback = LinkStatusStrings.English;
|
||||
strings = new LinkStatusStrings(
|
||||
resolver.Resolve(0x23000001u, 0x0D632F7Fu) ?? fallback.Description,
|
||||
resolver.Resolve(0x23000001u, 0x038FABF3u) ?? fallback.Legend,
|
||||
resolver.Resolve(0x23000001u, 0x05481084u) ?? fallback.DisconnectWarning,
|
||||
resolver.Resolve(0x23000001u, 0x0CADBA93u) ?? fallback.PacketLossPrefix,
|
||||
resolver.Resolve(0x23000001u, 0x0D6485F7u) ?? fallback.PingPrefix);
|
||||
}
|
||||
if (rootInfo is null || layout is null) return;
|
||||
|
||||
LinkStatusUiController? controller = Layout.LinkStatusUiController.Bind(
|
||||
layout,
|
||||
_bindings.Indicators.LinkStatus,
|
||||
_bindings.Indicators.CurrentTime,
|
||||
_bindings.Indicators.RequestLinkStatusPing,
|
||||
strings,
|
||||
() => CloseWindow(WindowNames.LinkStatus));
|
||||
if (controller is null) return;
|
||||
LinkStatusUiController = controller;
|
||||
RegisterIndicatorDetailPanel(
|
||||
RetailPanelCatalog.LinkStatus,
|
||||
WindowNames.LinkStatus,
|
||||
rootInfo,
|
||||
layout.Root,
|
||||
controller);
|
||||
}
|
||||
|
||||
private void MountVitaePanel()
|
||||
{
|
||||
ElementInfo? rootInfo;
|
||||
ImportedLayout? layout;
|
||||
VitaeStrings strings;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
rootInfo = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats,
|
||||
VitaeUiController.LayoutId,
|
||||
VitaeUiController.RootId);
|
||||
var resolver = new DatStringResolver(_bindings.Assets.Dats);
|
||||
layout = rootInfo is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
rootInfo,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
resolver.Resolve);
|
||||
VitaeStrings fallback = VitaeStrings.English;
|
||||
strings = new VitaeStrings(
|
||||
resolver.Resolve(0x23000001u, 0x0142DDECu) ?? fallback.FullStrength,
|
||||
resolver.Resolve(0x23000001u, 0x045D7665u, 0) ?? fallback.LostPrefix,
|
||||
resolver.Resolve(0x23000001u, 0x045D7665u, 1) ?? fallback.LostSuffix,
|
||||
resolver.Resolve(0x23000001u, 0x0DCD8C35u, 0) ?? fallback.RecoveryPrefix,
|
||||
resolver.Resolve(0x23000001u, 0x0DCD8C35u, 1) ?? fallback.RecoverySuffix);
|
||||
}
|
||||
if (rootInfo is null || layout is null) return;
|
||||
|
||||
VitaeUiController? controller = Layout.VitaeUiController.Bind(
|
||||
layout,
|
||||
_bindings.Indicators.Spellbook,
|
||||
_bindings.Indicators.Objects,
|
||||
_bindings.Indicators.PlayerGuid,
|
||||
strings,
|
||||
() => CloseWindow(WindowNames.Vitae));
|
||||
if (controller is null) return;
|
||||
VitaeUiController = controller;
|
||||
RegisterIndicatorDetailPanel(
|
||||
RetailPanelCatalog.Vitae,
|
||||
WindowNames.Vitae,
|
||||
rootInfo,
|
||||
layout.Root,
|
||||
controller);
|
||||
}
|
||||
|
||||
private void MountMiniGamePanel()
|
||||
{
|
||||
ElementInfo? rootInfo;
|
||||
ImportedLayout? layout;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
rootInfo = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats,
|
||||
MiniGameUiController.LayoutId,
|
||||
MiniGameUiController.RootId);
|
||||
layout = rootInfo is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
rootInfo,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
new DatStringResolver(_bindings.Assets.Dats).Resolve);
|
||||
}
|
||||
if (rootInfo is null || layout is null) return;
|
||||
|
||||
MiniGameUiController? controller = Layout.MiniGameUiController.Bind(
|
||||
layout,
|
||||
() => CloseWindow(WindowNames.MiniGame));
|
||||
if (controller is null) return;
|
||||
MiniGameUiController = controller;
|
||||
RegisterIndicatorDetailPanel(
|
||||
RetailPanelCatalog.MiniGame,
|
||||
WindowNames.MiniGame,
|
||||
rootInfo,
|
||||
layout.Root,
|
||||
controller);
|
||||
}
|
||||
|
||||
private void RegisterIndicatorDetailPanel(
|
||||
uint panelId,
|
||||
string windowName,
|
||||
ElementInfo rootInfo,
|
||||
UiElement root,
|
||||
IRetainedPanelController controller)
|
||||
{
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
Host.Root,
|
||||
root,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
new RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = windowName,
|
||||
Chrome = RetailWindowChrome.Imported,
|
||||
Left = 18f,
|
||||
Top = 18f,
|
||||
Visible = false,
|
||||
Resizable = false,
|
||||
ResizeX = false,
|
||||
ResizeY = false,
|
||||
ConstrainDragToParent = true,
|
||||
ContentClickThrough = false,
|
||||
Controller = controller,
|
||||
});
|
||||
_panelUi.RegisterMainPanel(
|
||||
panelId,
|
||||
windowName,
|
||||
handle,
|
||||
rootInfo.TryGetEffectiveBool(
|
||||
RetailPanelUiController.RestorePreviousPropertyId,
|
||||
out bool restorePrevious)
|
||||
&& restorePrevious);
|
||||
}
|
||||
|
||||
private void MountIndicators()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue