refactor(combat): single source active_buffs/active_conditions
This commit is contained in:
@@ -1066,7 +1066,6 @@ class CombatEngine:
|
|||||||
source="maneuver: trip",
|
source="maneuver: trip",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
target.conditions.append(CONDITIONS_BY_NAME["prone"])
|
|
||||||
self._log(
|
self._log(
|
||||||
f"round {self._current_round} {state.combatant.id}: trip vs "
|
f"round {self._current_round} {state.combatant.id}: trip vs "
|
||||||
f"{target.combatant.id} d20={m.roll}+{m.attacker_cmb}={m.total} "
|
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}",
|
source=f"maneuver: grapple by {state.combatant.id}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
target.conditions.append(CONDITIONS_BY_NAME["grappled"])
|
|
||||||
target.grappled_with = state.combatant.id
|
target.grappled_with = state.combatant.id
|
||||||
state.grappled_with = target.combatant.id
|
state.grappled_with = target.combatant.id
|
||||||
self._log(
|
self._log(
|
||||||
@@ -1304,7 +1302,6 @@ class CombatEngine:
|
|||||||
source=f"hex: evil_eye by {state.combatant.id}",
|
source=f"hex: evil_eye by {state.combatant.id}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
target.conditions.append(CONDITIONS_BY_NAME["shaken"])
|
|
||||||
self._log(
|
self._log(
|
||||||
f"round {self._current_round} {state.combatant.id}: evil eye on "
|
f"round {self._current_round} {state.combatant.id}: evil eye on "
|
||||||
f"{target.combatant.id} -> shaken (1r)"
|
f"{target.combatant.id} -> shaken (1r)"
|
||||||
@@ -1328,7 +1325,6 @@ class CombatEngine:
|
|||||||
source=f"hex: slumber by {state.combatant.id}",
|
source=f"hex: slumber by {state.combatant.id}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
target.conditions.append(CONDITIONS_BY_NAME["asleep"])
|
|
||||||
self._log(
|
self._log(
|
||||||
f"round {self._current_round} {state.combatant.id}: slumber on "
|
f"round {self._current_round} {state.combatant.id}: slumber on "
|
||||||
f"{target.combatant.id} -> asleep (1r)"
|
f"{target.combatant.id} -> asleep (1r)"
|
||||||
@@ -1452,11 +1448,6 @@ class CombatEngine:
|
|||||||
source=f"inspire courage by {state.combatant.id}",
|
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(
|
self._log(
|
||||||
f"round {self._current_round} {state.combatant.id}: "
|
f"round {self._current_round} {state.combatant.id}: "
|
||||||
f"inspire courage +1 morale attack/damage to {len(affected)} allies"
|
f"inspire courage +1 morale attack/damage to {len(affected)} allies"
|
||||||
@@ -1665,7 +1656,6 @@ class CombatEngine:
|
|||||||
source=f"spell: {spell_name}",
|
source=f"spell: {spell_name}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
target.conditions.append(cond)
|
|
||||||
return f"{effect.condition_name} ({effect.duration_rounds}r)"
|
return f"{effect.condition_name} ({effect.duration_rounds}r)"
|
||||||
|
|
||||||
def _apply_heal_effect(
|
def _apply_heal_effect(
|
||||||
@@ -1696,7 +1686,6 @@ class CombatEngine:
|
|||||||
source=f"spell: {spell_name}",
|
source=f"spell: {spell_name}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
target.effects.extend(effect.modifiers)
|
|
||||||
return f"buff {len(effect.modifiers)} mods ({effect.duration_rounds}r)"
|
return f"buff {len(effect.modifiers)} mods ({effect.duration_rounds}r)"
|
||||||
|
|
||||||
def _apply_area_effect(
|
def _apply_area_effect(
|
||||||
|
|||||||
+23
-5
@@ -255,14 +255,14 @@ class TestHoldPerson:
|
|||||||
target = _make_target()
|
target = _make_target()
|
||||||
engine, c, t = _make_engine(caster, target, [2])
|
engine, c, t = _make_engine(caster, target, [2])
|
||||||
engine._cast_spell(c, t, "Hold Person")
|
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:
|
def test_successful_save_no_condition(self) -> None:
|
||||||
caster = _make_caster()
|
caster = _make_caster()
|
||||||
target = _make_target()
|
target = _make_target()
|
||||||
engine, c, t = _make_engine(caster, target, [20])
|
engine, c, t = _make_engine(caster, target, [20])
|
||||||
engine._cast_spell(c, t, "Hold Person")
|
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:
|
def test_transcript_shows_negated_on_save(self) -> None:
|
||||||
caster = _make_caster()
|
caster = _make_caster()
|
||||||
@@ -282,7 +282,7 @@ class TestFear:
|
|||||||
target = _make_target()
|
target = _make_target()
|
||||||
engine, c, t = _make_engine(caster, target, [2])
|
engine, c, t = _make_engine(caster, target, [2])
|
||||||
engine._cast_spell(c, t, "Fear")
|
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:
|
class TestCureLightWounds:
|
||||||
@@ -317,8 +317,9 @@ class TestBullsStrength:
|
|||||||
target = _make_target()
|
target = _make_target()
|
||||||
engine, c, t = _make_engine(caster, target, [3, 3], target_pos=(1, 0))
|
engine, c, t = _make_engine(caster, target, [3, 3], target_pos=(1, 0))
|
||||||
engine._cast_spell(c, t, "Bull's Strength")
|
engine._cast_spell(c, t, "Bull's Strength")
|
||||||
atk_mods = [e for e in t.effects if e.target == "attack"]
|
buff_mods = [m for b in t.active_buffs for m in b.modifiers]
|
||||||
dmg_mods = [e for e in t.effects if e.target == "damage"]
|
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(atk_mods) == 1
|
||||||
assert len(dmg_mods) == 1
|
assert len(dmg_mods) == 1
|
||||||
assert atk_mods[0].value == 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
|
engine._cast_spell(c, t, "Magic Missile") # spends the only level-1 slot
|
||||||
assert c.spell_slots_left[1] == 0
|
assert c.spell_slots_left[1] == 0
|
||||||
assert _choose_spell_action(engine, c, t) is None
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user