Compare commits

...

3 Commits

Author SHA1 Message Date
krugd b7b9e00f19 Tweaked output 2025-05-22 17:33:32 -07:00
krugd 96624631b2 spell_templates working
Merged together cylinders+circles+spheres and squares+cubes for the
purpose of printing templates. Also added additional filtering to remove
spells just mentioning shining light, and ignoring self-targetting
spells.
2025-05-21 16:54:29 -07:00
krugd 632a8c4f1b Exclude emanations from the list 2025-05-20 23:28:48 -07:00
2 changed files with 74 additions and 22 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ class SpellRangeType(enum.StrEnum):
Line = "line" Line = "line"
Cube = "cube" Cube = "cube"
Cone = "cone" Cone = "cone"
EmanatioN = "emanation" Emanation = "emanation"
Radius = "radius" Radius = "radius"
Sphere = "sphere" Sphere = "sphere"
Hemisphere = "hemisphere" Hemisphere = "hemisphere"
+73 -21
View File
@@ -1,14 +1,31 @@
import collections
import pprint import pprint
import re import re
import typing
import dnd5etools.db.spells import dnd5etools.db.spells
import dnd5etools.scripts.argparse import dnd5etools.scripts.argparse
area_size_re = re.compile(r"\d+-foot") area_size_re = re.compile(r"\d+-foot", flags=re.IGNORECASE)
sphere_size_re = re.compile(r"(?P<size>\d+)-foot-radius sphere") light_size_re = re.compile(r"(light|light\|\w+\}) in a \d+-foot", flags=re.IGNORECASE)
cylinder_size_re = re.compile(r"(?P<size>\d+)-foot-radius, \d+-foot-high cylinder") circle_size_re = re.compile(
cube_size_re = re.compile(r"(?P<size>\d+)-foot cube") r"(?P<size>\d+)-foot-radius[ -](sphere|circle)", flags=re.IGNORECASE
cone_size_re = re.compile(r"(?P<size>\d+)-foot cone") )
radius_size_re = re.compile(r"(?P<size>\d+)-foot radius", flags=re.IGNORECASE)
circle_diam_size_re = re.compile(
r"(?P<size>\d+)-foot-diameter[ -](sphere|circle)", flags=re.IGNORECASE
)
cylinder_size_re = re.compile(
r"(?P<size>\d+)-foot-radius, \d+-foot[ -](high|tall) cylinder", flags=re.IGNORECASE
)
alt_cylinder_size_re = re.compile(
r"\d+-foot[ -](high|tall), (?P<size>\d+)-foot-radius cylinder", flags=re.IGNORECASE
)
square_size_re = re.compile(r"(?P<size>\d+)-foot[ -](cube|square)", flags=re.IGNORECASE)
cone_size_re = re.compile(r"(?P<size>\d+)-foot cone", flags=re.IGNORECASE)
line_size_re = re.compile(
r"\d+-foot-wide, (?P<size>\d+)-foot[ -]long line", flags=re.IGNORECASE
)
def main(): def main():
@@ -23,37 +40,63 @@ def main():
all_template_spells = [] all_template_spells = []
for code in codes: for code in codes:
all_template_spells += filter(is_template_spell, db.get_spell_list(code).spells) all_template_spells += filter(is_template_spell, db.get_spell_list(code).spells)
# pprint.pprint(all_template_spells) num_matches = 0
spheres = set() circles = collections.defaultdict(list)
cylinders = set() squares = collections.defaultdict(list)
cubes = set() cones = collections.defaultdict(list)
cones = set() lines = collections.defaultdict(list)
for spell in all_template_spells: for spell in all_template_spells:
found = False found = False
for search_re, dest in [ for search_re, dest in [
(sphere_size_re, spheres), (line_size_re, lines),
(cylinder_size_re, cylinders), (circle_size_re, circles),
(cube_size_re, cubes), (cylinder_size_re, circles),
(circle_diam_size_re, circles),
(alt_cylinder_size_re, circles),
(square_size_re, squares),
(cone_size_re, cones), (cone_size_re, cones),
(radius_size_re, circles),
]: ]:
m = search_re.search(spell.description) m = search_re.search(spell.description)
if m is not None: if m is not None:
dest.add(m.group("size")) size = int(m.group("size"))
if search_re not in (
cone_size_re,
square_size_re,
circle_diam_size_re,
line_size_re,
):
size *= 2
dest[size].append(spell.name)
found = True found = True
num_matches += 1
break break
if not found: print(f"{num_matches} matched from {len(all_template_spells)}")
print(spell) print("circles/diameter")
print("spheres", spheres) print_sizes(circles, 2)
print("cylinders", cylinders) print("squares/size")
print("cubes", cubes) print_sizes(squares, 2)
print("cones", cones) print("cones/size")
print_sizes(cones, 2)
print("lines/length")
print_sizes(lines, 2)
def is_template_spell(spell: dnd5etools.db.spells.Spell) -> bool: def is_template_spell(spell: dnd5etools.db.spells.Spell) -> bool:
num_area_sizes = 0
for _ in area_size_re.finditer(spell.description):
num_area_sizes += 1
num_light_sizes = 0
for _ in light_size_re.finditer(spell.description):
num_light_sizes += 1
return ( return (
len(spell.area_type) > 0 len(spell.area_type) > 0
and not "Wall" in spell.name
and has_timed_duration(spell) and has_timed_duration(spell)
and area_size_re.search(spell.description) is not None and spell.range.type != dnd5etools.db.spells.SpellRangeType.Emanation
and spell.range.distance_type != "self"
and num_area_sizes > 0
and num_light_sizes < num_area_sizes
) )
@@ -62,3 +105,12 @@ def has_timed_duration(spell: dnd5etools.db.spells.Spell) -> bool:
if sd.type != dnd5etools.db.spells.DurationType.Instant: if sd.type != dnd5etools.db.spells.DurationType.Instant:
return True return True
return False return False
def print_sizes(sizes_dict: typing.Mapping[int, int], indent: int) -> None:
indent_str = " " * indent
for s in sorted(sizes_dict.items(), key=lambda kv: len(kv[1]), reverse=True):
print(f"{indent_str} {s[0]}ft/{s[0]/5}in => {len(s[1])}")
print(
f"{indent_str}{indent_str}{pprint.pformat(s[1], indent=3*indent, compact=True)}"
)