Add Container class with lock/trap detection, corpse drops, and numbered duplicate corpses

This commit is contained in:
KansaiGaijin
2026-07-07 12:13:43 +12:00
parent db4b5cb543
commit 221cb71410
32 changed files with 938 additions and 160 deletions

221
spell_list.py Normal file
View File

@@ -0,0 +1,221 @@
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