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
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.");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue