diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index fe2dc33..c70746d 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -1066,7 +1066,6 @@ class CombatEngine: source="maneuver: trip", ) ) - target.conditions.append(CONDITIONS_BY_NAME["prone"]) self._log( f"round {self._current_round} {state.combatant.id}: trip vs " f"{target.combatant.id} d20={m.roll}+{m.attacker_cmb}={m.total} " @@ -1136,7 +1135,6 @@ class CombatEngine: source=f"maneuver: grapple by {state.combatant.id}", ) ) - target.conditions.append(CONDITIONS_BY_NAME["grappled"]) target.grappled_with = state.combatant.id state.grappled_with = target.combatant.id self._log( @@ -1304,7 +1302,6 @@ class CombatEngine: source=f"hex: evil_eye by {state.combatant.id}", ) ) - target.conditions.append(CONDITIONS_BY_NAME["shaken"]) self._log( f"round {self._current_round} {state.combatant.id}: evil eye on " f"{target.combatant.id} -> shaken (1r)" @@ -1328,7 +1325,6 @@ class CombatEngine: source=f"hex: slumber by {state.combatant.id}", ) ) - target.conditions.append(CONDITIONS_BY_NAME["asleep"]) self._log( f"round {self._current_round} {state.combatant.id}: slumber on " f"{target.combatant.id} -> asleep (1r)" @@ -1452,11 +1448,6 @@ class CombatEngine: source=f"inspire courage by {state.combatant.id}", ) ) - # Also update old effects field for backwards compatibility - ally.effects.extend(( - StatModifier(target="attack", value=1, bonus_type="morale"), - StatModifier(target="damage", value=1, bonus_type="morale"), - )) self._log( f"round {self._current_round} {state.combatant.id}: " f"inspire courage +1 morale attack/damage to {len(affected)} allies" @@ -1665,7 +1656,6 @@ class CombatEngine: source=f"spell: {spell_name}", ) ) - target.conditions.append(cond) return f"{effect.condition_name} ({effect.duration_rounds}r)" def _apply_heal_effect( @@ -1696,7 +1686,6 @@ class CombatEngine: source=f"spell: {spell_name}", ) ) - target.effects.extend(effect.modifiers) return f"buff {len(effect.modifiers)} mods ({effect.duration_rounds}r)" def _apply_area_effect( diff --git a/tests/test_spells.py b/tests/test_spells.py index 8e3c2b9..a14313e 100644 --- a/tests/test_spells.py +++ b/tests/test_spells.py @@ -255,14 +255,14 @@ class TestHoldPerson: target = _make_target() engine, c, t = _make_engine(caster, target, [2]) engine._cast_spell(c, t, "Hold Person") - assert any(cond.name == "paralyzed" for cond in t.conditions) + assert any(ac.condition.name == "paralyzed" for ac in t.active_conditions) def test_successful_save_no_condition(self) -> None: caster = _make_caster() target = _make_target() engine, c, t = _make_engine(caster, target, [20]) engine._cast_spell(c, t, "Hold Person") - assert not any(cond.name == "paralyzed" for cond in t.conditions) + assert not any(ac.condition.name == "paralyzed" for ac in t.active_conditions) def test_transcript_shows_negated_on_save(self) -> None: caster = _make_caster() @@ -282,7 +282,7 @@ class TestFear: target = _make_target() engine, c, t = _make_engine(caster, target, [2]) engine._cast_spell(c, t, "Fear") - assert any(cond.name == "frightened" for cond in t.conditions) + assert any(ac.condition.name == "frightened" for ac in t.active_conditions) class TestCureLightWounds: @@ -317,8 +317,9 @@ class TestBullsStrength: target = _make_target() engine, c, t = _make_engine(caster, target, [3, 3], target_pos=(1, 0)) engine._cast_spell(c, t, "Bull's Strength") - atk_mods = [e for e in t.effects if e.target == "attack"] - dmg_mods = [e for e in t.effects if e.target == "damage"] + buff_mods = [m for b in t.active_buffs for m in b.modifiers] + atk_mods = [e for e in buff_mods if e.target == "attack"] + dmg_mods = [e for e in buff_mods if e.target == "damage"] assert len(atk_mods) == 1 assert len(dmg_mods) == 1 assert atk_mods[0].value == 1 @@ -461,3 +462,20 @@ class TestSpellSlots: engine._cast_spell(c, t, "Magic Missile") # spends the only level-1 slot assert c.spell_slots_left[1] == 0 assert _choose_spell_action(engine, c, t) is None + + +class TestActiveIsSingleSource: + """Given: active_* is the single source of truth for conditions and buffs + When: _cast_spell applies a condition and a buff + Then: the legacy conditions/effects lists stay empty while the active_* lists are populated.""" + + def test_legacy_lists_stay_empty_active_lists_populated(self) -> None: + caster = _make_caster() + target = _make_target() + engine, c, t = _make_engine(caster, target, [2, 3, 3], target_pos=(1, 0)) + engine._cast_spell(c, t, "Hold Person") + engine._cast_spell(c, t, "Bull's Strength") + assert t.conditions == [] + assert t.effects == [] + assert any(ac.condition.name == "paralyzed" for ac in t.active_conditions) + assert any(b.source.startswith("spell:") for b in t.active_buffs)