fix(rules): summon duration scales with caster level (SRD summon-monster: 1 round/level (D)) - SummonEffect gains duration_rounds_per_level resolved at cast time, summon_monster_i/ii JSON updated

This commit is contained in:
2026-09-19 11:47:34 +02:00
parent 418fdc826b
commit cec3c5f459
6 changed files with 112 additions and 9 deletions
+11 -2
View File
@@ -7220,6 +7220,15 @@ class CombatEngine:
_spell_name: str,
) -> str:
"""Apply a summoning effect - create a summoned creature."""
# PF1e (SRD spells/summon-monster-1.md): "Duration 1 round/level (D)"
# - a per-level duration resolves against the caster's level at cast
# time, mirroring the ConditionEffect convention; a fixed duration is
# the pre-conformance fallback.
duration = effect.duration_rounds
if effect.duration_rounds_per_level > 0:
duration = max(
1, state.combatant.level * effect.duration_rounds_per_level
)
# Load the summoned creature
try:
summoned_combatant = load_combatant(Path(effect.creature))
@@ -7256,7 +7265,7 @@ class CombatEngine:
self._states.insert(caster_idx + 1, summoned_state)
# Set expiration
summoned_state.summon_duration = effect.duration_rounds
summoned_state.summon_duration = duration
summoned_state.summon_caster_id = state.combatant.id
self._apply_simple_monster_death(summoned_state)
@@ -7265,7 +7274,7 @@ class CombatEngine:
self._log(
f"round {self._current_round} {state.combatant.id}: "
f"summon {summoned_combatant.id} at {spawn_pos} ({effect.duration_rounds}r)"
f"summon {summoned_combatant.id} at {spawn_pos} ({duration}r)"
)
return f"summoned {summoned_combatant.id}"
+11 -1
View File
@@ -296,10 +296,17 @@ class SummonEffect:
"""Summoning spell effect.
- creature: path to the creature JSON file (e.g., "data/monsters/celestial_wolf.json")
- duration_rounds: how long the summoned creature stays
- duration_rounds: fixed rounds the summoned creature stays, used when
``duration_rounds_per_level`` is 0
- duration_rounds_per_level: PF1e "1 round/level" summoning duration
(SRD spells/summon-monster-1.md: "Duration 1 round/level (D)") - when
> 0 the rounds resolve at cast time as
``max(1, caster_level * duration_rounds_per_level)``, mirroring
``ConditionEffect``'s field of the same name.
"""
creature: str
duration_rounds: int
duration_rounds_per_level: int = 0
SpellEffect = (
@@ -690,6 +697,9 @@ def _parse_summon_effect(effect: dict[str, Json]) -> SummonEffect:
return SummonEffect(
creature=_as_str(effect["creature"]),
duration_rounds=_as_int(effect["duration_rounds"]),
duration_rounds_per_level=_as_int(
effect.get("duration_rounds_per_level", 0)
),
)