feat(spells): add spell system with 10 combat spells, cast_spell action

Spells are SpellSpec dataclasses with typed effects: DamageEffect
(dice + types, half-on-save), ConditionEffect (condition lookup +
save negates), HealEffect (capped at hp_max), BuffEffect (transient
StatModifiers). Range categories (personal/touch/close/medium/long)
scale with caster level via spell_range_ft. JSON loader
(load_spell/load_spell_registry) reads data/spells/*.json.

The engine integrates spells via Action(kind='cast_spell') and
_cast_spell: range check, line-of-effect gate, save resolution
(resolve_save), effect application (_apply_spell_effects with DR
via _apply_dr_for_types), transcript logging. The spell_registry
is passed to CombatEngine; Combatant.spells holds known spell names.

- spells.py: SpellSpec, 4 effect types, SpellRange/SaveType literals,
  spell_range_ft, JSON loader (uses Json type from foundry.py)
- conditions.py: CONDITIONS_BY_NAME registry for condition lookup
- combat.py: cast_spell Action kind, _execute_cast_spell dispatch,
  _cast_spell (range/LoE/save/effects/log), _apply_spell_effects,
  _apply_dr_for_types, _save_label helper, SpellSpec/BuffEffect/etc
  imports, spell_registry parameter on CombatEngine
- models.py: spells field on Combatant
- data/spells/: 10 spells (Magic Missile, Burning Hands, Fireball,
  Lightning Bolt, Acid Arrow, Cure Light/Moderate Wounds, Hold Person,
  Fear, Bull's Strength)
- tests/test_spells.py: 29 tests (loading, range, damage/save/condition/
  heal/buff/DR, out-of-range, unknown spell, dispatch, DC scaling)
- pyproject.toml: PLR0913 ignore for combat.py (CombatEngine.__init__)
- README.md: spells.py architecture section, Sorts removed from
  non-modeled list, 336 tests
This commit is contained in:
2026-08-17 22:49:50 +02:00
parent 97c0431479
commit 828afc15c0
17 changed files with 862 additions and 8 deletions
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Acid Arrow",
"level": 2,
"school": "evocation",
"range": "medium",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "2d4",
"types": ["acid"],
"half_on_save": false
}
],
"description": "An arrow of acid springs from your hand and speeds to its target, dealing 2d4 points of acid damage. No save."
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "Bull's Strength",
"level": 2,
"school": "transmutation",
"range": "touch",
"save": "fort",
"base_dc": 10,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "attack", "value": 1, "bonus_type": "enhancement" },
{ "target": "damage", "value": 1, "bonus_type": "enhancement" }
],
"duration_rounds": 60
}
],
"description": "The subject gains a +4 enhancement bonus to Strength, granting +2 attack and +2 damage (approximated as +1/+1). Fortitude negates (harmless)."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Burning Hands",
"level": 1,
"school": "evocation",
"range": "touch",
"save": "ref",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "1d4+1",
"types": ["fire"],
"half_on_save": true
}
],
"description": "A cone of searing flame shoots from your fingertips, dealing 1d4+1 points of fire damage per caster level (max 5d4+5). Reflex save for half."
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "Cure Light Wounds",
"level": 1,
"school": "conjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "heal",
"formula": "1d8+1"
}
],
"description": "Cures 1d8+1 points of damage. Will save negates (harmless)."
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "Cure Moderate Wounds",
"level": 2,
"school": "conjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "heal",
"formula": "2d8+3"
}
],
"description": "Cures 2d8+3 points of damage. Will save negates (harmless)."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Fear",
"level": 4,
"school": "necromancy",
"range": "medium",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "frightened",
"duration_rounds": 4,
"save_negates": true
}
],
"description": "The affected creature becomes frightened and flees from the caster. Will negates."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Fireball",
"level": 3,
"school": "evocation",
"range": "long",
"save": "ref",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "6d6",
"types": ["fire"],
"half_on_save": true
}
],
"description": "A glowing bead streaks from you and bursts into a 20-foot radius explosion of fire, dealing 6d6 points of fire damage. Reflex save for half."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Hold Person",
"level": 3,
"school": "enchantment",
"range": "medium",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "paralyzed",
"duration_rounds": 3,
"save_negates": true
}
],
"description": "The subject becomes paralyzed and freezes in place. Will negates."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Lightning Bolt",
"level": 3,
"school": "evocation",
"range": "medium",
"save": "ref",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "6d6",
"types": ["electricity"],
"half_on_save": true
}
],
"description": "You release a powerful stroke of electrical energy, dealing 6d6 points of electricity damage. Reflex save for half."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Magic Missile",
"level": 1,
"school": "evocation",
"range": "medium",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "1d4+1",
"types": ["force"],
"half_on_save": false
}
],
"description": "A missile of magical energy darts forth from your fingertip and strikes its target, dealing 1d4+1 points of force damage."
}