Files
TheriapolisV3/Theriapolis.Core/Rules/Combat/DeathSaveTracker.cs
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

80 lines
2.5 KiB
C#

using Theriapolis.Core.Rules.Stats;
namespace Theriapolis.Core.Rules.Combat;
/// <summary>
/// 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
/// <see cref="Game.Screens.DefeatedScreen"/> when this fires.
///
/// Tracker lives on <see cref="Combatant"/> only for the player; NPC
/// combatants skip death saves and are removed at 0 HP.
/// </summary>
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; }
/// <summary>Roll a death save and update counters. Returns the outcome.</summary>
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;
}
/// <summary>Called when the character is healed above 0 HP — cancels the loop.</summary>
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,
}