acdream/src/AcDream.UI.Abstractions/Panels/Vitals/VitalsVM.cs

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>.
/// The host assigns it after character selection and resets it to zero at
/// session teardown.
/// </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);
}