Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Installation

.. note::

Dungeon sheets requires **at least python 3.6**. This is mostly due
dungeon-sheets requires **at least python 3.6**. This is mostly due
to the liberal use of f-strings_. If you want to use it with
previous versions of python 3, you'll probably have to replace all
the f-strings with the older ``.format()`` method or string
Expand All @@ -72,8 +72,9 @@ pdftk is available in Debian and derivatives as **pdftk**, the package
is not available in some RPM distributions, such as Fedora and CentOS.
One alternative would be to build your PC sheets using docker.

If the ``pdflatex`` command is available on your system, spellcasters
will include a spellbook with descriptions of each spell known. If
If the ``pdflatex`` command is available on your system, dungeon-sheets
will include a description of a character's features. For spellcasters,
it will include a spellbook with descriptions of each spell known. If
not, then this feature will be skipped.

In order to properly format descriptions for spells/features/etc.,
Expand Down Expand Up @@ -115,6 +116,14 @@ By default, your character's spells are ordered alphabetically. If you
would like your spellbook to be ordered by level, you can use the ``-S``
option to do so.

Furthermore, your character's features are ordered alphabetically by
default as well. Pass the ``-N`` option to order feats by type
(character feats, class feats, racial feats and background feat) and,
if applicable, by sub-type (e.g., for Sorcerers, metamagic feature
choices are collected under the Metamagic feature; for the Battle
Master subclass, Maneuver feature choices are collected under
the Combat Superiority feature.)

If you'd like a **step-by-step walkthrough** for creating a new
character, just run ``create-character`` from a command line and a
helpful menu system will take care of the basics for you.
Expand Down
42 changes: 42 additions & 0 deletions dungeonsheets/character.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,48 @@ def features(self):

return sorted(tuple(fts), key=(lambda x: x.name))

@property
def features_by_type(self):
fts: dict[str, list[type[Feature]]] = {
"Feats": [],
"Class Features": [],
"Racial Features": [],
"Background Features": [],
}
other_feat_choices = list()
# Add player choices; distinguish between general feats and
# feat choices such as fighting styles and metamagic options.
for item in self.custom_features:
if item.source == "Feats":
fts["Feats"].append(item)
else:
other_feat_choices.append(item)
for c in self.class_list:
for item in list(c.features):
if item not in other_feat_choices:
fts["Class Features"].append(item)
# Now check whether any items in class_feat_choices
# is a subclass of current item.
for choice in other_feat_choices:
if choice.__class__.__bases__[0] is item.__class__:
fts["Class Features"].append(choice)
# Make sure we didn't miss any feat choices:
for choice in other_feat_choices:
if choice not in fts["Class Features"]:
fts["Class Features"].insert(0, choice)
if self.race is not None:
for item in getattr(self.race, "features", ()):
fts["Racial Features"].append(item)
# some races have level-based features (Ex: Aasimar)
if hasattr(self.race, "features_by_level"):
for lvl in range(1, self.level + 1):
for item in list(self.race.features_by_level[lvl]):
fts["Racial Features"].append(item)
if self.background is not None:
for item in getattr(self.background, "features", ()):
fts["Background Features"].append(item)
return fts

@property
def custom_features_text(self):
return tuple([f.name for f in self.custom_features])
Expand Down
125 changes: 52 additions & 73 deletions dungeonsheets/features/bloodhunter.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ class BloodMaledict(Feature):

name = "Blood Maledict"
source = "Blood Hunter"
at_will_spells = ()

def cast_spell_at_will(self, spell):
s = spell()
s.level = 0
if "M" in s.components:
c = list(s.components)
c.remove("M")
s.components = tuple(c)
self.spells_known += (s,)
self.spells_prepared += (s,)

def __init__(self, owner):
super().__init__(owner)
for s in self.at_will_spells:
self.cast_spell_at_will(s)


class BloodHunterFightingStyle(FeatureSelector):
Expand Down Expand Up @@ -75,6 +91,22 @@ class CrimsonRites(Feature):

name = "Crimson Rites"
source = "Blood Hunter"
at_will_spells = ()

def cast_spell_at_will(self, spell):
s = spell()
s.level = 0
if "M" in s.components:
c = list(s.components)
c.remove("M")
s.components = tuple(c)
self.spells_known += (s,)
self.spells_prepared += (s,)

def __init__(self, owner):
super().__init__(owner)
for s in self.at_will_spells:
self.cast_spell_at_will(s)


class ExtraAttackBloodHunter(Feature):
Expand Down Expand Up @@ -147,29 +179,7 @@ class SanguineMastery(Feature):


# All Rites
class Rites(Feature):
"""
A generic Rite. Add details in features/bloodhunter.py
"""

name = "Unnamed rite"
source = "BloodHunter (Crimson Rites)"
at_will_spells = ()

def cast_spell_at_will(self, spell):
s = spell()
s.level = 0
if "M" in s.components:
c = list(s.components)
c.remove("M")
s.components = tuple(c)
self.spells_known += (s,)
self.spells_prepared += (s,)

def __init__(self, owner):
super().__init__(owner)
for s in self.at_will_spells:
self.cast_spell_at_will(s)
Rites = CrimsonRites


class RiteOfTheFlame(Rites):
Expand Down Expand Up @@ -226,29 +236,7 @@ class RiteOfTheRoar(Rites):


#Blood Curses
class BloodCurses(Feature):
"""
A generic BloodCurse. Add details in features/bloodhunter.py
"""

name = "Unnamed Curse"
source = "BloodHunter (Blood Maledict)"
at_will_spells = ()

def cast_spell_at_will(self, spell):
s = spell()
s.level = 0
if "M" in s.components:
c = list(s.components)
c.remove("M")
s.components = tuple(c)
self.spells_known += (s,)
self.spells_prepared += (s,)

def __init__(self, owner):
super().__init__(owner)
for s in self.at_will_spells:
self.cast_spell_at_will(s)
BloodCurses = BloodMaledict


class BloodCurseoftheAnxious(BloodCurses):
Expand Down Expand Up @@ -535,8 +523,24 @@ class Formulas(Feature):

name = "Formulas"
source = "Blood Hunter (Order of the Mutant)"


at_will_spells = ()

def cast_spell_at_will(self, spell):
s = spell()
s.level = 0
if "M" in s.components:
c = list(s.components)
c.remove("M")
s.components = tuple(c)
self.spells_known += (s,)
self.spells_prepared += (s,)

def __init__(self, owner):
super().__init__(owner)
for s in self.at_will_spells:
self.cast_spell_at_will(s)


class Mutagencraft(Feature):
"""At 3rd level, you can concoct a single mutagen when you finish a short or long rest. Starting at 7th level, the number of mutagens you can create when you finish a rest increases to two, and at 15th level, you can now create three mutagens.

Expand Down Expand Up @@ -593,31 +597,6 @@ class ExaltedMutation(Feature):


#Formulas
class Formulas(Feature):
"""
A generic Formula. Add details in features/bloodhunter.py
"""

name = "Unnamed rite"
source = "BloodHunter (Crimson Rites)"
at_will_spells = ()

def cast_spell_at_will(self, spell):
s = spell()
s.level = 0
if "M" in s.components:
c = list(s.components)
c.remove("M")
s.components = tuple(c)
self.spells_known += (s,)
self.spells_prepared += (s,)

def __init__(self, owner):
super().__init__(owner)
for s in self.at_will_spells:
self.cast_spell_at_will(s)


class Aether(Formulas):
"""**Prerequisite: 11th level.**
You gain a flying speed of 20 feet for 1 hour.
Expand Down
Loading