mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-19 04:33:26 +02:00
Merge branch 'canismarko:master' into master
This commit is contained in:
@@ -55,6 +55,8 @@ the following random tables are available.
|
||||
|
||||
- **"conjure animals"** - A list of options to choose from when a
|
||||
player casts the *Conjure Animals* spell.
|
||||
- **"treasure"** - Tables for rolling treasure dropped by individuals
|
||||
or hoards.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: Example:
|
||||
|
||||
@@ -77,6 +77,7 @@ class Armor:
|
||||
cost = "0 gp"
|
||||
base_armor_class = 10
|
||||
dexterity_mod_max = None
|
||||
dexterity_applied = True
|
||||
strength_required = None
|
||||
stealth_disadvantage = False
|
||||
weight = 0 # In lbs
|
||||
@@ -112,6 +113,7 @@ class MediumArmor(Armor):
|
||||
|
||||
class HeavyArmor(Armor):
|
||||
name = "Heavy Armor"
|
||||
dexterity_applied = False
|
||||
|
||||
|
||||
class PaddedArmor(LightArmor):
|
||||
@@ -182,7 +184,6 @@ class RingMail(HeavyArmor):
|
||||
name = "Ring Mail"
|
||||
cost = "30 gp"
|
||||
base_armor_class = 14
|
||||
dexterity_mod_max = 0
|
||||
stealth_disadvantage = True
|
||||
weight = 40
|
||||
|
||||
@@ -191,7 +192,6 @@ class ChainMail(HeavyArmor):
|
||||
name = "Chain Mail"
|
||||
cost = "75 gp"
|
||||
base_armor_class = 16
|
||||
dexterity_mod_max = 0
|
||||
strength_required = 13
|
||||
stealth_disadvantage = True
|
||||
weight = 55
|
||||
@@ -201,7 +201,6 @@ class SplintArmor(HeavyArmor):
|
||||
name = "Splint Armor"
|
||||
cost = "200 gp"
|
||||
base_armor_class = 17
|
||||
dexterity_mod_max = 0
|
||||
strength_required = 15
|
||||
stealth_disadvantage = True
|
||||
weight = 60
|
||||
@@ -211,7 +210,6 @@ class PlateMail(HeavyArmor):
|
||||
name = "Plate Mail"
|
||||
cost = "1,500 gp"
|
||||
base_armor_class = 18
|
||||
dexterity_mod_max = 0
|
||||
strength_required = 15
|
||||
stealth_disadvantage = True
|
||||
weight = 65
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import warnings
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from dungeonsheets.features import Feature, FeatureSelector
|
||||
@@ -67,6 +69,7 @@ class CharClass:
|
||||
for sc in self.subclasses_available:
|
||||
if subclass_str.lower() in sc.name.lower():
|
||||
return sc(owner=self.owner)
|
||||
warnings.warn(f"Could not find subclass {subclass_str}.")
|
||||
return None
|
||||
|
||||
def apply_subclass(self, feature_choices=[]):
|
||||
|
||||
@@ -139,6 +139,39 @@ class Scout(SubClass):
|
||||
features_by_level[17] = [features.SuddenStrike]
|
||||
|
||||
|
||||
class Soulknife(SubClass):
|
||||
"""Most assassins strike with physical weapons, and many burglars and
|
||||
spies use thieves' tools to infiltrate secure locations. In
|
||||
contrast, a Soulknife strikes and infiltrates with the mind,
|
||||
cutting through barriers both physical and psychic. These rogues
|
||||
discover psionic power within themselves and channel it to do
|
||||
their roguish work. They find easy employment as members of
|
||||
thieves' guilds, though they are often mistrusted by rogues who
|
||||
are leery of anyone using strange mind powers to conduct their
|
||||
business. Most governments would also be happy to employ a
|
||||
Soulknife as a spy.
|
||||
|
||||
Amid the trees of ancient forests on the Material Plane and in the
|
||||
Feywild, some wood elves walk the path of the Soulknife, serving
|
||||
as silent, lethal guardians of their woods. In the endless war
|
||||
among the gith, a githzerai is encouraged to become a Soulknife
|
||||
when stealth is required against the githyanki foe.
|
||||
|
||||
As a Soulknife, your psionic abilities might have haunted you
|
||||
since you were a child, only revealing their full potential as you
|
||||
experienced the stress of adventure. Or you might have sought out
|
||||
a reclusive order of psychic adepts and spent years learning how
|
||||
to manifest your power.
|
||||
|
||||
"""
|
||||
name = "Soulknife"
|
||||
features_by_level = defaultdict(list)
|
||||
features_by_level[3] = [features.PsionicPower, features.PsychicBlades]
|
||||
features_by_level[9] = [features.SoulBlades]
|
||||
features_by_level[13] = [features.PsychicVeil]
|
||||
features_by_level[17] = [features.RendMind]
|
||||
|
||||
|
||||
class Swashbuckler(SubClass):
|
||||
"""You focus your training on the art of the blade, relying on speed,
|
||||
elegance, and charm in equal parts. While some warriors are brutes clad in
|
||||
@@ -216,6 +249,7 @@ class Rogue(CharClass):
|
||||
Inquisitive,
|
||||
Mastermind,
|
||||
Scout,
|
||||
Soulknife,
|
||||
Swashbuckler,
|
||||
)
|
||||
|
||||
|
||||
+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
|
||||
|
||||
@@ -251,7 +251,7 @@ def html_parts(
|
||||
return parts
|
||||
|
||||
|
||||
def rst_to_html(rst, top_heading_level=0):
|
||||
def rst_to_html(rst, top_heading_level: int = 0, format_dice: bool = True):
|
||||
"""Basic markup of reST to HTML code.
|
||||
|
||||
The translation between reST headings and LaTeX headings is
|
||||
@@ -268,6 +268,9 @@ def rst_to_html(rst, top_heading_level=0):
|
||||
top_heading_level : optional
|
||||
The highest level heading that will be added to the HTML as
|
||||
described above.
|
||||
format_dice
|
||||
If true, dice strings (e.g. "1d4") will be formatted in
|
||||
monospace font.
|
||||
|
||||
Returns
|
||||
=======
|
||||
@@ -280,7 +283,8 @@ def rst_to_html(rst, top_heading_level=0):
|
||||
html = ""
|
||||
else:
|
||||
# Mark hit dice in monospace font
|
||||
rst = dice_re.sub(r"``\1``", rst)
|
||||
if format_dice:
|
||||
rst = dice_re.sub(r"``\1``", rst)
|
||||
_html_parts = html_parts(rst)
|
||||
html = _html_parts["body"]
|
||||
return html
|
||||
|
||||
@@ -610,3 +610,134 @@ class MasterDuelist(Feature):
|
||||
|
||||
name = "Master Duelist"
|
||||
source = "Rogue (Swashbuckler)"
|
||||
|
||||
|
||||
# Soulknife
|
||||
class PsionicPower(Feature):
|
||||
"""You harbor a wellspring of psionic energy within yourself. This·
|
||||
energy is represented by your Psionic Energy dice, which are each
|
||||
a ``d6``. You have a number of these dice equal to twice your
|
||||
proficiency bonus, and they fuel various psionic powers you have,
|
||||
which are detailed below.
|
||||
|
||||
Some of your powers expend the Psionic Energy die they use, as
|
||||
specified in a power's description, and you can't use a power if
|
||||
it requires you to use a die when your dice are all expended. You
|
||||
regain all your expended Psionic Energy dice when you finish a
|
||||
long rest. In addition, as a bonus action, you can regain one
|
||||
expended Psionic Energy die, but you can't do so again until you
|
||||
finish a short or long rest.
|
||||
|
||||
When you reach certain levels in this class, the size of your
|
||||
Psionic Energy dice increases: at 5th level (``d8``), 11th level
|
||||
(``dlO``), and 17th level (``d12``).
|
||||
|
||||
The powers below use your Psionic Energy dice.
|
||||
|
||||
Psi-Bolstered Knack
|
||||
When your nonpsionic training fails you, your psionic power can
|
||||
help: if you fail an ability check using a skill or tool with
|
||||
which you have proficiency, you can roll one Psionic Energy die
|
||||
and add the number rolled to the check, potentially turning
|
||||
failure into success. You expend the die only if the roll
|
||||
succeeds.
|
||||
Psychic Whispers
|
||||
You can establish telepathic communication between yourself and
|
||||
others-perfect for quiet infiltration. As an action, choose one
|
||||
or more creatures you can see, up to a number of creatures equal
|
||||
to your proficiency bonus, and then roll one Psionic Energy
|
||||
die. For a number of hours equal to the number rolled, the
|
||||
chosen creatures can speak telepathically with you, and you can
|
||||
speak telepathically with them. To send or receive a message (no
|
||||
action required), you and the other creature must be within 1
|
||||
mile of each other. A creature can't use this telepathy if it
|
||||
can't speak any languages, and a creature can end the telepathic
|
||||
connection at any time (no action required). You and the
|
||||
creature don't need to speak a common language to understand
|
||||
each other.
|
||||
|
||||
The first time you use this power after each long rest, you
|
||||
don't expend the Psionic Energy die. All other times you use the
|
||||
power, you expend the die.
|
||||
|
||||
"""
|
||||
name = "Psionic Power"
|
||||
source = "Rogue (Soulknife)"
|
||||
|
||||
|
||||
class PsychicBlades(Feature):
|
||||
"""You can manifest your psionic power as shimmering blades of psychic
|
||||
energy. Whenever you take the Attack action, you can manifest a
|
||||
psychic blade from your free hand and make the attack with that
|
||||
blade. This magic blade is a simple melee weapon with the finesse
|
||||
and thrown properties. It has a normal range of 60 feet and no
|
||||
long range, and on a hit, it deals psychic damage equal to 1d6
|
||||
plus the ability modifier you used for the attack roll. The blade
|
||||
vanishes immediately after it hits or misses its target, and it
|
||||
leaves no mark on its target if it deals damage.
|
||||
|
||||
After you attack with the blade, you can make a melee or ranged
|
||||
weapon attack with a second psychic blade as a bonus action on the
|
||||
same turn, provided your other hand is free to create it. The
|
||||
damage die of this bonus attack is 1d4, instead of 1d6.
|
||||
|
||||
"""
|
||||
name = "Psychic Blades"
|
||||
source = "Rogue (Soulknife)"
|
||||
|
||||
|
||||
class SoulBlades(Feature):
|
||||
"""Your Psychic Blades are now an expression of your psi-suffused
|
||||
soul, giving you these powers that use your Psionic Energy dice:
|
||||
|
||||
Homing Strikes
|
||||
If you make an attack roll with your Psychic Blades and miss the
|
||||
target, you can roll one Psionic Energy die and add the number
|
||||
rolled to the attack roll. If this causes the attack to hit, you
|
||||
expend the Psionic Energy die.
|
||||
Psychic Teleportation
|
||||
As a bonus action, you manifest one of your Psychic Blades,
|
||||
expend one Psionic Energy die and roll it, and throw the blade
|
||||
at an unoccupied space you can see, up to a number of feet away
|
||||
equal to 10 times the number rolled. You then teleport to that
|
||||
space, and the blade vanishes.
|
||||
|
||||
"""
|
||||
name = "Soul Blades"
|
||||
source = "Rogue (Soulknife)"
|
||||
|
||||
|
||||
class PsychicVeil(Feature):
|
||||
"""You can weave a veil of psychic static to mask yourself. As an
|
||||
action, you can magically become invisible, along with anything
|
||||
you are wearing or carrying, for 1 hour or until you dismiss this
|
||||
effect (no action required). This invisibility ends early
|
||||
immediately after you deal damage to a creature or you force a
|
||||
creature to make a saving throw.
|
||||
|
||||
Once you use this feature, you can't do so again until you finish
|
||||
a long rest, unless you expend a Psionic Energy die to use this
|
||||
feature again.
|
||||
|
||||
"""
|
||||
name = "Psychic Veil"
|
||||
source = "Rogue (Soulknife)"
|
||||
|
||||
|
||||
class RendMind(Feature):
|
||||
"""You can sweep your Psychic Blades directly through a creature's
|
||||
mind. When you use your Psychic Blades to deal Sneak Attack damage
|
||||
to a creature, you can force that target to make a Wisdom saving
|
||||
throw (DC equal to 8 + your proficiency bonus + your Dexterity
|
||||
modifier). If the save fails, the target is stunned for 1
|
||||
minute. The stunned target can repeat the saving throw at the end
|
||||
of each of its turns, ending the effect on itself on a success.
|
||||
|
||||
Once you use this feature, you can't do so again until you finish
|
||||
a long rest, unless you expend three Psionic Energy dice to use it
|
||||
again.
|
||||
|
||||
"""
|
||||
name = "Rend Mind"
|
||||
source = "Rogue (Soulknife)"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
body {
|
||||
/* background: #f4ecdb; */
|
||||
background-image: url("../images/paper.jpg");
|
||||
/* background-image: url("../images/paper.jpg"); */
|
||||
background-size: 100%;
|
||||
}
|
||||
p {
|
||||
@@ -156,14 +156,14 @@ table p {
|
||||
}
|
||||
|
||||
th {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 67 KiB |
@@ -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,539 +1,8 @@
|
||||
<h1 id="gm-random-tables">Random Tables</h1>
|
||||
|
||||
[% if conjure_animals %]
|
||||
[% for table in tables %]
|
||||
<h2 id="gm-random-tables-[[ table.name | to_heading_id ]]">[[ table.name ]]</h2>
|
||||
|
||||
<!-- https://the-azure-triskele.obsidianportal.com/wikis/conjure-animals-table -->
|
||||
<h2 id="gm-random-tables-conjure-animals">Conjure Animals</h2>
|
||||
[[ table.__doc__ | rst_to_html(format_dice=False) ]]
|
||||
|
||||
<!-- Which category of beasts to summon -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d4</th>
|
||||
<th>Number of Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>One beast of challenge rating 2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Two beasts of challenge rating 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Four beasts of challenge rating 1/2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Eight beasts of challenge rating 1/4 or lower</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- CR2 Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>CR2 Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-2</td>
|
||||
<td>Allosaurus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-4</td>
|
||||
<td>Giant Boar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5-6</td>
|
||||
<td>Giant Constrictor Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7-8</td>
|
||||
<td>Giant Elk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9-10</td>
|
||||
<td>Hunter Shark</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Plesiosaurus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12-13</td>
|
||||
<td>Polar Bear</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14-15</td>
|
||||
<td>Rhinoceros</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16-17</td>
|
||||
<td>Saber-toothed Tiger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18-19</td>
|
||||
<td>Swarm of Poisonous Snakes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Roll on CR 1 Beast Table</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- CR1 Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d12</th>
|
||||
<th>Challenge Rating 1 Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Brown Bear</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Dire Wolf</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Fire Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Giant Eagle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Giant Hyena</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Giant Octopus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Giant Spider</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Giant Toad</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Giant Vulture</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Lion</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Tiger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Roll on CR ½ Beast Table</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Challenge Rating ½ Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-2</td>
|
||||
<td>Ape</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-4</td>
|
||||
<td>Black Bear</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5-6</td>
|
||||
<td>Crocodile</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7-8</td>
|
||||
<td>Giant Goat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9-10</td>
|
||||
<td>Giant Sea Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11-12</td>
|
||||
<td>Giant Wasp</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13-14</td>
|
||||
<td>Reef Shark</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15-16</td>
|
||||
<td>Swarm of Insects (below)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17-18</td>
|
||||
<td>Warhorse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Worg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Roll on Lesser Beast Menu Table</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- Swarm of insects (mostly for flavor) -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d6</th>
|
||||
<th>Swarm of Insects</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Ant</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Beatles</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Centipedes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Locusts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Spiders</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Wasps</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Challenge Rating 1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d6</th>
|
||||
<th>CR ¼ and Lesser Beast Menu</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-2</td>
|
||||
<td>Menu A</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-4</td>
|
||||
<td>Menu B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5-6</td>
|
||||
<td>Menu C</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- CR1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Lesser Beast Menu A</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Axe Beak</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Baboon</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Badger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Bat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Blood Hawk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Boar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Camel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Cat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Chicken*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Constrictor Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Crab</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Deer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td>Draft Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>Eagle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>Elk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>Flying Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17</td>
|
||||
<td>Frog</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18</td>
|
||||
<td>Giant Badger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Giant Bat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Giant Centipede</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<dl class="random-table-definitions">
|
||||
<dt>*Chicken</dt>
|
||||
<dd>Raven stats with Advantage on checks to wake
|
||||
up targets instead of mimicry</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<!-- CR1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Lesser Beast Menu B</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Giant Crab </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Giant Fire Beetle </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Giant Frog</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Giant Lizard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Giant Owl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Giant Poisonous Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Giant Rat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Giant Weasel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Giant Wolf Spider</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Goat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Hawk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Hyena</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td>Jackal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>Lemur*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>Lizard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>Mastiff</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17</td>
|
||||
<td>Mule</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18</td>
|
||||
<td>Newt**</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Octopus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Octopus, Cascadian Tree***</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<dl class="random-table-definitions">
|
||||
<dt>*Lemur</dt>
|
||||
<dd>Weasel stats with a common Climb speed instead of a
|
||||
bite attack</dd>
|
||||
<dt>**Newt</dt>
|
||||
<dd>Lizard stats with Amphibious instead of a bite
|
||||
attack</dd>
|
||||
<dt>***Octopus, Cascadian Tree</dt>
|
||||
<dd>Octopus stats with Amphibious
|
||||
and a 10 ft land speed instead of camouflage</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<!-- CR1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Lesser Beast Menu C</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Owl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Panther</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Poisonous Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Pony</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Pteranodon</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Quipper</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Rat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Raven</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Riding Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Scorpion</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Sea Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Shocker Lizard*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td>Spider</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>Swarm of Bats</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>Swarm of Rats</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>Swarm of Ravens</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17</td>
|
||||
<td>Turtle**</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18</td>
|
||||
<td>Vulture</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Weasel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Wolf</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<dl class="random-table-definitions">
|
||||
<dt>*Shocker Lizard</dt>
|
||||
<dd>Lizard stats with Static Electricity ranged attack of 1d6
|
||||
Electricity damage Close/Medium.</dd>
|
||||
<dt>**Turtle</dt>
|
||||
<dd>Lizard stats with 14 natural armor and no climb speed.</dd>
|
||||
</dl>
|
||||
|
||||
[% endif %]
|
||||
[% endfor %]
|
||||
|
||||
@@ -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
@@ -168,7 +168,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
|
||||
@@ -185,7 +185,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
|
||||
@@ -197,9 +200,15 @@ 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
|
||||
|
||||
def rst_to_boxlatex(rst):
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -71,6 +72,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}")
|
||||
@@ -106,13 +108,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,
|
||||
)
|
||||
|
||||
|
||||
@@ -271,12 +275,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):
|
||||
|
||||
@@ -136,6 +136,9 @@ class DarkElf(_Elf):
|
||||
spells_known = (spells.DancingLights,)
|
||||
|
||||
|
||||
Drow = DarkElf
|
||||
|
||||
|
||||
# Halflings
|
||||
class _Halfling(Race):
|
||||
name = "Halfling"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -195,10 +195,11 @@ class ArmorClass:
|
||||
armor = actor.armor or NoArmor()
|
||||
ac = armor.base_armor_class
|
||||
# calculate and apply modifiers
|
||||
if armor.dexterity_mod_max is None:
|
||||
ac += actor.dexterity.modifier
|
||||
else:
|
||||
ac += min(actor.dexterity.modifier, armor.dexterity_mod_max)
|
||||
if armor.dexterity_applied:
|
||||
if armor.dexterity_mod_max is None:
|
||||
ac += actor.dexterity.modifier
|
||||
else:
|
||||
ac += min(actor.dexterity.modifier, armor.dexterity_mod_max)
|
||||
if actor.has_feature(NaturalArmor):
|
||||
ac = max(ac, 13 + actor.dexterity.modifier)
|
||||
shield = actor.shield or NoShield()
|
||||
|
||||
@@ -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