Three fixes to the Vitals HUD path:
1. EnchantmentMask Vitae/Cooldown bit values (parser regression).
ACE's enum at references/ACE/Source/ACE.Entity/Enum/EnchantmentCategory.cs
has Vitae=0x4 and Cooldown=0x8. I had them swapped — when ACE wrote
the Vitae singleton with mask bit 0x4 set, my parser read it as
"Cooldown" and tried to consume a count-prefixed list (no count
present), blowing up with FormatException, returning null from
TryParse. PlayerDescription consequently failed to parse on every
live login. Fix: swap the bit values + bucket constants to match ACE.
2. Vitae applies regardless of StatModKey. Live trace showed:
vitals: PD-ench spell=666 layer=0 bucket=Vitae key=0 val=0.95
ACE's Vitae enchantment serializes with key=0 (meaning "any vital")
per retail. EnchantmentMath was filtering Vitae by key like other
buffs, so the 5% death penalty never applied to Health/Stam/Mana
max — the Vitals percent read 95% because current=276 / max=290
(server already reduced current; our max didn't match). Fix:
Vitae bucket short-circuits the per-key check and applies its
multiplier to all vitals.
3. Absolute current/max in HUD overlay. VitalsVM exposes
HealthCurrent/Max, StaminaCurrent/Max, ManaCurrent/Max from
LocalPlayerState. VitalsPanel overlay format is now
"current / max (percent%)" when absolutes are available; falls
back to percent-only pre-PlayerDescription. Matches the retail
look the user requested ("HP 400/400" style).
Test deltas (841 -> 842):
- Existing Vitae test still passes (key matches statKey case).
- New Vitae key=0 test pins the "any vital" semantics.
- Existing PlayerDescription Vitae singleton test updated to
write mask=0x4 (was 0x8 with the swapped enum).
Live verification: with +Acdream's Vitae-666 active and Endurance.current=290:
HP : current=138, max=145×0.95≈138 → bar 100% (was 95%)
Stam : current=276, max=290×0.95≈276 → bar 100%
Mana : current=190, max=200×0.95≈190 → bar 100%
Overlay reads e.g. "276 / 276 (100%)".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
2.8 KiB
C#
86 lines
2.8 KiB
C#
namespace AcDream.UI.Abstractions.Panels.Vitals;
|
|
|
|
/// <summary>
|
|
/// First real UI panel — shows the local player's vitals as progress bars.
|
|
/// Backend-agnostic; renders exclusively through <see cref="IPanelRenderer"/>
|
|
/// so the same file works under ImGui.NET (D.2a) and the future custom
|
|
/// retail-look toolkit (D.2b).
|
|
///
|
|
/// <para>
|
|
/// D.2a shows only HP (percent). <see cref="VitalsVM.StaminaPercent"/> /
|
|
/// <see cref="VitalsVM.ManaPercent"/> return null until a
|
|
/// <c>LocalPlayerState</c> is wired (follow-up issue). When they start
|
|
/// returning non-null, this panel picks them up automatically.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class VitalsPanel : IPanel
|
|
{
|
|
private const float BarWidth = 200f;
|
|
|
|
private readonly VitalsVM _vm;
|
|
|
|
public VitalsPanel(VitalsVM vm)
|
|
{
|
|
_vm = vm ?? throw new ArgumentNullException(nameof(vm));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string Id => "acdream.vitals";
|
|
|
|
/// <inheritdoc />
|
|
public string Title => "Vitals";
|
|
|
|
/// <inheritdoc />
|
|
public bool IsVisible { get; set; } = true;
|
|
|
|
/// <inheritdoc />
|
|
public void Render(PanelContext ctx, IPanelRenderer renderer)
|
|
{
|
|
if (!renderer.Begin(Title))
|
|
{
|
|
renderer.End();
|
|
return;
|
|
}
|
|
|
|
// HP — always available from CombatState. If LocalPlayer has the
|
|
// absolute values too, prefer "current/max (percent)" overlay.
|
|
float hp = _vm.HealthPercent;
|
|
renderer.Text("HP");
|
|
renderer.SameLine();
|
|
renderer.ProgressBar(hp, BarWidth, overlay: FormatOverlay(
|
|
hp, _vm.HealthCurrent, _vm.HealthMax));
|
|
|
|
// Stamina — show only when the VM has a real value.
|
|
if (_vm.StaminaPercent is float stam)
|
|
{
|
|
renderer.Text("Stam");
|
|
renderer.SameLine();
|
|
renderer.ProgressBar(stam, BarWidth, overlay: FormatOverlay(
|
|
stam, _vm.StaminaCurrent, _vm.StaminaMax));
|
|
}
|
|
|
|
// Mana — show only when the VM has a real value.
|
|
if (_vm.ManaPercent is float mana)
|
|
{
|
|
renderer.Text("Mana");
|
|
renderer.SameLine();
|
|
renderer.ProgressBar(mana, BarWidth, overlay: FormatOverlay(
|
|
mana, _vm.ManaCurrent, _vm.ManaMax));
|
|
}
|
|
|
|
renderer.End();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Format a vital-bar overlay. Prefers <c>current / max (percent%)</c>
|
|
/// when absolute values are available; falls back to percent-only
|
|
/// when not (e.g. HP pre-PlayerDescription, where only the
|
|
/// CombatState percent has been wired).
|
|
/// </summary>
|
|
private static string FormatOverlay(float percent, uint? current, uint? max)
|
|
{
|
|
if (current is uint c && max is uint m && m > 0)
|
|
return $"{c} / {m} ({percent * 100f:F0}%)";
|
|
return $"{percent * 100f:F0}%";
|
|
}
|
|
}
|