feat(player): #5 LocalPlayerState — Stam/Mana wired through PlayerDescription
Closes ISSUES.md #5. The Vitals devtools window now draws three bars (HP / Stamina / Mana) once the server sends the first PlayerDescription (0x0013), instead of HP only. Built test-first per CLAUDE.md TDD rule — 16 new tests went red before the implementation went in. New AcDream.Core.Player.LocalPlayerState (cache): - {CurrentStamina, MaxStamina, CurrentMana, MaxMana} as uint? — null until first received. - StaminaPercent / ManaPercent: 0..1 fraction or null when either field is missing or max is zero. Clamps to 1.0 if current > max (server can briefly report this during buff transitions). - OnPlayerDescription preserves any previously known good value when an incoming field is null — partial profiles don't wipe state. - Changed event for future subscribers. GameEventWiring.WireAll: - New optional 6th parameter: LocalPlayerState? localPlayer = null. Existing 5-arg call sites still work; without the parameter the new PlayerDescription handler still parses + feeds the spellbook but skips the cache update. - PlayerDescription (0x0013) shares AppraiseInfo wire format with IdentifyObjectResponse (0x00C9) per AppraiseInfoParser docstring, so the new handler reuses the existing parser and pulls CreatureProfile.{Stamina, StaminaMax, Mana, ManaMax}. - Player's full learned spellbook also lands here (previously only item-scoped Identify responses fed the spellbook). VitalsVM: - Constructor adds optional LocalPlayerState? parameter (default null keeps every existing caller compiling). - StaminaPercent / ManaPercent now read through to LocalPlayerState every access — no VM-side caching, so a server-side delta to the cache surfaces next frame without any explicit refresh. GameWindow: - Public readonly LocalPlayer field alongside Combat / Chat / Items / SpellBook so plugins + future panels can bind directly. - WireAll call updated to pass LocalPlayer. - VitalsVM construction passes LocalPlayer so the existing VitalsPanel automatically picks up the two new bars. Test counts: - AcDream.Core.Tests: 550 → 561 (+11 LocalPlayerStateTests) - AcDream.UI.Abstractions.Tests: 23 → 26 (+3 VitalsVM through-cache) - AcDream.Core.Net.Tests: 192 → 194 (+2 PlayerDescription wiring) - Total: 765 → 781 Build: 0 warnings, 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9faf9d7e3a
commit
d42bf5735d
8 changed files with 436 additions and 50 deletions
|
|
@ -135,32 +135,15 @@ Copy this block when adding a new issue:
|
|||
|
||||
---
|
||||
|
||||
## #5 — VitalsPanel stamina/mana bars always null (absolute values not stored)
|
||||
|
||||
**Status:** OPEN
|
||||
**Severity:** LOW (cosmetic — HP bar already works; stam/mana would be a nice-to-have)
|
||||
**Filed:** 2026-04-25
|
||||
**Component:** ui / net / player-state
|
||||
|
||||
**Description:** Phase D.2a shipped `VitalsVM` with `StaminaPercent` / `ManaPercent` returning `float?` null. `VitalsPanel` already renders an HP progress bar from `CombatState.GetHealthPercent(localGuid)` because per-entity health is tracked from combat notifications. Stamina and mana are absolute values and only arrive in `PlayerDescription (0x0013)` — which we currently parse then discard. Result: the Vitals window shows HP only.
|
||||
|
||||
**Root cause / status:** We need a `LocalPlayerState` Core class (analogous to `CombatState` but scoped to the local player) that retains parsed `PlayerDescription` fields — at minimum: `CurrentStamina` + `MaxStamina` + `CurrentMana` + `MaxMana`. `AppraiseInfoParser.CreatureProfile` already has the shape for these values; we just don't persist them.
|
||||
|
||||
**Files:**
|
||||
- `src/AcDream.Core.Net/Parsers/PlayerDescriptionParser.cs` — parses then discards (verify path)
|
||||
- `src/AcDream.Core.Net/Parsers/AppraiseInfoParser.cs` — has `CreatureProfile` with absolute values
|
||||
- `src/AcDream.UI.Abstractions/Panels/Vitals/VitalsVM.cs` — `StaminaPercent` / `ManaPercent` would divide `LocalPlayerState.Current*` by `Max*`
|
||||
- `src/AcDream.App/Rendering/GameWindow.cs` — construct `LocalPlayerState`, hand to `VitalsVM`, wire into event dispatch
|
||||
|
||||
**Research:** none needed — wire-level field positions are already decoded in `PlayerDescriptionParser`.
|
||||
|
||||
**Acceptance:** With `ACDREAM_DEVTOOLS=1`, the Vitals window shows three progress bars (HP / Stamina / Mana) that update when the server sends `PlayerDescription` or any delta event (`UpdateHealth`, `UpdateStamina`, `UpdateMana`).
|
||||
|
||||
---
|
||||
|
||||
# Recently closed
|
||||
|
||||
*(none yet — move DONE items here with closed-date + commit SHA)*
|
||||
## #5 — [DONE 2026-04-25] VitalsPanel stamina/mana bars always null
|
||||
|
||||
**Closed:** 2026-04-25
|
||||
**Commit:** `feat(player): #5 LocalPlayerState — Stam/Mana wired through PlayerDescription`
|
||||
**Resolution:** Added `AcDream.Core.Player.LocalPlayerState` (caches `CurrentStamina` / `MaxStamina` / `CurrentMana` / `MaxMana` from `PlayerDescription (0x0013)`'s `CreatureProfile`). Wired into `GameEventWiring.WireAll` as a new optional 6th parameter so back-compat preserved. `VitalsVM` constructor now takes an optional `LocalPlayerState`; when wired, `StaminaPercent` / `ManaPercent` read through to the cache (no VM-side caching) and `VitalsPanel` automatically draws the two extra bars. Edge cases covered: zero `Max*` returns `null` (no /0); current > max clamps to 1.0; partial profiles preserve previous good values rather than wiping them. 16 new tests (11 `LocalPlayerStateTests` + 3 `VitalsVMTests` + 2 `GameEventWiringTests`).
|
||||
|
||||
<!--
|
||||
Example:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue