using Theriapolis.Core.Rules.Stats; namespace Theriapolis.Core.Rules.Combat; /// /// Phase 5 M6 player death-save loop. d20 every turn while at 0 HP: /// - 1 → 2 failures /// - 2..9 → 1 failure /// - 10..19 → 1 success /// - 20 → revive at 1 HP (zero out failures + successes) /// /// 3 cumulative successes (≥10) → stabilised at 0 HP (cleared on heal). /// 3 cumulative failures (<10) → dead. CombatHUDScreen pushes /// when this fires. /// /// Tracker lives on only for the player; NPC /// combatants skip death saves and are removed at 0 HP. /// public sealed class DeathSaveTracker { public int Successes { get; private set; } public int Failures { get; private set; } public bool Stabilised { get; private set; } public bool Dead { get; private set; } /// Roll a death save and update counters. Returns the outcome. public DeathSaveOutcome Roll(Encounter enc, Combatant target) { if (Dead || Stabilised) return DeathSaveOutcome.NoOp; int d20 = enc.RollD20(); DeathSaveOutcome outcome; if (d20 == 20) { // Critical success — revive at 1 HP. target.CurrentHp = 1; target.Conditions.Remove(Condition.Unconscious); Successes = 0; Failures = 0; outcome = DeathSaveOutcome.CriticalRevive; } else if (d20 >= 10) { Successes++; outcome = Successes >= 3 ? DeathSaveOutcome.Stabilised : DeathSaveOutcome.Success; if (Successes >= 3) Stabilised = true; } else { int failsThisRoll = d20 == 1 ? 2 : 1; Failures += failsThisRoll; outcome = Failures >= 3 ? DeathSaveOutcome.Dead : DeathSaveOutcome.Failure; if (Failures >= 3) Dead = true; } enc.AppendLog(CombatLogEntry.Kind.Save, $"{target.Name} death save: {d20} → {outcome} ({Successes}S/{Failures}F)"); return outcome; } /// Called when the character is healed above 0 HP — cancels the loop. public void Reset() { Successes = 0; Failures = 0; Stabilised = false; // Don't reset Dead — once dead, stays dead. } } public enum DeathSaveOutcome { NoOp = 0, Success = 1, Failure = 2, Stabilised = 3, Dead = 4, CriticalRevive = 5, }