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
|
|
@ -85,7 +85,10 @@ public sealed class IndicatorBarController : IRetainedPanelController
|
|||
|
||||
_helpful.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.PositiveEffects);
|
||||
_harmful.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.NegativeEffects);
|
||||
_link.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.LinkStatus);
|
||||
_vitae.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.Vitae);
|
||||
_burden.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.Character);
|
||||
_miniGame.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.MiniGame);
|
||||
_endCharacterSession.OnClick = bindings.RequestEndCharacterSession;
|
||||
|
||||
bindings.Spellbook.EnchantmentsChanged += UpdateEnchantments;
|
||||
|
|
|
|||
40
src/AcDream.App/UI/Layout/IndicatorDetailText.cs
Normal file
40
src/AcDream.App/UI/Layout/IndicatorDetailText.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Shared text shaping for retail indicator detail pages. Retail
|
||||
/// <c>UIElement_Text::SetText</c> reflows newline-delimited strings into the
|
||||
/// authored field width; acdream's retained text seam carries already-shaped
|
||||
/// lines, so controllers perform the equivalent shaping before publication.
|
||||
/// </summary>
|
||||
internal static class IndicatorDetailText
|
||||
{
|
||||
public static IReadOnlyList<UiText.Line> Shape(UiText target, string text)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(target);
|
||||
text ??= string.Empty;
|
||||
|
||||
float maxWidth = Math.Max(1f, target.Width - (2f * target.Padding));
|
||||
float Measure(string value)
|
||||
=> target.DatFont?.MeasureWidth(value)
|
||||
?? target.Font?.MeasureWidth(value)
|
||||
?? value.Length * 8f;
|
||||
|
||||
var lines = new List<UiText.Line>();
|
||||
string normalized = text.Replace("\\n", "\n", StringComparison.Ordinal);
|
||||
foreach (string paragraph in normalized.Split('\n'))
|
||||
{
|
||||
if (paragraph.Length == 0)
|
||||
{
|
||||
lines.Add(new UiText.Line(string.Empty, target.DefaultColor));
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (string line in ChatWindowController.WrapText(paragraph, maxWidth, Measure))
|
||||
lines.Add(new UiText.Line(line, target.DefaultColor));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
142
src/AcDream.App/UI/Layout/LinkStatusUiController.cs
Normal file
142
src/AcDream.App/UI/Layout/LinkStatusUiController.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using AcDream.Core.Net;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Retained port of retail <c>gmLinkStatusUI</c>. LayoutDesc owns the page and
|
||||
/// chrome; this controller owns its five-second text refresh and 120-second
|
||||
/// ping cadence.
|
||||
/// </summary>
|
||||
public sealed class LinkStatusUiController : IRetainedPanelController
|
||||
{
|
||||
public const uint LayoutId = 0x2100001Du;
|
||||
public const uint RootId = 0x10000167u;
|
||||
public const uint MainTextId = 0x10000169u;
|
||||
public const uint CloseId = 0x100000FCu;
|
||||
|
||||
private const double UpdateIntervalSeconds = 5d;
|
||||
private const double PingIntervalSeconds = 120d;
|
||||
|
||||
private readonly UiText _mainText;
|
||||
private readonly UiButton? _close;
|
||||
private readonly Func<LinkStatusSnapshot> _snapshot;
|
||||
private readonly Func<double> _currentTime;
|
||||
private readonly Action _requestPing;
|
||||
private readonly LinkStatusStrings _strings;
|
||||
private IReadOnlyList<UiText.Line> _lines = Array.Empty<UiText.Line>();
|
||||
private double _nextUpdateTime = -1d;
|
||||
private double _lastPingRequestTime = -1d;
|
||||
private bool _pleaseRequestPing;
|
||||
private bool _visible;
|
||||
|
||||
private LinkStatusUiController(
|
||||
ImportedLayout layout,
|
||||
Func<LinkStatusSnapshot> snapshot,
|
||||
Func<double> currentTime,
|
||||
Action requestPing,
|
||||
LinkStatusStrings strings,
|
||||
Action? close)
|
||||
{
|
||||
_mainText = (UiText)layout.FindElement(MainTextId)!;
|
||||
_close = layout.FindElement(CloseId) as UiButton;
|
||||
_snapshot = snapshot;
|
||||
_currentTime = currentTime;
|
||||
_requestPing = requestPing;
|
||||
_strings = strings;
|
||||
_mainText.LinesProvider = () => _lines;
|
||||
if (_close is not null) _close.OnClick = close;
|
||||
}
|
||||
|
||||
public static LinkStatusUiController? Bind(
|
||||
ImportedLayout layout,
|
||||
Func<LinkStatusSnapshot> snapshot,
|
||||
Func<double> currentTime,
|
||||
Action requestPing,
|
||||
LinkStatusStrings strings,
|
||||
Action? close = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(layout);
|
||||
ArgumentNullException.ThrowIfNull(snapshot);
|
||||
ArgumentNullException.ThrowIfNull(currentTime);
|
||||
ArgumentNullException.ThrowIfNull(requestPing);
|
||||
ArgumentNullException.ThrowIfNull(strings);
|
||||
return layout.FindElement(MainTextId) is UiText
|
||||
? new LinkStatusUiController(
|
||||
layout, snapshot, currentTime, requestPing, strings, close)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of <c>gmLinkStatusUI::ListenToGlobalMessage @ 0x004AB110</c>.
|
||||
/// </summary>
|
||||
public void Tick()
|
||||
{
|
||||
if (!_visible) return;
|
||||
double now = _currentTime();
|
||||
if (!double.IsFinite(now) || now < _nextUpdateTime) return;
|
||||
_nextUpdateTime = now + UpdateIntervalSeconds;
|
||||
Update(now);
|
||||
}
|
||||
|
||||
public void OnShown()
|
||||
{
|
||||
_visible = true;
|
||||
_pleaseRequestPing = true;
|
||||
Update(_currentTime());
|
||||
}
|
||||
|
||||
public void OnHidden()
|
||||
{
|
||||
_visible = false;
|
||||
_pleaseRequestPing = false;
|
||||
}
|
||||
|
||||
private void Update(double now)
|
||||
{
|
||||
LinkStatusSnapshot value = _snapshot();
|
||||
string ping = value.RoundTripSeconds is double seconds
|
||||
&& double.IsFinite(seconds)
|
||||
&& seconds >= 0d
|
||||
? (seconds * 1000d).ToString("F0", CultureInfo.CurrentCulture)
|
||||
: "????";
|
||||
string body = _strings.Description
|
||||
+ _strings.Legend
|
||||
+ _strings.DisconnectWarning
|
||||
+ _strings.PacketLossPrefix
|
||||
+ value.PacketLossPercentage.ToString("F2", CultureInfo.CurrentCulture)
|
||||
+ _strings.PingPrefix
|
||||
+ ping;
|
||||
_lines = IndicatorDetailText.Shape(_mainText, body);
|
||||
|
||||
if (!double.IsFinite(now)) return;
|
||||
bool periodicPing = _lastPingRequestTime >= 0d
|
||||
&& now - _lastPingRequestTime >= PingIntervalSeconds;
|
||||
if (!_pleaseRequestPing && !periodicPing) return;
|
||||
_lastPingRequestTime = now;
|
||||
_pleaseRequestPing = false;
|
||||
_requestPing();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_close is not null) _close.OnClick = null;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record LinkStatusStrings(
|
||||
string Description,
|
||||
string Legend,
|
||||
string DisconnectWarning,
|
||||
string PacketLossPrefix,
|
||||
string PingPrefix)
|
||||
{
|
||||
public static LinkStatusStrings English { get; } = new(
|
||||
"The Link Indicator shows the current status of your connection to the game servers.",
|
||||
"\n\nGREEN = your link is good.\nYELLOW = no packets for at least 5 sec.\nRED = no packets for at least 20 sec.",
|
||||
"\n\nIf approximately forty seconds pass without receiving a packet, you will be disconnected from the server.",
|
||||
"\n\n\nPacket loss for the last 10 sec: ",
|
||||
"\n\nRoundtrip Ping time to Server: ");
|
||||
}
|
||||
35
src/AcDream.App/UI/Layout/MiniGameUiController.cs
Normal file
35
src/AcDream.App/UI/Layout/MiniGameUiController.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Authored shell for retail <c>gmMiniGameUI</c>. The indicator remains
|
||||
/// ghosted until a future game-state owner calls <c>SetMiniGameActive</c>;
|
||||
/// mounting the page now keeps its window lifecycle on the shared retail host.
|
||||
/// </summary>
|
||||
public sealed class MiniGameUiController : IRetainedPanelController
|
||||
{
|
||||
public const uint LayoutId = 0x2100001Eu;
|
||||
public const uint RootId = 0x1000016Au;
|
||||
public const uint CloseId = 0x1000016Bu;
|
||||
|
||||
private readonly UiButton _close;
|
||||
|
||||
private MiniGameUiController(UiButton close, Action? closeWindow)
|
||||
{
|
||||
_close = close;
|
||||
_close.OnClick = closeWindow;
|
||||
}
|
||||
|
||||
public static MiniGameUiController? Bind(
|
||||
ImportedLayout layout,
|
||||
Action? close = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(layout);
|
||||
return layout.FindElement(CloseId) is UiButton button
|
||||
? new MiniGameUiController(button, close)
|
||||
: null;
|
||||
}
|
||||
|
||||
public void Dispose() => _close.OnClick = null;
|
||||
}
|
||||
158
src/AcDream.App/UI/Layout/VitaeUiController.cs
Normal file
158
src/AcDream.App/UI/Layout/VitaeUiController.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Retained port of retail <c>gmVitaeUI</c>. It presents the active vitae
|
||||
/// modifier and the authoritative experience pool needed to regain one percent.
|
||||
/// </summary>
|
||||
public sealed class VitaeUiController : IRetainedPanelController
|
||||
{
|
||||
public const uint LayoutId = 0x21000020u;
|
||||
public const uint RootId = 0x100001C1u;
|
||||
public const uint MainTextId = 0x100001C3u;
|
||||
public const uint CloseId = 0x100000FCu;
|
||||
|
||||
internal const uint VitaeCpPoolProperty = 0x81u;
|
||||
internal const uint DeathLevelProperty = 0x8Bu;
|
||||
internal const uint LevelProperty = 0x19u;
|
||||
|
||||
private readonly Spellbook _spellbook;
|
||||
private readonly ClientObjectTable _objects;
|
||||
private readonly Func<uint> _playerGuid;
|
||||
private readonly VitaeStrings _strings;
|
||||
private readonly UiText _mainText;
|
||||
private readonly UiButton? _close;
|
||||
private IReadOnlyList<UiText.Line> _lines = Array.Empty<UiText.Line>();
|
||||
private bool _disposed;
|
||||
|
||||
private VitaeUiController(
|
||||
ImportedLayout layout,
|
||||
Spellbook spellbook,
|
||||
ClientObjectTable objects,
|
||||
Func<uint> playerGuid,
|
||||
VitaeStrings strings,
|
||||
Action? close)
|
||||
{
|
||||
_spellbook = spellbook;
|
||||
_objects = objects;
|
||||
_playerGuid = playerGuid;
|
||||
_strings = strings;
|
||||
_mainText = (UiText)layout.FindElement(MainTextId)!;
|
||||
_close = layout.FindElement(CloseId) as UiButton;
|
||||
_mainText.LinesProvider = () => _lines;
|
||||
if (_close is not null) _close.OnClick = close;
|
||||
|
||||
_spellbook.EnchantmentsChanged += Update;
|
||||
_objects.ObjectAdded += OnObjectChanged;
|
||||
_objects.ObjectUpdated += OnObjectChanged;
|
||||
_objects.Cleared += Update;
|
||||
Update();
|
||||
}
|
||||
|
||||
public static VitaeUiController? Bind(
|
||||
ImportedLayout layout,
|
||||
Spellbook spellbook,
|
||||
ClientObjectTable objects,
|
||||
Func<uint> playerGuid,
|
||||
VitaeStrings strings,
|
||||
Action? close = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(layout);
|
||||
ArgumentNullException.ThrowIfNull(spellbook);
|
||||
ArgumentNullException.ThrowIfNull(objects);
|
||||
ArgumentNullException.ThrowIfNull(playerGuid);
|
||||
ArgumentNullException.ThrowIfNull(strings);
|
||||
return layout.FindElement(MainTextId) is UiText
|
||||
? new VitaeUiController(
|
||||
layout, spellbook, objects, playerGuid, strings, close)
|
||||
: null;
|
||||
}
|
||||
|
||||
public void OnShown() => Update();
|
||||
|
||||
private void OnObjectChanged(ClientObject item)
|
||||
{
|
||||
if (item.ObjectId == _playerGuid()) Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
float vitae = CurrentVitae();
|
||||
int lostPercent = 100 - (int)(vitae * 100f);
|
||||
string body;
|
||||
if (lostPercent <= 0)
|
||||
{
|
||||
body = _strings.FullStrength;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientObject? player = _objects.Get(_playerGuid());
|
||||
int pool = player?.Properties.GetInt(VitaeCpPoolProperty) ?? 0;
|
||||
int level = 0;
|
||||
if (player is not null)
|
||||
{
|
||||
level = player.Properties.Ints.TryGetValue(
|
||||
DeathLevelProperty, out int deathLevel)
|
||||
? deathLevel
|
||||
: player.Properties.GetInt(LevelProperty);
|
||||
}
|
||||
int remaining = VitaeCpPoolThreshold(vitae, level) - pool;
|
||||
body = _strings.LostPrefix
|
||||
+ lostPercent
|
||||
+ _strings.LostSuffix
|
||||
+ _strings.RecoveryPrefix
|
||||
+ remaining
|
||||
+ _strings.RecoverySuffix;
|
||||
}
|
||||
_lines = IndicatorDetailText.Shape(_mainText, body);
|
||||
}
|
||||
|
||||
private float CurrentVitae()
|
||||
=> _spellbook.ActiveEnchantmentSnapshot
|
||||
.Where(record => record.Bucket == 4u)
|
||||
.Select(record => record.StatModValue)
|
||||
.OfType<float>()
|
||||
.Where(float.IsFinite)
|
||||
.DefaultIfEmpty(1f)
|
||||
.Last();
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>VitaeSystem::VitaeCPPoolThreshold @ 0x005C8FD0</c>, with
|
||||
/// the x87 expression cross-checked against ACE's Player_Xp port.
|
||||
/// </summary>
|
||||
internal static int VitaeCpPoolThreshold(float vitae, int level)
|
||||
=> (int)(((Math.Pow(level, 2.5d) * 2.5d) + 20d)
|
||||
* Math.Pow(vitae, 5d)
|
||||
+ 0.5d);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_spellbook.EnchantmentsChanged -= Update;
|
||||
_objects.ObjectAdded -= OnObjectChanged;
|
||||
_objects.ObjectUpdated -= OnObjectChanged;
|
||||
_objects.Cleared -= Update;
|
||||
if (_close is not null) _close.OnClick = null;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record VitaeStrings(
|
||||
string FullStrength,
|
||||
string LostPrefix,
|
||||
string LostSuffix,
|
||||
string RecoveryPrefix,
|
||||
string RecoverySuffix)
|
||||
{
|
||||
public static VitaeStrings English { get; } = new(
|
||||
"Your Vitae, or life force, is at full strength.",
|
||||
"Due to your recent death, you have temporarily lost ",
|
||||
" % of your Vitae, or life force.",
|
||||
"\n\nYou will regain 1% of your Vitae once you earn ",
|
||||
" more experience.");
|
||||
}
|
||||
|
|
@ -10,16 +10,22 @@ public static class RetailPanelCatalog
|
|||
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;
|
||||
|
||||
private static readonly (uint PanelId, string WindowName)[] Mounted =
|
||||
{
|
||||
(PositiveEffects, WindowNames.PositiveEffects),
|
||||
(NegativeEffects, WindowNames.NegativeEffects),
|
||||
(Inventory, WindowNames.Inventory),
|
||||
(LinkStatus, WindowNames.LinkStatus),
|
||||
(MiniGame, WindowNames.MiniGame),
|
||||
(Character, WindowNames.Character),
|
||||
(Magic, WindowNames.Spellbook),
|
||||
(Vitae, WindowNames.Vitae),
|
||||
};
|
||||
|
||||
private static readonly (uint PanelId, string WindowName)[] Toolbar =
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -16,4 +16,7 @@ public static class WindowNames
|
|||
public const string Indicators = "indicators";
|
||||
public const string PositiveEffects = "effects-positive";
|
||||
public const string NegativeEffects = "effects-negative";
|
||||
public const string LinkStatus = "link-status";
|
||||
public const string MiniGame = "mini-game";
|
||||
public const string Vitae = "vitae";
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue