Route ACE server commands through the existing chat path, bind the retail paperdoll hit mask instead of its obscured viewport, and prefer authoritative private health vitals. Record the user-confirmed live gate and pin the production DAT widget type in tests.
108 lines
4.6 KiB
C#
108 lines
4.6 KiB
C#
using AcDream.Core.Combat;
|
|
using AcDream.Core.Player;
|
|
|
|
namespace AcDream.UI.Abstractions.Panels.Vitals;
|
|
|
|
/// <summary>
|
|
/// ViewModel for the vitals HUD panel. Reads the local player's authoritative
|
|
/// private vital snapshot, falling back to world-combat health before that
|
|
/// snapshot is available.
|
|
///
|
|
/// <para>
|
|
/// <b>Sources:</b>
|
|
/// </para>
|
|
///
|
|
/// <list type="bullet">
|
|
/// <item>HP — <see cref="LocalPlayerState.HealthPercent"/> from
|
|
/// <c>PrivateUpdateVital(Current)</c>, with
|
|
/// <see cref="CombatState.GetHealthPercent"/> as fallback.</item>
|
|
/// <item>Stamina / Mana — <see cref="LocalPlayerState"/>'s
|
|
/// <c>StaminaPercent</c> / <c>ManaPercent</c>, populated from the
|
|
/// <c>CreatureProfile</c> embedded in <c>PlayerDescription
|
|
/// (0x0013)</c>. When no <see cref="LocalPlayerState"/> is wired
|
|
/// (older constructor / tests), both stay <c>null</c> and
|
|
/// <c>VitalsPanel</c> simply skips those bars.</item>
|
|
/// </list>
|
|
///
|
|
/// <para>
|
|
/// <b>GUID timing:</b> the local player's server GUID isn't known at
|
|
/// <c>OnLoad</c> (pre-login). Construct with <see cref="SetLocalPlayerGuid"/>
|
|
/// left as 0; <c>GameWindow</c> calls the setter when the live session
|
|
/// receives its guid at <c>EnterWorld</c>. Before the GUID is set,
|
|
/// <see cref="HealthPercent"/> returns 1.0 (via <c>CombatState</c>'s safe
|
|
/// default for unknown guids) — the bar reads "full", which is harmless.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class VitalsVM
|
|
{
|
|
private readonly CombatState _combat;
|
|
private readonly LocalPlayerState? _local;
|
|
private uint _localPlayerGuid;
|
|
|
|
/// <summary>
|
|
/// Build a VitalsVM bound to a <see cref="CombatState"/> and (optionally)
|
|
/// a <see cref="LocalPlayerState"/>. The GUID starts at 0; call
|
|
/// <see cref="SetLocalPlayerGuid"/> once the live session assigns it.
|
|
/// When <paramref name="localPlayer"/> is <c>null</c> (back-compat),
|
|
/// stamina + mana stay <c>null</c> and <c>VitalsPanel</c> skips those bars.
|
|
/// </summary>
|
|
public VitalsVM(CombatState combat, LocalPlayerState? localPlayer = null)
|
|
{
|
|
_combat = combat ?? throw new ArgumentNullException(nameof(combat));
|
|
_local = localPlayer;
|
|
_localPlayerGuid = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Push the authoritative local-player GUID from <c>WorldSession</c>.
|
|
/// One-way setter — only <c>GameWindow</c> should call it, exactly once
|
|
/// per live session.
|
|
/// </summary>
|
|
public void SetLocalPlayerGuid(uint guid) => _localPlayerGuid = guid;
|
|
|
|
/// <summary>
|
|
/// Current health percent (0..1) for the local player. The private vital
|
|
/// is authoritative for self; world-combat percentage is the pre-login
|
|
/// and legacy fallback.
|
|
/// </summary>
|
|
public float HealthPercent
|
|
=> _local?.HealthPercent ?? _combat.GetHealthPercent(_localPlayerGuid);
|
|
|
|
/// <summary>
|
|
/// Stamina percent (0..1), or <c>null</c> when no
|
|
/// <see cref="LocalPlayerState"/> is wired or it hasn't received a
|
|
/// <c>PlayerDescription</c> with both current and max yet. Reads
|
|
/// through to the cache every access — no VM-side caching.
|
|
/// </summary>
|
|
public float? StaminaPercent => _local?.StaminaPercent;
|
|
|
|
/// <summary>
|
|
/// Mana percent (0..1), or <c>null</c> under the same conditions as
|
|
/// <see cref="StaminaPercent"/>.
|
|
/// </summary>
|
|
public float? ManaPercent => _local?.ManaPercent;
|
|
|
|
// ── Absolute values for HUD overlays ──────────────────────────────────
|
|
|
|
/// <summary>Current health value (server-authoritative absolute) or
|
|
/// <c>null</c> if <see cref="LocalPlayerState"/> hasn't received the
|
|
/// vital snapshot yet.</summary>
|
|
public uint? HealthCurrent => _local?.Get(LocalPlayerState.VitalKind.Health)?.Current;
|
|
|
|
/// <summary>Max health value, accounting for attribute contribution
|
|
/// + active enchantment buffs + vitae. <c>null</c> if no vital
|
|
/// snapshot yet.</summary>
|
|
public uint? HealthMax => _local?.GetMaxApprox(LocalPlayerState.VitalKind.Health);
|
|
|
|
/// <summary>Current stamina value.</summary>
|
|
public uint? StaminaCurrent => _local?.Get(LocalPlayerState.VitalKind.Stamina)?.Current;
|
|
|
|
/// <summary>Max stamina including buffs + vitae.</summary>
|
|
public uint? StaminaMax => _local?.GetMaxApprox(LocalPlayerState.VitalKind.Stamina);
|
|
|
|
/// <summary>Current mana value.</summary>
|
|
public uint? ManaCurrent => _local?.Get(LocalPlayerState.VitalKind.Mana)?.Current;
|
|
|
|
/// <summary>Max mana including buffs + vitae.</summary>
|
|
public uint? ManaMax => _local?.GetMaxApprox(LocalPlayerState.VitalKind.Mana);
|
|
}
|