mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 13:15:53 +02:00
Added more flexible way of specifying random tables in the GM sheet.
This commit is contained in:
+81
-80
@@ -26,6 +26,87 @@ class Content(ABC):
|
||||
dungeonsheets_version = __version__
|
||||
name = "Generic content"
|
||||
|
||||
@staticmethod
|
||||
def _resolve_mechanic(mechanic, SuperClass, warning_message=None):
|
||||
"""Take a raw entry in a character sheet and turn it into a usable object.
|
||||
|
||||
Eg: spells can be defined in many ways. This function accepts all
|
||||
of those options and returns an actual *Spell* class that can be
|
||||
used by a character::
|
||||
|
||||
>>> _resolve_mechanic("mage_hand", SuperClass=spells.Spell)
|
||||
|
||||
>>> _resolve_mechanic("mage_hand", SuperClass=None)
|
||||
|
||||
>>> from dungeonsheets import spells
|
||||
>>> class MySpell(spells.Spell): pass
|
||||
>>> _resolve_mechanic(MySpell, SuperClass=spells.Spell)
|
||||
|
||||
>>> _resolve_mechanic("hocus pocus", SuperClass=spells.Spell)
|
||||
|
||||
The acceptable entries for *mechanic*, in priority order, are:
|
||||
1. A subclass of *SuperClass*
|
||||
2. A string with the name of defined content
|
||||
3. The name of an unknown spell (creates generic object using *factory*)
|
||||
|
||||
*SuperClass* can be ``None`` to match any class, however this will
|
||||
raise an exception if more than one content type has this
|
||||
name. For example, "shield" can refer to both the armor or the
|
||||
spell, so ``_resolve_mechanic("shield")`` will raise an exception.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
mechanic : str, type
|
||||
The thing to be resolved, either a string with the name of the
|
||||
mechanic, or a subclass of *ParentClass* describing the
|
||||
mechanic.
|
||||
SuperClass : type
|
||||
Class to determine whether *mechanic* should just be allowed
|
||||
through as is.
|
||||
error_message : str, optional
|
||||
A string whose ``str.format()`` method (receiving one positional
|
||||
argument *mechanic*) will be used for displaying a warning when an
|
||||
unknown mechanic is resolved. If omitted, no warning will be
|
||||
displayed.
|
||||
|
||||
Returns
|
||||
=======
|
||||
Mechanic
|
||||
A class representing the resolved game mechanic. This will
|
||||
likely be a subclass of *SuperClass* if the other parameters are
|
||||
well behaved, but this is not enforced.
|
||||
|
||||
"""
|
||||
is_already_resolved = isinstance(mechanic, type) and issubclass(
|
||||
mechanic, SuperClass
|
||||
)
|
||||
if is_already_resolved:
|
||||
Mechanic = mechanic
|
||||
elif SuperClass is not None and isinstance(mechanic, SuperClass):
|
||||
# Has been instantiated for some reason
|
||||
Mechanic = type(Mechanic)
|
||||
else:
|
||||
try:
|
||||
# Retrieve pre-defined mechanic
|
||||
valid_classes = [SuperClass] if SuperClass is not None else []
|
||||
Mechanic = find_content(mechanic, valid_classes=valid_classes)
|
||||
except ValueError:
|
||||
# No pre-defined mechanic available
|
||||
if warning_message is not None:
|
||||
# Emit the warning
|
||||
msg = warning_message.format(mechanic)
|
||||
warnings.warn(msg)
|
||||
else:
|
||||
# Create a generic message so we can make a docstring later.
|
||||
msg = f'Mechanic "{mechanic}" not defined. Please add it.'
|
||||
# Create generic mechanic from the factory
|
||||
class_name = "".join([s.title() for s in mechanic.split("_")])
|
||||
mechanic_name = mechanic.replace("_", " ").title()
|
||||
attrs = {"name": mechanic_name, "__doc__": msg, "source": "Unknown"}
|
||||
Mechanic = type(class_name, (SuperClass,), attrs)
|
||||
return Mechanic
|
||||
|
||||
|
||||
|
||||
class Creature(Content):
|
||||
"""A thing with stats. Use Monster or Character, not this class
|
||||
@@ -119,83 +200,3 @@ class Creature(Content):
|
||||
self.medicine, self.nature, self.perception,
|
||||
self.performance, self.persuasion, self.religion,
|
||||
self.sleight_of_hand, self.stealth, self.survival]
|
||||
|
||||
@staticmethod
|
||||
def _resolve_mechanic(mechanic, SuperClass, warning_message=None):
|
||||
"""Take a raw entry in a character sheet and turn it into a usable object.
|
||||
|
||||
Eg: spells can be defined in many ways. This function accepts all
|
||||
of those options and returns an actual *Spell* class that can be
|
||||
used by a character::
|
||||
|
||||
>>> _resolve_mechanic("mage_hand", SuperClass=spells.Spell)
|
||||
|
||||
>>> _resolve_mechanic("mage_hand", SuperClass=None)
|
||||
|
||||
>>> from dungeonsheets import spells
|
||||
>>> class MySpell(spells.Spell): pass
|
||||
>>> _resolve_mechanic(MySpell, SuperClass=spells.Spell)
|
||||
|
||||
>>> _resolve_mechanic("hocus pocus", SuperClass=spells.Spell)
|
||||
|
||||
The acceptable entries for *mechanic*, in priority order, are:
|
||||
1. A subclass of *SuperClass*
|
||||
2. A string with the name of defined content
|
||||
3. The name of an unknown spell (creates generic object using *factory*)
|
||||
|
||||
*SuperClass* can be ``None`` to match any class, however this will
|
||||
raise an exception if more than one content type has this
|
||||
name. For example, "shield" can refer to both the armor or the
|
||||
spell, so ``_resolve_mechanic("shield")`` will raise an exception.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
mechanic : str, type
|
||||
The thing to be resolved, either a string with the name of the
|
||||
mechanic, or a subclass of *ParentClass* describing the
|
||||
mechanic.
|
||||
SuperClass : type
|
||||
Class to determine whether *mechanic* should just be allowed
|
||||
through as is.
|
||||
error_message : str, optional
|
||||
A string whose ``str.format()`` method (receiving one positional
|
||||
argument *mechanic*) will be used for displaying a warning when an
|
||||
unknown mechanic is resolved. If omitted, no warning will be
|
||||
displayed.
|
||||
|
||||
Returns
|
||||
=======
|
||||
Mechanic
|
||||
A class representing the resolved game mechanic. This will
|
||||
likely be a subclass of *SuperClass* if the other parameters are
|
||||
well behaved, but this is not enforced.
|
||||
|
||||
"""
|
||||
is_already_resolved = isinstance(mechanic, type) and issubclass(
|
||||
mechanic, SuperClass
|
||||
)
|
||||
if is_already_resolved:
|
||||
Mechanic = mechanic
|
||||
elif SuperClass is not None and isinstance(mechanic, SuperClass):
|
||||
# Has been instantiated for some reason
|
||||
Mechanic = type(Mechanic)
|
||||
else:
|
||||
try:
|
||||
# Retrieve pre-defined mechanic
|
||||
valid_classes = [SuperClass] if SuperClass is not None else []
|
||||
Mechanic = find_content(mechanic, valid_classes=valid_classes)
|
||||
except ValueError:
|
||||
# No pre-defined mechanic available
|
||||
if warning_message is not None:
|
||||
# Emit the warning
|
||||
msg = warning_message.format(mechanic)
|
||||
warnings.warn(msg)
|
||||
else:
|
||||
# Create a generic message so we can make a docstring later.
|
||||
msg = f'Mechanic "{mechanic}" not defined. Please add it.'
|
||||
# Create generic mechanic from the factory
|
||||
class_name = "".join([s.title() for s in mechanic.split("_")])
|
||||
mechanic_name = mechanic.replace("_", " ").title()
|
||||
attrs = {"name": mechanic_name, "__doc__": msg, "source": "Unknown"}
|
||||
Mechanic = type(class_name, (SuperClass,), attrs)
|
||||
return Mechanic
|
||||
|
||||
@@ -21,7 +21,19 @@
|
||||
\usepackage[dvipsnames]{color}
|
||||
\setlength{\zerosep}{-1em}
|
||||
[% endif %]
|
||||
\definecolor{mygrey}{gray}{0.7}
|
||||
\definecolor{mygrey}{gray}{0.7}
|
||||
|
||||
%% Unicode definitions for superscripts/subscripts
|
||||
\DeclareUnicodeCharacter{00B9}{\textsuperscript{1}}
|
||||
\DeclareUnicodeCharacter{00B2}{\textsuperscript{2}}
|
||||
\DeclareUnicodeCharacter{00B3}{\textsuperscript{3}}
|
||||
\DeclareUnicodeCharacter{2074}{\textsuperscript{4}}
|
||||
\DeclareUnicodeCharacter{2075}{\textsuperscript{5}}
|
||||
\DeclareUnicodeCharacter{2076}{\textsuperscript{6}}
|
||||
\DeclareUnicodeCharacter{2077}{\textsuperscript{7}}
|
||||
\DeclareUnicodeCharacter{2078}{\textsuperscript{8}}
|
||||
\DeclareUnicodeCharacter{2079}{\textsuperscript{9}}
|
||||
\DeclareUnicodeCharacter{2070}{\textsuperscript{0}}
|
||||
|
||||
%%% Fallback definitions for Docutils-specific commands
|
||||
[% raw %]
|
||||
@@ -39,6 +51,18 @@
|
||||
\end{center}
|
||||
\fi
|
||||
}
|
||||
\newenvironment{DUlineblock}[1]{%
|
||||
\list{}{\setlength{\partopsep}{\parskip}
|
||||
\addtolength{\partopsep}{\baselineskip}
|
||||
\setlength{\topsep}{0pt}
|
||||
\setlength{\itemsep}{0.15\baselineskip}
|
||||
\setlength{\parsep}{0pt}
|
||||
\setlength{\leftmargin}{#1}}
|
||||
\raggedright
|
||||
}
|
||||
|
||||
% titlereference standard role
|
||||
\providecommand*{\DUroletitlereference}[1]{\textsl{#1}}
|
||||
|
||||
% title for topics, admonitions, unsupported section levels, and sidebar
|
||||
\providecommand*{\DUtitle}[2][class-arg]{%
|
||||
|
||||
@@ -1,277 +1,10 @@
|
||||
\pdfbookmark[0]{Random Tables}{Random Tables}
|
||||
\section*{Random Tables}
|
||||
|
||||
[% if conjure_animals %]
|
||||
[% for table in tables %]
|
||||
\pdfbookmark[0]{[[ table.name ]]}{Random Table - [[ table.name ]]}
|
||||
\subsection*{[[ table.name ]]}
|
||||
|
||||
%% https://the-azure-triskele.obsidianportal.com/wikis/conjure-animals-table
|
||||
\pdfbookmark[1]{Conjure Animals}{Random Tables - Conjure Animals}
|
||||
\subsection*{Conjure Animals}
|
||||
[[ table.__doc__ | rst_to_latex(format_dice=False, use_dnd_decorations=use_dnd_decorations) ]]
|
||||
|
||||
%% Which category of beasts to summon
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d4 & Number of Beasts \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d4 & Number of Beasts \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1 & One beast of challenge rating 2 \\
|
||||
2 & Two beasts of challenge rating 1 \\
|
||||
3 & Four beasts of challenge rating 1/2 \\
|
||||
4 & Eight beasts of challenge rating 1/4 or lower \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
|
||||
%% CR2 Beasts
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d20 & CR2 Beasts \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d20 & Challenge Rating 2 Beasts \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1-2 & Allosaurus \\
|
||||
3-4 & Giant Boar \\
|
||||
5-6 & Giant Constrictor Snake \\
|
||||
7-8 & Giant Elk \\
|
||||
9-10 & Hunter Shark \\
|
||||
11 & Plesiosaurus \\
|
||||
12-13 & Polar Bear \\
|
||||
14-15 & Rhinoceros \\
|
||||
16-17 & Saber-toothed Tiger \\
|
||||
18-19 & Swarm of Poisonous Snakes \\
|
||||
20 & Roll on CR 1 Beast Table \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
|
||||
%% CR1 Beasts
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d12 & Challenge Rating 1 Beasts \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d12 & Challenge Rating 1 Beasts \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1 & Brown Bear \\
|
||||
2 & Dire Wolf \\
|
||||
3 & Fire Snake \\
|
||||
4 & Giant Eagle \\
|
||||
5 & Giant Hyena \\
|
||||
6 & Giant Octopus \\
|
||||
7 & Giant Spider \\
|
||||
8 & Giant Toad \\
|
||||
9 & Giant Vulture \\
|
||||
10 & Lion \\
|
||||
11 & Tiger \\
|
||||
12 & Roll on CR 1/2 Beast Table \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
%% CR1/2 Beasts
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d20 & Challenge Rating $\frac{1}{2}$ Beasts \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d20 & Challenge Rating $\frac{1}{2}$ Beasts \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1-2 & Ape \\
|
||||
3-4 & Black Bear \\
|
||||
5-6 & Crocodile \\
|
||||
7-8 & Giant Goat \\
|
||||
9-10 & Giant Sea Horse \\
|
||||
11-12 & Giant Wasp \\
|
||||
13-14 & Reef Shark \\
|
||||
15-16 & Swarm of Insects (below) \\
|
||||
17-18 & Warhorse \\
|
||||
19 & Worg \\
|
||||
20 & Roll on Lesser Beast Menu Table \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
|
||||
%% Swarm of insects (mostly for flavor)
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d6 & Swarm of Insects \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d6 & Swarm of Insects \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1 & Ant \\
|
||||
2 & Beatles \\
|
||||
3 & Centipedes \\
|
||||
4 & Locusts \\
|
||||
5 & Spiders \\
|
||||
6 & Wasps \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
|
||||
%% Challenge Rating 1/4 and Lesser Beasts
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d6 & CR $\frac{1}{4}$ and Lesser Beast Menu \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d6 & CR $\frac{1}{4}$ and Lesser Beast Menu \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1-2 & Menu A \\
|
||||
3-4 & Menu B \\
|
||||
5-6 & Menu C \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
|
||||
%% CR1/4 and Lesser Beasts
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d20 & Lesser Beast Menu A \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d12 & Lesser Beast Menu A \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1 & Axe Beak \\
|
||||
2 & Baboon \\
|
||||
3 & Badger \\
|
||||
4 & Bat \\
|
||||
5 & Blood Hawk \\
|
||||
6 & Boar \\
|
||||
7 & Camel \\
|
||||
8 & Cat \\
|
||||
9 & Chicken* \\
|
||||
10 & Constrictor Snake \\
|
||||
11 & Crab \\
|
||||
12 & Deer \\
|
||||
13 & Draft Horse \\
|
||||
14 & Eagle \\
|
||||
15 & Elk \\
|
||||
16 & Flying Snake \\
|
||||
17 & Frog \\
|
||||
18 & Giant Badger \\
|
||||
19 & Giant Bat \\
|
||||
20 & Giant Centipede \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
\begin{description}
|
||||
\item [*Chicken:] Raven stats with Advantage on checks to wake
|
||||
up targets instead of mimicry
|
||||
\end{description}
|
||||
%% CR1/4 and Lesser Beasts
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d20 & Lesser Beast Menu B \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d12 & Lesser Beast Menu B \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1 & Giant Crab \\
|
||||
2 & Giant Fire Beetle \\
|
||||
3 & Giant Frog \\
|
||||
4 & Giant Lizard \\
|
||||
5 & Giant Owl \\
|
||||
6 & Giant Poisonous Snake \\
|
||||
7 & Giant Rat \\
|
||||
8 & Giant Weasel \\
|
||||
9 & Giant Wolf Spider \\
|
||||
10 & Goat \\
|
||||
11 & Hawk \\
|
||||
12 & Hyena \\
|
||||
13 & Jackal \\
|
||||
14 & Lemur* \\
|
||||
15 & Lizard \\
|
||||
16 & Mastiff \\
|
||||
17 & Mule \\
|
||||
18 & Newt** \\
|
||||
19 & Octopus \\
|
||||
20 & Octopus, Cascadian Tree*** \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
\begin{description}
|
||||
\item [*Lemur] Weasel stats with a common Climb speed instead of a
|
||||
bite attack
|
||||
\item [**Newt:] Lizard stats with Amphibious instead of a bite
|
||||
attack
|
||||
\item [***Octopus, Cascadian Tree:] Octopus stats with Amphibious
|
||||
and a 10 ft land speed instead of camouflage
|
||||
\end{description}
|
||||
|
||||
%% CR1/4 and Lesser Beasts
|
||||
[% if use_dnd_decorations %]
|
||||
\begin{DndTable}{c l}
|
||||
1d20 & Lesser Beast Menu C \\
|
||||
[% else %]
|
||||
\begin{tabular}{c | l}
|
||||
1d12 & Lesser Beast Menu C \\
|
||||
\hline\hline
|
||||
[% endif %]
|
||||
1 & Owl \\
|
||||
2 & Panther \\
|
||||
3 & Poisonous Snake \\
|
||||
4 & Pony \\
|
||||
5 & Pteranodon \\
|
||||
6 & Quipper \\
|
||||
7 & Rat \\
|
||||
8 & Raven \\
|
||||
9 & Riding Horse \\
|
||||
10 & Scorpion \\
|
||||
11 & Sea Horse \\
|
||||
12 & Shocker Lizard* \\
|
||||
13 & Spider \\
|
||||
14 & Swarm of Bats \\
|
||||
15 & Swarm of Rats \\
|
||||
16 & Swarm of Ravens \\
|
||||
17 & Turtle** \\
|
||||
18 & Vulture \\
|
||||
19 & Weasel \\
|
||||
20 & Wolf \\
|
||||
[% if use_dnd_decorations %]
|
||||
\end{DndTable}
|
||||
[% else %]
|
||||
\end{tabular}
|
||||
[% endif %]
|
||||
|
||||
\begin{description}
|
||||
\item [*Shocker Lizard] Lizard stats with Static Electricity ranged
|
||||
attack of 1d6 Electricity damage Close/Medium.
|
||||
\item [**Turtle] Lizard stats with 14 natural armor and no climb
|
||||
speed.
|
||||
\end{description}
|
||||
|
||||
[% endif %]
|
||||
[% endfor %]
|
||||
|
||||
+12
-3
@@ -167,7 +167,7 @@ def latex_parts(
|
||||
return parts
|
||||
|
||||
|
||||
def rst_to_latex(rst, top_heading_level=0):
|
||||
def rst_to_latex(rst, top_heading_level: int=0, format_dice: bool = True, use_dnd_decorations=False):
|
||||
"""Basic markup of reST to LaTeX code.
|
||||
|
||||
The translation between reST headings and LaTeX headings is
|
||||
@@ -184,7 +184,10 @@ def rst_to_latex(rst, top_heading_level=0):
|
||||
top_heading_level : optional
|
||||
The highest level heading that will be added to the LaTeX as
|
||||
described above.
|
||||
|
||||
format_dice
|
||||
If true, dice strings (e.g. "1d4") will be formatted in
|
||||
monospace font.
|
||||
|
||||
Returns
|
||||
=======
|
||||
tex : str
|
||||
@@ -196,7 +199,13 @@ def rst_to_latex(rst, top_heading_level=0):
|
||||
tex = ""
|
||||
else:
|
||||
# Mark hit dice in monospace font
|
||||
rst = dice_re.sub(r"``\1``", rst)
|
||||
if format_dice:
|
||||
rst = dice_re.sub(r"``\1``", rst)
|
||||
tex_parts = latex_parts(rst)
|
||||
tex = tex_parts["body"]
|
||||
# Apply fancy D&D decorations
|
||||
if use_dnd_decorations:
|
||||
tex = re.sub(r"p{[0-9.]+\\DUtablewidth}", "l ", tex, flags=re.M)
|
||||
tex = tex.replace(r"\begin{supertabular}[c]", r"\begin{DndTable}")
|
||||
tex = tex.replace(r"\end{supertabular}", r"\end{DndTable}")
|
||||
return tex
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Program to take character definitions and build a PDF of the
|
||||
character sheet."""
|
||||
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
@@ -19,8 +22,8 @@ from dungeonsheets import (
|
||||
epub,
|
||||
monsters,
|
||||
forms,
|
||||
random_tables,
|
||||
)
|
||||
from dungeonsheets.forms import mod_str
|
||||
from dungeonsheets.content_registry import find_content
|
||||
from dungeonsheets.fill_pdf_template import (
|
||||
create_character_pdf_template,
|
||||
@@ -30,8 +33,6 @@ from dungeonsheets.fill_pdf_template import (
|
||||
from dungeonsheets.character import Character
|
||||
from dungeonsheets.content import Creature
|
||||
|
||||
"""Program to take character definitions and build a PDF of the
|
||||
character sheet."""
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,6 +71,7 @@ class CharacterRenderer():
|
||||
return template.render(character=character,
|
||||
use_dnd_decorations=use_dnd_decorations, ordinals=ORDINALS)
|
||||
|
||||
|
||||
create_character_sheet_content = CharacterRenderer("character_sheet_template.{suffix}")
|
||||
create_subclasses_content = CharacterRenderer("subclasses_template.{suffix}")
|
||||
create_features_content = CharacterRenderer("features_template.{suffix}")
|
||||
@@ -105,13 +107,15 @@ def create_party_summary_content(
|
||||
|
||||
|
||||
def create_random_tables_content(
|
||||
conjure_animals: bool,
|
||||
tables: Sequence[random_tables.RandomTable],
|
||||
suffix: str,
|
||||
use_dnd_decorations: bool = False,
|
||||
) -> str:
|
||||
template = jinja_env.get_template(f"random_tables_template.{suffix}")
|
||||
return template.render(
|
||||
conjure_animals=conjure_animals, use_dnd_decorations=use_dnd_decorations
|
||||
conjure_animals=True,
|
||||
tables=tables,
|
||||
use_dnd_decorations=use_dnd_decorations,
|
||||
)
|
||||
|
||||
|
||||
@@ -268,12 +272,13 @@ def make_gm_sheet(
|
||||
)
|
||||
)
|
||||
# Add the random tables
|
||||
random_tables = [
|
||||
s.replace(" ", "_").lower() for s in gm_props.pop("random_tables", [])
|
||||
tables = [
|
||||
find_content(s, valid_classes=[random_tables.RandomTable])
|
||||
for s in gm_props.pop("random_tables", [])
|
||||
]
|
||||
content.append(
|
||||
create_random_tables_content(
|
||||
conjure_animals=("conjure_animals" in random_tables),
|
||||
tables=tables,
|
||||
suffix=content_suffix,
|
||||
use_dnd_decorations=fancy_decorations,
|
||||
)
|
||||
|
||||
@@ -59,13 +59,13 @@ def challenge_rating_to_xp(cr):
|
||||
|
||||
class SpellFactory(ABCMeta):
|
||||
"""Meta class to resolve spell strings into the ``spells.Spell``.
|
||||
|
||||
|
||||
For classes using this metaclass, the *spell* attribute, if
|
||||
present, should be a list of spells that the entity knows. For
|
||||
each entry on that list, anything that is not already a spell
|
||||
class (so probably a string) will be resolved into the
|
||||
corresponding ``spells.Spell`` class.
|
||||
|
||||
|
||||
"""
|
||||
def __init__(self, name, bases, attrs):
|
||||
for idx, spell in enumerate(self.spells):
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
from abc import ABCMeta
|
||||
from typing import Sequence
|
||||
|
||||
from dungeonsheets.content import Content
|
||||
from dungeonsheets.content_registry import default_content_registry
|
||||
|
||||
|
||||
default_content_registry.add_module(__name__)
|
||||
|
||||
|
||||
class SubtableFactory(ABCMeta):
|
||||
"""Meta class to append subtables to the docstring of a RandomTable..
|
||||
|
||||
For classes using this metaclass, the *subtables* attribute, if
|
||||
present, should be a list of subtables that are to be
|
||||
included. For each entry on that list, it will first be resolved
|
||||
into a RandomTable class, if appropriate, then its docstring will
|
||||
be added to the docstring of the calling class.
|
||||
|
||||
"""
|
||||
def __init__(self, name, bases, attrs):
|
||||
# Resolve subtables to RandomTable classes
|
||||
for idx, subtable in enumerate(self.subtables):
|
||||
TheTable = self._resolve_mechanic(subtable, SuperClass=RandomTable)
|
||||
self.subtables[idx] = TheTable
|
||||
# Append docstrings for subtables
|
||||
docstring = self.__doc__ if self.__doc__ is not None else ""
|
||||
for table in self.subtables:
|
||||
docstring += f"\n\n**{table.name}**\n\n{table.__doc__}\n"
|
||||
self.__doc__ = docstring
|
||||
|
||||
|
||||
class RandomTable(Content, metaclass=SubtableFactory):
|
||||
"""A generic table for rolling treasure, monsters, etc.
|
||||
|
||||
Additional tables can be included by using the *subtables*
|
||||
attribute. A use case for this is to create a table for rolling
|
||||
random treasure, which may include subtables for gems, art, magic
|
||||
items, etc. By including these as subtables, each subtable could
|
||||
be included by itself if the verbosity of the full *Treasure*
|
||||
table is not needed.
|
||||
|
||||
Attributes
|
||||
==========
|
||||
subtables
|
||||
A sequence of other random tables that will be included in this
|
||||
table.
|
||||
|
||||
"""
|
||||
name = "Generic Random Table"
|
||||
subtables: Sequence = []
|
||||
|
||||
|
||||
class ConjureAnimals(RandomTable):
|
||||
"""
|
||||
+-----+-----------------------------------------------+
|
||||
| 1d4 | Number of Beasts |
|
||||
+=====+===============================================+
|
||||
| 1 | One beast of challenge rating 2 |
|
||||
+-----+-----------------------------------------------+
|
||||
| 2 | Two beasts of challenge rating 1 |
|
||||
+-----+-----------------------------------------------+
|
||||
| 3 | Four beasts of challenge rating 1/2 |
|
||||
+-----+-----------------------------------------------+
|
||||
| 4 | Eight beasts of challenge rating 1/4 or lower |
|
||||
+-----+-----------------------------------------------+
|
||||
|
||||
+-------+---------------------------+
|
||||
| 1d20 | CR2 Beasts |
|
||||
+=======+===========================+
|
||||
| 1-2 | Allosaurus |
|
||||
+-------+---------------------------+
|
||||
| 3-4 | Giant Boar |
|
||||
+-------+---------------------------+
|
||||
| 5-6 | Giant Constrictor Snake |
|
||||
+-------+---------------------------+
|
||||
| 7-8 | Giant Elk |
|
||||
+-------+---------------------------+
|
||||
| 9-10 | Hunter Shark |
|
||||
+-------+---------------------------+
|
||||
| 11 | Plesiosaurus |
|
||||
+-------+---------------------------+
|
||||
| 12-13 | Polar Bear |
|
||||
+-------+---------------------------+
|
||||
| 14-15 | Rhinoceros |
|
||||
+-------+---------------------------+
|
||||
| 16-17 | Saber-toothed Tiger |
|
||||
+-------+---------------------------+
|
||||
| 18-19 | Swarm of Poisonous Snakes |
|
||||
+-------+---------------------------+
|
||||
| 20 | Roll on CR 1 Beast Table |
|
||||
+-------+---------------------------+
|
||||
|
||||
+------+----------------------------+
|
||||
| 1d12 | Challenge Rating 1 Beasts |
|
||||
+======+============================+
|
||||
| 1 | Brown Bear |
|
||||
+------+----------------------------+
|
||||
| 2 | Dire Wolf |
|
||||
+------+----------------------------+
|
||||
| 3 | Fire Snake |
|
||||
+------+----------------------------+
|
||||
| 4 | Giant Eagle |
|
||||
+------+----------------------------+
|
||||
| 5 | Giant Hyena |
|
||||
+------+----------------------------+
|
||||
| 6 | Giant Octopus |
|
||||
+------+----------------------------+
|
||||
| 7 | Giant Spider |
|
||||
+------+----------------------------+
|
||||
| 8 | Giant Toad |
|
||||
+------+----------------------------+
|
||||
| 9 | Giant Vulture |
|
||||
+------+----------------------------+
|
||||
| 10 | Lion |
|
||||
+------+----------------------------+
|
||||
| 11 | Tiger |
|
||||
+------+----------------------------+
|
||||
| 12 | Roll on CR 1/2 Beast Table |
|
||||
+------+----------------------------+
|
||||
|
||||
+-------+---------------------------------+
|
||||
| 1d20 | Challenge Rating 1/2 Beasts |
|
||||
+=======+=================================+
|
||||
| 1-2 | Ape |
|
||||
+-------+---------------------------------+
|
||||
| 3-4 | Black Bear |
|
||||
+-------+---------------------------------+
|
||||
| 5-6 | Crocodile |
|
||||
+-------+---------------------------------+
|
||||
| 7-8 | Giant Goat |
|
||||
+-------+---------------------------------+
|
||||
| 9-10 | Giant Sea Horse |
|
||||
+-------+---------------------------------+
|
||||
| 11-12 | Giant Wasp |
|
||||
+-------+---------------------------------+
|
||||
| 13-14 | Reef Shark |
|
||||
+-------+---------------------------------+
|
||||
| 15-16 | Swarm of Insects (below) |
|
||||
+-------+---------------------------------+
|
||||
| 17-18 | Warhorse |
|
||||
+-------+---------------------------------+
|
||||
| 19 | Worg |
|
||||
+-------+---------------------------------+
|
||||
| 20 | Roll on Lesser Beast Menu Table |
|
||||
+-------+---------------------------------+
|
||||
|
||||
+-----+------------------+
|
||||
| 1d6 | Swarm of Insects |
|
||||
+=====+==================+
|
||||
| 1 | Ant |
|
||||
+-----+------------------+
|
||||
| 2 | Beatles |
|
||||
+-----+------------------+
|
||||
| 3 | Centipedes |
|
||||
+-----+------------------+
|
||||
| 4 | Locusts |
|
||||
+-----+------------------+
|
||||
| 5 | Spiders |
|
||||
+-----+------------------+
|
||||
| 6 | Wasps |
|
||||
+-----+------------------+
|
||||
|
||||
+-----+------------------------------+
|
||||
| 1d6 | CR 1/4 and Lesser Beast Menu |
|
||||
+=====+==============================+
|
||||
| 1-2 | Menu A |
|
||||
+-----+------------------------------+
|
||||
| 3-4 | Menu B |
|
||||
+-----+------------------------------+
|
||||
| 5-6 | Menu C |
|
||||
+-----+------------------------------+
|
||||
|
||||
+------+---------------------+
|
||||
| 1d20 | Lesser Beast Menu A |
|
||||
+======+=====================+
|
||||
| 1 | Axe Beak |
|
||||
+------+---------------------+
|
||||
| 2 | Baboon |
|
||||
+------+---------------------+
|
||||
| 3 | Badger |
|
||||
+------+---------------------+
|
||||
| 4 | Bat |
|
||||
+------+---------------------+
|
||||
| 5 | Blood Hawk |
|
||||
+------+---------------------+
|
||||
| 6 | Boar |
|
||||
+------+---------------------+
|
||||
| 7 | Camel |
|
||||
+------+---------------------+
|
||||
| 8 | Cat |
|
||||
+------+---------------------+
|
||||
| 9 | Chicken¹ |
|
||||
+------+---------------------+
|
||||
| 10 | Constrictor Snake |
|
||||
+------+---------------------+
|
||||
| 11 | Crab |
|
||||
+------+---------------------+
|
||||
| 12 | Deer |
|
||||
+------+---------------------+
|
||||
| 13 | Draft Horse |
|
||||
+------+---------------------+
|
||||
| 14 | Eagle |
|
||||
+------+---------------------+
|
||||
| 15 | Elk |
|
||||
+------+---------------------+
|
||||
| 16 | Flying Snake |
|
||||
+------+---------------------+
|
||||
| 17 | Frog |
|
||||
+------+---------------------+
|
||||
| 18 | Giant Badger |
|
||||
+------+---------------------+
|
||||
| 19 | Giant Bat |
|
||||
+------+---------------------+
|
||||
| 20 | Giant Centipede |
|
||||
+------+---------------------+
|
||||
|
||||
¹Chicken
|
||||
Raven stats with Advantage on checks to wake up targets instead
|
||||
of mimicry
|
||||
|
||||
+------+--------------------------+
|
||||
| 1d20 | Lesser Beast Menu B |
|
||||
+======+==========================+
|
||||
| 1 | Giant Crab |
|
||||
+------+--------------------------+
|
||||
| 2 | Giant Fire Beetle |
|
||||
+------+--------------------------+
|
||||
| 3 | Giant Frog |
|
||||
+------+--------------------------+
|
||||
| 4 | Giant Lizard |
|
||||
+------+--------------------------+
|
||||
| 5 | Giant Owl |
|
||||
+------+--------------------------+
|
||||
| 6 | Giant Poisonous Snake |
|
||||
+------+--------------------------+
|
||||
| 7 | Giant Rat |
|
||||
+------+--------------------------+
|
||||
| 8 | Giant Weasel |
|
||||
+------+--------------------------+
|
||||
| 9 | Giant Wolf Spider |
|
||||
+------+--------------------------+
|
||||
| 10 | Goat |
|
||||
+------+--------------------------+
|
||||
| 11 | Hawk |
|
||||
+------+--------------------------+
|
||||
| 12 | Hyena |
|
||||
+------+--------------------------+
|
||||
| 13 | Jackal |
|
||||
+------+--------------------------+
|
||||
| 14 | Lemur² |
|
||||
+------+--------------------------+
|
||||
| 15 | Lizard |
|
||||
+------+--------------------------+
|
||||
| 16 | Mastiff |
|
||||
+------+--------------------------+
|
||||
| 17 | Mule |
|
||||
+------+--------------------------+
|
||||
| 18 | Newt³ |
|
||||
+------+--------------------------+
|
||||
| 19 | Octopus |
|
||||
+------+--------------------------+
|
||||
| 20 | Octopus, Cascadian Tree⁴ |
|
||||
+------+--------------------------+
|
||||
|
||||
²Lemur
|
||||
Weasel stats with a common Climb speed instead of a bite attack
|
||||
³Newt
|
||||
Lizard stats with Amphibious instead of a bite attack
|
||||
⁴Octopus, Cascadian Tree:
|
||||
Octopus stats with Amphibious and a 10 ft land speed instead of
|
||||
camouflage
|
||||
|
||||
+------+---------------------+
|
||||
| 1d20 | Lesser Beast Menu C |
|
||||
+======+=====================+
|
||||
| 1 | Owl |
|
||||
+------+---------------------+
|
||||
| 2 | Panther |
|
||||
+------+---------------------+
|
||||
| 3 | Poisonous Snake |
|
||||
+------+---------------------+
|
||||
| 4 | Pony |
|
||||
+------+---------------------+
|
||||
| 5 | Pteranodon |
|
||||
+------+---------------------+
|
||||
| 6 | Quipper |
|
||||
+------+---------------------+
|
||||
| 7 | Rat |
|
||||
+------+---------------------+
|
||||
| 8 | Raven |
|
||||
+------+---------------------+
|
||||
| 9 | Riding Horse |
|
||||
+------+---------------------+
|
||||
| 10 | Scorpion |
|
||||
+------+---------------------+
|
||||
| 11 | Sea Horse |
|
||||
+------+---------------------+
|
||||
| 12 | Shocker Lizard⁵ |
|
||||
+------+---------------------+
|
||||
| 13 | Spider |
|
||||
+------+---------------------+
|
||||
| 14 | Swarm of Bats |
|
||||
+------+---------------------+
|
||||
| 15 | Swarm of Rats |
|
||||
+------+---------------------+
|
||||
| 16 | Swarm of Ravens |
|
||||
+------+---------------------+
|
||||
| 17 | Turtle⁶ |
|
||||
+------+---------------------+
|
||||
| 18 | Vulture |
|
||||
+------+---------------------+
|
||||
| 19 | Weasel |
|
||||
+------+---------------------+
|
||||
| 20 | Wolf |
|
||||
+------+---------------------+
|
||||
|
||||
⁵Shocker Lizard
|
||||
Lizard stats with Static Electricity ranged attack of 1d6
|
||||
Electricity damage Close/Medium.
|
||||
⁶Turtle
|
||||
Lizard stats with 14 natural armor and no climb speed.
|
||||
|
||||
"""
|
||||
# https://the-azure-triskele.obsidianportal.com/wikis/conjure-animals-table
|
||||
name = "Conjure Animals"
|
||||
@@ -103,6 +103,9 @@ class MarkdownTestCase(unittest.TestCase):
|
||||
self.assertNotIn("endfoot", tex)
|
||||
self.assertNotIn("endhead", tex)
|
||||
self.assertNotIn("endfirsthead", tex)
|
||||
# Check that fancy decorations uses the DndTable environment
|
||||
tex = latex.rst_to_latex(table_rst, use_dnd_decorations=True)
|
||||
self.assertIn(r"\begin{DndTable}{l l l }", tex)
|
||||
|
||||
def test_rst_all_spells(self):
|
||||
for spell in spells.all_spells():
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import unittest
|
||||
|
||||
from dungeonsheets import random_tables
|
||||
|
||||
|
||||
class ChildTable(random_tables.RandomTable):
|
||||
"""I'm a table too, but where is everyone else?"""
|
||||
name = "Child Table"
|
||||
|
||||
|
||||
class ParentTable(random_tables.RandomTable):
|
||||
"""Hello, world. I'm a table."""
|
||||
name = "Parent Table"
|
||||
subtables = [ChildTable]
|
||||
|
||||
|
||||
class RandomTableTests(unittest.TestCase):
|
||||
def test_docstring(self):
|
||||
self.assertIn("Hello, world", ParentTable.__doc__)
|
||||
parent_table = ParentTable()
|
||||
self.assertIn("Hello, world", parent_table.__doc__)
|
||||
|
||||
def test_subtables(self):
|
||||
# Check that docstrings are combined
|
||||
# parent_table = ParentTable()
|
||||
self.assertIn("**Child Table**", ParentTable.__doc__)
|
||||
Reference in New Issue
Block a user