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>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user