Added subclasses from PHB, SCAG, XGTE. Need to add features for all

This commit is contained in:
Ben Cook
2018-12-21 10:37:02 -05:00
parent aa84911efd
commit 35b272be5f
27 changed files with 790 additions and 113 deletions
+1 -1
View File
@@ -1 +1 @@
0.8.0
0.8.1
BIN
View File
Binary file not shown.
+90
View File
@@ -0,0 +1,90 @@
"""This file describes the heroic adventurer Ben.
It's used primarily for saving characters from create-character,
where there will be many missing sections.
Modify this file as you level up and then re-generate the character
sheet by running ``makesheets`` from the command line.
"""
dungeonsheets_version = "0.8.0"
name = "Ben"
classes_levels = ['barbarian 1', 'bard 1', 'cleric 1', 'druid 1', 'fighter 1', 'monk 1', 'ranger 1', 'rogue 1', 'sorceror 1', 'warlock 1', 'wizard 1', 'revised ranger 1']
subclasses = ["Path of the Berserker", "College of Lore", "Arcana Domain", "Circle of the Land", "Arcane Archer", "Way of the Open Hand", <dungeonsheets.classes.ranger.Hunter object at 0x108e7ba20>, "Thief", "Draconic Bloodline", "The Archfey Patron", "School of Abjuration", '']
player_name = "Ben"
background = "Mercenary Veteran"
race = "Hill Dwarf"
alignment = "Neutral good"
xp = 0
hp_max = 10
# Ability Scores
strength = 14
dexterity = 15
constitution = 15
intelligence = 12
wisdom = 11
charisma = 8
# Select what skills you're proficient with
skill_proficiencies = ('nature', 'animal handling', 'athletics', 'persuasion')
# Named features / feats that aren't part of your classes,
# race, or background.
# Example:
# features = ('Tavern Brawler',) # take the optional Feat from PHB
features = ()
# If selecting among multiple feature options: ex Fighting Style
# Example (Fighting Style):
# feature_choices = ('Archery',)
feature_choices = ()
# Proficiencies and languages
languages = """Common, Dwarvish"""
# Inventory
# TODO: Get yourself some money
cp = 0
sp = 0
ep = 0
gp = 0
pp = 0
# TODO: Put your equipped weapons and armor here
weapons = () # Example: ('shortsword', 'longsword')
armor = "" # Eg "light leather armor"
shield = "" # Eg "shield"
equipment = """TODO: list the equipment and magic items your character carries"""
attacks_and_spellcasting = """TODO: Describe how your character usually attacks
or uses spells."""
# List of known spells
# Example: spells_prepared = ('magic missile', 'mage armor')
spells_prepared = () # Todo: Learn some spells
# Which spells have not been prepared
__spells_unprepared = ()
# all spells known
spells = spells_prepared + __spells_unprepared
# Backstory
# Describe your backstory here
personality_traits = """TODO: How does your character behave? See the PHB for
examples of all the sections below"""
ideals = """TODO: What does your character believe in?"""
bonds = """TODO: Describe what debts your character has to pay,
and other commitments or ongoing quests they have."""
flaws = """TODO: Describe your characters interesting flaws.
"""
features_and_traits = """TODO: Describe other features and abilities your
character has."""
+2 -1
View File
@@ -12,7 +12,8 @@ class Background():
languages = ()
def __init__(self):
self.features = tuple([f() for f in self.features])
cls = type(self)
self.features = tuple([f() for f in cls.features])
def __str__(self):
return self.name
+31 -24
View File
@@ -27,27 +27,27 @@ __version__ = read('../VERSION')
dice_re = re.compile('(\d+)d(\d+)')
multiclass_spellslots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
1: (0, 2, 0, 0, 0, 0, 0, 0, 0, 0),
2: (0, 3, 0, 0, 0, 0, 0, 0, 0, 0),
3: (0, 4, 2, 0, 0, 0, 0, 0, 0, 0),
4: (0, 4, 3, 0, 0, 0, 0, 0, 0, 0),
5: (0, 4, 3, 2, 0, 0, 0, 0, 0, 0),
6: (0, 4, 3, 3, 0, 0, 0, 0, 0, 0),
7: (0, 4, 3, 3, 1, 0, 0, 0, 0, 0),
8: (0, 4, 3, 3, 2, 0, 0, 0, 0, 0),
9: (0, 4, 3, 3, 3, 1, 0, 0, 0, 0),
10: (0, 4, 3, 3, 3, 2, 0, 0, 0, 0),
11: (0, 4, 3, 3, 3, 2, 1, 0, 0, 0),
12: (0, 4, 3, 3, 3, 2, 1, 0, 0, 0),
13: (0, 4, 3, 3, 3, 2, 1, 1, 0, 0),
14: (0, 4, 3, 3, 3, 2, 1, 1, 0, 0),
15: (0, 4, 3, 3, 3, 2, 1, 1, 1, 0),
16: (0, 4, 3, 3, 3, 2, 1, 1, 1, 0),
17: (0, 4, 3, 3, 3, 2, 1, 1, 1, 1),
18: (0, 4, 3, 3, 3, 3, 1, 1, 1, 1),
19: (0, 4, 3, 3, 3, 3, 2, 1, 1, 1),
20: (0, 4, 3, 3, 3, 3, 2, 2, 1, 1),
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
1: (0, 2, 0, 0, 0, 0, 0, 0, 0, 0),
2: (0, 3, 0, 0, 0, 0, 0, 0, 0, 0),
3: (0, 4, 2, 0, 0, 0, 0, 0, 0, 0),
4: (0, 4, 3, 0, 0, 0, 0, 0, 0, 0),
5: (0, 4, 3, 2, 0, 0, 0, 0, 0, 0),
6: (0, 4, 3, 3, 0, 0, 0, 0, 0, 0),
7: (0, 4, 3, 3, 1, 0, 0, 0, 0, 0),
8: (0, 4, 3, 3, 2, 0, 0, 0, 0, 0),
9: (0, 4, 3, 3, 3, 1, 0, 0, 0, 0),
10: (0, 4, 3, 3, 3, 2, 0, 0, 0, 0),
11: (0, 4, 3, 3, 3, 2, 1, 0, 0, 0),
12: (0, 4, 3, 3, 3, 2, 1, 0, 0, 0),
13: (0, 4, 3, 3, 3, 2, 1, 1, 0, 0),
14: (0, 4, 3, 3, 3, 2, 1, 1, 0, 0),
15: (0, 4, 3, 3, 3, 2, 1, 1, 1, 0),
16: (0, 4, 3, 3, 3, 2, 1, 1, 1, 0),
17: (0, 4, 3, 3, 3, 2, 1, 1, 1, 1),
18: (0, 4, 3, 3, 3, 3, 1, 1, 1, 1),
19: (0, 4, 3, 3, 3, 3, 2, 1, 1, 1),
20: (0, 4, 3, 3, 3, 3, 2, 2, 1, 1),
}
@@ -152,6 +152,10 @@ class Character():
def class_name(self):
return ' / '.join([f'{c.class_name} {c.class_level}'
for c in self.class_list])
@property
def subclasses(self):
return list([c.subclass or '' for c in self.class_list])
@property
def speed(self):
@@ -555,16 +559,17 @@ class Character():
assert len(classes_levels) == len(subclasses), (
'the length of classes_levels {:d} does not match length of '
'subclasses {:d}'.format(len(classes_levels), len(subclasses)))
circle = char_props.pop('circle', None)
class_list = []
for cl, sub in zip(classes_levels, subclasses):
try:
c, lvl = cl.strip().split(' ') # " wizard 3 " => "wizard", "3"
c, _, lvl = cl.strip().rpartition(' ') # " wizard 3 " => "wizard", "3"
except ValueError:
raise ValueError(
'classes_levels not properly formatted. Each entry should '
'be formatted \"class level\", but got {:s}'.format(cl))
try:
this_class = getattr(classes, c.capitalize())
this_class = getattr(classes, c.title.replace(' ', ''))
this_level = int(lvl)
except AttributeError:
raise AttributeError(
@@ -572,6 +577,8 @@ class Character():
except ValueError:
raise ValueError(
'level was not recognizable as an int: {:s}'.format(lvl))
if issubclass(this_class, classes.Druid):
sub = circle or sub
params = {}
params['feature_choices'] = char_props.get('feature_choices', [])
class_list += [this_class(this_level, subclass=sub, **params)]
@@ -606,7 +613,7 @@ class Character():
self.save(filename,
template_file=kwargs.get('template_file',
'character_template.txt'))
subprocess.call(['makesheets', filename)
subprocess.call(['makesheets', filename])
def read_character_file(filename):
+1
View File
@@ -9,6 +9,7 @@ dungeonsheets_version = "{{ char.dungeonsheets_version }}"
name = "{{ char.name }}"
classes_levels = {{ char.classes_levels }}
subclasses = {{ char.subclasses }}
player_name = "{{ char.player_name }}"
background = "{{ char.background.name }}"
race = "{{ char.race.name }}"
+3 -3
View File
@@ -1,6 +1,6 @@
__all__ = ('CharClass', 'Barbarian', 'Bard', 'Cleric', 'Druid', 'Fighter',
'Monk', 'Paladin', 'Ranger', 'Rogue', 'Sorceror', 'Warlock',
'Wizard', 'Revisedranger', 'available_classes')
'Wizard', 'RevisedRanger', 'available_classes')
from .classes import CharClass
from .barbarian import Barbarian
@@ -10,11 +10,11 @@ from .druid import Druid
from .fighter import Fighter
from .monk import Monk
from .paladin import Paladin
from .ranger import (Ranger, Revisedranger)
from .ranger import (Ranger, RevisedRanger)
from .rogue import Rogue
from .sorceror import Sorceror
from .warlock import Warlock
from .wizard import Wizard
available_classes = [Barbarian, Bard, Cleric, Druid, Fighter, Monk, Ranger,
Rogue, Sorceror, Warlock, Wizard, Revisedranger]
Rogue, Sorceror, Warlock, Wizard, RevisedRanger]
+40 -4
View File
@@ -1,8 +1,41 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (features, weapons)
from .classes import (CharClass, SubClass)
from collections import defaultdict
# PHB
class BerserkerPath(SubClass):
name = "Path of the Berserker"
class_features_by_level = defaultdict(list)
class TotemWarriorPath(SubClass):
name = "Path of the Totem Warrior"
class_features_by_level = defaultdict(list)
# SCAG
class BattleragerPath(SubClass):
name = "Path of the Battlerager"
class_features_by_level = defaultdict(list)
# XGTE
class AncestralGuardianPath(SubClass):
name = "Path of the Ancestral Guardian"
class_features_by_level = defaultdict(list)
class StormHeraldPath(SubClass):
name = "Path of the Storm Herald"
class_features_by_level = defaultdict(list)
class ZealotPath(SubClass):
name = "Path of the Zealot"
class_features_by_level = defaultdict(list)
class Barbarian(CharClass):
class_name = 'Barbarian'
hit_dice_faces = 12
@@ -12,4 +45,7 @@ class Barbarian(CharClass):
weapon_proficiencies = (weapons.simple_weapons + weapons.martial_weapons)
class_skill_choices = ('Animal Handling', 'Athletics',
'Intimidation', 'Nature', 'Perception', 'Survival')
subclasses_available = (BerserkerPath, TotemWarriorPath, BattleragerPath,
AncestralGuardianPath, StormHeraldPath, ZealotPath)
features_by_level = defaultdict(list)
+33 -3
View File
@@ -1,6 +1,33 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
# PHB
class CollegeOfLore(SubClass):
name = "College of Lore"
class_features_by_level = defaultdict(list)
class CollegeOfValor(SubClass):
name = "College of Valor"
class_features_by_level = defaultdict(list)
# XGTE
class CollegeOfGlamour(SubClass):
name = "College of Glamour"
class_features_by_level = defaultdict(list)
class CollegeOfSwords(SubClass):
name = "College of Swords"
class_features_by_level = defaultdict(list)
class CollegeOfWhispers(SubClass):
name = "College of Whispers"
class_features_by_level = defaultdict(list)
class Bard(CharClass):
@@ -20,6 +47,9 @@ class Bard(CharClass):
'Religion', 'Sleight of Hand', 'Stealth',
'Survival')
num_skill_choices = 3
features_by_level = defaultdict(list)
subclasses_available = (CollegeOfLore, CollegeOfValor, CollegeOfGlamour,
CollegeOfSwords, CollegeOfWhispers)
spellcasting_ability = 'charisma'
spell_slots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
+65 -9
View File
@@ -25,23 +25,56 @@ class CharClass():
def __init__(self, level, subclass=None, **params):
self.class_level = level
if subclass in [None, '', 'None']:
self.subclass = None
else:
self.subclass = subclass
# Instantiate the features
self.features_by_level = defaultdict(list)
cls = type(self)
for i in range(1, 21):
fs = [f() for f in cls.features_by_level[i]]
self.features_by_level[i] = fs
for k, v in params.items():
setattr(self, k, v)
# Instantiate the features
# Apply subclass
self.subclass = self.select_subclass(subclass)
if isinstance(self.subclass, SubClass):
self.apply_subclass()
def select_subclass(self, subclass_str):
"""
Return a SubClass object corresponding to given string.
Intended to be replaced by classes so they can
define their own methods of picking subclass by string.
"""
if subclass_str in ['', 'None', 'none', None]:
return None
for sc in self.subclasses_available:
if subclass_str.lower() in sc.name.lower():
return sc(level=self.class_level)
return None
def apply_subclass(self):
if self.subclass is None:
return
for i in range(1, 21):
self.features_by_level[i] = [f() for f in self.features_by_level[i]]
self.features_by_level[i] += ([f() for f in
self.subclass.features_by_level[i]])
for attr in ('weapon_proficiencies', '_proficiencies_text',
'spells_known', 'spells_prepared'):
new_list = getattr(self, attr, ()) + getattr(self.subclass, attr, ())
setattr(self, attr, new_list)
self.multiclass_weapon_proficiencies += (self.subclass.weapon_proficiencies)
self._multiclass_proficiencies_text += (self._proficiencies_text)
self.spellcasting_ability = (self.spellcasting_ability or
self.subclass.spellcasting_ability)
self.spell_slots_by_level = (self.spell_slots_by_level or
self.subclass.spell_slots_by_level)
@property
def features(self):
features = ()
for lvl in range(1, self.class_level+1):
features += tuple(self.features_by_level[lvl])
if self.subclass is not None and not isinstance(self.subclass, str):
features += tuple(self.subclass.features_by_level[lvl])
return features
@property
@@ -55,3 +88,26 @@ class CharClass():
return 0
else:
return self.spell_slots_by_level[self.class_level][spell_level]
class SubClass():
"""
A generic subclass object
"""
name = ''
features_by_level = defaultdict(list)
weapon_proficiencies = ()
_proficiencies_text = ()
spellcasting_ability = None
spell_slots_by_level = None
spells_known = ()
spells_prepared = ()
def __init__(self, level):
self.class_level = level
def __str__(self):
return self.name
def __repr__(self):
return "\"{:s}\"".format(self.name)
+60 -3
View File
@@ -1,6 +1,58 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
class KnowledgeDomain(SubClass):
name = "Knowledge Domain"
features_by_level = defaultdict(list)
class LifeDomain(SubClass):
name = "Life Domain"
features_by_level = defaultdict(list)
class LightDomain(SubClass):
name = "Light Domain"
features_by_level = defaultdict(list)
class NatureDomain(SubClass):
name = "Nature Domain"
features_by_level = defaultdict(list)
class TempestDomain(SubClass):
name = "Tempest Domain"
features_by_level = defaultdict(list)
class TrickeryDomain(SubClass):
name = "Trickery Domain"
features_by_level = defaultdict(list)
class WarDomain(SubClass):
name = "War Domain"
features_by_level = defaultdict(list)
# SCAG
class ArcanaDomain(SubClass):
name = "Arcana Domain"
features_by_level = defaultdict(list)
# XGTE
class ForgeDomain(SubClass):
name = "Forge Domain"
features_by_level = defaultdict(list)
class GraveDomain(SubClass):
name = "Grave Domain"
features_by_level = defaultdict(list)
class Cleric(CharClass):
@@ -12,6 +64,11 @@ class Cleric(CharClass):
weapon_proficiencies = weapons.simple_weapons
class_skill_choices = ('History', 'Insight', 'Medicine',
'Persuasion', 'Religion')
features_by_level = defaultdict(list)
subclasses_available = (KnowledgeDomain, LifeDomain, LightDomain,
NatureDomain, TempestDomain, TrickeryDomain,
WarDomain, ArcanaDomain, ForgeDomain,
GraveDomain)
spellcasting_ability = 'wisdom'
spell_slots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
+50 -17
View File
@@ -1,18 +1,42 @@
from ..stats import findattr
from .. import (weapons, monsters, exceptions)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, monsters, exceptions, features)
from .classes import CharClass, SubClass
from collections import defaultdict
import warnings
import math
# PHB
class LandCircle(SubClass):
name = "Circle of the Land"
circle = "land"
features_by_level = defaultdict(list)
class MoonCircle(SubClass):
name = "Circle of the Moon"
circle = "moon"
features_by_level = defaultdict(list)
# XGTE
class DreamsCircle(SubClass):
name = "Circle of Dreams"
circle = "dreams"
features_by_level = defaultdict(list)
class ShepherdCircle(SubClass):
name = "Circle of the Shepherd"
circle = "shepherd"
features_by_level = defaultdict(list)
class Druid(CharClass):
class_name = 'Druid'
circle = "" # moon, land
_wild_shapes = ()
hit_dice_faces = 8
saving_throw_proficiencies = ('intelligence', 'wisdom')
spellcasting_ability = 'wisdom'
languages = 'Druidic'
_proficiencies_text = (
'Light armor', 'medium armor',
@@ -26,6 +50,10 @@ class Druid(CharClass):
class_skill_choices = ('Arcana', 'Animal Handling', 'Insight',
'Medicine', 'Nature', 'Perception', 'Religion',
'Survival')
features_by_class = defaultdict(list)
subclasses_available = (LandCircle, MoonCircle, DreamsCircle,
ShepherdCircle)
spellcasting_ability = 'wisdom'
spell_slots_by_level = {
1: (2, 2, 0, 0, 0, 0, 0, 0, 0, 0),
2: (2, 3, 0, 0, 0, 0, 0, 0, 0, 0),
@@ -49,17 +77,22 @@ class Druid(CharClass):
20: (4, 4, 3, 3, 3, 3, 2, 2, 1, 1),
}
def __init__(self, level, subclass=None, **params):
if subclass is not None:
sc = str(subclass).lower()
if 'moon' in sc:
self.circle = 'moon'
params.pop('circle', '')
elif 'land' in sc:
self.circle = 'land'
params.pop('circle', '')
super().__init__(level, **params)
def select_subclass(self, subclass_str):
if subclass_str in ['', 'None', 'none', None]:
return None
for sc in self.subclasses_available:
if ((subclass_str.lower() == sc.circle.lower())
or (subclass_str.lower() in sc.name.lower())):
return sc(level=self.class_level)
return None
@property
def circle(self):
if isinstance(self.subclass, SubClass):
return self.subclass.circle.lower()
else:
return ''
@property
def all_wild_shapes(self):
"""Return all wild shapes, regardless of validity."""
@@ -130,7 +163,7 @@ class Druid(CharClass):
max_swim = None
max_fly = None
# Make adjustments for moon circle druids
if self.circle.lower() == "moon":
if self.circle == "moon":
if 2 <= self.class_level < 6:
max_cr = 1
elif self.class_level >= 6:
+69 -4
View File
@@ -1,8 +1,70 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
# PHB
class Champion(SubClass):
name = "Champion"
features_by_level = defaultdict(list)
class BattleMaster(SubClass):
name = "Battle Master"
features_by_level = defaultdict(list)
class EldritchKnight(SubClass):
name = "Eldritch Knight"
features_by_level = defaultdict(list)
spellcasting_ability = 'intelligence'
multiclass_spellslots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
1: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
2: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
3: (2, 2, 0, 0, 0, 0, 0, 0, 0, 0),
4: (2, 3, 0, 0, 0, 0, 0, 0, 0, 0),
5: (2, 3, 0, 0, 0, 0, 0, 0, 0, 0),
6: (2, 3, 0, 0, 0, 0, 0, 0, 0, 0),
7: (2, 4, 2, 0, 0, 0, 0, 0, 0, 0),
8: (2, 4, 2, 0, 0, 0, 0, 0, 0, 0),
9: (2, 4, 2, 0, 0, 0, 0, 0, 0, 0),
10: (3, 4, 3, 0, 0, 0, 0, 0, 0, 0),
11: (3, 4, 3, 0, 0, 0, 0, 0, 0, 0),
12: (3, 4, 3, 0, 0, 0, 0, 0, 0, 0),
13: (3, 4, 3, 2, 0, 0, 0, 0, 0, 0),
14: (3, 4, 3, 2, 0, 0, 0, 0, 0, 0),
15: (3, 4, 3, 2, 0, 0, 0, 0, 0, 0),
16: (3, 4, 3, 3, 0, 0, 0, 0, 0, 0),
17: (3, 4, 3, 3, 0, 0, 0, 0, 0, 0),
18: (3, 4, 3, 3, 0, 0, 0, 0, 0, 0),
19: (3, 4, 3, 3, 1, 0, 0, 0, 0, 0),
20: (3, 4, 3, 3, 1, 0, 0, 0, 0, 0),
}
# SCAG
class PurpleDragonKnight(SubClass):
name = "Purple Dragon Knight"
features_by_level = defaultdict(list)
# XGTE
class ArcaneArcher(SubClass):
name = "Arcane Archer"
features_by_level = defaultdict(list)
class Cavalier(SubClass):
name = "Cavalier"
features_by_level = defaultdict(list)
class Samurai(SubClass):
name = "Samurai"
features_by_level = defaultdict(list)
class Fighter(CharClass):
class_name = 'Fighter'
hit_dice_faces = 10
@@ -17,4 +79,7 @@ class Fighter(CharClass):
class_skill_choices = ('Acrobatics', 'Animal Handling',
'Athletics', 'History', 'Insight', 'Intimidation',
'Perception', 'Survival')
features_by_level = defaultdict(list)
subclasses_available = (Champion, BattleMaster, EldritchKnight,
PurpleDragonKnight, ArcaneArcher, Cavalier,
Samurai)
+40 -18
View File
@@ -1,10 +1,45 @@
__all__ = ('Monk')
from .. import (features, weapons)
from .classes import CharClass
from .classes import CharClass, SubClass
from collections import defaultdict
class OpenHandWay(SubClass):
name = "Way of the Open Hand"
features_by_level = defaultdict(list)
class ShadowWay(SubClass):
name = "Way of Shadow"
features_by_level = defaultdict(list)
class FourElementsWay(SubClass):
name = "Way of the Four Elements"
features_by_level = defaultdict(list)
class SunSoulWay(SubClass):
name = "Way of the Sun Soul"
features_by_level = defaultdict(list)
class LongDeathWay(SubClass):
name = "Way of the Long Death"
features_by_level = defaultdict(list)
class DrunkenMasterWay(SubClass):
name = "Way of the Drunken Master"
features_by_level = defaultdict(list)
class KenseiWay(SubClass):
name = "Way of the Kensei"
features_by_level = defaultdict(list)
class Monk(CharClass):
class_name = 'Monk'
hit_dice_faces = 8
@@ -15,7 +50,10 @@ class Monk(CharClass):
weapon_proficiencies = (weapons.Shortsword, weapons.Unarmed) + weapons.simple_weapons
class_skill_choices = ('Acrobatics', 'Athletics', 'History', 'Insight',
'Religion', 'Stealth')
subclasses_available = ('SunSoul', 'OpenHand')
subclasses_available = (OpenHandWay, ShadowWay,
FourElementsWay, SunSoulWay,
LongDeathWay, DrunkenMasterWay,
KenseiWay)
features_by_level = defaultdict(list)
features_by_level[1] = [features.UnarmoredDefense,
features.MartialArts]
@@ -25,19 +63,3 @@ class Monk(CharClass):
for f in self.features_by_level[1]:
if isinstance(f, features.MartialArts):
f.level = self.class_level
if subclass == 'sunsoul':
self.subclass = SunSoul(level=self.class_level)
else:
self.subclass = None
if self.subclass is not None:
self._proficiencies_text += self.subclass._proficiencies_text
self.weapon_proficiences += self.subclass.weapon_proficiencies
class SunSoul:
class_features_by_level = defaultdict(list)
weapon_proficiencies = ()
_profiencies_text = ()
def __init__(self, level):
self.class_level = level
+36 -3
View File
@@ -1,7 +1,37 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
class OathOfDevotion(SubClass):
name = "Oath of Devotion"
features_by_level = defaultdict(list)
class OathOfAncients(SubClass):
name = "Oath of The Ancients"
features_by_level = defaultdict(list)
class OathOfVengance(SubClass):
name = "Oath of Vengance"
features_by_level = defaultdict(list)
class OathOfCrown(SubClass):
name = "Oath of The Crown"
features_by_level = defaultdict(list)
class OathOfConquest(SubClass):
name = "Oath of Conquest"
features_by_level = defaultdict(list)
class OathOfRedemption(SubClass):
name = "Oath of Redemption"
features_by_level = defaultdict(list)
class Paladin(CharClass):
class_name = 'Paladin'
@@ -12,6 +42,9 @@ class Paladin(CharClass):
weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
class_skill_choices = ("Athletics", 'Insight', 'Intimidation',
'Medicine', 'Persuasion', 'Religion')
features_by_level = defaultdict(list)
subclasses_available = (OathOfDevotion, OathOfAncients, OathOfVengance,
OathOfCrown, OathOfConquest, OathOfRedemption)
spellcasting_ability = 'charisma'
spell_slots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
+53 -6
View File
@@ -1,10 +1,37 @@
__all__ = ('Ranger', 'Revisedranger')
__all__ = ('Ranger', 'RevisedRanger')
from .. import (weapons, features)
from .classes import CharClass
from .classes import CharClass, SubClass
from collections import defaultdict
# PHB
class Hunter(SubClass):
name = "Hunter"
features_by_level = defaultdict(list)
class BeastMaster(SubClass):
name = "Beast Master"
features_by_level = defaultdict(list)
# XGTE
class GloomStalker(SubClass):
name = "Gloom Stalker"
features_by_level = defaultdict(list)
class HorizonWalker(SubClass):
name = "Horizon Walker"
features_by_level = defaultdict(list)
class MonsterSlayer(SubClass):
name = "Monster Slayer"
features_by_level = defaultdict(list)
class Ranger(CharClass):
class_name = 'Ranger'
hit_dice_faces = 10
@@ -16,8 +43,10 @@ class Ranger(CharClass):
'Investigation', 'Nature', 'Perception', 'Stealth',
'Survival')
num_skill_choices = 3
spellcasting_ability = 'wisdom'
features_by_level = defaultdict(list)
subclasses_available = (Hunter, BeastMaster, GloomStalker,
HorizonWalker, MonsterSlayer)
spellcasting_ability = 'wisdom'
spell_slots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
1: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
@@ -49,6 +78,24 @@ class Ranger(CharClass):
self.features_by_level[2].append(fighting_style)
# Custom Classes
class Revisedranger(Ranger):
class_name = 'Revisedranger'
# Revised Ranger
class BeastConclave(SubClass):
name = "Beast Conclave"
features_by_level = defaultdict(list)
class HunterConclave(SubClass):
name = "Hunter Conclave"
features_by_level = defaultdict(list)
class DeepStalkerConclave(SubClass):
name = "Deep Stalker Conclave"
features_by_level = defaultdict(list)
class RevisedRanger(Ranger):
class_name = 'Revised Ranger'
features_by_level = defaultdict(list)
subclasses_available = (BeastConclave, HunterConclave, DeepStalkerConclave)
+62 -4
View File
@@ -1,6 +1,62 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
# PHB
class Thief(SubClass):
name = "Thief"
features_by_level = defaultdict(list)
class Assassin(SubClass):
name = "Assassin"
features_by_level = defaultdict(list)
class ArcaneTrickster(SubClass):
name = "Arcane Trickster"
features_by_level = defaultdict(list)
spellcasting_ability = 'intelligence'
multiclass_spellslots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
1: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
2: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
3: (3, 2, 0, 0, 0, 0, 0, 0, 0, 0),
4: (3, 3, 0, 0, 0, 0, 0, 0, 0, 0),
5: (3, 3, 0, 0, 0, 0, 0, 0, 0, 0),
6: (3, 3, 0, 0, 0, 0, 0, 0, 0, 0),
7: (3, 4, 2, 0, 0, 0, 0, 0, 0, 0),
8: (3, 4, 2, 0, 0, 0, 0, 0, 0, 0),
9: (3, 4, 2, 0, 0, 0, 0, 0, 0, 0),
10: (4, 4, 3, 0, 0, 0, 0, 0, 0, 0),
11: (4, 4, 3, 0, 0, 0, 0, 0, 0, 0),
12: (4, 4, 3, 0, 0, 0, 0, 0, 0, 0),
13: (4, 4, 3, 2, 0, 0, 0, 0, 0, 0),
14: (4, 4, 3, 2, 0, 0, 0, 0, 0, 0),
15: (4, 4, 3, 2, 0, 0, 0, 0, 0, 0),
16: (4, 4, 3, 3, 0, 0, 0, 0, 0, 0),
17: (4, 4, 3, 3, 0, 0, 0, 0, 0, 0),
18: (4, 4, 3, 3, 0, 0, 0, 0, 0, 0),
19: (4, 4, 3, 3, 1, 0, 0, 0, 0, 0),
20: (4, 4, 3, 3, 1, 0, 0, 0, 0, 0),
}
# XGTE
class Inquisitive(SubClass):
name = "Inquisitive"
features_by_level = defaultdict(list)
class Mastermind(SubClass):
name = "Mastermind"
features_by_level = defaultdict(list)
class Swashbuckler(SubClass):
name = "Swashbuckler"
features_by_level = defaultdict(list)
class Rogue(CharClass):
@@ -17,4 +73,6 @@ class Rogue(CharClass):
'Insight', 'Intimidation', 'Investigation',
'Perception', 'Performance', 'Persuasion',
'Sleight of Hand', 'Stealth')
features_by_level = defaultdict(list)
subclasses_available = (Thief, Assassin, ArcaneTrickster,
Inquisitive, Mastermind, Swashbuckler)
+33 -3
View File
@@ -1,6 +1,33 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
# PHB
class DraconicBloodline(SubClass):
name = "Draconic Bloodline"
features_by_level = defaultdict(list)
class WildMagic(SubClass):
name = "Wild Magic"
features_by_level = defaultdict(list)
# XGTE
class DivineSoul(SubClass):
name = "Divine Soul"
features_by_level = defaultdict(list)
class ShadowMagic(SubClass):
name = "Shadow Magic"
features_by_level = defaultdict(list)
class StormSorcery(SubClass):
name = "Storm Sorcery"
features_by_level = defaultdict(list)
class Sorceror(CharClass):
@@ -14,6 +41,9 @@ class Sorceror(CharClass):
weapons.LightCrossbow)
class_skill_choices = ('Arcana', 'Deception', 'Insight',
'Intimidation', 'Persuasion', 'Religion')
features_by_level = defaultdict(list)
subclasses_available = (DraconicBloodline, WildMagic, DivineSoul,
ShadowMagic, StormSorcery)
spellcasting_ability = 'charisma'
spell_slots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
+39 -3
View File
@@ -1,6 +1,39 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
# PHB
class Archfey(SubClass):
name = "The Archfey Patron"
features_by_level = defaultdict(list)
class Fiend(SubClass):
name = "The Fiend Patron"
features_by_level = defaultdict(list)
class GreatOldOne(SubClass):
name = "The Great Old One Patron"
features_by_level = defaultdict(list)
# SCAG
class Undying(SubClass):
name = "The Undying Patron"
features_by_level = defaultdict(list)
# XGTE
class Celestial(SubClass):
name = "The Celestial Patron"
features_by_level = defaultdict(list)
class Hexblade(SubClass):
name = "Hexblade Patron"
features_by_level = defaultdict(list)
class Warlock(CharClass):
@@ -12,6 +45,9 @@ class Warlock(CharClass):
'Intimidation', 'Investigation', 'Nature',
'Religion')
weapon_proficiencies = weapons.simple_weapons
features_by_level = defaultdict(list)
subclasses_available = (Archfey, Fiend, GreatOldOne, Undying, Celestial,
Hexblade)
spellcasting_ability = 'charisma'
spell_slots_by_level = {
1: (2, 1, 0, 0, 0, 0, 0, 0, 0, 0),
+60 -3
View File
@@ -1,6 +1,59 @@
from .. import (weapons)
from .. import features as feats
from .classes import CharClass
from .. import (weapons, features)
from .classes import CharClass, SubClass
from collections import defaultdict
# PHB
class Abjuration(SubClass):
name = "School of Abjuration"
features_by_level = defaultdict(list)
class Conjuration(SubClass):
name = "School of Conjuration"
features_by_level = defaultdict(list)
class Divination(SubClass):
name = "School of Divination"
features_by_level = defaultdict(list)
class Enchantment(SubClass):
name = "School of Enchantment"
features_by_level = defaultdict(list)
class Evocation(SubClass):
name = "School of Evocation"
features_by_level = defaultdict(list)
class Illusion(SubClass):
name = "School of Illusion"
features_by_level = defaultdict(list)
class Necromancy(SubClass):
name = "School of Necromancy"
features_by_level = defaultdict(list)
class Transmutation(SubClass):
name = "School of Transmutation"
features_by_level = defaultdict(list)
# SCAG
class Bladeslinging(SubClass):
name = "School of Bladeslinging"
features_by_level = defaultdict(list)
# XGTE
class WarMagic(SubClass):
name = "School of War Magic"
features_by_level = defaultdict(list)
class Wizard(CharClass):
@@ -14,6 +67,10 @@ class Wizard(CharClass):
weapons.LightCrossbow)
class_skill_choices = ('Arcana', 'History', 'Investigation',
'Medicine', 'Religion')
features_by_level = defaultdict(list)
subclasses_available = (Abjuration, Conjuration, Divination, Enchantment,
Evocation, Illusion, Necromancy, Transmutation,
Bladeslinging, WarMagic)
spellcasting_ability = 'intelligence'
spell_slots_by_level = {
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
+4 -1
View File
@@ -243,7 +243,10 @@ class SubclassForm(npyscreen.ActionForm):
def __init__(self, newclass, level, num=1, **kwargs):
self.class_num = num
self.parent_class = newclass
self.subclass_options = newclass.subclasses_available or ('None',)
if len(newclass.subclasses_available) > 0:
self.subclass_options = [sc.name for sc in newclass.subclasses_available]
else:
self.subclass_options = ('None',)
self.level = level
super().__init__(**kwargs)
+1
View File
@@ -12,6 +12,7 @@ dungeonsheets_version = "{{ char.dungeonsheets_version }}"
name = "{{ char.name }}"
classes_levels = {{ char.classes_levels }}
subclasses = {{ char.subclasses }}
player_name = "{{ char.player_name }}"
background = "{{ char.background.name }}"
race = "{{ char.race.name }}"
+1
View File
@@ -14,3 +14,4 @@ from .warlock import *
from .wizard import *
from .races import *
from .backgrounds import *
from .feats import *
View File
+9 -1
View File
@@ -5,7 +5,7 @@
\usepackage[dvipsnames]{color}
\definecolor{mygrey}{gray}{0.7}
\title{Features and Traits}
\title{Features and Subclass}
\author{[[ character.name ]]}
\date{}
@@ -13,6 +13,14 @@
\maketitle
[% for sc in character.subclasses if sc not in ['', None, 'None', 'none']%]
\section**{[[ sc.name ]]}
[[ sc.__doc__|rst_to_latex ]]
[% endfor %]
[% for feat in character.features %]
\section*{[[ feat.name ]]}
+6 -2
View File
@@ -26,9 +26,13 @@ class Race():
spells_prepared = ()
def __init__(self):
self.features = tuple([f() for f in self.features])
cls = type(self)
# Instantiate the features
self.features = tuple([f() for f in cls.features])
self.features_by_level = defaultdict(list)
for i in range(1, 21):
self.features_by_level[i] = [f() for f in self.features_by_level[i]]
self.features_by_level[i] = [f()for f in
cls.features_by_level[i]]
def __str__(self):
return self.name
+1
View File
@@ -23,6 +23,7 @@ setup(name='dungeonsheets',
'dungeonsheets': ['blank-character-sheet-default.pdf',
'blank-spell-sheet-default.pdf',
'spellbook_template.tex', '../VERSION',
'empty_template.txt',
'character_template.txt', 'features_template.tex',
'druid_shapes_template.tex']
},