added primary ability and new AC and speed calculations

This commit is contained in:
Ben Cook
2018-12-23 01:45:04 -05:00
parent 66880cd6bf
commit 6c4589d03f
28 changed files with 366 additions and 197 deletions
+16
View File
@@ -9,3 +9,19 @@ Add race / class AC bonuses
Add features
**hard** integrate features automatically into math
Add Inspiration points
Weird features will have to:
--Update Speed (Fast Movement / Unarmored Movement)
--Access current level and class levels
--Give advantage to initiative
--Add to proficiencies (Jack of All Trades, Remarkable Athelete)
--Double proficiencies (Expertiese)
--Add extra skill proficiencies (College of Lore, Blessings of Knowledge...optional)
--Add extra proficiencies
--Add extra spells known/prepared
--Select among multiple options
--Select many options (maneuvers)
--Update damage / bonus to weapons (Martial Arts)
--Add to HP Max (Draconic Bloodline, Dwarven Toughness)
+39 -23
View File
@@ -10,6 +10,7 @@ class Shield():
def __repr__(self):
return "\"{:s}\"".format(self.name)
class WoodenShield(Shield):
name = 'Wooden shield'
@@ -42,6 +43,8 @@ class Armor():
Minimum strength needed to use this armor properly.
stealth_disadvantage : bool
If true, the armor causes disadvantage on stealth rolls.
weight_class : str
light, medium, or heavy
weight : int
In lbs.
@@ -60,33 +63,46 @@ class Armor():
def __repr__(self):
return "\"{:s}\"".format(self.name)
class NoArmor(Armor):
name = "No armor"
class LightPaddedArmor(Armor):
name = "Light padded armor"
class LightArmor(Armor):
name = "Generic Light Armor"
class MediumArmor(Armor):
name = "Generic Medium Armor"
class HeavyArmor(Armor):
name = "Generic Heavy Armor"
class PaddedArmor(LightArmor):
name = "Padded armor"
cost = "5 gp"
base_armor_class = 11
weight = 8
stealth_disadvantage = True
class LeatherArmor(Armor):
class LeatherArmor(LightArmor):
name = "Leather armor"
cost = "10 gp"
base_armor_class = 11
weight = 10
class StuddedArmor(Armor):
name = "Studded armor"
class StuddedArmor(LightArmor):
name = "Studded leather armor"
cost = "45 gp"
base_armor_class = 12
weight = 13
class HideArmor(Armor):
class HideArmor(MediumArmor):
name = "Hide armor"
cost = "10 gp"
base_armor_class = 12
@@ -94,16 +110,16 @@ class HideArmor(Armor):
weight = 12
class MediumChainShirtArmor(Armor):
name = "Medium chain shirt armor"
class ChainShirt(MediumArmor):
name = "Chain shirt"
cost = "50 gp"
base_armor_class = 13
dexterity_mod_max = 2
weight = 20
class MediumScaleMailArmor(Armor):
name = "Medium scale mail armor"
class ScaleMail(MediumArmor):
name = "Scale mail"
cost = "50 gp"
base_armor_class = 14
dexterity_mod_max = 2
@@ -111,16 +127,16 @@ class MediumScaleMailArmor(Armor):
weight = 45
class MediumBrassplateArmor(Armor):
name = "Medium brassplate armor"
class Breastplate(MediumArmor):
name = "Breastplate"
cost = "400 gp"
base_armor_class = 14
dexterity_mod_max = 2
weight = 20
class MediumHalfPlateArmor(Armor):
name = "Medium half plate armor"
class HalfPlate(MediumArmor):
name = "Half Plate"
cost = "750 gp"
base_armor_class = 15
dexterity_mod_max = 2
@@ -128,8 +144,8 @@ class MediumHalfPlateArmor(Armor):
weight = 40
class HeavyRingMailArmor(Armor):
name = "Heavy ring mail armor"
class RingMail(HeavyArmor):
name = "Ring mail"
cost = "30 gp"
base_armor_class = 14
dexterity_mod_max = 0
@@ -137,8 +153,8 @@ class HeavyRingMailArmor(Armor):
weight = 40
class HeavyChainMailArmor(Armor):
name = "Heavy chain mail armor"
class ChainMail(HeavyArmor):
name = "Chain Mail"
cost = "75 gp"
base_armor_class = 16
dexterity_mod_max = 0
@@ -147,8 +163,8 @@ class HeavyChainMailArmor(Armor):
weight = 55
class HeavySplintArmor(Armor):
name = "Heavy splint armor"
class SplintArmor(HeavyArmor):
name = "Splint Armor"
cost = "200 gp"
base_armor_class = 17
dexterity_mod_max = 0
@@ -157,8 +173,8 @@ class HeavySplintArmor(Armor):
weight = 60
class HeavyPlateArmor(Armor):
name = "Heavy plate armor"
class PlateMail(HeavyArmor):
name = "Plate Mail"
cost = "1,500 gp"
base_armor_class = 18
dexterity_mod_max = 0
@@ -168,7 +184,7 @@ class HeavyPlateArmor(Armor):
# Custom Armor
class ElvenChain(Armor):
class ElvenChain(MediumArmor):
name = 'Elven Chain'
cost = '5,000 gp'
base_armor_class = 14
+4 -2
View File
@@ -3,6 +3,7 @@ from . import features as feats
class Background():
name = "Generic background"
owner = None
skill_proficiencies = ()
weapon_proficiencies = ()
proficiencies_text = ()
@@ -11,9 +12,10 @@ class Background():
features = ()
languages = ()
def __init__(self):
def __init__(self, owner):
self.owner = owner
cls = type(self)
self.features = tuple([f() for f in cls.features])
self.features = tuple([f(owner=self.owner) for f in cls.features])
def __str__(self):
return self.name
+20 -37
View File
@@ -9,7 +9,7 @@ import importlib.util
import jinja2
import subprocess
from .stats import Ability, Skill, findattr
from .stats import Ability, Skill, findattr, ArmorClass, Speed
from .dice import read_dice_str
from . import (weapons, race, background, spells, armor, monsters,
exceptions, classes, features)
@@ -75,6 +75,8 @@ class Character():
intelligence = Ability()
wisdom = Ability()
charisma = Ability()
armor_class = ArmorClass()
speed = Speed()
_saving_throw_proficiencies = []
other_weapon_proficiencies = tuple()
skill_proficiencies = tuple()
@@ -117,6 +119,7 @@ class Character():
pp = 0
equipment = ""
weapons = [] # Replaced in __init__ constructor
magic_items = []
armor = None
shield = None
_proficiencies_text = tuple()
@@ -130,7 +133,6 @@ class Character():
def __init__(self, **attrs):
"""Takes a bunch of attrs and passes them to ``set_attrs``"""
self.weapons = []
# make sure class, race, background are set first
my_classes = attrs.pop('classes', [])
my_levels = attrs.pop('levels', [])
@@ -164,15 +166,16 @@ class Character():
def race(self, newrace):
if isinstance(newrace, race.Race):
self._race = newrace
self._race.owner = self
elif isinstance(newrace, type) and issubclass(newrace, race.Race):
self._race = newrace()
self._race = newrace(owner=self)
elif isinstance(newrace, str):
try:
self._race = findattr(race, newrace)()
self._race = findattr(race, newrace)(owner=self)
except AttributeError:
msg = (f'Race "{newrace}" not defined. '
f'Please add it to ``race.py``')
self._race = race.Race()
self._race = race.Race(owner=self)
warnings.warn(msg)
@property
@@ -183,15 +186,16 @@ class Character():
def background(self, bg):
if isinstance(bg, background.Background):
self._background = bg
self._background.owner = self
elif isinstance(bg, type) and issubclass(bg, background.Background):
self._background = bg()
self._background = bg(owner=self)
elif isinstance(bg, str):
try:
self._background = findattr(background, bg)()
self._background = findattr(background, bg)(owner=self)
except AttributeError:
msg = (f'Background "{bg}" not defined. '
f'Please add it to ``background.py``')
self._background = background.Background()
self._background = background.Background(owner=self)
warnings.warn(msg)
@property
@@ -218,10 +222,6 @@ class Character():
def subclasses(self):
return list([c.subclass or '' for c in self.class_list])
@property
def speed(self):
return getattr(self.race, 'speed', 30)
@property
def level(self):
if self.num_classes == 0:
@@ -367,9 +367,16 @@ class Character():
if attr == 'dungeonsheets_version':
pass # Maybe we'll verify this later?
elif attr == 'weapons':
if isinstance(val, str):
val = [val]
# Treat weapons specially
for weap in val:
self.wield_weapon(weap)
elif attr == 'magic_items':
if isinstance(val, str):
val = [val]
for mitem in val:
self.magic_items.append(mitem(owner=self))
elif attr == 'weapon_proficiencies':
self.other_weapon_proficiencies = tuple([findattr(weapons, w)
for w in val])
@@ -398,7 +405,7 @@ class Character():
name=f, source='Unknown',
__doc__="""Unknown Feature. Add to features.py"""))
warnings.warn(msg)
self.custom_features += tuple(F() for F in _features)
self.custom_features += tuple(F(owner=self) for F in _features)
elif (attr == 'spells') or (attr == 'spells_prepared'):
# Create a list of actual spell objects
_spells = []
@@ -600,30 +607,6 @@ class Character():
prof = 6
return prof
@property
def default_AC(self):
"""Armor class, including contributions from worn armor and shield."""
# Retrieve current armor (or a generic armor substitute)
armor = self.armor if self.armor is not None else NoArmor()
shield = self.shield if self.shield is not None else NoShield()
# Calculate and apply modifiers
if armor.dexterity_mod_max is None:
modifier = self.dexterity.modifier
else:
modifier = min(self.dexterity.modifier, armor.dexterity_mod_max)
# Calculate final armor class
ac = armor.base_armor_class + shield.base_armor_class + modifier
return ac
@property
def armor_class(self):
"""Armor class, including any applicable features"""
if hasattr(self, 'force_AC'):
return self.force_AC
ac = [self.default_AC]
ac += [f.AC_func(self) for f in self.features]
return max(ac)
def can_assume_shape(self, shape: monsters.Monster):
for c in self.class_list:
if isinstance(c, classes.Druid):
+2 -1
View File
@@ -100,7 +100,8 @@ class Barbarian(CharClass):
name = 'Barbarian'
hit_dice_faces = 12
saving_throw_proficiencies = ('strength', 'constitution')
weapon_proficiencies = (weapons.simple_weapons + weapons.martial_weapons)
primary_abilities = ('strength',)
weapon_proficiencies = (weapons.SimpleWeapon + weapons.MartialWearpon)
_proficiencies_text = ('light armor', 'medium armor', 'shields',
'simple weapons', 'martial weapons')
multiclass_weapon_proficiencies = weapon_proficiencies
+2 -1
View File
@@ -117,12 +117,13 @@ class Bard(CharClass):
name = 'Bard'
hit_dice_faces = 8
saving_throw_proficiencies = ('dexterity', 'charisma')
primary_abilities = ('charisma',)
_proficiencies_text = (
'Light armor', 'simple weapons', 'hand crossbows', 'longswords',
'rapiers', 'shortswords', 'three musical instruments of your choice')
weapon_proficiencies = ((weapons.HandCrossbow, weapons.Longsword,
weapons.Rapier, weapons.Shortsword) +
weapons.simple_weapons)
weapons.SimpleWeapon)
class_skill_choices = ('Acrobatics', 'Animal Handling', 'Arcana',
'Athletics', 'Deception', 'History', 'Insight',
'Intimidation', 'Investigation', 'Medicine',
+9 -7
View File
@@ -13,6 +13,8 @@ class CharClass():
_proficiencies_text = ()
multiclass_weapon_proficiencies = ()
_multiclass_proficiencies_text = ()
saving_throw_proficiencies = ()
primary_abilities = ()
languages = ()
class_skill_choices = ()
num_skill_choices = 2
@@ -34,10 +36,10 @@ class CharClass():
fs = []
for f in cls.features_by_level[i]:
if issubclass(f, FeatureSelector):
fs.append(f(feature_choices=feature_choices))
fs.append(f(owner=self.owner,
feature_choices=feature_choices))
elif issubclass(f, Feature):
fs.append(f())
fs = [f() for f in cls.features_by_level[i]]
fs.append(f(owner=self.owner))
self.features_by_level[i] = fs
for k, v in params.items():
setattr(self, k, v)
@@ -58,14 +60,14 @@ class CharClass():
return None
for sc in self.subclasses_available:
if subclass_str.lower() in sc.name.lower():
return sc(level=self.level)
return sc(owner=self.owner)
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] += ([f(owner=self.owner) for f in
self.subclass.features_by_level[i]])
for attr in ('weapon_proficiencies', '_proficiencies_text',
'spells_known', 'spells_prepared'):
@@ -112,9 +114,9 @@ class SubClass():
spells_known = ()
spells_prepared = ()
def __init__(self, level):
def __init__(self, owner):
self.owner = owner
self.__doc__ = self.__doc__ or SubClass.__doc__
self.level = level
def __str__(self):
return self.name
+2 -1
View File
@@ -184,9 +184,10 @@ class Cleric(CharClass):
name = 'Cleric'
hit_dice_faces = 8
saving_throw_proficiencies = ('wisdom', 'charisma')
primary_abilities = ('wisdom',)
_proficiencies_text = ('light armor', 'medium armor', 'shields',
'all simple weapons')
weapon_proficiencies = weapons.simple_weapons
weapon_proficiencies = (weapons.SimpleWeapon,)
multiclass_weapon_proficiencies = ()
_multiclass_proficiencies_text = ('light armor', 'medium armor', 'shields')
class_skill_choices = ('History', 'Insight', 'Medicine',
+1
View File
@@ -87,6 +87,7 @@ class Druid(CharClass):
_circle = ''
hit_dice_faces = 8
saving_throw_proficiencies = ('intelligence', 'wisdom')
primary_abilities = ('wisdom',)
languages = 'Druidic'
_proficiencies_text = (
'Light armor', 'medium armor',
+2 -1
View File
@@ -166,9 +166,10 @@ class Fighter(CharClass):
name = 'Fighter'
hit_dice_faces = 10
saving_throw_proficiencies = ('strength', 'constitution')
primary_abilities = ('strength', 'dexterity',)
_proficiencies_text = ('All armor', 'shields', 'simple weapons',
'martial weapons')
weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
weapon_proficiencies = (weapons.SimpleWeapon, weapons.MartialWeapon)
multiclass_weapon_proficiencies = weapon_proficiencies
_multiclass_proficiencies_text = ('light armor', 'medium armor',
'shields', 'simple weapons',
+4 -2
View File
@@ -111,10 +111,12 @@ class Monk(CharClass):
name = 'Monk'
hit_dice_faces = 8
saving_throw_proficiencies = ('strength', 'dexterity')
primary_abilities = ('dexterity', 'wisdom')
_proficiencies_text = (
'simple weapons', 'shortswords', 'unarmed',
"one type of artisan's tools or one musical instrument")
weapon_proficiencies = (weapons.Shortsword, weapons.Unarmed) + weapons.simple_weapons
weapon_proficiencies = (weapons.Shortsword, weapons.Unarmed,
weapons.SimpleWeapon)
multiclass_weapon_proficiencies = weapon_proficiencies
_multiclass_proficiencies_text = ('simple weapons', 'shortswords',
'unarmed')
@@ -125,7 +127,7 @@ class Monk(CharClass):
LongDeathWay, DrunkenMasterWay,
KenseiWay)
features_by_level = defaultdict(list)
features_by_level[1] = [features.UnarmoredDefense,
features_by_level[1] = [features.UnarmoredDefenseMonk,
features.MartialArts]
def __init__(self, level, subclass=None, **params):
+2 -1
View File
@@ -223,9 +223,10 @@ class Paladin(CharClass):
name = 'Paladin'
hit_dice_faces = 10
saving_throw_proficiencies = ('wisdom', 'charisma')
primary_abilities = ('strength', 'charisma')
_proficiencies_text = ('All armor', 'shields', 'simple weapons',
'martial weapons')
weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
weapon_proficiencies = (weapons.SimpleWeapon, weapons.MartialWeapon)
multiclass_weapon_proficiencies = weapon_proficiencies
_multiclass_proficiencies_text = ('light armor', 'medium armor', 'shields',
'simple weapons', 'martial weapons')
+2 -1
View File
@@ -75,9 +75,10 @@ class Ranger(CharClass):
name = 'Ranger'
hit_dice_faces = 10
saving_throw_proficiencies = ('strength', 'dexterity')
primary_abilities = ('dexterity', 'wisdom')
_proficiencies_text = ("light armor", "medium armor", "shields",
"simple weapons", "martial weapons")
weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
weapon_proficiencies = (weapons.SimpleWeapon, weapons.MartialWeapon)
multiclass_weapon_proficiencies = weapon_proficiencies
_multiclass_proficiencies_text = ('light armor', 'medium armor', 'shields',
'simple weapons', 'martial weapons',
+4 -3
View File
@@ -119,12 +119,13 @@ class Rogue(CharClass):
name = 'Rogue'
hit_dice_faces = 8
saving_throw_proficiencies = ('dexterity', 'intelligence')
primary_abilities = ('dexterity',)
_proficiencies_text = (
'light armor', 'simple weapons', 'hand crossbows', 'longswords',
'rapiers', 'shortswords', "thieves' tools")
weapon_proficiencies = weapons.simple_weapons + (
weapons.HandCrossbow, weapons.Longsword, weapons.Rapier,
weapons.Shortsword)
weapon_proficiencies = (weapons,SimpleWeapon, weapons.HandCrossbow,
weapons.Longsword, weapons.Rapier,
weapons.Shortsword)
multiclass_weapon_proficiencies = ()
_multiclass_proficiencies_text = ('light armor', "thieves' tools",
'[choose one skill from Rogue list]')
+1
View File
@@ -95,6 +95,7 @@ class Sorceror(CharClass):
name = 'Sorceror'
hit_dice_faces = 6
saving_throw_proficiencies = ('constitution', 'charisma')
primary_abilities = ('charisma',)
_proficiencies_text = ('daggers', 'darts', 'slings',
'quarterstaffs', 'light crossbows')
weapon_proficiencies = (weapons.Dagger, weapons.Dart,
+2 -1
View File
@@ -116,11 +116,12 @@ class Warlock(CharClass):
name = 'Warlock'
hit_dice_faces = 8
saving_throw_proficiencies = ('wisdom', 'charisma')
primary_abilities = ('charisma',)
_proficiencies_text = ("light Armor", "simple weapons")
class_skill_choices = ('Arcana', 'Deception', 'History',
'Intimidation', 'Investigation', 'Nature',
'Religion')
weapon_proficiencies = weapons.simple_weapons
weapon_proficiencies = (weapons.SimpleWeapon,)
multiclass_weapon_proficiencies = weapon_proficiencies
_multiclass_proficiencies_text = ('light armor', 'simple weapons')
features_by_level = defaultdict(list)
+1
View File
@@ -160,6 +160,7 @@ class Wizard(CharClass):
name = 'Wizard'
hit_dice_faces = 6
saving_throw_proficiencies = ('intelligence', 'wisdom')
primary_abilities = ('intelligence',)
_proficiencies_text = ('daggers', 'darts', 'slings',
'quarterstaffs', 'light crossbows')
weapon_proficiencies = (weapons.Dagger, weapons.Dart,
+23
View File
@@ -0,0 +1,23 @@
from .features import Feature
from .. import (weapons, armor)
class UnarmoredDefenseBarbarian(Feature):
"""While you are not wearing any armor, your Armor Class equals 10 + your
Dexterity modifier + your Constitution modifier. You can use a shield and
still gain this benefit.
This bonus is computed in the AC given on the Character Sheet above.
"""
name = "Unarmored Defense"
source = 'Barbarian'
class FastMovement(Feature):
"""Starting at 5th level, your speed increases by 10 feet while you arent
wearing heavy armor.
"""
name = "Fast Movement"
source = "Barbarian"
+7 -21
View File
@@ -26,11 +26,15 @@ class Feature():
Provide full text of rules in documentation
"""
name = "Generic Feature"
owner = None
source = '' # race, class, background, etc.
spells_known = ()
spells_prepared = ()
needs_implementation = False # Set to True if need to find way to compute stats
def __init__(self, owner):
self.owner = owner
def __eq__(self, other):
return (self.name == other.name) and (self.source == other.source)
@@ -62,26 +66,6 @@ class Feature():
"""
return weapon
def AC_func(self, char, **kwargs):
"""
Return the alternative AC from having this feat
The character will take max AC from all available feats / standard AC,
so the default is to output very low AC
Parameters
----------
char
Character object, to check for necessary abilities, etc.
kwargs
Any other key-word arguments the function may require
Returns
-------
AC : integer armor class from this feature
"""
return -100
class FeatureSelector(Feature):
"""
@@ -89,11 +73,13 @@ class FeatureSelector(Feature):
"""
options = dict()
def __init__(self, feature_choices=[]):
def __init__(self, owner, feature_choices=[]):
self.owner = owner
keep_source = self.source
# Look for matching feature_choices
for selection in feature_choices:
if selection.lower() in self.options():
feature_choices.remove(selection)
new_feat = self.options[selection.lower()]
self.__dict__.update(new_feat.__dict__)
new_feat.__init__(self)
+39 -21
View File
@@ -2,24 +2,16 @@ from .features import Feature
from .. import (weapons, armor)
class UnarmoredDefense(Feature):
class UnarmoredDefenseMonk(Feature):
"""Beginning at 1st level, while you are wearing no armor and not wearing a
shield, your AC equals 10 + your Dexterity modifier + your Wisdom modifier.
This bonus is computed in the AC given on the Character Sheet above.
"""
name = "Unarmored Defense"
source = 'Monk'
def AC_func(self, char, **kwargs):
no_armor = ((char.armor is None)
or (isinstance(char.armor, armor.NoAmor)))
no_shield = ((char.shield is None)
or (isinstance(char.shield, armor.NoShield)))
if no_armor and no_shield:
return 10 + char.dexterity.modifier + char.wisdom.modifier
else:
return -100
class MartialArts(Feature):
"""At 1st level, your practice of martial arts gives you mastery of combat
@@ -50,13 +42,17 @@ class MartialArts(Feature):
"""
name = "Martial Arts"
source = 'Monk'
die_by_level = {1: 'd4', 2: 'd4', 3: 'd4', 4: 'd4',
5: 'd6', 6: 'd6', 7: 'd6', 8: 'd6',
9: 'd6', 10: 'd6', 11: 'd8', 12: 'd8',
13: 'd8', 14: 'd8', 15: 'd8', 16: 'd8',
17: 'd10', 18: 'd10', 19: 'd10', 20: 'd10'}
level = 1
die = 'd4'
def __init__(self, owner):
self.owner = owner
if self.owner.level >= 5:
self.die = 'd6'
if self.owner.level >= 11:
self.die = 'd8'
if self.owner.level >= 17:
self.die = 'd10'
def weapon_func(self, weapon: weapons.Weapon, char=None, **kwargs):
"""
Update increasing damage dice and DEX mod of Monk weapons
@@ -68,10 +64,32 @@ class MartialArts(Feature):
if char is None:
return weapon
# check if new damage is better than default
new_die = int(self.die_by_level[self.level].strip('d'))
if new_die > int(weapon.base_damage.split('d')[-1]):
weapon.base_damage = '1d' + str(new_die)
if self.die > int(weapon.base_damage.split('d')[-1]):
weapon.base_damage = '1d' + str(self.die)
weapon.is_finesse = True
return weapon
class UnarmoredMovement(Feature):
"""Starting at 2nd level, your speed increases by 10 feet while you are not
wearing armor or wielding a shield. This bonus increases when you reach
certain monk levels, as shown in the Monk table.
At 9th level, you gain the ability to move along vertical surfaces and
across liquids on your turn without falling during the move.
"""
name = "Unarmored Movement"
source = "Monk"
speed_bonus = 10
def __init__(self, owner):
self.owner = owner
if self.owner.level >= 6:
self.speed_bonus = 15
if self.owner.level >= 10:
self.speed_bonus = 20
if self.owner.level >= 14:
self.speed_bonus = 25
if self.owner.level >= 18:
self.speed_bonus = 30
-9
View File
@@ -40,15 +40,6 @@ class Defense(Feature):
name = "Fighting Style (Defense)"
source = "Ranger"
def AC_func(self, char, **kwargs):
"""
Apply a +1 bonus if wearing armor
"""
if (char.armor is None) or (isinstance(char.armor, armor.NoArmor)):
return char.default_AC
else:
return char.default_AC + 1
class Dueling(Feature):
"""When you are wielding a melee weapon in one hand and no other weapons, you
+17
View File
@@ -0,0 +1,17 @@
from .features import Feature
class DraconicResilience(Feature):
"""As magic flows through your body, it causes physical traits of your dragon
ancestors to emerge. At 1st level, your hit point maximum increases by 1
and increases by 1 again whenever you gain a level in this class.
Additionally, parts of your skin are covered by a thin sheen of dragon-like
scales. When you arent wearing armor, your AC equals 13 + your Dexterity
modifier.
This bonus is computed in the AC given on the Character Sheet above.
"""
name = "Draconic Resilience"
source = "Sorceror (Draconic Bloodline)"
+6 -3
View File
@@ -10,9 +10,10 @@ dungeonsheets_version = "{{ char.dungeonsheets_version }}"
name = "{{ char.name }}"
player_name = "{{ char.player_name }}"
classes = {{ char.class_names }}
levels = {{ char.levels }}
subclasses = {{ char.subclasses }}
# Be sure to list Primary class first
classes = {{ char.class_names }} # ex: ['Wizard'] or ['Rogue', 'Fighter']
levels = {{ char.levels }} # ex: [10] or [3, 2]
subclasses = {{ char.subclasses }} # ex: ['Necromacy'] or ['Thief', None]
background = "{{ char.background.name }}"
race = "{{ char.race.name }}"
alignment = "{{ char.alignment }}"
@@ -28,6 +29,7 @@ wisdom = {{ char.wisdom.value }}
charisma = {{ char.charisma.value }}
# Select what skills you're proficient with
# ex: skill_proficiencies = ('athletics', 'acrobatics', 'arcana')
skill_proficiencies = {{ char.skill_proficiencies }}
# Named features / feats that aren't part of your classes,
@@ -54,6 +56,7 @@ pp = 0
# TODO: Put your equipped weapons and armor here
weapons = {{ char.weapons }} # Example: ('shortsword', 'longsword')
magic_items = {{ char.magic_items }} # Example: ('ring of protection',)
armor = "{{ char.armor }}" # Eg "light leather armor"
shield = "{{ char.shield }}" # Eg "shield"
+7 -5
View File
@@ -13,9 +13,10 @@ dungeonsheets_version = "{{ char.dungeonsheets_version }}"
name = "{{ char.name }}"
player_name = "{{ char.player_name }}"
classes = {{ char.class_names }}
levels = {{ char.levels }}
subclasses = {{ char.subclasses }}
# Be sure to list Primary class first
classes = {{ char.class_names }} # ex: ['Wizard'] or ['Rogue', 'Fighter']
levels = {{ char.levels }} # ex: [10] or [3, 2]
subclasses = {{ char.subclasses }} # ex: ['Necromacy'] or ['Thief', None]
background = "{{ char.background.name }}"
race = "{{ char.race.name }}"
alignment = "{{ char.alignment }}"
@@ -31,10 +32,10 @@ wisdom = {{ char.wisdom.value }}
charisma = {{ char.charisma.value }}
# Select what skills you're proficient with
# ex: skill_proficiencies = ('athletics', 'acrobatics', 'arcana')
skill_proficiencies = {{ char.skill_proficiencies }}
# Named features / feats that aren't part of your classes,
# race, or background.
# Named features / feats that aren't part of your classes, race, or background.
# Example:
# features = ('Tavern Brawler',) # take the optional Feat from PHB
features = ()
@@ -57,6 +58,7 @@ pp = 0
# TODO: Put your equipped weapons and armor here
weapons = () # Example: ('shortsword', 'longsword')
magic_items = () # Example: ('ring of protection',)
armor = "" # Eg "light leather armor"
shield = "" # Eg "shield"
+20
View File
@@ -0,0 +1,20 @@
class MagicItem():
"""
Generic Magic Item. Add description here.
"""
name = ''
ac_bonus = 0
requires_attunement = False
rarity = ''
class RingOfProtection(MagicItem):
"""
You gain a +1 bonus to AC and Saving Throws while wearing this ring.
"""
name = "Ring of Protection"
ac_bonus = 1
requires_attunement = True
rarity = 'Rare'
+5 -3
View File
@@ -7,6 +7,7 @@ class Race():
name = "Unknown"
size = "medium"
speed = 30
owner = None
languages = ('Common', )
proficiencies_text = tuple()
weapon_proficiences = tuple()
@@ -25,13 +26,14 @@ class Race():
spells_known = ()
spells_prepared = ()
def __init__(self):
def __init__(self, owner):
self.owner = owner
cls = type(self)
# Instantiate the features
self.features = tuple([f() for f in cls.features])
self.features = tuple([f(owner=self.owner) 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] = [f(owner=self.owner)for f in
cls.features_by_level[i]]
def __str__(self):
+68 -5
View File
@@ -1,5 +1,10 @@
import math
from collections import namedtuple
from .armor import NoArmor, NoShield, HeavyArmor
from . import (weapons)
from .features import (UnarmoredDefenseMonk, UnarmoredDefenseBarbarian,
DraconicResilience, Defense, FastMovement,
UnarmoredMovement)
def findattr(obj, name):
@@ -20,16 +25,18 @@ def findattr(obj, name):
raise AttributeError(f'{obj} has no attribute {name}')
return attr
def mod_str(modifier):
"""Converts a modifier to a string, eg 2 -> '+2'."""
if modifier > 0:
mod_str = '+' + str(modifier)
return '{:+d}'.format(modifier)
if modifier == 0:
return str(modifier)
else:
mod_str = str(modifier)
return mod_str
return '{:+}'.format(modifier)
AbilityScore = namedtuple('AbilityScore', ('value', 'modifier', 'saving_throw'))
AbilityScore = namedtuple('AbilityScore',
('value', 'modifier', 'saving_throw'))
class Ability():
@@ -93,3 +100,59 @@ class Skill():
if is_expert:
modifier += character.proficiency_bonus
return modifier
class ArmorClass():
"""
The Armor Class of a character
"""
def __get__(self, char, Character):
armor = char.armor or NoArmor()
ac = armor.base_armor_class
shield = char.shield or NoShield()
ac += shield.base_armor_class
# calculate and apply modifiers
if armor.dexterity_mod_max is None:
ac += char.dexterity.modifier
else:
ac += min(char.dexterity.modifier, armor.dexterity_mod_max)
# Compute feature-specific additions
if any([isinstance(f, UnarmoredDefenseMonk) for f in char.features]):
if (isinstance(armor, NoArmor) and isinstance(shield, NoShield)):
ac += char.wisdom.modifier
if any([isinstance(f, UnarmoredDefenseBarbarian) for f in char.features]):
if isinstance(armor, NoArmor):
ac += char.constitution.modifier
if any([isinstance(f, DraconicResilience) for f in char.features]):
if isinstance(armor, NoArmor):
ac += 3
if any([isinstance(f, Defense) for f in char.features]):
if not isinstance(armor, NoArmor):
ac += 1
# Check if any magic items add to AC
for mitem in char.magic_items:
if hasattr(mitem, 'ac_bonus'):
ac += mitem.ac_bonus
return ac
class Speed():
"""
The speed of a character
"""
def __get__(self, char, Character):
base_speed = char.race.speed
other_speed = ''
if isinstance(base_speed, str):
base_speed = int(base_speed[:2]) # ignore other speeds, like fly
other_speed = base_speed[2:]
if any([isinstance(f, FastMovement) for f in char.features]):
if not isinstance(char.armor, HeavyArmor):
base_speed += 10
if isinstance(char.armor, NoArmor) or (char.armor is None):
for f in char.features:
if isinstance(f, UnarmoredMovement):
base_speed += f.speed_bonus
return '{:d}{:s}'.format(base_speed, other_speed)
+61 -49
View File
@@ -20,18 +20,30 @@ class Weapon():
dam_str += mod_str(self.bonus_damage)
return dam_str
@property
def is_ranged(self):
return ('range' in self.properties.lower()) and ('thrown' not in self.properties.lower())
def __str__(self):
return self.name
def __repr__(self):
return "\"{:s}\"".format(self.name)
class MeleeWeapon(Weapon):
name = "Melee Weapons"
class RangedWeapon(Weapon):
name = "Ranged Weapons"
class SimpleWeapon(Weapon):
name = "Simple Weapons"
class MartialWeapon(Weapon):
name = "Martial Weapons"
class Club(Weapon):
class Club(SimpleWeapon, MeleeWeapon):
name = "Club"
cost = "1 sp"
base_damage = "1d4"
@@ -41,7 +53,7 @@ class Club(Weapon):
ability = 'strength'
class Dagger(Weapon):
class Dagger(SimpleWeapon, MeleeWeapon):
name = "Dagger"
cost = "2 gp"
base_damage = "1d4"
@@ -52,7 +64,7 @@ class Dagger(Weapon):
ability = 'strength'
class Greatclub(Weapon):
class Greatclub(SimpleWeapon, MeleeWeapon):
name = "Greatclub"
cost = "2 sp"
base_damage = "1d8"
@@ -62,7 +74,7 @@ class Greatclub(Weapon):
ability = 'strength'
class Handaxe(Weapon):
class Handaxe(SimpleWeapon, MeleeWeapon):
name = "Handaxe"
cost = "5 gp"
base_damage = "1d6"
@@ -72,7 +84,7 @@ class Handaxe(Weapon):
ability = 'strength'
class Javelin(Weapon):
class Javelin(SimpleWeapon, MeleeWeapon):
name = "Javelin"
cost = "5 sp"
base_damage = "1d6"
@@ -82,7 +94,7 @@ class Javelin(Weapon):
ability = 'strength'
class LightHammer(Weapon):
class LightHammer(SimpleWeapon, MeleeWeapon):
name = "Light hammer"
cost = "2 gp"
base_damage = "1d4"
@@ -92,7 +104,7 @@ class LightHammer(Weapon):
ability = 'strength'
class Mace(Weapon):
class Mace(SimpleWeapon, MeleeWeapon):
name = "Mace"
cost = "5 gp"
base_damage = "1d6"
@@ -102,7 +114,7 @@ class Mace(Weapon):
ability = 'strength'
class Quarterstaff(Weapon):
class Quarterstaff(SimpleWeapon, MeleeWeapon):
name = "Quarterstaff"
cost = "2 sp"
base_damage = "1d6"
@@ -112,7 +124,7 @@ class Quarterstaff(Weapon):
ability = 'strength'
class Sickle(Weapon):
class Sickle(SimpleWeapon, MeleeWeapon):
name = "Sickle"
cost = "1 gp"
base_damage = "1d4"
@@ -122,7 +134,7 @@ class Sickle(Weapon):
ability = 'strength'
class Spear(Weapon):
class Spear(SimpleWeapon, MeleeWeapon):
name = "Spear"
cost = "1 gp"
base_damage = "1d6"
@@ -132,7 +144,7 @@ class Spear(Weapon):
ability = 'strength'
class LightCrossbow(Weapon):
class LightCrossbow(SimpleWeapon, RangedWeapon):
name = "Light crossbow"
cost = "25 gp"
base_damage = "1d8"
@@ -142,7 +154,7 @@ class LightCrossbow(Weapon):
ability = 'dexterity'
class Dart(Weapon):
class Dart(SimpleWeapon, RangedWeapon):
name = "Dart"
cost = "5 cp"
base_damage = "1d4"
@@ -153,7 +165,7 @@ class Dart(Weapon):
ability = 'dexterity'
class Shortbow(Weapon):
class Shortbow(SimpleWeapon, RangedWeapon):
name = "Shortbow"
cost = "25 gp"
base_damage = "1d6"
@@ -163,7 +175,7 @@ class Shortbow(Weapon):
ability = 'dexterity'
class Sling(Weapon):
class Sling(SimpleWeapon, RangedWeapon):
name = "Sling"
cost = "1 sp"
base_damage = "1d4"
@@ -173,7 +185,7 @@ class Sling(Weapon):
ability = 'dexterity'
class Battleaxe(Weapon):
class Battleaxe(MartialWeapon, MeleeWeapon):
name = "Battleaxe"
cost = "10 gp"
base_damage = "1d8"
@@ -183,7 +195,7 @@ class Battleaxe(Weapon):
ability = 'strength'
class Flail(Weapon):
class Flail(MartialWeapon, MeleeWeapon):
name = "Flail"
cost = "10gp"
base_damage = "1d8"
@@ -193,7 +205,7 @@ class Flail(Weapon):
ability = 'strength'
class Glaive(Weapon):
class Glaive(MartialWeapon, MeleeWeapon):
name = "Glaive"
cost = "20 gp"
base_damage = "1d10"
@@ -203,7 +215,7 @@ class Glaive(Weapon):
ability = 'strength'
class Greataxe(Weapon):
class Greataxe(MartialWeapon, MeleeWeapon):
name = "Greataxe"
cost = "30 gp"
base_damage = "1d12"
@@ -213,7 +225,7 @@ class Greataxe(Weapon):
ability = 'strength'
class Greatsword(Weapon):
class Greatsword(MartialWeapon, MeleeWeapon):
name = "Greatsword"
cost = "50 gp"
base_damage = "2d6"
@@ -223,7 +235,7 @@ class Greatsword(Weapon):
ability = 'strength'
class Halberd(Weapon):
class Halberd(MartialWeapon, MeleeWeapon):
name = "Halberd"
cost = "20 gp"
base_damage = "1d10"
@@ -233,7 +245,7 @@ class Halberd(Weapon):
ability = 'strength'
class Lance(Weapon):
class Lance(MartialWeapon, MeleeWeapon):
name = "Lance"
cost = "10gp"
base_damage = "1d12"
@@ -243,7 +255,7 @@ class Lance(Weapon):
ability = 'strength'
class Longsword(Weapon):
class Longsword(MartialWeapon, MeleeWeapon):
name = "Longsword"
cost = "15 gp"
base_damage = "1d8"
@@ -253,7 +265,7 @@ class Longsword(Weapon):
ability = 'strength'
class Maul(Weapon):
class Maul(MartialWeapon, MeleeWeapon):
name = "Maul"
cost = "10 gp"
base_damage = "2d6"
@@ -263,7 +275,7 @@ class Maul(Weapon):
ability = 'strength'
class Morningstar(Weapon):
class Morningstar(MartialWeapon, MeleeWeapon):
name = "Morningstar"
cost = "15 gp"
base_damage = "1d8"
@@ -273,7 +285,7 @@ class Morningstar(Weapon):
ability = 'strength'
class Pike(Weapon):
class Pike(MartialWeapon, MeleeWeapon):
name = "Pike"
cost = "5 gp"
base_damage = "1d10"
@@ -283,7 +295,7 @@ class Pike(Weapon):
ability = 'strength'
class Rapier(Weapon):
class Rapier(MartialWeapon, MeleeWeapon):
name = "Rapier"
cost = "25 gp"
base_damage = "1d8"
@@ -294,7 +306,7 @@ class Rapier(Weapon):
ability = 'strength'
class Scimitar(Weapon):
class Scimitar(MartialWeapon, MeleeWeapon):
name = "Scimitar"
cost = "25 gp"
base_damage = "1d6"
@@ -305,7 +317,7 @@ class Scimitar(Weapon):
ability = 'strength'
class Shortsword(Weapon):
class Shortsword(MartialWeapon, MeleeWeapon):
name = "Shortsword"
cost = "10 gp"
base_damage = "1d6"
@@ -316,7 +328,7 @@ class Shortsword(Weapon):
ability = 'strength'
class ThrowingHammer(Weapon):
class ThrowingHammer(MartialWeapon, MeleeWeapon):
name = "Throwing Hammer"
cost = "15 gp"
base_damage = '1d6'
@@ -326,7 +338,7 @@ class ThrowingHammer(Weapon):
ability = "strength"
class Trident(Weapon):
class Trident(MartialWeapon, MeleeWeapon):
name = "Trident"
cost = "5 gp"
base_damage = "1d6"
@@ -336,7 +348,7 @@ class Trident(Weapon):
ability = 'strength'
class WarPick(Weapon):
class WarPick(MartialWeapon, MeleeWeapon):
name = "War pick"
cost = "5 gp"
base_damage = "1d8"
@@ -346,7 +358,7 @@ class WarPick(Weapon):
ability = 'strength'
class Warhammer(Weapon):
class Warhammer(MartialWeapon, MeleeWeapon):
name = "Warhammer"
cost = "15 gp"
base_damage = "1d8"
@@ -356,7 +368,7 @@ class Warhammer(Weapon):
ability = 'strength'
class Whip(Weapon):
class Whip(MartialWeapon, MeleeWeapon):
name = "Whip"
cost = "2 gp"
base_damage = "1d4"
@@ -367,7 +379,7 @@ class Whip(Weapon):
ability = 'strength'
class Blowgun(Weapon):
class Blowgun(MartialWeapon, RangedWeapon):
name = "Blowgun"
cost = "10 gp"
base_damage = "1"
@@ -377,7 +389,7 @@ class Blowgun(Weapon):
ability = 'dexterity'
class HandCrossbow(Weapon):
class HandCrossbow(MartialWeapon, RangedWeapon):
name = "Crossbow, hand"
cost = "75 gp"
base_damage = "1d6"
@@ -387,7 +399,7 @@ class HandCrossbow(Weapon):
ability = 'dexterity'
class HeavyCrossbow(Weapon):
class HeavyCrossbow(MartialWeapon, RangedWeapon):
name = "Crossbow, heavy"
cost = "50 gp"
base_damage = "1d10"
@@ -397,7 +409,7 @@ class HeavyCrossbow(Weapon):
ability = 'dexterity'
class Longbow(Weapon):
class Longbow(MartialWeapon, RangedWeapon):
name = "Longbow"
cost = "50 gp"
base_damage = "1d8"
@@ -407,7 +419,7 @@ class Longbow(Weapon):
ability = 'dexterity'
class Net(Weapon):
class Net(MartialWeapon, RangedWeapon):
name = "Net"
cost = "1 gp"
base_damage = "-"
@@ -417,7 +429,7 @@ class Net(Weapon):
ability = 'strength'
class Unarmed(Weapon):
class Unarmed(MeleeWeapon):
name = "Unarmed"
cost = "0 gp"
base_damage = "1"
@@ -428,7 +440,7 @@ class Unarmed(Weapon):
# Custom weapons
class HeavyRight(Weapon):
class HeavyRight(MeleeWeapon):
base_damage = "1d4"
name = "Heavy Right"
damage_type = 'b'
@@ -436,7 +448,7 @@ class HeavyRight(Weapon):
attack_bonus = -5 # Heavy weapon master
class HeavyLeft(Weapon):
class HeavyLeft(MeleeWeapon):
base_damage = "1d4"
name = "Heavy Left"
damage_type = 'b'
@@ -444,7 +456,7 @@ class HeavyLeft(Weapon):
attack_bonus = -5 # Heavy weapon master
class Bite(Weapon):
class Bite(MeleeWeapon):
name = "Bite"
base_damage = "1d4"
damage_type = 'p'
@@ -454,7 +466,7 @@ class Bite(Weapon):
ability = "strength"
class Talons(Weapon):
class Talons(MeleeWeapon):
name = 'Talons'
base_damage = '1d4'
damage_type = 's'
@@ -464,7 +476,7 @@ class Talons(Weapon):
ability = 'strength'
class Claws(Weapon):
class Claws(MeleeWeapon):
name = 'Claws'
base_damage = '1d4'
damage_type = 's'
@@ -474,7 +486,7 @@ class Claws(Weapon):
ability = 'strength'
class Firearm(Weapon):
class Firearm(RangedWeapon):
name = 'Firearm'
ability = 'dexterity'
damage_type = 'p'