diff --git a/dungeonsheets/character.py b/dungeonsheets/character.py index 43da0a0..8c94f1c 100644 --- a/dungeonsheets/character.py +++ b/dungeonsheets/character.py @@ -40,6 +40,8 @@ class Character(): charisma = Ability() saving_throw_proficiencies = [] skill_proficiencies = tuple() + class_skill_choices = tuple() + num_skill_choices = 2 weapon_proficiencies = tuple() proficiencies_extra = tuple() languages = "" @@ -304,6 +306,8 @@ class Barbarian(Character): _proficiencies_text = ('light armor', 'medium armor', 'shields', 'simple weapons', 'martial weapons') weapon_proficiencies = (weapons.simple_weapons + weapons.martial_weapons) + class_skill_choices = ('Animal Handling', 'Athletics', + 'Intimidation', 'Nature', 'Perception', 'Survival') class Bard(Character): @@ -316,6 +320,12 @@ class Bard(Character): weapon_proficiencies = ((weapons.HandCrossbow, weapons.Longsword, weapons.Rapier, weapons.Shortsword) + weapons.simple_weapons) + class_skill_choices = ('Acrobatics', 'Animal Handling', 'Arcana', + 'Athletics', 'Deception', 'History', 'Insight', + 'Intimidation', 'Investigation', 'Medicine', 'Nature', + 'Perception', 'Performance', 'Persuasion', 'Religion', + 'Sleight of Hand', 'Stealth', 'Survival') + num_skill_choices = 3 class Cleric(Character): @@ -325,6 +335,8 @@ class Cleric(Character): _proficiencies_text = ('light armor', 'medium armor', 'shields', 'all simple weapons') weapon_proficiencies = weapons.simple_weapons + class_skill_choices = ('History', 'Insight', 'Medicine', + 'Persuasion', 'Religion') class Druid(Character): @@ -339,6 +351,8 @@ class Druid(Character): weapon_proficiencies = (weapons.Club, weapons.Dagger, weapons.Dart, weapons.Javelin, weapons.Mace, weapons.Quarterstaff, weapons.Scimitar, weapons.Sickle, weapons.Sling, weapons.Spear) + class_skill_choices = ('Arcana', 'Animal Handling', 'Insight', + 'Medicine', 'Nature', 'Perception', 'Religion', 'Survival') class Fighter(Character): @@ -347,6 +361,9 @@ class Fighter(Character): saving_throw_proficiencies = ('strength', 'constitution') _proficiencies_text = ('All armar', 'shields', 'simple weapons', 'martial weapons') weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons + class_skill_choices = ('Acrobatics', 'Animal Handling', + 'Athletics', 'History', 'Insight', 'Intimidation', 'Perception', + 'Survival') class Monk(Character): @@ -357,7 +374,7 @@ class Monk(Character): 'simple weapons', 'shortswords', "one type of artisan's tools or one musical instrument") weapon_proficiencies = (weapons.Shortsword,) + weapons.simple_weapons - + class_skill_choices = ('Acrobatics', 'Athletics', 'History', 'Insight', 'Religion', 'Stealth') class Paladin(Character): class_name = 'Paladin' @@ -366,6 +383,8 @@ class Paladin(Character): _proficiencies_text = ('All armor', 'shields', 'simple weapons', 'martial weapons') weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons + class_skill_choices = ("Athletics", 'Insight', 'Intimidation', + 'Medicine', 'Persuasion', 'Religion') class Ranger(Character): @@ -375,6 +394,9 @@ class Ranger(Character): _proficiencies_text = ("light armor", "medium armor", "shields", "simple weapons", "martial weapons") weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons + class_skill_choices = ('Animal Handling', 'Athletics', 'Insight', + 'Investigation', 'Nature', 'Perception', 'Stealth', 'Survival') + num_skill_choices = 3 class Rogue(Character): @@ -386,6 +408,9 @@ class Rogue(Character): 'rapiers', 'shortswords', "thieves' tools") weapon_proficiencies = (weapons.HandCrossbow, weapons.Longsword, weapons.Rapier, weapons.Shortsword) + weapons.simple_weapons + class_skill_choices = ('Acrobatics', 'Athletics', 'Deception', + 'Insight', 'Intimidation', 'Investigation', 'Perception', + 'Performance', 'Persuasion', 'Sleight of Hand', 'Stealth') class Sorceror(Character): @@ -397,6 +422,8 @@ class Sorceror(Character): weapon_proficiencies = (weapons.Dagger, weapons.Dart, weapons.Sling, weapons.Quarterstaff, weapons.LightCrossbow) + class_skill_choices = ('Arcana', 'Deception', 'Insight', + 'Intimidation' ,'Persuasion', 'Religion') class Warlock(Character): @@ -404,6 +431,8 @@ class Warlock(Character): hit_dice_faces = 8 saving_throw_proficiencies = ('wisdom', 'charisma') _proficiencies_text = ("light Armor", "simple weapons") + class_skill_choices = ('Arcana', 'Deception', 'History', + 'Intimidation', 'Investigation', 'Nature', 'Religion') weapon_proficiencies = weapons.simple_weapons spellcasting_ability = 'charisma' spell_slots_by_level = { @@ -439,6 +468,8 @@ class Wizard(Character): weapon_proficiencies = (weapons.Dagger, weapons.Dart, weapons.Sling, weapons.Quarterstaff, weapons.LightCrossbow) + class_skill_choices = ('Arcana', 'History', 'Investigation', + 'Medicine', 'Religion') spellcasting_ability = 'intelligence' spell_slots_by_level = { # char_lvl: (cantrips, 1st, 2nd, 3rd, ...) diff --git a/dungeonsheets/character_template.txt b/dungeonsheets/character_template.txt new file mode 100644 index 0000000..893da24 --- /dev/null +++ b/dungeonsheets/character_template.txt @@ -0,0 +1,94 @@ +name = '{{ char.name }}' +character_class = '{{ char.class_name }}' +player_name = '{{ char.player_name }}' +background = "{{ char.background }}" +race = "{{ char.race.name }}" +level = {{ char.level }} +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 }} +skill_proficiencies = ( + # Put your skill proficiencies here, including the {{ char.race }} race. +) + +# Proficiencies and languages +languages = "Common, Elvish, Draconic, Dwarvish, Goblin." + +# Inventory +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.""") + +# 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') +# Which spells have been prepared (not including cantrips) +spells_prepared = ('blindness deafness', 'false life', 'mage armor', + 'ray of sickness', 'shield', 'sleep',) + +# 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.""" + +ideals = """Knowledge. The path to power and self-improvement is through +knowledge.""" + +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.""" + +flaws = """I’ll do just about anything to uncover historical secrets that +would add to my research.""" + +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.""") + diff --git a/dungeonsheets/create_character.py b/dungeonsheets/create_character.py index 786cff5..af95dbe 100755 --- a/dungeonsheets/create_character.py +++ b/dungeonsheets/create_character.py @@ -7,9 +7,11 @@ logging.basicConfig(filename='character_creater.log', level=logging.DEBUG) log = logging.getLogger(__name__) import math +import os +from random import randint import npyscreen -from random import randint +import jinja2 from dungeonsheets import character, race, dice @@ -48,11 +50,24 @@ races = { class App(npyscreen.NPSAppManaged): - # STARTING_FORM = 'CLASS' + # STARTING_FORM = 'SKILLS' character = None def save_character(self): - pass + # Create the template context + context = dict( + char=self.character + ) + # Render the template + src_path = os.path.dirname(__file__) + src_filename = 'character_template.txt' + text = jinja2.Environment( + loader=jinja2.FileSystemLoader(src_path or './') + ).get_template(src_filename).render(context) + # Save the file + filename = self.getForm("SAVE").filename.value + with open(filename, mode='w') as f: + f.write(text) @property def character_class(self, *args, **kwargs): @@ -87,15 +102,44 @@ class App(npyscreen.NPSAppManaged): max_hp_fld.value = str(max_hp) def onStart(self): + self.character = character.Character() self.addForm("MAIN", BasicInfoForm, name="Basic Info:") self.addForm("CLASS", CharacterClassForm, name="Select your character's class:") self.addForm("RACE", RaceForm, name="Select your character's race:") self.addForm("ALIGNMENT", AlignmentForm, name="Select your character's alignment:") self.addForm("ABILITIES", AbilityScoreForm, name="Choose ability scores:") self.addForm("BACKGROUND", BackgroundForm, name="Choose background:") + self.addForm("SKILLS", SkillForm, name="Choose skill proficiencies") self.addForm("SAVE", SaveForm, name="Save character:") +class SkillForm(npyscreen.ActionForm): + + def while_editing(self): + self.skill_proficiencies.set_values(self.parentApp.character.class_skill_choices) + self.update_remaining() + + def update_remaining(self, widget=None): + remaining = self.parentApp.character.num_skill_choices - len(self.skill_proficiencies.value) + log.debug(f'Remaining: {remaining}') + self.remaining.value = str(remaining) + self.display() + + def create(self): + self.remaining = self.add( + npyscreen.TitleText, name="Remaining:", + value=0, editable=False) + self.skill_proficiencies = self.add( + npyscreen.TitleMultiSelect, name="Skill Proficiencies:", + values=self.parentApp.character.class_skill_choices, + value_changed_callback=self.update_remaining) + + def on_ok(self): + 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.""" @@ -194,7 +238,7 @@ class BackgroundForm(npyscreen.ActionForm): background = self.backgrounds[self.background.value] self.parentApp.character.background = background log.debug("Selected character background: %s", background) - self.parentApp.setNextForm('SAVE') + self.parentApp.setNextForm('SKILLS') def on_cancel(self): self.parentApp.setNextForm('ABILITIES') @@ -260,7 +304,7 @@ class BasicInfoForm(npyscreen.ActionForm): self.parentApp.setNextForm('CLASS') def on_cancel(self): - self.parentApp.setNextForm('SAVE') + self.parentApp.setNextForm(None) class SaveForm(npyscreen.ActionForm): @@ -273,7 +317,7 @@ class SaveForm(npyscreen.ActionForm): self.parentApp.setNextForm(None) def on_cancel(self): - self.parentApp.setNextForm('BACKGROUND') + self.parentApp.setNextForm('SKILLS') my_app = App() diff --git a/requirements.txt b/requirements.txt index 286edf5..8e976d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ certifi>=2018.1.18 fdfgen>=0.16 +npyscreen +jinja diff --git a/setup.py b/setup.py index 571b3c3..6d2af5e 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup(name='dungeonsheets', 'dungeonsheets': ['blank-character-sheet-default.pdf', 'blank-spell-sheet-default.pdf'] }, install_requires=[ - 'fdfgen', 'npyscreen', + 'fdfgen', 'npyscreen', 'jinja', ], entry_points={ 'console_scripts': [