fully tested new multiclass create-characters, with features for races and backgrounds

This commit is contained in:
Ben Cook
2018-12-20 22:09:46 -05:00
parent 371fb327d3
commit aa84911efd
16 changed files with 804 additions and 82 deletions
+7 -1
View File
@@ -1,8 +1,14 @@
from . import weapons, character, features, race, background, spells
__all__ = ('__version__', 'Character', 'weapons', 'features',
'character', 'race', 'background', 'spells')
from . import weapons, features, race, background, spells
from .character import Character
import os
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
__version__ = read('../VERSION')
+114 -7
View File
@@ -11,6 +11,9 @@ class Background():
features = ()
languages = ()
def __init__(self):
self.features = tuple([f() for f in self.features])
def __str__(self):
return self.name
@@ -19,16 +22,19 @@ class Acolyte(Background):
name = "Acolyte"
skill_proficiencies = ('insight', 'religion')
languages = ("[choose one]", "[choose one]")
features = (feats.ShelterOfTheFaithful,)
class Charlatan(Background):
name = "Charlatan"
skill_proficiencies = ('deception', 'sleight of hand')
features = (feats.FalseIdentity,)
class Criminal(Background):
name = "Criminal"
skill_proficiencies = ('deception', 'stealth')
features = (feats.CriminalContact,)
class Spy(Criminal):
@@ -38,6 +44,7 @@ class Spy(Criminal):
class Entertainer(Background):
name = "Entertainer"
skill_proficiencies = ('acrobatics', 'performance')
features = (feats.ByPopularDemand,)
class Gladiator(Entertainer):
@@ -47,12 +54,14 @@ class Gladiator(Entertainer):
class FolkHero(Background):
name = "Folk Hero"
skill_proficiencies = ('animal handling', 'survival')
features = (feats.RusticHospitality,)
class GuildArtisan(Background):
name = "Guild Artisan"
skill_proficiencies = ('insight', 'persuasion')
languages = ("[choose one]", "[choose one]")
features = (feats.GuildMembership,)
class GuildMerchant(GuildArtisan):
@@ -63,12 +72,14 @@ class Hermit(Background):
name = "Hermit"
skill_proficiencies = ("medicine", "religion")
languages = ("[choose one]", )
features = (feats.Discovery,)
class Noble(Background):
name = "Noble"
skill_proficiencies = ("history", 'persuasion')
languages = ("[choose one]", )
features = (feats.PositionOfPrivilege,)
class Knight(Noble):
@@ -79,17 +90,20 @@ class Outlander(Background):
name = "Outlander"
skill_proficiencies = ('athletics', 'survival')
languages = ("[choose one]", )
features = (feats.Wanderer,)
class Sage(Background):
name = "Sage"
skill_proficiencies = ('arcana', 'history')
languages = ("[choose one]", '[choose one]')
features = (feats.Researcher,)
class Sailor(Background):
name = "Sailor"
skill_proficiencies = ('athletics', 'perception')
features = (feats.ShipsPassage,)
class Pirate(Sailor):
@@ -99,11 +113,87 @@ class Pirate(Sailor):
class Soldier(Background):
name = "Soldier"
skill_proficiencies = ('athletics', 'intimidation')
features = (feats.MilitaryRank,)
class Urchin(Background):
name = "Urchin"
skill_proficiencies = ('sleight of hand', 'stealth')
features = (feats.CitySecrets,)
# Sword's Coast Adventurers Guide
class CityWatch(Background):
name = "City Watch"
skill_proficiencies = ('athletics', 'insight')
languages = ('[choose one]', '[choose one]')
features = (feats.WatchersEye,)
class ClanCrafter(Background):
name = "Clan Crafter"
skill_proficiencies = ('history', 'insight')
languages = ('Dwarvish')
features = (feats.RespectOfTheStoutFolk,)
class CloisteredScholar(Background):
name = "Cloistered Scholar"
skill_proficiencies = ('history',)
skill_choices = ('arcana', 'nature', 'religion')
num_skill_choices = 1
languages = ('[choose one]', '[choose one]')
features = (feats.LibraryAccess,)
class Courtier(Background):
name = "Courtier"
skill_proficiencies = ("insight", 'persuasion')
languages = ('[choose one]', '[choose one]')
features = (feats.CourtFunctionary,)
class FactionAgent(Background):
name = "Faction Agent"
skill_proficiencies = ('insight',)
skill_choices = ('animal handling', 'arcana', 'deception',
'history', 'intimidation', 'investigation',
'medicine', 'nature', 'perception', 'performance',
'persuasion', 'religion', 'survival')
num_skill_choices = 1
languages = ('[choose one]', '[choose one]')
features = (feats.SafeHaven,)
class FarTraveler(Background):
name = 'Far Traveler'
skill_proficiencies = ('insight', 'perception')
languages = ('[choose one]',)
features = (feats.AllEyesOnYou,)
class Inheritor(Background):
name = "Inheritor"
skill_proficiencies = ('survival',)
skill_choices = ('arcana', 'history', 'religion')
num_skill_choices = 1
languages = ('[choose one]',)
features = (feats.Inheritance,)
class KnightOfTheOrder(Background):
name = "Knight of the Order"
skill_proficiencies = ('persuasion',)
skill_choices = ('arcana', 'history', 'nature', 'religion')
num_skill_choices = 1
languages = ('[choose one]')
features = (feats.KnightlyRegard,)
class MercenaryVeteran(Background):
name = "Mercenary Veteran"
skill_proficiencies = ('athletics', 'persuasion')
features = (feats.MercenaryLife,)
class UrbanBountyHunter(Background):
@@ -111,17 +201,34 @@ class UrbanBountyHunter(Background):
skill_proficiencies = ()
skill_choices = ('Deception', 'Insight', 'Persuasion', 'Stealth')
num_skill_choices = 2
features = (feats.EarToTheGround,)
class FarTraveler(Background):
name = 'Far Traveler'
skill_proficiencies = ('insight', 'perception')
languages = ('[choose one]',)
class UthgardtTribeMember(Background):
name = "Uthgardt Tribe Member"
skill_profifiencies = ('athletics', 'survival')
languages = ('[choose one]')
features = (feats.UthgardtHeritage,)
available_backgrounds = [Acolyte, Charlatan, Criminal, Spy, Entertainer,
class WaterdhavianNoble(Background):
name = "Waterdhavian Noble"
skill_proficiencies = ('history', 'persuasion')
languages = ('[choose one]')
features = (feats.KeptInStyle,)
PHB_backgrounds = [Acolyte, Charlatan, Criminal, Spy, Entertainer,
Gladiator, FolkHero, GuildArtisan, GuildMerchant,
Hermit, Noble, Knight, Outlander, Sage, Sailor,
Pirate, Soldier, Urchin, UrbanBountyHunter,
FarTraveler]
Pirate, Soldier, Urchin]
SCAG_backgrounds = [CityWatch, ClanCrafter, CloisteredScholar, Courtier,
FactionAgent, FarTraveler, Inheritor, KnightOfTheOrder,
MercenaryVeteran, UrbanBountyHunter, UthgardtTribeMember,
WaterdhavianNoble]
available_backgrounds = PHB_backgrounds + SCAG_backgrounds
__all__ = tuple([b.name for b in available_backgrounds]) + (
'PHB_backgrounds', 'SCAG_backgrounds', 'available_backgrounds')
+40 -9
View File
@@ -1,4 +1,5 @@
"""Tools for describing a player character."""
__all__ = ('Character',)
import re
import os
@@ -12,10 +13,17 @@ from .stats import Ability, Skill, findattr
from .dice import read_dice_str
from . import (weapons, race, background, spells, armor, monsters,
exceptions, classes, features)
from .__init__ import __version__
from .weapons import Weapon
from .armor import Armor, NoArmor, Shield, NoShield
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
__version__ = read('../VERSION')
dice_re = re.compile('(\d+)d(\d+)')
multiclass_spellslots_by_level = {
@@ -112,8 +120,8 @@ class Character():
_proficiencies_text = tuple()
# Magic
spellcasting_ability = None
spells = tuple()
spells_prepared = tuple()
_spells = tuple()
_spells_prepared = tuple()
# Features IN MAJOR DEVELOPMENT
custom_features = ()
feature_choices = ()
@@ -132,7 +140,7 @@ class Character():
# instantiate any spells not listed properly
for S in self.spells_prepared:
if S not in [type(spl) for spl in self.spells]:
self.spells += (S(),)
self._spells += (S(),)
def __str__(self):
return self.name
@@ -249,6 +257,28 @@ class Character():
else:
return multiclass_spellslots_by_level[eff_level][spell_level]
@property
def spells(self):
spells = set(self._spells)
for f in self.features:
spells |= set(f.spells_known) | set(f.spells_prepared)
for c in self.spellcasting_classes:
spells |= set(c.spells_known) | set(c.spells_prepared)
if self.race is not None:
spells |= set(self.race.spells_known) | set(self.race.spells_prepared)
return tuple(spells)
@property
def spells_prepared(self):
spells = set(self._spells_prepared)
for f in self.features:
spells |= set(f.spells_prepared)
for c in self.spellcasting_classes:
spells |= set(c.spells_prepared)
if self.race is not None:
spells |= set(self.race.spells_prepared)
return tuple(spells)
def set_attrs(self, **attrs):
"""Bulk setting of attributes. Useful for loading a character from a
dictionary."""
@@ -322,10 +352,10 @@ class Character():
# Save list of spells to character atribute
if attr == 'spells':
# Instantiate them all for the spells list
self.spells = tuple(S() for S in _spells)
self._spells = tuple(S() for S in _spells)
else:
# Instantiate them all for the spells list
self.spells_prepared = tuple(S() for S in _spells)
self._spells_prepared = tuple(S() for S in _spells)
else:
if not hasattr(self, attr):
warnings.warn(f"Setting unknown character attribute {attr}",
@@ -571,11 +601,12 @@ class Character():
f.write(text)
def to_pdf(self, filename, **kwargs):
char_file = filename.replace('pdf', 'py')
self.save(char_file,
if filename.endswith('.pdf'):
filename = filename.replace('pdf', 'py')
self.save(filename,
template_file=kwargs.get('template_file',
'character_template.txt'))
subprocess.call(['makesheets', char_file])
subprocess.call(['makesheets', filename)
def read_character_file(filename):
+4 -1
View File
@@ -1,6 +1,6 @@
__all__ = ('CharClass', 'Barbarian', 'Bard', 'Cleric', 'Druid', 'Fighter',
'Monk', 'Paladin', 'Ranger', 'Rogue', 'Sorceror', 'Warlock',
'Wizard', 'Revisedranger')
'Wizard', 'Revisedranger', 'available_classes')
from .classes import CharClass
from .barbarian import Barbarian
@@ -15,3 +15,6 @@ 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]
+2
View File
@@ -17,6 +17,8 @@ class CharClass():
num_skill_choices = 2
spellcasting_ability = None
spell_slots_by_level = None
spells_known = ()
spells_prepared = ()
subclass = None
subclasses_available = ()
features_by_level = defaultdict(list)
+5 -19
View File
@@ -23,26 +23,11 @@ def read_version():
return version
char_classes = {
'Barbarian': classes.Barbarian,
'Bard': classes.Bard,
'Cleric': classes.Cleric,
'Druid': classes.Druid,
'Fighter': classes.Fighter,
'Monk': classes.Monk,
'Paladin': classes.Paladin,
'Ranger': classes.Ranger,
'Rogue': classes.Rogue,
'Sorceror': classes.Sorceror,
'Warlock': classes.Warlock,
'Wizard': classes.Wizard
}
char_classes = {c.class_name: c for c in classes.available_classes}
races = race.race_dict
races = {r.name: r for r in race.available_races}
backgrounds = background.available_backgrounds
backgrounds = {bg.name: bg for bg in backgrounds}
backgrounds = {b.name: b for b in background.available_backgrounds}
class App(npyscreen.NPSAppManaged):
@@ -53,10 +38,11 @@ class App(npyscreen.NPSAppManaged):
def save_character(self):
# Save the file
filename = self.getForm("SAVE").filename.value
self.character.save(filename)
self.character.save(filename, template_file='empty_template.tex')
# Create the PDF character sheet
if self.getForm('SAVE').make_pdf.value:
log.debug("Creating PDF")
self.character.to_pdf(filename, template_file='empty_template.tex')
subprocess.call(['makesheets', filename])
def update_max_hp(self):
+90
View File
@@ -0,0 +1,90 @@
"""This file describes the heroic adventurer {{ char.name }}.
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 = "{{ char.dungeonsheets_version }}"
name = "{{ char.name }}"
classes_levels = {{ char.classes_levels }}
player_name = "{{ char.player_name }}"
background = "{{ char.background.name }}"
race = "{{ char.race.name }}"
alignment = "{{ char.alignment }}"
xp = {{ char.xp }}
hp_max = {{ char.hp_max }}
# Ability Scores
strength = {{ char.strength.value }}
dexterity = {{ char.dexterity.value }}
constitution = {{ char.constitution.value }}
intelligence = {{ char.intelligence.value }}
wisdom = {{ char.wisdom.value }}
charisma = {{ char.charisma.value }}
# Select what skills you're proficient with
skill_proficiencies = {{ char.skill_proficiencies }}
# 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 = """{{ char.languages }}"""
# 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."""
+387
View File
@@ -0,0 +1,387 @@
from .features import Feature
class ShelterOfTheFaithful(Feature):
"""As an acolyte, you command the respect of those who share your faith, and
you can perform the religious ceremonies of your deity. You and your
adventuring companions can expect to receive free healing and care at a
temple, shrine, or other established presence of your faith, though you
must provide any material components needed for spells. Those who share
your religion will support you (but only you) at a modest lifestyle.
You might also have ties to a specific temple dedicated to your chosen
deity or pantheon, and you have a residence there. This could be the temple
where you used to serve, if you remain on good terms with it, or a temple
where you have found a new home. While near your temple, you can call upon
the priests for assistance, provided the assistance you ask for is not
hazardous and you remain in good standing with your temple.
"""
name = "Shelter of the Faithful"
source = "Background (Acolyte)"
class FalseIdentity(Feature):
"""You have created a second identity that includes documentation, established
acquaintances, and disguises that allow you to assume that
persona. Additionally, you can forge documents including official papers
and personal letters, as long as you have seen an example of the kind of
document or the handwriting you are trying to copy.
"""
name = "False Identity"
source = "Background (Charlattan)"
class CriminalContact(Feature):
"""You have a reliable and trustworthy contact who acts as your liaison to a
network o f other criminals. You know how to get messages to and from your
contact, even over great distances; specifically, you know the local
messengers, corrupt caravan masters, and seedy sailors who can deliver
messages for you.
"""
name = "Criminal Contact"
source = "Background (Criminal)"
class ByPopularDemand(Feature):
"""You can always find a place to perform, usually in an inn or tavern but
possibly with a circus, at a theater, or even in a nobles court. At such a
place, you receive free lodging and food of a modest or comfortable
standard (depending on the quality of the establishment), as long as you
perform each night. In addition, your performance makes you something of a
local figure. When strangers recognize you in a town where you have
performed, they typically take a liking to you.
"""
name = "By Popular Demand"
source = "Background (Entertainer)"
class RusticHospitality(Feature):
"""Since you come from the ranks of the common folk, you fit in among them
with ease. You can find a place to hide, rest, or recuperate among other
commoners, unless you have shown yourself to be a danger to them. They will
shield you from the law or anyone else searching for you, though they will
not risk their lives for you.
"""
name = "Rustic Hospitality"
source = "Background (Folk Hero)"
class GuildMembership(Feature):
"""As an established and respected member of a guild, you can rely on certain
benefits that membership provides. Your fellow guild members will provide
you with lodging and food if necessary, and pay for your funeral if
needed. In some cities and towns, a guildhall offers a central place to
meet other members of your profession, which can be a good place to meet
potential patrons, allies, or hirelings.
Guilds often wield tremendous political power. If you are accused of a
crime, your guild will support you if a good case can be made for your
innocence or the crime is justifiable. You can also gain access to powerful
political figures through the guild, if you are a member in good
standing. Such connections might require the donation of money or magic
items to the guilds coffers.
You must pay dues of 5 gp per month to the guild. If you miss payments, you
must make up back dues to remain in the guilds good graces.
"""
name = "Guild Membership"
source = "Background (Guild Artisan)"
class Discovery(Feature):
"""The quiet seclusion of your extended hermitage gave you access to a unique
and powerful discovery. The exact nature of this revelation depends on the
nature of your seclusion. It might be a great truth about the cosmos, the
deities, the powerful beings of the outer planes, or the forces of
nature. It could be a site that no one else has ever seen. You might have
uncovered a fact that has long been forgotten, or unearthed some relic of
the past that could rewrite history. It might be information that would be
damaging to the people who or consigned you to exile, and hence the reason
for your return to society.
Work with your DM to determine the details of
your discovery and its impact on the campaign.
"""
name = "Discovery"
source = "Background (Hermit)"
class PositionOfPrivilege(Feature):
"""Thanks to your noble birth, people are inclined to think the best of
you. You are welcome in high society, and people assume you have the right
to be wherever you are. The common folk make every effort to accommodate
you and avoid your displeasure, and other people of high birth treat you as
a member of the same social sphere. You can secure an audience with a local
noble if you need to.
"""
name = "Position of Privilege"
source = "Background (Noble)"
class Wanderer(Feature):
"""You have an excellent memory for maps and geography, and you can always
recall the general layout of terrain, settlements, and other features
around you. In addition, you can find food and fresh water for yourself and
up to five other people each day, provided that the land offers berries,
small game, water, and so forth.
"""
name = "Wanderer"
source = "Background (Outlander)"
class Researcher(Feature):
"""When you attempt to learn or recall a piece of lore, if you do not know
that information, you often know where and from whom you can obtain
it. Usually, this information comes from a library, scriptorium,
university, or a sage or other learned person or creature. Your DM might
rule that the knowledge you seek is secreted away in an almost inaccessible
place, or that it simply cannot be found. Unearthing the deepest secrets of
the multiverse can require an adventure or even a whole campaign.
"""
name = "Researcher"
source = "Background (Sage)"
class ShipsPassage(Feature):
"""When you need to, you can secure free passage on a sailing ship for
yourself and your adventuring companions. You might sail on the ship you
served on, or another ship you have good relations with (perhaps one
captained by a former crewmate). Because youre calling in a favor, you
cant be certain of a schedule or route that will meet your every
need. Your Dungeon Master will determine how long it takes to get where you
need to go. In return for your free passage, you and your companions are
expected to assist the crew during the voyage
"""
name = "Ship's Passage"
source = "Background (Sailor)"
class MilitaryRank(Feature):
"""You have a military rank from your career as a soldier. Soldiers loyal to
your former military organization still recognize your authority and
influence, and they defer to you if they are of a lower rank. You can
invoke your rank to exert influence over other soldiers and requisition
simple equipment or horses for temporary use. You can also usually gain
access to friendly military encampments and fortresses where your rank is
recognized.
"""
name = "Military Rank"
source = "Background (Soldier)"
class CitySecrets(Feature):
"""You know the secret patterns and flow to cities and can find passages
through the urban sprawl that others would miss. When you are not in
combat, you (and companions you lead) can travel between any two locations
in the city twice as fast as your speed would normally allow.
"""
name = "City Secrets"
source = "Background (Urchin)"
# Swords Coast Adventurer's Guide
class AllEyesOnYou(Feature):
"""Your accent, mannerisms, figures of speech, and per- haps even your
appearance all mark you as foreign. Curious glances are directed your way
wherever you go, which can be a nuisance, but you also gain the friendly
interest of scholars and others intrigued by far-off lands, to say nothing
of everyday folk who are eager to hear stories of your homeland.
You can parley this attention into access to people and places you might
not otherwise have, for you and your traveling companions. Noble lords,
scholars, and merchant princes, to name a few, might be interested in
hearing about your distant homeland and people.
"""
name = "All Eyes on You"
source = "Background (Far Traveler)"
class EarToTheGround(Feature):
"""You are in frequent contact with people in the segment of society that your
chosen quarries move through. These people might be associated with the
criminal underworld, the rough-and-tumble folk of the streets, or members
of high society. This connection comes in the form of a contact in any city
you visit, a person who provides information about the people and places of
the local area.
"""
name = "Ear to the Ground"
source = "Background (Urban Bounty Hunter)"
class WatchersEye(Feature):
"""Your experience in enforcing the law, and dealing with lawbreakers, gives
you a feel for local laws and crimi- nals. You can easily find the local
outpost of the watch or a simila r organization, and just as easily pick
out the dens of criminal activity in a community, although you're more
likely to be welcome in the former locations rather than the latter.
"""
name = "Watcher's Eye"
source = "Background (City Watch)"
class RespectOfTheStoutFolk(Feature):
"""As well respected as clan crafters are among outsiders, no one esteems them
quite so highly as dwarves do. You always have free room and board in any
place where shield dwarves or gold dwarves dwell, and the individu- als in
such a settlement might vie among themselves to determine who can offer you
(and possibly your compa- triots) the finest accommodations and assistance.
"""
name = "Respect of the Stout Folk"
source = "Background (Clan Crafter)"
class LibraryAccess(Feature):
"""Though others must often endure extensive interviews and significant fees to
gain access to even the most common archives in your library, you have free
and easy access to the majority of the library, though it might also have
repositories of lore that are too valuable, magical, or secret to permit
anyone immediate access.
You have a working knowledge of your cloister's personnel and bureaucracy,
and you know how to navigate those connections with some ease.
Additionally, you are likely to gain preferential treatment at other
libraries across the Realms, as profes- sional courtesy shown to a fellow
scholar.
"""
name = "Library Access"
source = "Background (Cloistered Scholar)"
class CourtFunctionary(Feature):
"""Your knowledge of how bureaucracies function lets you gain access to the
records and inner workings of any no- ble court or government you
encounter. You know who the movers and shakers are, whom to go to for the
favors you seek, and what the current intrigues of interest in the group
are.
"""
name = "Court Functionary"
source = "Background (Courtier)"
class SafeHaven(Feature):
"""As a faction agent, you have access to a secret network of supporters and
operatives who can provide assis- tance on your adventures. You know a set
of secret signs and passwords you can use to identify such operatives , who
can provide you with access to a hidden safe house, free room and board, or
assistance in finding informa- tion. These agents never risk their lives
for you or risk revealing their true identities.
"""
name = "Save Haven"
source = "Background (Faction Agent)"
class Inheritance(Feature):
"""Choose or randomly determine your inheritance from among the possibilities
in the table in SCAG. Work with your Dungeon Master to come up with
details: Why is your inheritance so important, and what is its full story?
You might prefer for the DM to invent these details as part of the game,
allowing you to learn more about your inheritance as your character does.
The Dungeon Master is free to use your inheritance as a story hook, sending
you on quests to learn more about its history or true nature, or
confronting you with foes who want to claim it for themselves or prevent
you from learning what you seek. The DM also determines the properties of
your inheritance and how they figure into the item's history and
importance. For instance, the object might be a minor magic item, or one
that begins with a modest ability and increases in potency with the passage
of time. Or, the true nature of your inheritance might not be apparent at
first and is revealed only when certain conditions are met.
When you begin your adventuring career, you can decide whether to tell your
companions about your inheritance right away. Rather than attracting
attention to yourself, you might want to keep your inheritance a secret
until you learn more about what it means to you and what it can do for you.
"""
name = "Inheritance"
source = "Background (Inheritor)"
class KnightlyRegard(Feature):
"""You receive shelter and succor from members of your knightly order and those
who are sympathetic to its aims. If your order is a religious one, you can
gain aid from temples and other religious communities of your
deity. Knights of civic orders can get help from the com- munity- whether a
lone settlement or a great nation- that they serve, and knights of
philosophical orders can find help from those they have aided in pursuit of
their ideals , and those who share those ideals.
This help comes in the form of shelter and meals, and healing when
appropriate, as well as occasionally risky assistance, such as a band of
local citizens rallying to aid a sorely pressed knight in a fight , or
those who sup- port the order helping to smuggle a knight out of town when
he or she is being hunted unjustly.
"""
name = "Knightly Regard"
source = "Background (Knight of the Order)"
class MercenaryLife(Feature):
"""You know the mercenary life as only someone who has experienced it can. You
are able to identify mercenary companies by their emblems, and you know a
little about any such company, including the names and reputations of its
commanders and leaders, and who has hired them recently. You can find the
taverns and festhalls where mercenaries abide in any area, as long as you
speak the language. You can find mercenary work between adven- tures
sufficient to maintain a comfortable lifestyle (see "Practicing a
Profession" under "Downtime Activities" in chapter 8 of the Player's
Handbook).
"""
name = "Mercenary Life"
source = "Background (Mercenary Veteran)"
class UthgardtHeritage(Feature):
"""You have an excellent knowledge of not only your tribe's territory, but also
the terrain and natural resources of the rest of the North. You are
familiar enough with any wilderness area that you find twice as much food
and water as you normally would when you forage there.
Additionally, you can call upon the hospitality of your people, and those
folk allied with your tribe, often including members of druid circles,
tribes of nomadic elves, the Harpers, and the priesthoods devoted to the
gods of the First Circle.
"""
name = "Uthgardt Heritage"
source = "Background (Uthgardt Tribe Member)"
class KeptInStyle(Feature):
"""While you are in Waterdeep or elsewhere in the North your house sees to your
everyday needs. Your name and signet are sufficient to cover most of your
expenses; the inns, taverns, and festhalls you frequent are glad to re-
cord your debt and send an accounting to your family's estate in Waterdeep
to settle what you owe.
This advantage enables you to live a comfortable life- style without having
to pay 2 gp a day for it, or reduces the cost of a wealthy or aristocratic
lifestyle by that amount. You may not maintain a less affluent lifestyle
and use the difference as income-the benefit is a line of credit, not an
actual monetary reward.
"""
name = "Kept in Style"
source = "Background (Waterdhavian Noble)"
+2
View File
@@ -27,6 +27,8 @@ class Feature():
"""
name = "Generic Feature"
source = '' # race, class, background, etc.
spells_known = ()
spells_prepared = ()
needs_implementation = False # Set to True if need to find way to compute stats
def __eq__(self, other):
+2 -2
View File
@@ -125,6 +125,7 @@ class SunlightSensitivity(Feature):
name = "Sunlight Sensitivity"
source = "Race (Dark Elf)"
class DrowMagic(Feature):
"""You know the dancing lights cantrip. When you reach 3rd level, you can
cast the faerie fire spell once per day. When you reach 5th level, you can
@@ -259,7 +260,6 @@ class NaturalIllusionist(Feature):
"""
name = "Natural Illusionist"
source = "Race (Forest Gnome)"
needs_implementation = True
class SpeakWithSmallBeasts(Feature):
@@ -357,6 +357,7 @@ class InfernalLegacy(Feature):
"""
name = "Infernal Legacy"
source = "Race (Tiefling)"
needs_implementation = True
# Aasimar
@@ -385,7 +386,6 @@ class LightBearer(Feature):
"""
name = "Light Bearer"
source = "Race (Aasimar)"
needs_implementation = True
class RadiantSoul(Feature):
+1 -2
View File
@@ -97,8 +97,7 @@ def create_latex_pdf(char, basename, template):
f'{basename_}.log']
for filename in filenames:
if os.path.exists(filename):
pass
#os.remove(filename)
os.remove(filename)
# Compile the PDF
pdf_file = f'{basename}.pdf'
output_dir = os.path.abspath(os.path.dirname(pdf_file))
+27 -33
View File
@@ -1,4 +1,4 @@
from . import weapons
from . import (weapons, spells)
from . import features as feats
from collections import defaultdict
@@ -22,6 +22,8 @@ class Race():
wisdom_bonus = 0
charisma_bonus = 0
hit_point_bonus = 0
spells_known = ()
spells_prepared = ()
def __init__(self):
self.features = tuple([f() for f in self.features])
@@ -99,6 +101,8 @@ class DarkElf(Elf):
charisma_bonus = 1
features = (feats.SuperiorDarkvision, feats.FeyAncestry, feats.Trance,
feats.SunlightSensitivity, feats.DrowMagic)
spells_known = (spells.DancingLights(),)
spells_prepared = (spells.DancingLights(),)
# Halflings
@@ -164,6 +168,8 @@ class ForestGnome(Gnome):
dexterity_bonus = 1
features = Gnome.features + (feats.NaturalIllusionist,
feats.SpeakWithSmallBeasts)
spells_known = (spells.MinorIllusion(),)
spells_prepared = (spells.MinorIllusion(),)
class RockGnome(Gnome):
@@ -230,6 +236,8 @@ class Aasimar(Race):
languages = ("Common", "Celestial")
features = (feats.Darkvision, feats.CelestialResistance,
feats.HealingHands, feats.LightBearer)
spells_known = (spells.Light(),)
spells_prepared = (spells.Light(),)
# Protector Aasimar
@@ -334,6 +342,8 @@ class Triton(Race):
features = (feats.Amphibious, feats.ControlAirAndWater,
feats.EmissaryOfTheSea, feats.GuardiansOfTheDepths)
languages = ("Common", "Primordial")
spells_known = (spells.FogCloud(),)
spells_prepared = (spells.FogCloud(),)
# Aarakocra
@@ -350,6 +360,7 @@ class Aarakocra(Race):
# Genasi
class Genasi(Race):
name = "Genasi"
constitution_bonus = 2
size = 'medium'
speed = 30
@@ -357,61 +368,44 @@ class Genasi(Race):
class AirGenasi(Genasi):
name = "Air Genasi"
dexterity_bonus = 1
features = (feats.UnendingBreath,
feats.MingleWithTheWind)
class EarthGenasi(Genasi):
name = "Earth Genasi"
strength_bonus = 1
features = (feats.EarthWalk, feats.MergeWithStone)
class FireGenasi(Genasi):
name = "Fire Genasi"
intelligence_bonus = 1
features = (feats.Darkvision, feats.FireResistance,
feats.ReachToTheBlaze)
class WaterGenasi(Genasi):
name = "Water Genasi"
wisdom_bonus = 1
speed = "30 (30 swim)"
features = (feats.AcidResistance, feats.Amphibious,
feats.CallToTheWave)
race_dict = {
"Hill Dwarf": HillDwarf,
'Mountain Dwarf': MountainDwarf,
'High Elf': HighElf,
'Wood Elf': WoodElf,
'Dark Elf': DarkElf,
'Lightfoot Halfling': LightfootHalfling,
'Stout Halfling': StoutHalfling,
'Human': Human,
'Dragonborn': Dragonborn,
'Forest Gnome': ForestGnome,
'Rock Gnome': RockGnome,
'Deep Gnome': DeepGnome,
'Half-Elf': HalfElf,
'Half-Orc': HalfOrc,
'Tiefling': Tiefling,
'Fallen Aasimar': FallenAasimar,
'Protector Aasimar': ProtectorAasimar,
'Scourge Aasimar': ScourgeAasimar,
'Firbolg': Firbolg,
'Goliath': Goliath,
'Lizardfolk': Lizardfolk,
'Kenku': Kenku,
'Tabaxi': Tabaxi,
'Triton': Triton,
'Aarakocra': Aarakocra,
'Fire Genasi': FireGenasi,
'Earth Genasi': EarthGenasi,
'Water Genasi': WaterGenasi,
'Air Genasi': AirGenasi,
}
PHB_races = [HillDwarf, MountainDwarf, HighElf, WoodElf, DarkElf,
LightfootHalfling, StoutHalfling, Human, Dragonborn,
ForestGnome, RockGnome, HalfElf, HalfOrc, Tiefling]
__all__ = tuple(race_dict.keys())
VOLO_races = [ProtectorAasimar, ScourgeAasimar, FallenAasimar,
Firbolg, Goliath, Lizardfolk, Kenku, Tabaxi, Triton]
EE_races = [Aarakocra, DeepGnome, AirGenasi, FireGenasi, EarthGenasi,
WaterGenasi]
available_races = PHB_races + VOLO_races + EE_races
__all__ = tuple([r.name for r in available_races]) + (
'available_races', 'PHB_races', 'VOLO_races', 'EE_races')
+1 -1
View File
@@ -14,7 +14,7 @@
\maketitle
[% for spl in character.spells %]
[% if spl.__class__ in character.spells_prepared %]
[% if spl in character.spells_prepared %]
{
[% elif spl.level == 0 %]
{
+3
View File
@@ -48,6 +48,9 @@ class Spell():
def __eq__(self, other):
return (self.name == other.name) and (self.level == other.level)
def __hash__(self):
return 0
@property
def component_string(self):
s = f'{", ".join(self.components)}'
+19
View File
@@ -1704,6 +1704,25 @@ class DetectMagic(Spell):
classes = ('Bard', 'Cleric', 'Druid', 'Paladin', 'Ranger', 'Sorceror', 'Wizard', )
class DetectPoisonAndDisease(Spell):
"""For the duration, you can sense the presence and location of poisons,
poisonous creatures, and diseases within 30 feet of you. You also identify
the kind of poison, poisonous creature, or disease in each case.
The spell can penetrate most barriers, but is blocked by 1 foot of stone, 1
inch of common metal, a thin sheet of lead, or 3 feet of wood or dirt.
"""
name = "Detect Poison and Disease"
level = 1
casting_time = '1 action'
casting_range = "Self (30 feet)"
components = ("V", "S", "M")
materials = "a yew leaf"
magic_school = "Divination"
classes = ("Cleric", 'Druid', 'Paladin', 'Ranger')
class DimensionDoor(Spell):
"""You teleport yourself from your current location to any other spot
within range. You arrive at exactly the spot desired. It can be a
+94 -1
View File
@@ -174,7 +174,23 @@ class Etherealness(Spell):
materials = ""
duration = "Up to 8 hours"
magic_school = "Transmutation"
classes = ()
classes = ("Bard", 'Cleric', 'Sorceror', 'Warlock', 'Wizard')
class ExpeditiousRetreat(Spell):
"""This spell allows you to move at an incredible pace. When you cast this
spell, and then as a bonus action on each of your turns until the spell
ends, you can take the Dash action.
"""
name = "Expeditious Retreat"
level = 1
casting_time = '1 bonus action'
components = ('V', 'S')
duration = "Concentration, up to 10 minutes"
casting_range = "self"
magic_school = "Transmutation"
classes = ("Sorceror", "Warlock", "Wizard")
class Eyebite(Spell):
@@ -469,6 +485,28 @@ class FindSteed(Spell):
classes = ('Paladin', )
class FogCloud(Spell):
"""You create a 20-foot-radius sphere of fog centered on a point within
range. The sphere spreads around corners, and its area is heavily obscured,
It lasts for the duration or until a wind of moderate or greater speed (at
least 10 miles per hour) disperses it.
At Higher Level:
When you cast this spell using a spell slot of 2nd level or higher, the
radius of the fog increases by 20 feet for each slot level above 1st.
"""
name = "Fog Cloud"
level = 1
casting_time = "1 action"
casting_range = "120 feet"
components = ("V", "S")
duration = "Concentration, up to 1 hour"
magic_school = "Conjuration"
classes = ('Druid', 'Ranger', 'Sorceror', 'Wizard')
class Foresight(Spell):
"""You touch a willing creature and bestow a limited ability to see
into the immediate future. For the duration, the target cant be
@@ -681,6 +719,35 @@ class GuidingBolt(Spell):
classes = ()
class GustOfWind(Spell):
"""A line of strong wind 60 feet long and 10 feet wide blasts from you in a
direction you choose for the spells duration. Each creature that starts
its turn in the line must succeed on a Strength saving throw or be pushed
15 feet away from you in a direction following the line.
Any creature in the line must spend 2 feet of movement for every 1 foot it
moves when moving closer to you.
The gust disperses gas or vapor, and it extinguishes candles, torches, and
similar unprotected flames in the area. It causes protected flames, such as
those of lanterns, to dance wildly and has a 50 percent chance to
extinguish them.
As a bonus action on each of your turns before the spell ends, you can
change the direction in which the line blasts from you.
"""
name = "Gust of Wind"
level = 2
casting_time = "1 action"
casting_range = "Self (60-foot line)"
components = ("V", 'S', 'M')
materials = "A legume seed"
duration = "Concentration, up to 1 minute"
magic_school = "Evocation"
classes = ("Druid", "Sorceror", 'Wizard')
class Harm(Spell):
"""You unleash a virulent disease on a creature that you can see
within range. The target must make a Constitution saving throw. On
@@ -872,6 +939,32 @@ class HolyAura(Spell):
classes = ()
class HuntersMark(Spell):
"""You choose a creature you can see within range and mystically mark it as
your quarry. Until the spell ends, you deal an extra 1d6 damage to the
target whenever you hit it with a weapon attack, and you have advantage on
any Wisdom (Perception) or Wisdom (Survival) check you make to find it. If
the target drops to 0 hit points before this spell ends, you can use a
bonus action on a subsequent turn of yours to mark a new creature.
At Higher Level:
When you cast this spell using a spell slot of 3rd or 4th level, you can
maintain your concentration on the spell for up to 8 hours. When you use a
spell slot of 5th level or higher, you can maintain your concentration on
the spell for up to 24 hours.
"""
name = "Hunter's Mark"
level = 1
casting_time = "1 bonus action"
casting_range = "90 feet"
components = ("V")
duration = "Concentration, up to 1 hour"
magic_school = "Diviniation"
classes = ("Ranger",)
class IceStorm(Spell):
"""A hail of rock-hard ice pounds to the ground in a 20-foot-radius,
40-foot-high cylinder centered on a point within range. Each