Added a test to check that all spells are valid rst.

This commit is contained in:
Mark Wolfman
2021-04-14 13:04:46 -05:00
parent 4aa43d2bec
commit c8577187b0
7 changed files with 133 additions and 111 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
from dungeonsheets.spells.spells import Spell, create_spell from dungeonsheets.spells.spells import Spell, create_spell, all_spells
from dungeonsheets.spells.spells_a import * from dungeonsheets.spells.spells_a import *
from dungeonsheets.spells.spells_b import * from dungeonsheets.spells.spells_b import *
from dungeonsheets.spells.spells_c import * from dungeonsheets.spells.spells_c import *
+11
View File
@@ -1,3 +1,14 @@
from dungeonsheets import spells
def all_spells():
"""Generate all the valid spell classes so far defined."""
for spell_name, spell in spells.__dict__.items():
# Check if it's (a) a class and (b) a subclass of ``Spell``
if isinstance(spell, type) and issubclass(spell, Spell):
yield spell
def create_spell(**params): def create_spell(**params):
"""Create a new subclass of ``Spell`` with given default parameters. """Create a new subclass of ``Spell`` with given default parameters.
+9 -9
View File
@@ -734,24 +734,24 @@ class AstralProjection(Spell):
class Augury(Spell): class Augury(Spell):
"""By casting gem-inlaid sticks, rolling dragon bones, laying out ornate cards, or """By casting gem-inlaid sticks, rolling dragon bones, laying out
employing some other divining tool, you receive an omen from an otherworldly ornate cards, or employing some other divining tool, you receive
entity about the results of a specific course of action that you plan to take an omen from an otherworldly entity about the results of a
within the next 30 minutes. The DM chooses from the following possible omens: specific course of action that you plan to take within the next 30
minutes. The DM chooses from the following possible omens:
- Weal, for good results - Weal, for good results
- Woe, for bad results - Woe, for bad results
- Weal and woe, for both good - Weal and woe, for both good and bad results
and bad results
- Nothing, for results that aren't especially good or bad - Nothing, for results that aren't especially good or bad
The spell doesn't take into account any possible circumstances The spell doesn't take into account any possible circumstances
that might change the outcome, such as the casting of additional that might change the outcome, such as the casting of additional
spells or the loss or gain of a companion. If you cast the spell spells or the loss or gain of a companion. If you cast the spell
two or more times before completing your next long rest, there is two or more times before completing your next long rest, there is
a cumulative 25 percent chance for each casting after the first a cumulative 25 percent chance for each casting after the first
that you get a random reading. The DM makes this roll in secret. that you get a random reading. The DM makes this roll in secret.
""" """
name = "Augury" name = "Augury"
level = 2 level = 2
+38 -37
View File
@@ -214,28 +214,30 @@ class ChainLightning(Spell):
class ChaosBolt(Spell): class ChaosBolt(Spell):
"""You hurl an undulating, warbling mass of chaotic energy at one """You hurl an undulating, warbling mass of chaotic energy at one
creature in range. Make a ranged spell attack against the creature in range. Make a ranged spell attack against the
target. On a hit, the target takes 2d8 + 1d6 damage. Choose one of target. On a hit, the target takes ``2d8+1d6`` damage. Choose one
the dSs. The number rolled on that die determines the attacks of the d8s. The number rolled on that die determines the attacks
damage type, as shown below. damage type, as shown below.
d8 / Damage Type == ===========
1 / Acid d8 Damage Type
2 == ===========
/ Cold 1 Acid
3 / Fire 2 Cold
4 / Force 3 Fire
5 / Lightning 4 Force
6 / Poison 5 Lightning
7 / Psychic 6 Poison
8 / Thunder 7 Psychic
8 Thunder
== ===========
If you roll the same number on both d8s, the chaotic energy leaps If you roll the same number on both d8s, the chaotic energy leaps
from the target to a different creature of your choice within 30 from the target to a different creature of your choice within 30
feet of it. Make a new attack roll against the new target, and feet of it. Make a new attack roll against the new target, and
make a new damage roll, which could cause the chaotic energy to make a new damage roll, which could cause the chaotic energy to
leap again. A creature can be targeted only once by each casting leap again. A creature can be targeted only once by each casting
of this spell. of this spell.
**At Higher Levels:** When you cast this spell using a spell slot of **At Higher Levels:** When you cast this spell using a spell slot of
2nd level or higher, each target takes 1d6 extra damage of the 2nd level or higher, each target takes 1d6 extra damage of the
type rolled for each slot level above 1st. type rolled for each slot level above 1st.
@@ -619,22 +621,20 @@ class CommuneWithNature(Spell):
natural underground settings, the radius is limited to 300 natural underground settings, the radius is limited to 300
feet. The spell doesn't function where nature has been replaced by feet. The spell doesn't function where nature has been replaced by
construction, such as in dungeons and towns. construction, such as in dungeons and towns.
You instantly gain knowledge of up to three facts of your choice You instantly gain knowledge of up to three facts of your choice
about any of the following subjects as they relate to the area: about any of the following subjects as they relate to the area:
- terrain and bodies of water - terrain and bodies of water
- prevalent plants, - prevalent plants, minerals, animals, or peoples
minerals, animals, or peoples - powerful celestials, fey, fiends, elementals, or undead
- powerful celestials, fey, fiends, elementals, - influence from other planes of existence
or undead - buildings
- influence from other planes of existence
- buildings
For example, you could determine the location of powerful undead For example, you could determine the location of powerful undead
in the area, the location of major sources of safe drinking water, in the area, the location of major sources of safe drinking water,
and the location of any nearby towns. and the location of any nearby towns.
""" """
name = "Commune With Nature" name = "Commune With Nature"
level = 5 level = 5
@@ -1211,21 +1211,22 @@ class ContinualFlame(Spell):
class ControlFlames(Spell): class ControlFlames(Spell):
"""You choose a nonmagical flame that you can see within range and that fits within """You choose a nonmagical flame that you can see within range and
a 5-foot cube. You affect it in one of the following ways: that fits within a 5-foot cube. You affect it in one of the
following ways:
- You instantaneously expand the flame 5 feet in one direction, - You instantaneously expand the flame 5 feet in one direction,
provided that wood or other fuel is present in the new location. provided that wood or other fuel is present in the new location.
- You instantaneously extinguish the flames within the cube. - You instantaneously extinguish the flames within the cube.
- You double or halve the area of bright light and dim light cast - You double or halve the area of bright light and dim light cast
by the flame, change its color, or both. The change lasts for 1 by the flame, change its color, or both. The change lasts for 1
hour. hour.
- You cause simple shapes - such as the vague form of a creature, - You cause simple shapes - such as the vague form of a creature,
an inanimate object, or a location - to appear within the flames an inanimate object, or a location - to appear within the flames
and animate as you like. The shapes last for 1 hour. If you and animate as you like. The shapes last for 1 hour. If you
cast this spell multiple times, you can have up to three cast this spell multiple times, you can have up to three
non-instantaneous non-instantaneous
""" """
name = "Control Flames" name = "Control Flames"
level = 0 level = 0
+53 -62
View File
@@ -441,25 +441,26 @@ class GuardianOfNature(Spell):
powerful guardian. The transformation lasts until the spell powerful guardian. The transformation lasts until the spell
ends. You choose one of the following forms to assume: Primal ends. You choose one of the following forms to assume: Primal
Beast or Great Tree. Beast or Great Tree.
**Primal Beast.** Bestial fur covers your body, your facial **Primal Beast.** Bestial fur covers your body, your facial
features become feral, and you gain the following benefits: features become feral, and you gain the following benefits:
- Your walking speed increases by 10 feet. - Your walking speed increases by 10 feet.
- You gain darkvision with a range of 120 feet. - You gain darkvision with a range of 120 feet.
- You make Strength-based attack rolls with advantage. - You make Strength-based attack rolls with advantage.
- Your melee weapon attacks deal an extra 1d6 force damage on a - Your melee weapon attacks deal an extra 1d6 force damage on a
hit. hit.
**Great Tree.** Your skin appears barky, leaves sprout from your **Great Tree.** Your skin appears barky, leaves sprout from your
hair, and you gain the following benefits: hair, and you gain the following benefits:
- You gain 10 temporary hit points. - You gain 10 temporary hit points.
- You make Constitution saving throws with advantage. - You make Constitution saving throws with advantage.
- You make Dexterity- and Wisdom-based attack rolls with advantage. - You make Dexterity- and Wisdom-based attack rolls with
advantage.
- While you are on the ground, the ground within 15 feet of you is - While you are on the ground, the ground within 15 feet of you is
difficult terrain for your enemies. difficult terrain for your enemies.
""" """
name = "Guardian Of Nature" name = "Guardian Of Nature"
level = 4 level = 4
@@ -480,57 +481,49 @@ class GuardsAndWards(Spell):
tall, and shaped as you desire. You can ward several stories of a tall, and shaped as you desire. You can ward several stories of a
stronghold by dividing the area among them, as long as you can stronghold by dividing the area among them, as long as you can
walk into each contiguous area while you are casting the spell. walk into each contiguous area while you are casting the spell.
When you cast this spell, you can specify individuals that are When you cast this spell, you can specify individuals that are
unaffected by any or all of the effects that you choose. You can unaffected by any or all of the effects that you choose. You can
also specify a password that, when spoken aloud, makes the speaker also specify a password that, when spoken aloud, makes the speaker
immune to these effects. immune to these effects.
Guards and wards creates the following effects within the warded Guards and wards creates the following effects within the warded
area. area.
Corridors: Fog fills all the warded corridors, making them heavily - **Corridors.** Fog fills all the warded corridors, making them
obscured. In addition, at each intersection or branching passage heavily obscured. In addition, at each intersection or branching
offering a choice of direction, there is a 50 percent chance that passage offering a choice of direction, there is a 50 percent
a creature other than you will believe it is going in the opposite chance that a creature other than you will believe it is going
direction from the one it chooses. in the opposite direction from the one it chooses.
- **Doors.** All doors in the warded area are magically locked, as
Doors: All doors in the warded area are magically locked, as if if sealed by an arcane lock spell. In addition, you can cover up
sealed by an arcane lock spell. In addition, you can cover up to to ten doors with an illusion (equivalent to the illusory object
ten doors with an illusion (equivalent to the illusory object function of the m inor illusion spell) to make them appear as
function of the m inor illusion spell) to make them appear as plain sections of wall.
plain sections of wall. - **Stairs.** Webs fill all stairs in the warded area from top to
bottom, as the web spell. These strands regrow in 10 minutes if
Stairs: Webs fill all stairs in the warded area from top to they are burned or torn away while guards and wards lasts.
bottom, as the web spell. These strands regrow in 10 minutes if - **Other Spell Effect.** You can place your choice of one of the
they are burned or torn away while guards and wards lasts. following magical effects within the warded area of the
stronghold.
Other Spell Effect: You can place your choice of one of the
following magical effects within the warded area of the - Place dancing lights in four corridors. You can designate a
stronghold. simple program that the lights repeat as long as guards and
wards lasts.
- Place dancing lights in four corridors. You can designate a - Place magic mouth in two locations.
simple program that the lights repeat as long as guards and - Place stinking cloud in two locations. The vapors appear in
wards lasts. the places you designate; they return within 10 minutes if
dispersed by wind while guards and wards lasts.
- Place magic mouth in two locations. - Place a constant gust of wind in one corridor or room.
- Place a suggestion in one location. You select an area of up
- Place stinking cloud in two locations. The vapors appear in the to 5 feet square, and any creature that enters or passes
places you designate; they return within 10 minutes if dispersed through the area receives the suggestion mentally.
by wind while guards and wards lasts.
- Place a constant gust of wind in one corridor or room.
- Place a suggestion in one location. You select an area of up to
5 feet square, and any creature that enters or passes through
the area receives the suggestion mentally.
The whole warded area radiates magic. A dispel magic cast on a The whole warded area radiates magic. A dispel magic cast on a
specific effect, if successful, removes only that effect. specific effect, if successful, removes only that effect. You can
create a permanently guarded and warded structure by casting this
You can create a permanently guarded and warded structure by spell there every day for one year.
casting this spell there every day for one year.
""" """
name = "Guards And Wards" name = "Guards And Wards"
level = 6 level = 6
@@ -619,19 +612,17 @@ class GuidingHand(Spell):
class Gust(Spell): class Gust(Spell):
"""You seize the air and compel it to create one of the following """You seize the air and compel it to create one of the following
effects at a point you can see within range: effects at a point you can see within range:
- One Medium or smaller creature that you choose must succeed on a - One Medium or smaller creature that you choose must succeed on a
Strength saving throw or be pushed up to 5 feet away from you. Strength saving throw or be pushed up to 5 feet away from you.
- You create a small blast of air capable of moving one object - You create a small blast of air capable of moving one object
that is neither held nor carried and that weighs no more than 5 that is neither held nor carried and that weighs no more than 5
pounds. The object is pushed up to 10 feet away from you. It pounds. The object is pushed up to 10 feet away from you. It
isn't pushed with enough force to cause damage. isn't pushed with enough force to cause damage.
- You create a harmless sensory affect using air, such as causing - You create a harmless sensory affect using air, such as causing
leaves to rustle, wind to slam shutters shut, or your clothing leaves to rustle, wind to slam shutters shut, or your clothing
to ripple in a breeze. to ripple in a breeze.
""" """
name = "Gust" name = "Gust"
level = 0 level = 0
+7 -1
View File
@@ -3,7 +3,7 @@ import os
from pathlib import Path from pathlib import Path
import subprocess import subprocess
from dungeonsheets import make_sheets, character from dungeonsheets import make_sheets, character, spells
EG_DIR = os.path.abspath(os.path.join(os.path.split(__file__)[0], '../examples/')) EG_DIR = os.path.abspath(os.path.join(os.path.split(__file__)[0], '../examples/'))
CHARFILE = os.path.join(EG_DIR, 'rogue1.py') CHARFILE = os.path.join(EG_DIR, 'rogue1.py')
@@ -136,3 +136,9 @@ class MarkdownTestCase(unittest.TestCase):
self.assertNotIn("endfoot", tex) self.assertNotIn("endfoot", tex)
self.assertNotIn("endhead", tex) self.assertNotIn("endhead", tex)
self.assertNotIn("endfirsthead", tex) self.assertNotIn("endfirsthead", tex)
def test_rst_all_spells(self):
for spell in spells.all_spells():
tex = make_sheets.rst_to_latex(spell.__doc__)
self.assertNotIn("DUadmonition", tex,
f"spell {spell} is not valid reStructured text")
+13 -1
View File
@@ -2,11 +2,23 @@
from unittest import TestCase from unittest import TestCase
from dungeonsheets.spells import create_spell, Spell from dungeonsheets import spells
from dungeonsheets.spells import create_spell, Spell, all_spells
class TestSpells(TestCase): class TestSpells(TestCase):
"""Tests for spells and spell-related activities.""" """Tests for spells and spell-related activities."""
def test_all_spells(self):
# Make sure only spells are returned
for ThisSpell in all_spells():
self.assertTrue(isinstance(ThisSpell, type),
f"``all_spells`` returned {ThisSpell} (not a class)")
self.assertTrue(issubclass(ThisSpell, Spell),
f"``all_spells`` returned {ThisSpell} (not a spell)")
# Pick a couple of known spells to spot-check for
all_the_spells = list(all_spells())
self.assertIn(spells.MagicMissile, all_the_spells)
self.assertIn(spells.Thunderwave, all_the_spells)
def test_create_spell(self): def test_create_spell(self):
NewSpell = create_spell(name="Hello world") NewSpell = create_spell(name="Hello world")