fix(rules): mirror image rolled image count with dynamic images/(images+1) concealment miss chance, bleed cantrip gated on the SRD dying-target eligibility, bane -1 fear-scoped penalty via a dedicated bane condition (SRD spells/mirror-image.md, spells/bleed.md, spells/bane.md)
This commit is contained in:
@@ -117,7 +117,7 @@ Phase 0 documented deviations from PF1e (conventions):
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from dataclasses import asdict, dataclass, field, replace
|
||||
from itertools import pairwise
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
@@ -6815,6 +6815,10 @@ class CombatEngine:
|
||||
spell_name: str,
|
||||
caster_level: int = 0,
|
||||
) -> str:
|
||||
# SRD spells/bleed.md targets "a living creature that has -1 or fewer
|
||||
# hit points" - a healthy target is simply not eligible for the effect.
|
||||
if effect.requires_dying and target.hp >= 0:
|
||||
return "no effect (target not dying)"
|
||||
duration = effect.duration_rounds
|
||||
if effect.duration_rounds_per_level > 0:
|
||||
duration = max(1, caster_level * effect.duration_rounds_per_level)
|
||||
@@ -6822,7 +6826,7 @@ class CombatEngine:
|
||||
duration = min(duration, effect.max_duration_rounds)
|
||||
if saved and effect.save_negates:
|
||||
# PF1e partial save (SRD spells/fear.md): a successful save does
|
||||
# not always spare the target — it may impose a weaker condition
|
||||
# not always spare the target - it may impose a weaker condition
|
||||
# (Fear: shaken for 1 round instead of panicked).
|
||||
if effect.save_partial is not None:
|
||||
name, partial_duration = effect.save_partial
|
||||
@@ -6831,10 +6835,51 @@ class CombatEngine:
|
||||
return ""
|
||||
return f"{name} ({partial_duration}r) (partial save)"
|
||||
return f"{effect.condition_name} negated"
|
||||
if effect.images is not None:
|
||||
return self._impose_mirror_images(
|
||||
target, effect, duration, spell_name, caster_level
|
||||
)
|
||||
return self._impose_condition(
|
||||
target, effect.condition_name, duration, spell_name
|
||||
)
|
||||
|
||||
def _impose_mirror_images(
|
||||
self,
|
||||
target: CombatantState,
|
||||
effect: ConditionEffect,
|
||||
duration_rounds: int,
|
||||
spell_name: str,
|
||||
caster_level: int,
|
||||
) -> str:
|
||||
"""Impose Mirror Image's concealment with a cast-time image roll.
|
||||
|
||||
PF1e (SRD spells/mirror-image.md): "1d4 images plus one image per
|
||||
three caster levels (maximum eight images total)". The engine has no
|
||||
figment-destruction track, so the image count is folded into the
|
||||
existing concealment machinery as a dynamic miss chance on the
|
||||
condition instance: an attack that would hit strikes a figment with
|
||||
probability images / (images + 1).
|
||||
"""
|
||||
spec = effect.images
|
||||
if spec is None:
|
||||
return ""
|
||||
base = CONDITIONS_BY_NAME.get(effect.condition_name)
|
||||
if base is None:
|
||||
return ""
|
||||
rolled = spec.dice.roll(self._rng)
|
||||
images = min(spec.max_images, rolled + caster_level // spec.per_levels)
|
||||
if images <= 0:
|
||||
return "0 images"
|
||||
miss_pct = 100 * images // (images + 1)
|
||||
target.active_conditions.append(
|
||||
ActiveCondition(
|
||||
condition=replace(base, miss_chance=miss_pct),
|
||||
rounds_left=duration_rounds,
|
||||
source=f"spell: {spell_name}",
|
||||
)
|
||||
)
|
||||
return f"{effect.condition_name} {miss_pct}% ({images} images, {duration_rounds}r)"
|
||||
|
||||
def _impose_condition(
|
||||
self,
|
||||
target: CombatantState,
|
||||
@@ -7187,6 +7232,8 @@ class CombatEngine:
|
||||
save_partial=partial,
|
||||
duration_rounds_per_level=per_level,
|
||||
max_duration_rounds=max_rounds,
|
||||
images=effect.images,
|
||||
requires_dying=effect.requires_dying,
|
||||
)
|
||||
if isinstance(effect, BuffEffect):
|
||||
duration = effect.duration_rounds * 2 if has_extend else effect.duration_rounds
|
||||
|
||||
Reference in New Issue
Block a user