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