mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 13:15:53 +02:00
first features implemented
This commit is contained in:
@@ -3,6 +3,10 @@ Add multiclass proficiencies to classes
|
|||||||
Add Warlock Incantations
|
Add Warlock Incantations
|
||||||
Add Warlock multiclass spell slots
|
Add Warlock multiclass spell slots
|
||||||
|
|
||||||
|
Add disadvantage on STEALTH with armor
|
||||||
|
|
||||||
|
Add race / class AC bonuses
|
||||||
|
|
||||||
Add subclasses
|
Add subclasses
|
||||||
Add features
|
Add features
|
||||||
Auto-add features to PDF
|
Auto-add features to PDF
|
||||||
|
|||||||
+11
-1
@@ -154,10 +154,20 @@ class HeavySplintArmor(Armor):
|
|||||||
|
|
||||||
|
|
||||||
class HeavyPlateArmor(Armor):
|
class HeavyPlateArmor(Armor):
|
||||||
name = "Heavy splint armor"
|
name = "Heavy plate armor"
|
||||||
cost = "1,500 gp"
|
cost = "1,500 gp"
|
||||||
base_armor_class = 18
|
base_armor_class = 18
|
||||||
dexterity_mod_max = 0
|
dexterity_mod_max = 0
|
||||||
strength_required = 15
|
strength_required = 15
|
||||||
stealth_disadvantage = True
|
stealth_disadvantage = True
|
||||||
weight = 65
|
weight = 65
|
||||||
|
|
||||||
|
|
||||||
|
# Custom Armor
|
||||||
|
class ElvenChain(Armor):
|
||||||
|
name = 'Elven Chain'
|
||||||
|
cost = '5,000 gp'
|
||||||
|
base_armor_class = 14
|
||||||
|
dexerity_mod_max = 2
|
||||||
|
weight = 20
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
from . import features as feats
|
||||||
|
|
||||||
|
|
||||||
class Background():
|
class Background():
|
||||||
name = "Generic background"
|
name = "Generic background"
|
||||||
skill_proficiencies = ()
|
skill_proficiencies = ()
|
||||||
weapon_proficiencies = ()
|
weapon_proficiencies = ()
|
||||||
proficiencies_text = ()
|
proficiencies_text = ()
|
||||||
|
features = ()
|
||||||
languages = ()
|
languages = ()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -98,3 +102,14 @@ class Soldier(Background):
|
|||||||
class Urchin(Background):
|
class Urchin(Background):
|
||||||
name = "Urchin"
|
name = "Urchin"
|
||||||
skill_proficiencies = ('sleight of hand', 'stealth')
|
skill_proficiencies = ('sleight of hand', 'stealth')
|
||||||
|
|
||||||
|
|
||||||
|
class UrbanBountyHunter(Background):
|
||||||
|
name = 'Urban Bounty Hunter'
|
||||||
|
skill_proficiencies = ('[choose one]', '[choose one]')
|
||||||
|
|
||||||
|
|
||||||
|
class FarTraveler(Background):
|
||||||
|
name = 'Far Traveler'
|
||||||
|
skill_proficiencies = ('insight', 'perception')
|
||||||
|
languages = ('[choose one]',)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import importlib.util
|
|||||||
from .stats import Ability, Skill, findattr
|
from .stats import Ability, Skill, findattr
|
||||||
from .dice import read_dice_str
|
from .dice import read_dice_str
|
||||||
from . import (weapons, race, background, spells, armor, monsters,
|
from . import (weapons, race, background, spells, armor, monsters,
|
||||||
exceptions, classes)
|
exceptions, classes, features)
|
||||||
from .weapons import Weapon
|
from .weapons import Weapon
|
||||||
from .armor import Armor, NoArmor, Shield, NoShield
|
from .armor import Armor, NoArmor, Shield, NoShield
|
||||||
|
|
||||||
@@ -62,7 +62,9 @@ class Character():
|
|||||||
intelligence = Ability()
|
intelligence = Ability()
|
||||||
wisdom = Ability()
|
wisdom = Ability()
|
||||||
charisma = Ability()
|
charisma = Ability()
|
||||||
|
other_weapon_proficiencies = tuple()
|
||||||
skill_proficiencies = tuple()
|
skill_proficiencies = tuple()
|
||||||
|
skill_expertise = tuple()
|
||||||
class_skill_choices = tuple()
|
class_skill_choices = tuple()
|
||||||
num_skill_choices = 2
|
num_skill_choices = 2
|
||||||
proficiencies_extra = tuple()
|
proficiencies_extra = tuple()
|
||||||
@@ -108,7 +110,8 @@ class Character():
|
|||||||
spellcasting_ability = None
|
spellcasting_ability = None
|
||||||
spells = tuple()
|
spells = tuple()
|
||||||
spells_prepared = tuple()
|
spells_prepared = tuple()
|
||||||
# MISC
|
# Features IN MAJOR DEVELOPMENT
|
||||||
|
other_features = ()
|
||||||
|
|
||||||
def __init__(self, **attrs):
|
def __init__(self, **attrs):
|
||||||
"""Takes a bunch of attrs and passes them to ``set_attrs``"""
|
"""Takes a bunch of attrs and passes them to ``set_attrs``"""
|
||||||
@@ -158,9 +161,10 @@ class Character():
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def weapon_proficiencies(self):
|
def weapon_proficiencies(self):
|
||||||
|
wp = set(self.other_weapon_proficiencies)
|
||||||
if not self.class_initialized:
|
if not self.class_initialized:
|
||||||
return ()
|
return wp
|
||||||
wp = set(self.primary_class.weapon_proficiencies)
|
wp |= set(self.primary_class.weapon_proficiencies)
|
||||||
if self.num_classes > 1:
|
if self.num_classes > 1:
|
||||||
for c in self.class_list[1:]:
|
for c in self.class_list[1:]:
|
||||||
wp |= set(c.multiclass_weapon_proficiencies)
|
wp |= set(c.multiclass_weapon_proficiencies)
|
||||||
@@ -168,7 +172,20 @@ class Character():
|
|||||||
wp |= set(getattr(self.race, 'weapon_proficiencies', ()))
|
wp |= set(getattr(self.race, 'weapon_proficiencies', ()))
|
||||||
if self.background is not None:
|
if self.background is not None:
|
||||||
wp |= set(getattr(self.background, 'weapon_proficiencies', ()))
|
wp |= set(getattr(self.background, 'weapon_proficiencies', ()))
|
||||||
return wp
|
return tuple(wp)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def features(self):
|
||||||
|
fts = set(self.other_features)
|
||||||
|
if not self.class_initialized:
|
||||||
|
return fts
|
||||||
|
for c in self.class_list:
|
||||||
|
fts |= set(c.features)
|
||||||
|
if self.race is not None:
|
||||||
|
fts |= set(getattr(self.race, 'features', ()))
|
||||||
|
if self.background is not None:
|
||||||
|
fts |= set(getattr(self.background, 'features', ()))
|
||||||
|
return tuple(fts)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def saving_throw_proficiencies(self):
|
def saving_throw_proficiencies(self):
|
||||||
@@ -216,6 +233,9 @@ class Character():
|
|||||||
# Treat weapons specially
|
# Treat weapons specially
|
||||||
for weap in val:
|
for weap in val:
|
||||||
self.wield_weapon(weap)
|
self.wield_weapon(weap)
|
||||||
|
elif attr == 'weapon_proficiencies':
|
||||||
|
self.other_weapon_proficiencies = tuple([findattr(weapons, w)
|
||||||
|
for w in val])
|
||||||
elif attr == 'race':
|
elif attr == 'race':
|
||||||
MyRace = findattr(race, val)
|
MyRace = findattr(race, val)
|
||||||
self.race = MyRace()
|
self.race = MyRace()
|
||||||
@@ -240,6 +260,18 @@ class Character():
|
|||||||
c.circle = val
|
c.circle = val
|
||||||
self.circle = val
|
self.circle = val
|
||||||
break
|
break
|
||||||
|
elif attr == 'features':
|
||||||
|
if isinstance(val, str):
|
||||||
|
val = [val]
|
||||||
|
_features = []
|
||||||
|
for f in val:
|
||||||
|
try:
|
||||||
|
_features.append(findattr(features, f)())
|
||||||
|
except AttributeError:
|
||||||
|
msg = (f'Feature "{f}" not defined. '
|
||||||
|
f'Please add it to ``features.py``')
|
||||||
|
warnings.warn(msg)
|
||||||
|
self.other_features = tuple(_features)
|
||||||
elif (attr == 'spells') or (attr == 'spells_prepared'):
|
elif (attr == 'spells') or (attr == 'spells_prepared'):
|
||||||
# Create a list of actual spell objects
|
# Create a list of actual spell objects
|
||||||
_spells = []
|
_spells = []
|
||||||
@@ -287,6 +319,10 @@ class Character():
|
|||||||
weapon
|
weapon
|
||||||
The weapon to be tested for proficiency.
|
The weapon to be tested for proficiency.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Boolean: is this character proficient with this weapon?
|
||||||
|
|
||||||
"""
|
"""
|
||||||
all_proficiencies = self.weapon_proficiencies
|
all_proficiencies = self.weapon_proficiencies
|
||||||
is_proficient = any((isinstance(weapon, W) for W in all_proficiencies))
|
is_proficient = any((isinstance(weapon, W) for W in all_proficiencies))
|
||||||
@@ -320,6 +356,14 @@ class Character():
|
|||||||
final_text += '.'
|
final_text += '.'
|
||||||
return final_text
|
return final_text
|
||||||
|
|
||||||
|
@property
|
||||||
|
def features_text(self):
|
||||||
|
s = '\n\n*'.join([f.name for f in self.features])
|
||||||
|
if s != '':
|
||||||
|
s = '(See Features Details Page)\n\n*' + s
|
||||||
|
s += '\n\n=================\n\n'
|
||||||
|
return s
|
||||||
|
|
||||||
def wear_armor(self, new_armor):
|
def wear_armor(self, new_armor):
|
||||||
"""Accepts a string or Armor class and replaces the current armor.
|
"""Accepts a string or Armor class and replaces the current armor.
|
||||||
|
|
||||||
@@ -381,6 +425,11 @@ class Character():
|
|||||||
# Check for prifiency
|
# Check for prifiency
|
||||||
if self.is_proficient(weapon_):
|
if self.is_proficient(weapon_):
|
||||||
weapon_.attack_bonus += self.proficiency_bonus
|
weapon_.attack_bonus += self.proficiency_bonus
|
||||||
|
# check if features add any bonuses
|
||||||
|
for f in self.features:
|
||||||
|
a_bonus, d_bonus = f.weapon_func(weapon_)
|
||||||
|
weapon_.attack_bonus += a_bonus
|
||||||
|
weapon_.bonus_damage += d_bonus
|
||||||
# Save it to the array
|
# Save it to the array
|
||||||
self.weapons.append(weapon_)
|
self.weapons.append(weapon_)
|
||||||
|
|
||||||
@@ -422,8 +471,9 @@ class Character():
|
|||||||
else:
|
else:
|
||||||
modifier = min(self.dexterity.modifier, armor.dexterity_mod_max)
|
modifier = min(self.dexterity.modifier, armor.dexterity_mod_max)
|
||||||
# Calculate final armor class
|
# Calculate final armor class
|
||||||
ac = armor.base_armor_class + shield.base_armor_class + modifier
|
ac = [armor.base_armor_class + shield.base_armor_class + modifier]
|
||||||
return ac
|
ac += [f.AC_func(self) for f in self.features]
|
||||||
|
return max(ac)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, character_file):
|
def load(cls, character_file):
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
__all__ = ('Barbarian', 'Bard', 'Cleric', 'Druid', 'Fighter', 'Monk',
|
__all__ = ('Barbarian', 'Bard', 'Cleric', 'Druid', 'Fighter', 'Monk',
|
||||||
'Paladin', 'Ranger', 'Rogue', 'Sorceror', 'Warlock', 'Wizard', )
|
'Paladin', 'Ranger', 'Rogue', 'Sorceror', 'Warlock', 'Wizard',
|
||||||
|
'Revisedranger')
|
||||||
|
|
||||||
from .stats import findattr
|
from .stats import findattr
|
||||||
from . import (weapons, monsters, exceptions)
|
from . import (weapons, monsters, exceptions)
|
||||||
|
from . import features as feats
|
||||||
import math
|
import math
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ class CharClass():
|
|||||||
_proficiencies_text = ()
|
_proficiencies_text = ()
|
||||||
multiclass_weapon_proficiencies = ()
|
multiclass_weapon_proficiencies = ()
|
||||||
_multiclass_proficiencies_text = ()
|
_multiclass_proficiencies_text = ()
|
||||||
|
features = ()
|
||||||
languages = ()
|
languages = ()
|
||||||
class_skill_choices = ()
|
class_skill_choices = ()
|
||||||
num_skill_choices = 2
|
num_skill_choices = 2
|
||||||
@@ -418,6 +421,30 @@ class Sorceror(CharClass):
|
|||||||
weapons.LightCrossbow)
|
weapons.LightCrossbow)
|
||||||
class_skill_choices = ('Arcana', 'Deception', 'Insight',
|
class_skill_choices = ('Arcana', 'Deception', 'Insight',
|
||||||
'Intimidation', 'Persuasion', 'Religion')
|
'Intimidation', 'Persuasion', 'Religion')
|
||||||
|
spellcasting_ability = 'charisma'
|
||||||
|
spell_slots_by_level = {
|
||||||
|
# char_lvl: (cantrips, 1st, 2nd, 3rd, ...)
|
||||||
|
1: (4, 2, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
2: (4, 3, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
3: (4, 4, 2, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
4: (5, 4, 3, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
5: (5, 4, 3, 2, 0, 0, 0, 0, 0, 0),
|
||||||
|
6: (5, 4, 3, 3, 0, 0, 0, 0, 0, 0),
|
||||||
|
7: (5, 4, 3, 3, 1, 0, 0, 0, 0, 0),
|
||||||
|
8: (5, 4, 3, 3, 2, 0, 0, 0, 0, 0),
|
||||||
|
9: (5, 4, 3, 3, 3, 1, 0, 0, 0, 0),
|
||||||
|
10: (6, 4, 3, 3, 3, 2, 0, 0, 0, 0),
|
||||||
|
11: (6, 4, 3, 3, 3, 2, 1, 0, 0, 0),
|
||||||
|
12: (6, 4, 3, 3, 3, 2, 1, 0, 0, 0),
|
||||||
|
13: (6, 4, 3, 3, 3, 2, 1, 1, 0, 0),
|
||||||
|
14: (6, 4, 3, 3, 3, 2, 1, 1, 0, 0),
|
||||||
|
15: (6, 4, 3, 3, 3, 2, 1, 1, 1, 0),
|
||||||
|
16: (6, 4, 3, 3, 3, 2, 1, 1, 1, 0),
|
||||||
|
17: (6, 4, 3, 3, 3, 2, 1, 1, 1, 1),
|
||||||
|
18: (6, 4, 3, 3, 3, 3, 1, 1, 1, 1),
|
||||||
|
19: (6, 4, 3, 3, 3, 3, 2, 1, 1, 1),
|
||||||
|
20: (6, 4, 3, 3, 3, 3, 2, 2, 1, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Warlock(CharClass):
|
class Warlock(CharClass):
|
||||||
@@ -489,3 +516,8 @@ class Wizard(CharClass):
|
|||||||
19: (5, 4, 3, 3, 3, 3, 2, 1, 1, 1),
|
19: (5, 4, 3, 3, 3, 3, 2, 1, 1, 1),
|
||||||
20: (5, 4, 3, 3, 3, 3, 2, 2, 1, 1),
|
20: (5, 4, 3, 3, 3, 3, 2, 2, 1, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Custom Classes
|
||||||
|
class Revisedranger(Ranger):
|
||||||
|
class_name = 'Revisedranger'
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
from . import weapons, armor
|
||||||
|
|
||||||
|
|
||||||
|
class Feature():
|
||||||
|
"""
|
||||||
|
Provide full text of rules in documentation
|
||||||
|
"""
|
||||||
|
name = "Generic Feature"
|
||||||
|
source = '' # race, class, background, etc.
|
||||||
|
|
||||||
|
def weapon_func(self, weapon: weapons.Weapon, **kwargs):
|
||||||
|
"""
|
||||||
|
Return the attack/damage bonus from having this feature
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
weapon
|
||||||
|
The weapon to be tested for special bonuses
|
||||||
|
kwargs
|
||||||
|
Any other key-word arguments the function may require
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
attack bonus : integer attack bonus
|
||||||
|
damage bonus : integer attack bonus
|
||||||
|
|
||||||
|
"""
|
||||||
|
return (0, 0)
|
||||||
|
|
||||||
|
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 Archery(Feature):
|
||||||
|
"""
|
||||||
|
You gain a +2 bonus to attack rolls you make
|
||||||
|
with ranged weapons.
|
||||||
|
"""
|
||||||
|
name = "Archery"
|
||||||
|
source = 'Revised Ranger'
|
||||||
|
|
||||||
|
def weapon_func(self, weapon: weapons.Weapon):
|
||||||
|
"""
|
||||||
|
+2 attack roll bonus if weapon is ranged
|
||||||
|
"""
|
||||||
|
return (2, 0) if weapon.is_ranged else (0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class UnarmoredDefense(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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Unarmored Defense"
|
||||||
|
source = "Monk"
|
||||||
|
|
||||||
|
def AC_func(self, char):
|
||||||
|
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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
\documentclass[twocolumn,lettersize]{article}
|
||||||
|
|
||||||
|
%% \usepackage{fullpage}
|
||||||
|
\usepackage[margin=1.5cm]{geometry}
|
||||||
|
\usepackage[dvipsnames]{color}
|
||||||
|
\definecolor{mygrey}{gray}{0.7}
|
||||||
|
|
||||||
|
\title{Features and Traits}
|
||||||
|
\author{[[ character.name ]]}
|
||||||
|
\date{}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\maketitle
|
||||||
|
|
||||||
|
[% for feat in character.features %]
|
||||||
|
|
||||||
|
\section*{[[ feat.name ]]}
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
\textbf{Source:} [[ feat.source ]] \\
|
||||||
|
|
||||||
|
[[ feat.__doc__|rst_to_latex ]]
|
||||||
|
|
||||||
|
[% endfor %]
|
||||||
|
|
||||||
|
\end{document}
|
||||||
@@ -75,6 +75,11 @@ def create_spellbook_pdf(char, basename):
|
|||||||
return create_latex_pdf(char, basename, template)
|
return create_latex_pdf(char, basename, template)
|
||||||
|
|
||||||
|
|
||||||
|
def create_features_pdf(char, basename):
|
||||||
|
template = jinja_env.get_template('features_template.tex')
|
||||||
|
return create_latex_pdf(char, basename, template)
|
||||||
|
|
||||||
|
|
||||||
def create_latex_pdf(char, basename, template):
|
def create_latex_pdf(char, basename, template):
|
||||||
tex = template.render(character=char)
|
tex = template.render(character=char)
|
||||||
# Create tex document
|
# Create tex document
|
||||||
@@ -95,7 +100,7 @@ def create_latex_pdf(char, basename, template):
|
|||||||
try:
|
try:
|
||||||
result = subprocess.run(['pdflatex', '--output-directory',
|
result = subprocess.run(['pdflatex', '--output-directory',
|
||||||
output_dir, tex_file, '-halt-on-error'],
|
output_dir, tex_file, '-halt-on-error'],
|
||||||
stdout=subprocess.DEVNULL, timeout=10)
|
stdout=subprocess.DEVNULL, timeout=30)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# Remove temporary files
|
# Remove temporary files
|
||||||
remove_temp_files(basename)
|
remove_temp_files(basename)
|
||||||
@@ -244,7 +249,7 @@ def create_character_pdf(char, basename, flatten=False):
|
|||||||
'Ideals': text_box(char.ideals),
|
'Ideals': text_box(char.ideals),
|
||||||
'Bonds': text_box(char.bonds),
|
'Bonds': text_box(char.bonds),
|
||||||
'Flaws': text_box(char.flaws),
|
'Flaws': text_box(char.flaws),
|
||||||
'Features and Traits': text_box(char.features_and_traits),
|
'Features and Traits': text_box(char.features_text + char.features_and_traits),
|
||||||
# Inventory
|
# Inventory
|
||||||
'CP': char.cp,
|
'CP': char.cp,
|
||||||
'SP': char.sp,
|
'SP': char.sp,
|
||||||
@@ -441,6 +446,17 @@ def make_sheet(character_file, char=None, flatten=False):
|
|||||||
os.path.splitext(character_file)[0])
|
os.path.splitext(character_file)[0])
|
||||||
create_spells_pdf(char=char, basename=spell_base, flatten=flatten)
|
create_spells_pdf(char=char, basename=spell_base, flatten=flatten)
|
||||||
sheets.append(spell_base + '.pdf')
|
sheets.append(spell_base + '.pdf')
|
||||||
|
if len(char.features) > 0:
|
||||||
|
feat_base = '{:s}_feats'.format(
|
||||||
|
os.path.splitext(character_file)[0])
|
||||||
|
try:
|
||||||
|
create_features_pdf(char=char, basename=feat_base)
|
||||||
|
except exceptions.LatexNotFoundError as e:
|
||||||
|
log.warning('``pdflatex`` not available. Skipping features book '
|
||||||
|
f'for {char.name}')
|
||||||
|
else:
|
||||||
|
sheets.append(feat_base + '.pdf')
|
||||||
|
if char.is_spellcaster:
|
||||||
# Create spell book
|
# Create spell book
|
||||||
spellbook_base = os.path.splitext(character_file)[0] + '_spellbook'
|
spellbook_base = os.path.splitext(character_file)[0] + '_spellbook'
|
||||||
try:
|
try:
|
||||||
|
|||||||
+15
-1
@@ -1,11 +1,12 @@
|
|||||||
from . import weapons
|
from . import weapons
|
||||||
|
from . import features as feats
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('Dwarf', 'HillDwarf', 'MountainDwarf', 'Elf', 'HighElf',
|
__all__ = ('Dwarf', 'HillDwarf', 'MountainDwarf', 'Elf', 'HighElf',
|
||||||
'WoodElf', 'DarkElf', 'Halfling', 'LightfootHalfling',
|
'WoodElf', 'DarkElf', 'Halfling', 'LightfootHalfling',
|
||||||
'StoutHalfling', 'Human', 'Dragonborn', 'Gnome', 'ForestGnome',
|
'StoutHalfling', 'Human', 'Dragonborn', 'Gnome', 'ForestGnome',
|
||||||
'RockGnome', 'HalfElf', 'HalfOrc', 'Tiefling', 'Aasimar',
|
'RockGnome', 'HalfElf', 'HalfOrc', 'Tiefling', 'Aasimar',
|
||||||
'FallenAasimar', 'Lizardfolk', 'Kenku')
|
'FallenAasimar', 'Lizardfolk', 'Kenku', 'Aarakocra')
|
||||||
|
|
||||||
|
|
||||||
class Race():
|
class Race():
|
||||||
@@ -16,6 +17,7 @@ class Race():
|
|||||||
proficiencies_text = tuple()
|
proficiencies_text = tuple()
|
||||||
weapon_proficiences = tuple()
|
weapon_proficiences = tuple()
|
||||||
skill_proficiencies = ()
|
skill_proficiencies = ()
|
||||||
|
features = tuple()
|
||||||
strength_bonus = 0
|
strength_bonus = 0
|
||||||
dexterity_bonus = 0
|
dexterity_bonus = 0
|
||||||
constitution_bonus = 0
|
constitution_bonus = 0
|
||||||
@@ -214,3 +216,15 @@ class Kenku(Race):
|
|||||||
dexterity_bonus = 2
|
dexterity_bonus = 2
|
||||||
wisdom_bonus = 1
|
wisdom_bonus = 1
|
||||||
languages = ('Common', 'Auran')
|
languages = ('Common', 'Auran')
|
||||||
|
|
||||||
|
|
||||||
|
# Aarakocra
|
||||||
|
class Aarakocra(Race):
|
||||||
|
name = 'Aarakocra'
|
||||||
|
size = 'medium'
|
||||||
|
speed = "25 (50 fly)"
|
||||||
|
dexterity_bonus = 2
|
||||||
|
wisdom_bonus = 1
|
||||||
|
languages = ('Common', 'Aarakocra', 'Auran')
|
||||||
|
weapon_proficiencies = (weapons.Talons,)
|
||||||
|
proficiences_text = ('talons',)
|
||||||
|
|||||||
@@ -88,4 +88,8 @@ class Skill():
|
|||||||
is_proficient = self.skill_name in character.skill_proficiencies
|
is_proficient = self.skill_name in character.skill_proficiencies
|
||||||
if is_proficient:
|
if is_proficient:
|
||||||
modifier += character.proficiency_bonus
|
modifier += character.proficiency_bonus
|
||||||
|
# Check for expertise
|
||||||
|
is_expert = self.skill_name in character.skill_expertise
|
||||||
|
if is_expert:
|
||||||
|
modifier += character.proficiency_bonus
|
||||||
return modifier
|
return modifier
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from .stats import mod_str
|
from .stats import mod_str
|
||||||
|
|
||||||
|
|
||||||
class Weapon():
|
class Weapon():
|
||||||
name = ""
|
name = ""
|
||||||
cost = "0 gp"
|
cost = "0 gp"
|
||||||
@@ -19,6 +20,10 @@ class Weapon():
|
|||||||
dam_str += '' + mod_str(self.bonus_damage)
|
dam_str += '' + mod_str(self.bonus_damage)
|
||||||
return dam_str
|
return dam_str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_ranged(self):
|
||||||
|
return ('range' in self.properties.lower()) and ('thrown' not in self.properties.lower())
|
||||||
|
|
||||||
|
|
||||||
class Club(Weapon):
|
class Club(Weapon):
|
||||||
name = "Club"
|
name = "Club"
|
||||||
@@ -127,7 +132,7 @@ class LightCrossbow(Weapon):
|
|||||||
base_damage = "1d8"
|
base_damage = "1d8"
|
||||||
damage_type = "p"
|
damage_type = "p"
|
||||||
weight = 5
|
weight = 5
|
||||||
properties = "Ammunition (range 80/320, loading, two-handed"
|
properties = "Ammunition (range 80/320), loading, two-handed"
|
||||||
ability = 'dexterity'
|
ability = 'dexterity'
|
||||||
|
|
||||||
|
|
||||||
@@ -383,7 +388,7 @@ class HeavyCrossbow(Weapon):
|
|||||||
damage_type = "p"
|
damage_type = "p"
|
||||||
weight = 18
|
weight = 18
|
||||||
properties = "Ammunition (range 100/400), heaving, loading, two-handed"
|
properties = "Ammunition (range 100/400), heaving, loading, two-handed"
|
||||||
ability = 'strength'
|
ability = 'dexterity'
|
||||||
|
|
||||||
|
|
||||||
class Longbow(Weapon):
|
class Longbow(Weapon):
|
||||||
@@ -393,7 +398,7 @@ class Longbow(Weapon):
|
|||||||
damage_type = "p"
|
damage_type = "p"
|
||||||
weight = 2
|
weight = 2
|
||||||
properties = "Ammunition (range 150/600), heavy, two-handed"
|
properties = "Ammunition (range 150/600), heavy, two-handed"
|
||||||
ability = 'strength'
|
ability = 'dexterity'
|
||||||
|
|
||||||
|
|
||||||
class Net(Weapon):
|
class Net(Weapon):
|
||||||
@@ -445,6 +450,46 @@ class Bite(Weapon):
|
|||||||
ability = "strength"
|
ability = "strength"
|
||||||
|
|
||||||
|
|
||||||
|
class Talons(Weapon):
|
||||||
|
name = 'Talons'
|
||||||
|
base_damage = '1d4'
|
||||||
|
damage_type = 's'
|
||||||
|
cost = '0 gp'
|
||||||
|
weight = 0
|
||||||
|
properties = ''
|
||||||
|
ability = 'strength'
|
||||||
|
|
||||||
|
|
||||||
|
class Firearm(Weapon):
|
||||||
|
name = 'Firearm'
|
||||||
|
ability = 'dexterity'
|
||||||
|
damage_type = 'p'
|
||||||
|
|
||||||
|
|
||||||
|
class Blunderbuss(Firearm):
|
||||||
|
name = 'Blunderbuss'
|
||||||
|
base_damage = '2d8'
|
||||||
|
cost = '300 gp'
|
||||||
|
weight = 10
|
||||||
|
properties = "Ammunition (range 15/60), Reload 1, Misfire 2"
|
||||||
|
|
||||||
|
|
||||||
|
class Pistol(Firearm):
|
||||||
|
name = 'Pistol'
|
||||||
|
base_damage = '1d10'
|
||||||
|
cost = '150 gp'
|
||||||
|
weight = 3
|
||||||
|
properties = "Ammunition (range 60/240), Reload 4, Misfire 1"
|
||||||
|
|
||||||
|
|
||||||
|
class Musket(Firearm):
|
||||||
|
name = 'Musket'
|
||||||
|
base_damage = '1d12'
|
||||||
|
cost = '300'
|
||||||
|
weight = 10
|
||||||
|
properties = "Ammunition (range 120/480), Two-Handed, Reload 1, Misfire 2"
|
||||||
|
|
||||||
|
|
||||||
# Some lists of weapons for easy proficiency resolution
|
# Some lists of weapons for easy proficiency resolution
|
||||||
simple_melee_weapons = (Club, Dagger, Greatclub, Handaxe, Javelin,
|
simple_melee_weapons = (Club, Dagger, Greatclub, Handaxe, Javelin,
|
||||||
LightHammer, Mace, Quarterstaff, Sickle, Spear)
|
LightHammer, Mace, Quarterstaff, Sickle, Spear)
|
||||||
@@ -459,3 +504,5 @@ martial_melee_weapons = (Battleaxe, Flail, Glaive, Greataxe,
|
|||||||
martial_ranged_weapons = (Blowgun, HandCrossbow, HeavyCrossbow,
|
martial_ranged_weapons = (Blowgun, HandCrossbow, HeavyCrossbow,
|
||||||
Longbow, Net)
|
Longbow, Net)
|
||||||
martial_weapons = martial_melee_weapons + martial_ranged_weapons
|
martial_weapons = martial_melee_weapons + martial_ranged_weapons
|
||||||
|
|
||||||
|
firearms = (Firearm)
|
||||||
|
|||||||
Reference in New Issue
Block a user