using AcDream.Core.Combat; using AcDream.Core.Player; namespace AcDream.UI.Abstractions.Panels.Vitals; /// /// ViewModel for the vitals HUD panel. Reads live health percentage for the /// local player from (which is fed by the server's /// UpdateHealth (0x01C0) GameEvent). /// /// /// Sources: /// /// /// /// HP — (percent-only, /// updated by UpdateHealth (0x01C0)). /// Stamina / Mana — 's /// StaminaPercent / ManaPercent, populated from the /// CreatureProfile embedded in PlayerDescription /// (0x0013). When no is wired /// (older constructor / tests), both stay null and /// VitalsPanel simply skips those bars. /// /// /// /// GUID timing: the local player's server GUID isn't known at /// OnLoad (pre-login). Construct with /// left as 0; GameWindow calls the setter when the live session /// receives its guid at EnterWorld. Before the GUID is set, /// returns 1.0 (via CombatState's safe /// default for unknown guids) — the bar reads "full", which is harmless. /// /// public sealed class VitalsVM { private readonly CombatState _combat; private readonly LocalPlayerState? _local; private uint _localPlayerGuid; /// /// Build a VitalsVM bound to a and (optionally) /// a . The GUID starts at 0; call /// once the live session assigns it. /// When is null (back-compat), /// stamina + mana stay null and VitalsPanel skips those bars. /// public VitalsVM(CombatState combat, LocalPlayerState? localPlayer = null) { _combat = combat ?? throw new ArgumentNullException(nameof(combat)); _local = localPlayer; _localPlayerGuid = 0; } /// /// Push the authoritative local-player GUID from WorldSession. /// One-way setter — only GameWindow should call it, exactly once /// per live session. /// public void SetLocalPlayerGuid(uint guid) => _localPlayerGuid = guid; /// /// Current health percent (0..1) for the local player. Returns 1.0 /// before login or if the server has never sent an UpdateHealth for /// this GUID. /// public float HealthPercent => _combat.GetHealthPercent(_localPlayerGuid); /// /// Stamina percent (0..1), or null when no /// is wired or it hasn't received a /// PlayerDescription with both current and max yet. Reads /// through to the cache every access — no VM-side caching. /// public float? StaminaPercent => _local?.StaminaPercent; /// /// Mana percent (0..1), or null under the same conditions as /// . /// public float? ManaPercent => _local?.ManaPercent; // ── Absolute values for HUD overlays ────────────────────────────────── /// Current health value (server-authoritative absolute) or /// null if hasn't received the /// vital snapshot yet. public uint? HealthCurrent => _local?.Get(LocalPlayerState.VitalKind.Health)?.Current; /// Max health value, accounting for attribute contribution /// + active enchantment buffs + vitae. null if no vital /// snapshot yet. public uint? HealthMax => _local?.GetMaxApprox(LocalPlayerState.VitalKind.Health); /// Current stamina value. public uint? StaminaCurrent => _local?.Get(LocalPlayerState.VitalKind.Stamina)?.Current; /// Max stamina including buffs + vitae. public uint? StaminaMax => _local?.GetMaxApprox(LocalPlayerState.VitalKind.Stamina); /// Current mana value. public uint? ManaCurrent => _local?.Get(LocalPlayerState.VitalKind.Mana)?.Current; /// Max mana including buffs + vitae. public uint? ManaMax => _local?.GetMaxApprox(LocalPlayerState.VitalKind.Mana); }