"""PF1e conditions: predefined stat modifiers and behavioral flags. Each condition maps to a set of StatModifier effects (applied via resolve_modifiers with PF1e stacking rules) and optional behavioral flags that the engine checks at relevant resolution points. Behavioral flags are declared here but only stat modifiers are integrated into resolve_attack/resolve_save in this step. Flag enforcement (cant_act, must_flee, etc.) will be added incrementally with spell/condition application. Sources: PRPG Core Rulebook, Conditions chapter. """ from __future__ import annotations from dataclasses import dataclass from pf1e_simulator.effects import StatModifier @dataclass(frozen=True) class Condition: """A PF1e condition: stat modifiers + behavioral flags. ``modifiers`` are collected alongside transient ``effects`` by ``resolve_modifiers``. Behavioral flags are checked by the engine at relevant points (e.g. ``cant_act`` in ``_take_turn``). """ name: str modifiers: tuple[StatModifier, ...] = () denies_dex_to_ac: bool = False cant_act: bool = False cant_move: bool = False cant_attack: bool = False cant_charge: bool = False cant_aoo: bool = False must_flee: bool = False speed_mult: float = 1.0 # ── Fear conditions ────────────────────────────────────────────────────────── # Shaken: -2 attack rolls, saving throws, skill checks, ability checks (CRB) SHAKEN = Condition( name="shaken", modifiers=( StatModifier(target="attack", value=-2), StatModifier(target="fort", value=-2), StatModifier(target="ref", value=-2), StatModifier(target="will", value=-2), ), ) # Frightened: shaken penalties + must flee from source of fear (CRB) FRIGHTENED = Condition( name="frightened", modifiers=( StatModifier(target="attack", value=-2), StatModifier(target="fort", value=-2), StatModifier(target="ref", value=-2), StatModifier(target="will", value=-2), ), must_flee=True, ) # Panicked: drop items + flee at top speed + -2 saves (CRB) PANICKED = Condition( name="panicked", modifiers=( StatModifier(target="fort", value=-2), StatModifier(target="ref", value=-2), StatModifier(target="will", value=-2), ), must_flee=True, ) # Cowering: frozen in fear, can't act, -2 AC, loses Dex (CRB) COWERING = Condition( name="cowering", modifiers=( StatModifier(target="ac", value=-2), ), denies_dex_to_ac=True, cant_act=True, ) # ── Physical conditions ───────────────────────────────────────────────────── # Sickened: -2 attack, weapon damage, saving throws, skill/ability checks (CRB) SICKENED = Condition( name="sickened", modifiers=( StatModifier(target="attack", value=-2), StatModifier(target="damage", value=-2), StatModifier(target="fort", value=-2), StatModifier(target="ref", value=-2), StatModifier(target="will", value=-2), ), ) # Fatigued: -2 STR, -2 DEX; can't run or charge (CRB) # Approximation: -1 attack (STR/DEX), -1 damage (STR) FATIGUED = Condition( name="fatigued", modifiers=( StatModifier(target="attack", value=-1), StatModifier(target="damage", value=-1), ), cant_charge=True, ) # Exhausted: -6 STR, -6 DEX; half speed; can't run or charge (CRB) # Approximation: -3 attack, -3 damage EXHAUSTED = Condition( name="exhausted", modifiers=( StatModifier(target="attack", value=-3), StatModifier(target="damage", value=-3), ), cant_charge=True, speed_mult=0.5, ) # Entangled: -2 attack, -4 DEX; half speed; can't run or charge (CRB) # Approximation: -2 attack, -2 AC (from -4 DEX), -2 ref (from -4 DEX) ENTANGLED = Condition( name="entangled", modifiers=( StatModifier(target="attack", value=-2), StatModifier(target="ac", value=-2), StatModifier(target="ref", value=-2), ), cant_charge=True, speed_mult=0.5, ) # ── Sensory conditions ────────────────────────────────────────────────────── # Dazzled: -1 attack rolls (CRB) DAZZLED = Condition( name="dazzled", modifiers=( StatModifier(target="attack", value=-1), ), ) # Blinded: -2 AC, loses Dex, -4 STR/DEX skill checks (CRB) BLINDED = Condition( name="blinded", modifiers=( StatModifier(target="ac", value=-2), StatModifier(target="attack", value=-2), ), denies_dex_to_ac=True, ) # Deafened: -4 initiative, 20% spell failure (verbal) (CRB) DEAFENED = Condition( name="deafened", modifiers=( StatModifier(target="initiative", value=-4), ), ) # ── Positional conditions ──────────────────────────────────────────────────── # Flat-footed: loses Dex to AC, can't make AoO (CRB) FLAT_FOOTED = Condition( name="flat_footed", denies_dex_to_ac=True, cant_aoo=True, ) # Prone: -4 melee attack, +4 AC vs ranged, -4 AC vs melee (CRB) PRONE = Condition( name="prone", ) # ── Incapacitating conditions ──────────────────────────────────────────────── # Stunned: can't act, -2 AC, loses Dex (CRB) STUNNED = Condition( name="stunned", modifiers=( StatModifier(target="ac", value=-2), ), denies_dex_to_ac=True, cant_act=True, ) # Paralyzed: can't move or act, effective STR/DEX = 0, helpless (CRB) PARALYZED = Condition( name="paralyzed", denies_dex_to_ac=True, cant_act=True, cant_move=True, ) # Nauseated: can't attack/cast/concentrate; only single move action (CRB) NAUSEATED = Condition( name="nauseated", cant_attack=True, ) # Dazed: can't act, no AC penalty (CRB) DAZED = Condition( name="dazed", cant_act=True, ) # Asleep/Unconscious: helpless, can't act, -4 AC vs melee, no Dex to AC (CRB) ASLEEP = Condition( name="asleep", modifiers=( StatModifier(target="ac", value=-4), ), denies_dex_to_ac=True, cant_act=True, cant_move=True, ) # Grappled: -4 Dex, -2 attack, no movement, no AoO, can't cast without concentration (CRB) GRAPPLED = Condition( name="grappled", modifiers=( StatModifier(target="attack", value=-2), StatModifier(target="ac", value=-2), # -4 Dex = -2 AC StatModifier(target="ref", value=-2), # -4 Dex = -2 Ref StatModifier(target="dex_skills", value=-4), # -4 to Dex skills ), cant_move=True, cant_charge=True, cant_aoo=True, denies_dex_to_ac=False, # Doesn't lose Dex to AC, but -4 Dex penalty applies ) # Pinned: -4 Dex, -2 attack, no movement, can't cast, -4 AC vs melee (CRB) PINNED = Condition( name="pinned", modifiers=( StatModifier(target="attack", value=-2), StatModifier(target="ac", value=-4), StatModifier(target="ref", value=-2), StatModifier(target="dex_skills", value=-4), ), cant_move=True, cant_charge=True, cant_aoo=True, denies_dex_to_ac=True, ) # Staggered: only single move or standard action, no full-round (CRB) STAGGERED = Condition( name="staggered", ) # ── Registry ───────────────────────────────────────────────────────────────── CONDITIONS_BY_NAME: dict[str, Condition] = { c.name: c for c in [ SHAKEN, SICKENED, FATIGUED, EXHAUSTED, ENTANGLED, DAZZLED, BLINDED, DEAFENED, FLAT_FOOTED, PRONE, STUNNED, PARALYZED, NAUSEATED, DAZED, STAGGERED, FRIGHTENED, PANICKED, COWERING, ASLEEP, GRAPPLED, PINNED, ] }