222 lines
9.9 KiB
Python
222 lines
9.9 KiB
Python
from enum_list import SpellSchool, SaveType, DamageType
|
|
|
|
|
|
class Spell:
|
|
"""Base class for all spells, built from the D&D 5e SRD."""
|
|
def __init__(self, name, level, school, casting_time, range_, components,
|
|
duration, description, classes, damage=None, dmg_type=None,
|
|
scaling_damage=None, save=SaveType.NONE, heal=None,
|
|
ritual=False, concentration=False):
|
|
self.name = name
|
|
self.level = level # 0 = cantrip
|
|
self.school = school
|
|
self.casting_time = casting_time
|
|
self.range = range_
|
|
self.components = components
|
|
self.duration = duration
|
|
self.description = description
|
|
self.classes = classes # list of class names that can cast this, e.g. ["Wizard"]
|
|
self.damage = damage # base damage dice string e.g. "1d10"
|
|
self.dmg_type = dmg_type
|
|
self.scaling_damage = scaling_damage # dice added per extra slot level/character level, if any
|
|
self.save = save
|
|
self.heal = heal # healing dice string, e.g. "1d8"
|
|
self.ritual = ritual
|
|
self.concentration = concentration
|
|
|
|
def __repr__(self):
|
|
level_str = "Cantrip" if self.level == 0 else f"Level {self.level}"
|
|
tags = []
|
|
if self.ritual:
|
|
tags.append("Ritual")
|
|
if self.concentration:
|
|
tags.append("Concentration")
|
|
tag_str = f" ({', '.join(tags)})" if tags else ""
|
|
return (f"{self.name} - {level_str} {self.school.name}{tag_str}\n"
|
|
f"=====\n"
|
|
f"Casting Time: {self.casting_time}\n"
|
|
f"Range: {self.range}\n"
|
|
f"Components: {self.components}\n"
|
|
f"Duration: {self.duration}\n\n"
|
|
f"{self.description}")
|
|
|
|
|
|
# ----- Wizard Cantrips -----
|
|
fire_bolt = Spell(
|
|
name="Fire Bolt", level=0, school=SpellSchool.Evocation,
|
|
casting_time="1 action", range_="120 feet", components="V, S",
|
|
duration="Instantaneous",
|
|
description="You hurl a mote of fire at a creature or object within range. Make a ranged "
|
|
"spell attack against the target. On a hit, the target takes fire damage. "
|
|
"A flammable object hit by this spell ignites if it isn't being worn or carried.",
|
|
classes=["Wizard"], damage="1d10", dmg_type=DamageType.Fire,
|
|
scaling_damage={5: "2d10", 11: "3d10", 17: "4d10"}
|
|
)
|
|
|
|
ray_of_frost = Spell(
|
|
name="Ray of Frost", level=0, school=SpellSchool.Evocation,
|
|
casting_time="1 action", range_="60 feet", components="V, S",
|
|
duration="Instantaneous",
|
|
description="A frigid beam of blue-white light streaks toward a creature within range. "
|
|
"Make a ranged spell attack against the target. On a hit, it takes cold "
|
|
"damage and its speed is reduced by 10 feet until the start of your next turn.",
|
|
classes=["Wizard"], damage="1d8", dmg_type=DamageType.Cold,
|
|
scaling_damage={5: "2d8", 11: "3d8", 17: "4d8"}
|
|
)
|
|
|
|
mage_hand = Spell(
|
|
name="Mage Hand", level=0, school=SpellSchool.Conjuration,
|
|
casting_time="1 action", range_="30 feet", components="V, S",
|
|
duration="1 minute",
|
|
description="A spectral, floating hand appears at a point you choose within range. "
|
|
"The hand lasts for the duration and can manipulate objects, open unlocked "
|
|
"containers, and carry up to 10 pounds.",
|
|
classes=["Wizard"]
|
|
)
|
|
|
|
prestidigitation = Spell(
|
|
name="Prestidigitation", level=0, school=SpellSchool.Transmutation,
|
|
casting_time="1 action", range_="10 feet", components="V, S",
|
|
duration="Up to 1 hour",
|
|
description="This spell is a minor magical trick: create a harmless sensory effect, "
|
|
"light or snuff a small flame, clean or soil an object, chill/warm/flavor "
|
|
"a small amount of nonliving material, or make a small mark or symbol appear.",
|
|
classes=["Wizard"]
|
|
)
|
|
|
|
# ----- Wizard Level 1 Spells -----
|
|
magic_missile = Spell(
|
|
name="Magic Missile", level=1, school=SpellSchool.Evocation,
|
|
casting_time="1 action", range_="120 feet", components="V, S",
|
|
duration="Instantaneous",
|
|
description="You create three glowing darts of magical force. Each dart hits a creature "
|
|
"of your choice that you can see within range, dealing force damage. The "
|
|
"darts all strike simultaneously and automatically hit - no attack roll needed.",
|
|
classes=["Wizard"], damage="3d4+3", dmg_type=DamageType.Force,
|
|
scaling_damage={"per_slot_level": "1d4+1"}
|
|
)
|
|
|
|
shield = Spell(
|
|
name="Shield", level=1, school=SpellSchool.Abjuration,
|
|
casting_time="1 reaction", range_="Self", components="V, S",
|
|
duration="1 round",
|
|
description="An invisible barrier of magical force appears and protects you. Until the "
|
|
"start of your next turn, you have a +5 bonus to AC and you take no damage "
|
|
"from Magic Missile.",
|
|
classes=["Wizard"]
|
|
)
|
|
|
|
burning_hands = Spell(
|
|
name="Burning Hands", level=1, school=SpellSchool.Evocation,
|
|
casting_time="1 action", range_="Self (15-foot cone)", components="V, S",
|
|
duration="Instantaneous",
|
|
description="A thin sheet of flames shoots forth from your outstretched fingertips. Each "
|
|
"creature in a 15-foot cone must make a Dexterity saving throw. A creature "
|
|
"takes fire damage on a failed save, or half as much on a successful one.",
|
|
classes=["Wizard"], damage="3d6", dmg_type=DamageType.Fire, save=SaveType.Dexterity,
|
|
scaling_damage={"per_slot_level": "1d6"}
|
|
)
|
|
|
|
detect_magic = Spell(
|
|
name="Detect Magic", level=1, school=SpellSchool.Divination,
|
|
casting_time="1 action", range_="Self", components="V, S",
|
|
duration="Concentration, up to 10 minutes",
|
|
description="For the duration, you sense the presence of magic within 30 feet of you. "
|
|
"If you sense magic in this way, you can use your action to see a faint "
|
|
"aura around any visible creature or object that bears magic.",
|
|
classes=["Wizard", "Cleric"], ritual=True, concentration=True
|
|
)
|
|
|
|
# ----- Cleric Cantrips -----
|
|
sacred_flame = Spell(
|
|
name="Sacred Flame", level=0, school=SpellSchool.Evocation,
|
|
casting_time="1 action", range_="60 feet", components="V, S",
|
|
duration="Instantaneous",
|
|
description="Flame-like radiance descends on a creature that you can see within range. "
|
|
"The target must succeed on a Dexterity saving throw or take radiant damage. "
|
|
"The target gains no benefit from cover for this saving throw.",
|
|
classes=["Cleric"], damage="1d8", dmg_type=DamageType.Radiant, save=SaveType.Dexterity,
|
|
scaling_damage={5: "2d8", 11: "3d8", 17: "4d8"}
|
|
)
|
|
|
|
guidance = Spell(
|
|
name="Guidance", level=0, school=SpellSchool.Divination,
|
|
casting_time="1 action", range_="Touch", components="V, S",
|
|
duration="Concentration, up to 1 minute",
|
|
description="You touch one willing creature. Once before the spell ends, the target "
|
|
"can roll a d4 and add the number rolled to one ability check of its choice.",
|
|
classes=["Cleric"], concentration=True
|
|
)
|
|
|
|
spare_the_dying = Spell(
|
|
name="Spare the Dying", level=0, school=SpellSchool.Necromancy,
|
|
casting_time="1 action", range_="Touch", components="V, S",
|
|
duration="Instantaneous",
|
|
description="You touch a living creature that has 0 hit points. The creature becomes "
|
|
"stable. This spell has no effect on undead or constructs.",
|
|
classes=["Cleric"]
|
|
)
|
|
|
|
# ----- Cleric Level 1 Spells -----
|
|
cure_wounds = Spell(
|
|
name="Cure Wounds", level=1, school=SpellSchool.Evocation,
|
|
casting_time="1 action", range_="Touch", components="V, S",
|
|
duration="Instantaneous",
|
|
description="A creature you touch regains a number of hit points.",
|
|
classes=["Cleric"], heal="1d8", scaling_damage={"per_slot_level": "1d8"}
|
|
)
|
|
|
|
bless = Spell(
|
|
name="Bless", level=1, school=SpellSchool.Enchantment,
|
|
casting_time="1 action", range_="30 feet", components="V, S, M",
|
|
duration="Concentration, up to 1 minute",
|
|
description="You bless up to three creatures of your choice within range. Whenever a "
|
|
"target makes an attack roll or a saving throw before the spell ends, the "
|
|
"target can add 1d4 to the attack roll or saving throw.",
|
|
classes=["Cleric"], concentration=True
|
|
)
|
|
|
|
guiding_bolt = Spell(
|
|
name="Guiding Bolt", level=1, school=SpellSchool.Evocation,
|
|
casting_time="1 action", range_="120 feet", components="V, S",
|
|
duration="1 round",
|
|
description="A flash of light streaks toward a creature of your choice within range. "
|
|
"Make a ranged spell attack against the target. On a hit, the target takes "
|
|
"radiant damage, and the next attack roll made against this target before "
|
|
"the end of your next turn has advantage.",
|
|
classes=["Cleric"], damage="4d6", dmg_type=DamageType.Radiant,
|
|
scaling_damage={"per_slot_level": "1d6"}
|
|
)
|
|
|
|
healing_word = Spell(
|
|
name="Healing Word", level=1, school=SpellSchool.Evocation,
|
|
casting_time="1 bonus action", range_="60 feet", components="V",
|
|
duration="Instantaneous",
|
|
description="A creature of your choice that you can see within range regains hit points.",
|
|
classes=["Cleric"], heal="1d4", scaling_damage={"per_slot_level": "1d4"}
|
|
)
|
|
|
|
|
|
# Master lookup: {class_name: {spell_level: [Spell, ...]}}
|
|
SPELL_LISTS = {
|
|
"Wizard": {
|
|
0: [fire_bolt, ray_of_frost, mage_hand, prestidigitation],
|
|
1: [magic_missile, shield, burning_hands, detect_magic],
|
|
},
|
|
"Cleric": {
|
|
0: [sacred_flame, guidance, spare_the_dying],
|
|
1: [cure_wounds, bless, guiding_bolt, healing_word, detect_magic],
|
|
},
|
|
}
|
|
|
|
|
|
def get_spell_by_name(name):
|
|
"""Looks up a Spell object across all class lists by (case-insensitive) name."""
|
|
name = name.lower().strip()
|
|
for class_spells in SPELL_LISTS.values():
|
|
for level_spells in class_spells.values():
|
|
for spell in level_spells:
|
|
if spell.name.lower() == name:
|
|
return spell
|
|
return None
|