fix(combat): correct spell DC formula
This commit is contained in:
@@ -24,6 +24,8 @@
|
|||||||
"ref": 10,
|
"ref": 10,
|
||||||
"will": 5
|
"will": 5
|
||||||
},
|
},
|
||||||
|
"casting_ability": "int",
|
||||||
|
"resources": {},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "Lance",
|
"name": "Lance",
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
"ref": 5,
|
"ref": 5,
|
||||||
"will": 2
|
"will": 2
|
||||||
},
|
},
|
||||||
|
"casting_ability": "wis",
|
||||||
|
"resources": {},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "Talons (attaque naturelle)",
|
"name": "Talons (attaque naturelle)",
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
"ref": 6,
|
"ref": 6,
|
||||||
"will": 3
|
"will": 3
|
||||||
},
|
},
|
||||||
|
"casting_ability": "wis",
|
||||||
|
"resources": {},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "Bite (attaque naturelle)",
|
"name": "Bite (attaque naturelle)",
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
"ref": 6,
|
"ref": 6,
|
||||||
"will": 2
|
"will": 2
|
||||||
},
|
},
|
||||||
|
"casting_ability": "wis",
|
||||||
|
"resources": {},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "Hooves (attaque naturelle)",
|
"name": "Hooves (attaque naturelle)",
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
"ref": 9,
|
"ref": 9,
|
||||||
"will": 3
|
"will": 3
|
||||||
},
|
},
|
||||||
|
"casting_ability": "cha",
|
||||||
|
"resources": {
|
||||||
|
"bardic_rounds": 12
|
||||||
|
},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "MWK Rapier",
|
"name": "MWK Rapier",
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
"ref": 5,
|
"ref": 5,
|
||||||
"will": 7
|
"will": 7
|
||||||
},
|
},
|
||||||
|
"casting_ability": "int",
|
||||||
|
"resources": {
|
||||||
|
"hexes": 5
|
||||||
|
},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "Coup à mains nues (aucune arme encodée)",
|
"name": "Coup à mains nues (aucune arme encodée)",
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
"ref": 7,
|
"ref": 7,
|
||||||
"will": 9
|
"will": 9
|
||||||
},
|
},
|
||||||
|
"casting_ability": "wis",
|
||||||
|
"resources": {
|
||||||
|
"channel_energy": 6
|
||||||
|
},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "Longbow",
|
"name": "Longbow",
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
"ref": 10,
|
"ref": 10,
|
||||||
"will": 8
|
"will": 8
|
||||||
},
|
},
|
||||||
|
"casting_ability": "wis",
|
||||||
|
"resources": {
|
||||||
|
"grit": 3
|
||||||
|
},
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{
|
{
|
||||||
"name": "Rapier (masterwork)",
|
"name": "Rapier (masterwork)",
|
||||||
|
|||||||
@@ -834,8 +834,11 @@ class CombatEngine:
|
|||||||
f"round {self._current_round} {caster.combatant.id}: "
|
f"round {self._current_round} {caster.combatant.id}: "
|
||||||
f"cast {spell_name} -> NO LINE OF EFFECT"
|
f"cast {spell_name} -> NO LINE OF EFFECT"
|
||||||
)
|
)
|
||||||
return
|
# DC = 10 + spell level + caster's casting ability modifier
|
||||||
dc = spell.base_dc + caster.combatant.level
|
ability_name = caster.combatant.casting_ability.lower()
|
||||||
|
ability_score = getattr(caster.combatant.abilities, f"{ability_name}_score")
|
||||||
|
ability_mod = (ability_score - 10) // 2
|
||||||
|
dc = 10 + spell.level + ability_mod
|
||||||
save_result = (
|
save_result = (
|
||||||
self.resolve_save(target, spell.save, dc) if spell.save != "none" else None
|
self.resolve_save(target, spell.save, dc) if spell.save != "none" else None
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -113,3 +113,5 @@ class Combatant(_FrozenModel):
|
|||||||
notes: str = ""
|
notes: str = ""
|
||||||
features: list[AbilitySpec] = []
|
features: list[AbilitySpec] = []
|
||||||
spells: list[str] = []
|
spells: list[str] = []
|
||||||
|
casting_ability: str = "wis" # ability used for spell DCs: str/dex/con/int/wis/cha
|
||||||
|
resources: dict[str, int] = Field(default_factory=dict) # resource pools per combat
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ def _make_caster(cid: str = "caster", *, level: int = 5) -> Combatant:
|
|||||||
speed_land_ft=30,
|
speed_land_ft=30,
|
||||||
saves=Saves(fort=3, ref=4, will=4),
|
saves=Saves(fort=3, ref=4, will=4),
|
||||||
attacks=[attack],
|
attacks=[attack],
|
||||||
|
casting_ability="int",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -366,18 +367,18 @@ class TestSpellDispatchViaExecute:
|
|||||||
)
|
)
|
||||||
assert t.hp < hp_before
|
assert t.hp < hp_before
|
||||||
|
|
||||||
|
|
||||||
class TestSpellDcScalesWithLevel:
|
class TestSpellDcScalesWithLevel:
|
||||||
"""Given: a caster with level N
|
"""Given: a caster with level N and casting ability mod M
|
||||||
When: a spell with base_dc 10 is cast
|
When: a spell of level L is cast
|
||||||
Then: the DC is 10 + N."""
|
Then: the DC is 10 + L + M."""
|
||||||
|
|
||||||
def test_dc_is_base_plus_level(self) -> None:
|
def test_dc_is_ten_plus_spell_level_plus_ability_mod(self) -> None:
|
||||||
caster = _make_caster(level=5)
|
caster = _make_caster(level=5) # int 18 -> mod +4
|
||||||
target = _make_target()
|
target = _make_target()
|
||||||
engine, c, t = _make_engine(caster, target, [20, 1, 1, 1, 1, 1, 1])
|
engine, c, t = _make_engine(caster, target, [20, 1, 1, 1, 1, 1, 1])
|
||||||
engine._cast_spell(c, t, "Fireball")
|
engine._cast_spell(c, t, "Fireball") # fireball is level 3
|
||||||
assert any("DC15" in line for line in engine._transcript)
|
# DC = 10 + spell_level(3) + int_mod(4) = 17
|
||||||
|
assert any("DC17" in line for line in engine._transcript)
|
||||||
|
|
||||||
|
|
||||||
class TestSpellDamageWithDr:
|
class TestSpellDamageWithDr:
|
||||||
|
|||||||
Reference in New Issue
Block a user