using AcDream.Core.Player; namespace AcDream.Core.Tests.Player; /// /// Tests for — the cache that retains /// stamina / mana absolute + max values from PlayerDescription /// (0x0013)'s embedded CreatureProfile. Health stays in /// ; this class only /// covers the vitals that don't have a dedicated delta opcode in /// our currently-wired event set. /// public sealed class LocalPlayerStateTests { [Fact] public void Defaults_AllVitalsNull_PercentsNull() { var s = new LocalPlayerState(); Assert.Null(s.CurrentStamina); Assert.Null(s.MaxStamina); Assert.Null(s.CurrentMana); Assert.Null(s.MaxMana); Assert.Null(s.StaminaPercent); Assert.Null(s.ManaPercent); } [Fact] public void OnPlayerDescription_PopulatesFields_FromValidValues() { var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: 50, maxStamina: 100, currentMana: 150, maxMana: 200); Assert.Equal(50u, s.CurrentStamina); Assert.Equal(100u, s.MaxStamina); Assert.Equal(150u, s.CurrentMana); Assert.Equal(200u, s.MaxMana); } [Fact] public void StaminaPercent_IsCurrentOverMax_InZeroToOneRange() { var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: 50, maxStamina: 100, currentMana: null, maxMana: null); Assert.Equal(0.5f, s.StaminaPercent!.Value, precision: 3); } [Fact] public void ManaPercent_IsCurrentOverMax_InZeroToOneRange() { var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: null, maxStamina: null, currentMana: 75, maxMana: 100); Assert.Equal(0.75f, s.ManaPercent!.Value, precision: 3); } [Fact] public void StaminaPercent_NullWhenMaxIsZero_AvoidsDivByZero() { var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: 0, maxStamina: 0, currentMana: null, maxMana: null); Assert.Null(s.StaminaPercent); } [Fact] public void ManaPercent_NullWhenMaxIsZero_AvoidsDivByZero() { var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: null, maxStamina: null, currentMana: 0, maxMana: 0); Assert.Null(s.ManaPercent); } [Fact] public void StaminaPercent_Null_WhenOnlyCurrentKnown() { var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: 50, maxStamina: null, currentMana: null, maxMana: null); // Max never received → percent indeterminate. Assert.Null(s.StaminaPercent); } [Fact] public void StaminaPercent_Null_WhenOnlyMaxKnown() { var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: null, maxStamina: 100, currentMana: null, maxMana: null); // Current never received → percent indeterminate. Assert.Null(s.StaminaPercent); } [Fact] public void StaminaPercent_ClampsToOne_WhenCurrentExceedsMax() { var s = new LocalPlayerState(); // Server can momentarily report current > max during buff transitions. s.OnPlayerDescription(currentStamina: 150, maxStamina: 100, currentMana: null, maxMana: null); Assert.Equal(1f, s.StaminaPercent!.Value); } [Fact] public void Changed_EventFires_WhenAnyVitalUpdates() { var s = new LocalPlayerState(); int fires = 0; s.Changed += _ => fires++; s.OnPlayerDescription(currentStamina: 50, maxStamina: 100, currentMana: 75, maxMana: 200); Assert.Equal(1, fires); } [Fact] public void OnPlayerDescription_PreservesPreviousField_WhenIncomingValueIsNull() { // CreatureProfile occasionally has nullable fields if the server // sends a partial profile — the cache should preserve known-good // values rather than wipe them. Stamina set first, then a Mana-only // update should not clear Stamina. var s = new LocalPlayerState(); s.OnPlayerDescription(currentStamina: 50, maxStamina: 100, currentMana: null, maxMana: null); s.OnPlayerDescription(currentStamina: null, maxStamina: null, currentMana: 75, maxMana: 200); Assert.Equal(50u, s.CurrentStamina); Assert.Equal(100u, s.MaxStamina); Assert.Equal(75u, s.CurrentMana); Assert.Equal(200u, s.MaxMana); } }