fix(rules): expose healing_done in per-combatant stats and the CLI report - the channel/spell healing deliberately removed from damage_dealt (2e2873f) was invisible; CombatantStats/_LiveStats gain a healing_done counter booked with the hp actually restored (capped at the target's hp_max, overheal uncounted) by _channel_heal and by spell HealEffect on living targets, while cure-as-damage on undead stays in damage_dealt and the damage counters keep their exact semantics; the runner aggregates and averages the new field and the French report prints it as 'soins prodigués'; +4 tests (channel heal, cure spell, undead cure guard, run_encounter aggregate) +1 CLI assertion
This commit is contained in:
@@ -151,6 +151,7 @@ def format_report(report: EncounterReport, spec: EncounterSpec) -> str:
|
||||
lines.extend(
|
||||
f" {cid} : {attrition.hits:.2f} touches · {attrition.crits:.2f} critiques · "
|
||||
f"{attrition.damage_dealt:.2f} dégâts infligés · {attrition.damage_taken:.2f} subis"
|
||||
f" · {attrition.healing_done:.2f} soins prodigués"
|
||||
for cid, attrition in report.attrition.items()
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -651,12 +651,18 @@ def _bresenham_line(start: Pos, end: Pos) -> list[Pos]:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CombatantStats:
|
||||
"""Aggregated outcomes for one combatant over a battle."""
|
||||
"""Aggregated outcomes for one combatant over a battle.
|
||||
|
||||
``healing_done`` is hit points *restored* by the combatant (channel heal
|
||||
and spell HealEffect, capped at the target's hp_max); healing is never
|
||||
counted in ``damage_dealt``/``damage_taken`` (those stay damage-only).
|
||||
"""
|
||||
|
||||
hits: int = 0
|
||||
crits: int = 0
|
||||
damage_dealt: int = 0
|
||||
damage_taken: int = 0
|
||||
healing_done: int = 0
|
||||
|
||||
|
||||
_PC_SOURCE_PREFIX = "fiches_personnages/"
|
||||
@@ -1256,6 +1262,7 @@ class _LiveStats:
|
||||
crits: int = 0
|
||||
damage_dealt: int = 0
|
||||
damage_taken: int = 0
|
||||
healing_done: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -5882,9 +5889,10 @@ class CombatEngine:
|
||||
if affected_state.hp < 0:
|
||||
affected_state.stabilized_hp = affected_state.hp
|
||||
actual = affected_state.hp - old_hp
|
||||
# Healing is NOT damage dealt: the attrition counters track damage only
|
||||
# (README "degats infligés/subis"), consistent with spell healing
|
||||
# (HealEffect), which is counted nowhere.
|
||||
# Healing is NOT damage dealt: the damage counters stay damage-only
|
||||
# (README "degats infligés/subis"); restored hp are booked to the
|
||||
# healer's separate healing_done counter (mirrors spell HealEffect).
|
||||
self._stats[state.combatant.id].healing_done += actual
|
||||
self._stop_bleeding(affected_state)
|
||||
self._log(
|
||||
f"round {self._current_round} {state.combatant.id}: "
|
||||
@@ -7330,6 +7338,8 @@ class CombatEngine:
|
||||
if target.hp < 0:
|
||||
target.stabilized_hp = target.hp
|
||||
actual = target.hp - old_hp
|
||||
if caster_live is not None:
|
||||
caster_live.healing_done += actual
|
||||
self._stop_bleeding(target)
|
||||
return f"+{actual} HP"
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ class CombatantAttrition:
|
||||
crits: float
|
||||
damage_dealt: float
|
||||
damage_taken: float
|
||||
healing_done: float
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -232,6 +233,7 @@ def run_encounter(
|
||||
crits=current.crits + stats.crits,
|
||||
damage_dealt=current.damage_dealt + stats.damage_dealt,
|
||||
damage_taken=current.damage_taken + stats.damage_taken,
|
||||
healing_done=current.healing_done + stats.healing_done,
|
||||
)
|
||||
attrition = {
|
||||
cid: CombatantAttrition(
|
||||
@@ -239,6 +241,7 @@ def run_encounter(
|
||||
crits=stats.crits / runs,
|
||||
damage_dealt=stats.damage_dealt / runs,
|
||||
damage_taken=stats.damage_taken / runs,
|
||||
healing_done=stats.healing_done / runs,
|
||||
)
|
||||
for cid, stats in totals.items()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user