fix(data): lich fear aura, paralyzing touch, slot keys

This commit is contained in:
2026-09-04 22:56:01 +02:00
parent d0833c7a2c
commit 66a664ad85
3 changed files with 265 additions and 16 deletions
+68
View File
@@ -373,6 +373,7 @@ class CombatEngine:
if self.has_condition_flag(state, "cant_act"):
self._log(f"round {self._current_round} {state.combatant.id}: wait (cannot act)")
return self._winner_side() is not None
self._apply_fear_aura(state)
state.moved_this_turn = False
state.power_attack = False
actions = self._policy(self, state)
@@ -590,6 +591,62 @@ class CombatEngine:
"""True if the combatant has a feature with the given name."""
return any(f.name == feat_name for f in state.combatant.features)
def _is_paralyzing_touch(self, attacker: CombatantState, weapon: AttackSpec) -> bool:
"""True if a hit with this weapon should apply the Lich's paralyzing touch.
Robust check: the attacker has the "Paralyzing Touch" feature and the
weapon is a touch attack (or is literally named "Paralyzing Touch").
"""
if not self._has_feat(attacker, "Paralyzing Touch"):
return False
return weapon.kind == "touch" or weapon.name == "Paralyzing Touch"
_FEAR_AURA_DC = 22
_FEAR_AURA_RANGE_FT = 30
_FEAR_AURA_ROUNDS = 1
def _apply_fear_aura(self, state: CombatantState) -> None:
"""Apply the Lich's Fear Aura: enemies within 30 ft save Will (DC 22) or become shaken.
Triggered at the start of the aura-bearer's turn (simplification of the
PF1e "on range entry" trigger — the lich acts first each round, so this
covers entry into range). Only enemies within range who are not already
shaken are tested, so the effect does not re-roll on the same enemy each
turn. Shaken lasts a fixed 1 round.
"""
if not self._has_feat(state, "Fear Aura"):
return
for enemy in self._states:
if (
not enemy.active
or enemy.side == state.side
or self._has_condition_named(enemy, "shaken")
):
continue
dist_ft = self._grid.distance(state.pos, enemy.pos) * _SQUARE_FT
if dist_ft > self._FEAR_AURA_RANGE_FT:
continue
save = self.resolve_save(enemy, "will", self._FEAR_AURA_DC)
if save.success:
self._log(
f"round {self._current_round} {state.combatant.id}: fear aura on "
f"{enemy.combatant.id} -> SAVED"
)
continue
enemy.active_conditions.append(
ActiveCondition(
condition=CONDITIONS_BY_NAME["shaken"],
rounds_left=self._FEAR_AURA_ROUNDS,
source=f"monster ability: fear aura by {state.combatant.id}",
)
)
self._log(
f"round {self._current_round} {state.combatant.id}: fear aura on "
f"{enemy.combatant.id} -> shaken ({self._FEAR_AURA_ROUNDS}r)"
)
def _aoo_allowance(self, state: CombatantState) -> int:
"""Number of AoOs allowed per round for a combatant.
@@ -2052,6 +2109,17 @@ class CombatEngine:
)
if result.hit:
line += f" {result.damage} damage ({hp_before}->{target.hp})"
if self._is_paralyzing_touch(attacker, weapon):
duration = 1 + self._rng.roll_die(4)
if not self._has_condition_named(target, "paralyzed"):
target.active_conditions.append(
ActiveCondition(
condition=CONDITIONS_BY_NAME["paralyzed"],
rounds_left=duration,
source=f"monster ability: paralyzing touch by {attacker.combatant.id}",
)
)
line += f" paralyzed ({duration}r)"
self._log(line)
if target.dead:
self._log(