mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-19 04:33:26 +02:00
Finished the basic functionality of the character creator.
This commit is contained in:
+5
-1
@@ -35,7 +35,11 @@ The PDF's can then be generated using the ``makesheets`` command.
|
||||
.. code:: bash
|
||||
|
||||
$ cd examples
|
||||
$ makesheets
|
||||
$ makesheets wizard.py
|
||||
|
||||
dungeon-sheets contains definitions for standard weapons and spells,
|
||||
so attack bonuses and damage can be calculated automatically.
|
||||
|
||||
If you'd like a **step-by-step walkthrough** for creating a new
|
||||
character, just run ``create-character`` from a command line and a
|
||||
helpful menu system will take care of the basics for you.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
class Background():
|
||||
name = "Generic background"
|
||||
skill_proficiencies = ()
|
||||
languages = ()
|
||||
|
||||
|
||||
class Acolyte(Background):
|
||||
name = "Acolyte"
|
||||
skill_proficiencies = ('insight', 'religion')
|
||||
languages = ("[choose one]", "[choose one]")
|
||||
|
||||
|
||||
class Charlatan(Background):
|
||||
name = "Charlatan"
|
||||
skill_proficiencies = ('deception', 'sleight of hand')
|
||||
|
||||
|
||||
class Criminal(Background):
|
||||
name = "Criminal"
|
||||
skill_proficiencies = ('deception', 'stealth')
|
||||
|
||||
|
||||
class Spy(Criminal):
|
||||
name = "Spy"
|
||||
|
||||
|
||||
class Entertainer(Background):
|
||||
name = "Entertainer"
|
||||
skill_proficiencies = ('acrobatic', 'performance')
|
||||
|
||||
|
||||
class Gladiator(Entertainer):
|
||||
name = "Gladiator"
|
||||
|
||||
|
||||
class FolkHero(Background):
|
||||
name = "Folk Hero"
|
||||
skill_proficiencies = ('animal handling', 'survival')
|
||||
|
||||
|
||||
class GuildArtisan(Background):
|
||||
name = "Guild Artisan"
|
||||
skill_proficiencies = ('insight', 'persuasion')
|
||||
languages = ("[choose one]", "[choose one]")
|
||||
|
||||
|
||||
class GuildMerchant(GuildArtisan):
|
||||
name = "Guild Merchant"
|
||||
|
||||
|
||||
class Hermit(Background):
|
||||
name = "Hermit"
|
||||
skill_proficiencies = ("medicine", "religion")
|
||||
languages = ("[choose one]")
|
||||
|
||||
|
||||
class Noble(Background):
|
||||
name = "Noble"
|
||||
skill_proficiencies = ("history", 'persuasion')
|
||||
languages = ("[choose one]", )
|
||||
|
||||
|
||||
class Knight(Noble):
|
||||
name = "Knight"
|
||||
|
||||
|
||||
class Outlander(Background):
|
||||
name = "Outlander"
|
||||
skill_proficiencies = ('athletics', 'survival')
|
||||
languages = ("[choose one]", )
|
||||
|
||||
|
||||
class Sage(Background):
|
||||
name = "Sage"
|
||||
skill_proficiencies = ('arcana', 'history')
|
||||
languages = ("[choose one]", '[choose one]')
|
||||
|
||||
|
||||
class Sailor(Background):
|
||||
name = "Sailor"
|
||||
skill_proficiencies = ('athletics', 'perception')
|
||||
|
||||
|
||||
class Pirate(Sailor):
|
||||
name = "Pirate"
|
||||
|
||||
|
||||
class Soldier(Background):
|
||||
name = "Soldier"
|
||||
skill_proficiencies = ('athletics', 'intimidation')
|
||||
|
||||
|
||||
class Urchin(Background):
|
||||
name = "Urchin"
|
||||
skill_proficiencies = ('sleight of hand', 'stealth')
|
||||
@@ -210,6 +210,7 @@ class Character():
|
||||
directly.
|
||||
|
||||
"""
|
||||
if new_armor not in ('', None):
|
||||
try:
|
||||
NewArmor = findattr(armor, new_armor)
|
||||
except AttributeError:
|
||||
@@ -227,6 +228,7 @@ class Character():
|
||||
directly.
|
||||
|
||||
"""
|
||||
if shield not in ('', None):
|
||||
try:
|
||||
NewShield = findattr(armor, shield)
|
||||
except AttributeError:
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
"""This file describes the heroic adventurer {{ char.name }}.
|
||||
|
||||
Modify this file as you level up and then re-generate the character
|
||||
sheet by running ``makesheets`` from the command line.
|
||||
|
||||
"""
|
||||
|
||||
name = '{{ char.name }}'
|
||||
character_class = '{{ char.class_name }}'
|
||||
player_name = '{{ char.player_name }}'
|
||||
background = "{{ char.background }}"
|
||||
background = "{{ char.background.name }}"
|
||||
race = "{{ char.race.name }}"
|
||||
level = {{ char.level }}
|
||||
alignment = "{{ char.alignment }}"
|
||||
@@ -15,80 +22,49 @@ constitution = {{ char.constitution.value }}
|
||||
intelligence = {{ char.intelligence.value }}
|
||||
wisdom = {{ char.wisdom.value }}
|
||||
charisma = {{ char.charisma.value }}
|
||||
skill_proficiencies = (
|
||||
# Put your skill proficiencies here, including the {{ char.race }} race.
|
||||
)
|
||||
skill_proficiencies = {{ char.skill_proficiencies }}
|
||||
|
||||
# Proficiencies and languages
|
||||
languages = "Common, Elvish, Draconic, Dwarvish, Goblin."
|
||||
languages = "{{ char.languages }}"
|
||||
|
||||
# Inventory
|
||||
# TODO: Get yourself some money
|
||||
cp = 0
|
||||
sp = 0
|
||||
ep = 0
|
||||
gp = 0
|
||||
pp = 0
|
||||
weapons = ('shortsword', 'longsword')
|
||||
equipment = (
|
||||
"""Gallon of ale, red cloak, shortsword, longsword, jar of salt, vodka
|
||||
(500mL), potion of vitality, wand of magic missiles (7/7),
|
||||
component pouch, spellbook, backpack, bottle of ink, ink pen, 10
|
||||
sheets of parchment, small knife, tome of historical lore, holy
|
||||
symbol, prayer book, set of common clothes, pouch.""")
|
||||
|
||||
# TODO: Put your equipped weapons and armor here
|
||||
weapons = () # Example: ('shortsword', 'longsword')
|
||||
armor = "" # Eg "light leather armor"
|
||||
shield = "" # Eg "shield"
|
||||
|
||||
equipment = "TODO: Describe your equipment from your {{ char.class_name }} class and {{ char.background.name }} background."
|
||||
|
||||
attacks_and_spellcasting = "TODO: Describe specifics for how your {{ char.class_name }} attacks."
|
||||
|
||||
# List of known spells
|
||||
spells = ('blindness deafness', 'burning hands', 'detect magic',
|
||||
'false life', 'mage armor', 'mage hand', 'magic missile',
|
||||
'prestidigitation', 'ray of frost', 'ray of sickness', 'shield',
|
||||
'shocking grasp', 'sleep', 'some other spell')
|
||||
# Example: spells = ('magic missile', 'mage armor')
|
||||
spells = () # Todo: Learn some spells
|
||||
# Which spells have been prepared (not including cantrips)
|
||||
spells_prepared = ('blindness deafness', 'false life', 'mage armor',
|
||||
'ray of sickness', 'shield', 'sleep',)
|
||||
spells_prepared = ()
|
||||
|
||||
# Backstory
|
||||
personality_traits = """I use polysyllabic words that convey the impression of
|
||||
erudition. Also, I’ve spent so long in the temple that I have little
|
||||
experience dealing with people on a casual basis."""
|
||||
# TODO: Describe your backstory here
|
||||
personality_traits = """I am a leaf on the wind,
|
||||
watch how I...
|
||||
"""
|
||||
|
||||
ideals = """Knowledge. The path to power and self-improvement is through
|
||||
knowledge."""
|
||||
ideals = """
|
||||
"""
|
||||
|
||||
bonds = """The tome I carry with me is the record of my life’s work so far,
|
||||
and no vault is secure enough to keep it safe."""
|
||||
bonds = """
|
||||
"""
|
||||
|
||||
flaws = """I’ll do just about anything to uncover historical secrets that
|
||||
would add to my research."""
|
||||
flaws = """
|
||||
"""
|
||||
|
||||
features_and_traits = (
|
||||
"""Spellcasting Ability: Intelligence is your spellcasting ability for
|
||||
your spells. The saving throw DC to resist a spell you cast is
|
||||
13. Your attack bonus when you make an attack with a spell is
|
||||
+5. See the rulebook for rules on casting your spells.
|
||||
|
||||
Arcane Recovery: You can regain some of your magical energy by
|
||||
studying your spellbook. Once per day during a short rest, you can
|
||||
choose to recover expended spell slots with a combined level equal
|
||||
to or less than half your wizard level (rounded up).
|
||||
|
||||
Darkvision: You see in dim light within a 60-foot radius of you as
|
||||
if it were bright light, and in darkness in that radius as if it
|
||||
were dim light. You can’t discern color in darkness, only shades
|
||||
of gray.
|
||||
|
||||
Fey Ancestry: You have advantage on saving throws against being
|
||||
charmed, and magic can’t put you to sleep.
|
||||
|
||||
Trance: Elves don’t need to sleep. They meditate deeply, remaining
|
||||
semiconscious, for 4 hours a day and gain the same benefit a human
|
||||
does from 8 hours of sleep.
|
||||
|
||||
Shelter of the Faithful: As a servant of Oghma, you command the
|
||||
respect of those who share your faith, and you can perform the
|
||||
rites of Oghma. You and your companions can expect to receive free
|
||||
healing and care at a temple, shrine, or other established
|
||||
presence of Oghma’s faith. Those who share your religion will
|
||||
support you (and only you) at a modest lifestyle. You also have
|
||||
ties to the temple of Oghma in Neverwinter, where you have a
|
||||
residence. When you are in Neverwinter, you can call upon the
|
||||
priests there for assistance that won’t endanger them.""")
|
||||
features_and_traits = """
|
||||
"""
|
||||
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
"""Launch a system to interactively create a character."""
|
||||
|
||||
import logging
|
||||
logging.basicConfig(filename='character_creater.log', level=logging.DEBUG)
|
||||
# logging.basicConfig(filename='character_creater.log', level=logging.DEBUG)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import math
|
||||
import os
|
||||
from random import randint
|
||||
import subprocess
|
||||
|
||||
import npyscreen
|
||||
import jinja2
|
||||
|
||||
from dungeonsheets import character, race, dice
|
||||
from dungeonsheets import character, race, dice, background
|
||||
|
||||
char_classes = {
|
||||
'Barbarian': character.Barbarian,
|
||||
@@ -49,6 +50,18 @@ races = {
|
||||
}
|
||||
|
||||
|
||||
backgrounds = (background.Acolyte, background.Charlatan,
|
||||
background.Criminal, background.Spy,
|
||||
background.Entertainer, background.Gladiator,
|
||||
background.FolkHero, background.GuildArtisan,
|
||||
background.GuildMerchant, background.Hermit,
|
||||
background.Noble, background.Knight,
|
||||
background.Outlander, background.Sage,
|
||||
background.Sailor, background.Pirate,
|
||||
background.Soldier, background.Urchin)
|
||||
backgrounds = {bg.name: bg for bg in backgrounds}
|
||||
|
||||
|
||||
class App(npyscreen.NPSAppManaged):
|
||||
# STARTING_FORM = 'SKILLS'
|
||||
character = None
|
||||
@@ -68,6 +81,10 @@ class App(npyscreen.NPSAppManaged):
|
||||
filename = self.getForm("SAVE").filename.value
|
||||
with open(filename, mode='w') as f:
|
||||
f.write(text)
|
||||
# Create the PDF character sheet
|
||||
if self.getForm('SAVE').make_pdf.value:
|
||||
log.debug("Creating PDF")
|
||||
subprocess.call(['makesheets', filename])
|
||||
|
||||
@property
|
||||
def character_class(self, *args, **kwargs):
|
||||
@@ -114,18 +131,34 @@ class App(npyscreen.NPSAppManaged):
|
||||
|
||||
|
||||
class SkillForm(npyscreen.ActionForm):
|
||||
|
||||
def while_editing(self):
|
||||
self.skill_proficiencies.set_values(self.parentApp.character.class_skill_choices)
|
||||
# Update the static skills for race and background
|
||||
bg_skills = self.parentApp.character.background.skill_proficiencies
|
||||
self.bg_skills.value = str(bg_skills)[1:-1].replace("'", "")
|
||||
race_skills = self.parentApp.character.race.skill_proficiencies
|
||||
self.race_skills.value = str(race_skills)[1:-1].replace("'", "")
|
||||
# Now set the available discretionary choices
|
||||
choices = self.parentApp.character.class_skill_choices
|
||||
static_skills = bg_skills + race_skills
|
||||
choices = (c for c in choices if c.lower() not in static_skills)
|
||||
self.skill_proficiencies.set_values(tuple(choices))
|
||||
self.update_remaining()
|
||||
|
||||
def update_remaining(self, widget=None):
|
||||
remaining = self.parentApp.character.num_skill_choices - len(self.skill_proficiencies.value)
|
||||
num_choices = self.parentApp.character.num_skill_choices
|
||||
num_selected = len(self.skill_proficiencies.value)
|
||||
remaining = num_choices - num_selected
|
||||
log.debug(f'Remaining: {remaining}')
|
||||
self.remaining.value = str(remaining)
|
||||
self.display()
|
||||
|
||||
def create(self):
|
||||
self.bg_skills = self.add(
|
||||
npyscreen.TitleText, name="Background:",
|
||||
value="", editable=False)
|
||||
self.race_skills = self.add(
|
||||
npyscreen.TitleText, name="Racial:",
|
||||
value="", editable=False)
|
||||
self.remaining = self.add(
|
||||
npyscreen.TitleText, name="Remaining:",
|
||||
value=0, editable=False)
|
||||
@@ -135,11 +168,22 @@ class SkillForm(npyscreen.ActionForm):
|
||||
value_changed_callback=self.update_remaining)
|
||||
|
||||
def on_ok(self):
|
||||
new_skills = self.skill_proficiencies.get_selected_objects()
|
||||
if new_skills is not None:
|
||||
new_skills = tuple(s.lower() for s in new_skills)
|
||||
else:
|
||||
new_skills = ()
|
||||
bg_skills = tuple(self.parentApp.character.background.skill_proficiencies)
|
||||
race_skills = tuple(self.parentApp.character.race.skill_proficiencies)
|
||||
all_skills = new_skills + bg_skills + race_skills
|
||||
self.parentApp.character.skill_proficiencies = all_skills
|
||||
log.debug(f"Skill proficiencies: {all_skills}")
|
||||
self.parentApp.setNextForm('SAVE')
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm('BACKGROUND')
|
||||
|
||||
|
||||
class AbilityScoreForm(npyscreen.ActionForm):
|
||||
def roll_dice(self):
|
||||
"""Get six ability scores that can then be assigned to abilities."""
|
||||
@@ -226,18 +270,22 @@ class CharacterClassForm(npyscreen.ActionForm):
|
||||
|
||||
|
||||
class BackgroundForm(npyscreen.ActionForm):
|
||||
backgrounds = ('Acolyte', 'Charlatan', 'Criminal', 'Entertainer',
|
||||
'Folk hero', 'Guild Artison', 'Hermit', 'Noble', 'Knight',
|
||||
'Outlander', 'Pirate', 'Sage', 'Sailor', 'Soldier', 'Urchin',)
|
||||
|
||||
def create(self):
|
||||
self.background = self.add(npyscreen.TitleMultiLine,
|
||||
name="Background:", values=self.backgrounds)
|
||||
self.background = self.add(
|
||||
npyscreen.TitleMultiLine,
|
||||
name="Background:", values=tuple(backgrounds.keys()))
|
||||
|
||||
def on_ok(self):
|
||||
if self.background.value is not None:
|
||||
background = self.backgrounds[self.background.value]
|
||||
self.parentApp.character.background = background
|
||||
log.debug("Selected character background: %s", background)
|
||||
selected_bg = self.background.values[self.background.value]
|
||||
Background = backgrounds[selected_bg]
|
||||
self.parentApp.character.background = Background()
|
||||
# Update the languages based on background and race
|
||||
race_languages = self.parentApp.character.race.languages
|
||||
languages = Background.languages + race_languages
|
||||
self.parentApp.character.languages = ', '.join(languages)
|
||||
log.debug("Selected character background: %s", Background.name)
|
||||
self.parentApp.setNextForm('SKILLS')
|
||||
|
||||
def on_cancel(self):
|
||||
@@ -276,6 +324,7 @@ class AlignmentForm(npyscreen.ActionForm):
|
||||
if self.alignment.value is not None:
|
||||
selected_alignment = self.alignment.values[self.alignment.value]
|
||||
log.debug('Selected character alignment %s', selected_alignment)
|
||||
self.parentApp.character.alignment = selected_alignment
|
||||
self.parentApp.setNextForm('ABILITIES')
|
||||
|
||||
def on_cancel(self):
|
||||
@@ -312,6 +361,9 @@ class SaveForm(npyscreen.ActionForm):
|
||||
self.filename = self.add(
|
||||
npyscreen.TitleText, name='Filename:')
|
||||
self.make_pdf = self.add(npyscreen.Checkbox, name="Create PDF:", value=True)
|
||||
self.instructions = self.add(
|
||||
npyscreen.FixedText, editbale=False,
|
||||
value="After saving, edit this file to finish your personality, etc.")
|
||||
|
||||
def on_ok(self):
|
||||
self.parentApp.setNextForm(None)
|
||||
@@ -320,11 +372,16 @@ class SaveForm(npyscreen.ActionForm):
|
||||
self.parentApp.setNextForm('SKILLS')
|
||||
|
||||
|
||||
my_app = App()
|
||||
def main():
|
||||
my_app = App()
|
||||
|
||||
try:
|
||||
try:
|
||||
my_app.run()
|
||||
except KeyboardInterrupt:
|
||||
except KeyboardInterrupt:
|
||||
log.error("Aborted by user request")
|
||||
else:
|
||||
else:
|
||||
my_app.save_character()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -222,7 +222,7 @@ def create_character_pdf(character, basename, flatten=False):
|
||||
}
|
||||
# Add skill proficienies
|
||||
for skill in character.skill_proficiencies:
|
||||
fields.append((skill_boxes[skill], 'Yes'))
|
||||
fields.append((skill_boxes[skill.replace(' ', '_')], 'Yes'))
|
||||
# Add weapons
|
||||
weapon_fields = [('Wpn Name', 'Wpn1 AtkBonus', 'Wpn1 Damage'),
|
||||
('Wpn Name 2', 'Wpn2 AtkBonus ', 'Wpn2 Damage '),
|
||||
|
||||
@@ -11,8 +11,10 @@ class Race():
|
||||
name = "Unknown"
|
||||
size = "medium"
|
||||
speed = 30
|
||||
languages = ('Common', )
|
||||
proficiencies_text = tuple()
|
||||
weapon_proficiences = tuple()
|
||||
skill_proficiencies = ()
|
||||
strength_bonus = 0
|
||||
dexterity_bonus = 0
|
||||
constitution_bonus = 0
|
||||
@@ -33,6 +35,7 @@ class Dwarf(Race):
|
||||
name = "Dwarf"
|
||||
size = "medium"
|
||||
speed = 25
|
||||
languages = ("Common", "Dwarvish")
|
||||
constitution_bonus = 2
|
||||
proficiencies_text = ('battleaxes', 'handaxes', 'throwing hammers', 'warhammers')
|
||||
weapon_proficiences = (weapons.Battleaxe, weapons.Handaxe,
|
||||
@@ -56,6 +59,8 @@ class Elf(Race):
|
||||
size = "medium"
|
||||
speed = 30
|
||||
dexterity_bonus = 2
|
||||
skill_proficiencies = ('perception',)
|
||||
languages = ('Common', 'Elvish')
|
||||
|
||||
|
||||
class HighElf(Elf):
|
||||
@@ -64,6 +69,7 @@ class HighElf(Elf):
|
||||
weapons.Shortbow, weapons.Longbow)
|
||||
proficiencies_text = ('longswords', 'shortswords', 'shortbows', 'longbows')
|
||||
intelligence_bonus = 1
|
||||
languages = ('Common', 'Elvish', '[choose one]')
|
||||
|
||||
|
||||
class WoodElf(Elf):
|
||||
@@ -87,6 +93,7 @@ class Halfling(Race):
|
||||
size = "small"
|
||||
speed = 25
|
||||
dexterity_bonus = 2
|
||||
languages = ('Common', 'Halfling')
|
||||
|
||||
|
||||
class LightfootHalfling(Halfling):
|
||||
@@ -110,6 +117,7 @@ class Human(Race):
|
||||
intelligence_bonus = 1
|
||||
wisdom_bonus = 1
|
||||
charisma_bonus = 1
|
||||
languages = ("Common", '[choose one]')
|
||||
|
||||
|
||||
# Dragonborn
|
||||
@@ -119,6 +127,7 @@ class Dragonborn(Race):
|
||||
speed = 30
|
||||
strength_bonus = 2
|
||||
charisma_bonus = 1
|
||||
languages = ("Common", "Draconic")
|
||||
|
||||
|
||||
# Gnomes
|
||||
@@ -127,6 +136,7 @@ class Gnome(Race):
|
||||
size = "small"
|
||||
speed = 25
|
||||
intelligence_bonus = 2
|
||||
languages = ("Common", "Gnomish")
|
||||
|
||||
|
||||
class ForestGnome(Gnome):
|
||||
@@ -145,6 +155,7 @@ class HalfElf(Race):
|
||||
size = "medium"
|
||||
speed = 30
|
||||
charisma_bonus = 2
|
||||
languages = ("Common", "Elvish", "[choose one]")
|
||||
|
||||
|
||||
# Half-Orcs
|
||||
@@ -154,6 +165,7 @@ class HalfOrc(Race):
|
||||
speed = 30
|
||||
strength_bonus = 2
|
||||
constitution_bonus = 1
|
||||
languages = ("Common", "Orc")
|
||||
|
||||
|
||||
# Tielflings
|
||||
@@ -163,3 +175,4 @@ class Tiefling(Race):
|
||||
speed = 30
|
||||
intelligence_bonus = 1
|
||||
charisma_bonus = 2
|
||||
languages = ("Common", "Infernal")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
certifi>=2018.1.18
|
||||
fdfgen>=0.16
|
||||
npyscreen
|
||||
jinja
|
||||
jinja2
|
||||
|
||||
@@ -8,7 +8,7 @@ def read(fname):
|
||||
|
||||
|
||||
setup(name='dungeonsheets',
|
||||
version='0.2.2',
|
||||
version='0.3.0',
|
||||
description='Dungeons and Dragons 5e Character Tools',
|
||||
long_description=read('README.rst'),
|
||||
long_description_content_type='text/x-rst',
|
||||
@@ -23,11 +23,12 @@ setup(name='dungeonsheets',
|
||||
'dungeonsheets': ['blank-character-sheet-default.pdf', 'blank-spell-sheet-default.pdf']
|
||||
},
|
||||
install_requires=[
|
||||
'fdfgen', 'npyscreen', 'jinja',
|
||||
'fdfgen', 'npyscreen', 'jinja2',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'makesheets = dungeonsheets.make_sheets:main'
|
||||
'makesheets = dungeonsheets.make_sheets:main',
|
||||
'create-character = dungeonsheets.create_character:main',
|
||||
]
|
||||
},
|
||||
python_requires='>=3.6',
|
||||
|
||||
Reference in New Issue
Block a user