fix(rules): condition durations scale with caster level (hold person, doom, touch of fatigue, vanish)
ConditionEffect gains duration_rounds_per_level and max_duration_rounds (mirroring PenaltyEffect), applied as caster_level * per_level rounds capped at max: hold person 1 rd/lvl, doom 1 min/lvl, touch of fatigue 1 rd/lvl, vanish 1 rd/lvl (max 5). Extend Spell doubles both the per-level rate and the cap. Tests cover scaling and the vanish cap.
This commit is contained in:
@@ -176,6 +176,15 @@ class DamageEffect:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConditionEffect:
|
||||
"""Transient condition (PF1e: ray of enfeeblement, hold person, doom...).
|
||||
|
||||
``duration_rounds_per_level`` (mirroring ``PenaltyEffect``'s field of the
|
||||
same name) makes the duration caster-level-scaled — SRD hold person "1
|
||||
round/level", doom "1 min./level" — replacing the fixed
|
||||
``duration_rounds`` when > 0; ``max_duration_rounds`` caps it (0 = no
|
||||
cap, SRD vanish "maximum 5 rounds").
|
||||
"""
|
||||
|
||||
condition_name: str
|
||||
duration_rounds: int
|
||||
save_negates: bool = True
|
||||
@@ -183,6 +192,8 @@ class ConditionEffect:
|
||||
# is not spared outright but suffers a weaker condition instead. Encoded as
|
||||
# (condition_name, duration_rounds); None = a successful save fully negates.
|
||||
save_partial: tuple[str, int] | None = None
|
||||
duration_rounds_per_level: int = 0
|
||||
max_duration_rounds: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -602,11 +613,21 @@ def _parse_damage_effect(effect: dict[str, Json]) -> DamageEffect:
|
||||
def _parse_condition_effect(effect: dict[str, Json]) -> ConditionEffect:
|
||||
partial_raw = effect.get("on_success")
|
||||
save_partial = None if partial_raw is None else _parse_save_partial(partial_raw)
|
||||
per_level = _as_int(effect.get("duration_rounds_per_level", 0))
|
||||
if per_level < 0:
|
||||
msg = f"duration_rounds_per_level must be >= 0, got {per_level}"
|
||||
raise ValueError(msg)
|
||||
max_rounds = _as_int(effect.get("max_duration_rounds", 0))
|
||||
if max_rounds < 0:
|
||||
msg = f"max_duration_rounds must be >= 0, got {max_rounds}"
|
||||
raise ValueError(msg)
|
||||
return ConditionEffect(
|
||||
condition_name=_as_str(effect["condition_name"]),
|
||||
duration_rounds=_as_int(effect["duration_rounds"]),
|
||||
save_negates=bool(effect.get("save_negates", True)),
|
||||
save_partial=save_partial,
|
||||
duration_rounds_per_level=per_level,
|
||||
max_duration_rounds=max_rounds,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user