mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 13:15:53 +02:00
checked new code works for create-character
This commit is contained in:
@@ -8,31 +8,34 @@ sheet by running ``makesheets`` from the command line.
|
||||
|
||||
"""
|
||||
|
||||
dungeonsheets_version = "0.8.3"
|
||||
dungeonsheets_version = "0.9.0"
|
||||
|
||||
name = "Ben"
|
||||
classes_levels = ['paladin 1']
|
||||
subclasses = ["Oath of The Ancients"]
|
||||
player_name = "Ben"
|
||||
background = "Charlatan"
|
||||
race = "Hill Dwarf"
|
||||
|
||||
# Be sure to list Primary class first
|
||||
classes = ['Bard', 'Paladin'] # ex: ['Wizard'] or ['Rogue', 'Fighter']
|
||||
levels = [10, 2] # ex: [10] or [3, 2]
|
||||
subclasses = ['', ''] # ex: ['Necromacy'] or ['Thief', None]
|
||||
background = "Sailor"
|
||||
race = "Half-Orc"
|
||||
alignment = "Neutral good"
|
||||
xp = 0
|
||||
hp_max = 10
|
||||
|
||||
# Ability Scores
|
||||
strength = 15
|
||||
dexterity = 14
|
||||
constitution = 15
|
||||
strength = 20
|
||||
dexterity = 13
|
||||
constitution = 14
|
||||
intelligence = 12
|
||||
wisdom = 11
|
||||
charisma = 8
|
||||
wisdom = 10
|
||||
charisma = 9
|
||||
|
||||
# Select what skills you're proficient with
|
||||
skill_proficiencies = ('intimidation', 'athletics', 'deception', 'sleight of hand')
|
||||
# ex: skill_proficiencies = ('athletics', 'acrobatics', 'arcana')
|
||||
skill_proficiencies = ('arcana', 'medicine', 'athletics', 'perception', 'intimidation')
|
||||
|
||||
# Named features / feats that aren't part of your classes,
|
||||
# race, or background.
|
||||
# Named features / feats that aren't part of your classes, race, or background.
|
||||
# Example:
|
||||
# features = ('Tavern Brawler',) # take the optional Feat from PHB
|
||||
features = ()
|
||||
@@ -43,7 +46,7 @@ features = ()
|
||||
feature_choices = ()
|
||||
|
||||
# Proficiencies and languages
|
||||
languages = """Common, Dwarvish"""
|
||||
languages = """Common, Orc"""
|
||||
|
||||
# Inventory
|
||||
# TODO: Get yourself some money
|
||||
@@ -55,6 +58,7 @@ pp = 0
|
||||
|
||||
# TODO: Put your equipped weapons and armor here
|
||||
weapons = () # Example: ('shortsword', 'longsword')
|
||||
magic_items = () # Example: ('ring of protection',)
|
||||
armor = "" # Eg "light leather armor"
|
||||
shield = "" # Eg "shield"
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class Background():
|
||||
features = ()
|
||||
languages = ()
|
||||
|
||||
def __init__(self, owner):
|
||||
def __init__(self, owner=None):
|
||||
self.owner = owner
|
||||
cls = type(self)
|
||||
self.features = tuple([f(owner=self.owner) for f in cls.features])
|
||||
|
||||
+42
-37
@@ -143,7 +143,7 @@ class Character():
|
||||
my_levels = [attrs.pop('level', 1)]
|
||||
my_subclasses = [attrs.pop('subclass', None)]
|
||||
# Generate the list of class objects
|
||||
self.class_list = parse_classes(
|
||||
self.add_classes(
|
||||
my_classes, my_levels, my_subclasses,
|
||||
feature_choices=attrs.get('feature_choices', []))
|
||||
# parse race and background
|
||||
@@ -158,6 +158,47 @@ class Character():
|
||||
def __repr__(self):
|
||||
return f"<{self.class_name}: {self.name}>"
|
||||
|
||||
def add_class(self, cls: (classes.CharClass, type, str), level: (int, str),
|
||||
subclass=None, feature_choices=[]):
|
||||
if isinstance(cls, str):
|
||||
cls = cls.strip().title().replace(' ', '')
|
||||
try:
|
||||
cls = getattr(classes, cls)
|
||||
except AttributeError:
|
||||
raise AttributeError(
|
||||
'class was not recognized from classes.py: {:s}'.format(c))
|
||||
if isinstance(level, str):
|
||||
level = int(level)
|
||||
params = {}
|
||||
params['feature_choices'] = feature_choices
|
||||
self.class_list.append(cls(level, owner=self,
|
||||
subclass=subclass, **params))
|
||||
|
||||
def add_classes(self, classes_list=[], levels=[], subclasses=[],
|
||||
feature_choices=[]):
|
||||
if isinstance(classes_list, str):
|
||||
classes_list = [classes_list]
|
||||
if isinstance(levels, int) or isinstance(levels, float) or isinstance(levels, str):
|
||||
levels = [levels]
|
||||
if len(levels) == 0:
|
||||
levels = [1]*len(classes_list)
|
||||
if isinstance(subclasses, str):
|
||||
subclasses = [subclasses]
|
||||
if len(subclasses) == 0:
|
||||
subclasses = [None]*len(classes_list)
|
||||
assert len(classes_list) == len(levels), (
|
||||
'the length of classes {:d} does not match length of '
|
||||
'levels {:d}'.format(len(classes), len(levels)))
|
||||
assert len(classes_list) == len(subclasses), (
|
||||
'the length of classes {:d} does not match length of '
|
||||
'subclasses {:d}'.format(len(classes_list), len(subclasses)))
|
||||
class_list = []
|
||||
for cls, lvl, sub in zip(classes_list, levels, subclasses):
|
||||
params = {}
|
||||
params['feature_choices'] = feature_choices
|
||||
self.add_class(cls=cls, level=lvl, subclass=sub,
|
||||
**params)
|
||||
|
||||
@property
|
||||
def race(self):
|
||||
return self._race
|
||||
@@ -668,42 +709,6 @@ class Character():
|
||||
flatten=kwargs.get('flatten', True))
|
||||
|
||||
|
||||
def parse_classes(classes_list=[], levels=[], subclasses=[],
|
||||
feature_choices=[]):
|
||||
if isinstance(classes_list, str):
|
||||
classes_list = [classes_list]
|
||||
if isinstance(levels, int) or isinstance(levels, float) or isinstance(levels, str):
|
||||
levels = [levels]
|
||||
if len(levels) == 0:
|
||||
levels = [1]*len(classes_list)
|
||||
if isinstance(subclasses, str):
|
||||
subclasses = [subclasses]
|
||||
if len(subclasses) == 0:
|
||||
subclasses = [None]*len(classes_list)
|
||||
assert len(classes_list) == len(levels), (
|
||||
'the length of classes {:d} does not match length of '
|
||||
'levels {:d}'.format(len(classes), len(levels)))
|
||||
assert len(classes_list) == len(subclasses), (
|
||||
'the length of classes {:d} does not match length of '
|
||||
'subclasses {:d}'.format(len(classes_list), len(subclasses)))
|
||||
class_list = []
|
||||
for cls, lvl, sub in zip(classes_list, levels, subclasses):
|
||||
if isinstance(cls, str):
|
||||
cls = cls.strip().title().replace(' ', '')
|
||||
try:
|
||||
this_class = getattr(classes, cls)
|
||||
this_level = int(lvl)
|
||||
except AttributeError:
|
||||
raise AttributeError(
|
||||
'class was not recognized from classes.py: {:s}'.format(c))
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'level was not recognizable as an int: {:s}'.format(str(lvl)))
|
||||
params = {}
|
||||
params['feature_choices'] = feature_choices
|
||||
class_list += [this_class(this_level, subclass=sub, **params)]
|
||||
return class_list
|
||||
|
||||
|
||||
def read_character_file(filename):
|
||||
"""Create a character object from the given definition file.
|
||||
|
||||
@@ -99,13 +99,15 @@ class ZealotPath(SubClass):
|
||||
class Barbarian(CharClass):
|
||||
name = 'Barbarian'
|
||||
hit_dice_faces = 12
|
||||
subclass_select_level = 3
|
||||
saving_throw_proficiencies = ('strength', 'constitution')
|
||||
primary_abilities = ('strength',)
|
||||
weapon_proficiencies = (weapons.SimpleWeapon + weapons.MartialWearpon)
|
||||
weapon_proficiencies = (weapons.SimpleWeapon, weapons.MartialWeapon)
|
||||
_proficiencies_text = ('light armor', 'medium armor', 'shields',
|
||||
'simple weapons', 'martial weapons')
|
||||
multiclass_weapon_proficiencies = weapon_proficiencies
|
||||
_multiclass_proficiencies_text = ('shields', 'simple weapons', 'martial weapons')
|
||||
_multiclass_proficiencies_text = ('shields', 'simple weapons',
|
||||
'martial weapons')
|
||||
class_skill_choices = ('Animal Handling', 'Athletics',
|
||||
'Intimidation', 'Nature', 'Perception', 'Survival')
|
||||
subclasses_available = (BerserkerPath, TotemWarriorPath, BattleragerPath,
|
||||
|
||||
@@ -116,13 +116,14 @@ class CollegeOfWhispers(SubClass):
|
||||
class Bard(CharClass):
|
||||
name = 'Bard'
|
||||
hit_dice_faces = 8
|
||||
subclass_select_level = 3
|
||||
saving_throw_proficiencies = ('dexterity', 'charisma')
|
||||
primary_abilities = ('charisma',)
|
||||
_proficiencies_text = (
|
||||
'Light armor', 'simple weapons', 'hand crossbows', 'longswords',
|
||||
'rapiers', 'shortswords', 'three musical instruments of your choice')
|
||||
weapon_proficiencies = ((weapons.HandCrossbow, weapons.Longsword,
|
||||
weapons.Rapier, weapons.Shortsword) +
|
||||
weapon_proficiencies = (weapons.HandCrossbow, weapons.Longsword,
|
||||
weapons.Rapier, weapons.Shortsword,
|
||||
weapons.SimpleWeapon)
|
||||
class_skill_choices = ('Acrobatics', 'Animal Handling', 'Arcana',
|
||||
'Athletics', 'Deception', 'History', 'Insight',
|
||||
|
||||
@@ -9,6 +9,7 @@ class CharClass():
|
||||
name = "Default"
|
||||
level = 1
|
||||
hit_dice_faces = 2
|
||||
subclass_select_level = 3
|
||||
weapon_proficiencies = ()
|
||||
_proficiencies_text = ()
|
||||
multiclass_weapon_proficiencies = ()
|
||||
@@ -26,9 +27,12 @@ class CharClass():
|
||||
subclasses_available = ()
|
||||
features_by_level = defaultdict(list)
|
||||
|
||||
def __init__(self, level, subclass=None, feature_choices=[],
|
||||
def __init__(self, level, owner=None, subclass=None, feature_choices=[],
|
||||
**params):
|
||||
self.level = level
|
||||
self.owner = owner
|
||||
# For ex: add "char.Monk" attribute
|
||||
setattr(self.owner, self.name, self)
|
||||
# Instantiate the features
|
||||
self.features_by_level = defaultdict(list)
|
||||
cls = type(self)
|
||||
|
||||
@@ -183,6 +183,7 @@ class GraveDomain(SubClass):
|
||||
class Cleric(CharClass):
|
||||
name = 'Cleric'
|
||||
hit_dice_faces = 8
|
||||
subclass_select_level = 1
|
||||
saving_throw_proficiencies = ('wisdom', 'charisma')
|
||||
primary_abilities = ('wisdom',)
|
||||
_proficiencies_text = ('light armor', 'medium armor', 'shields',
|
||||
|
||||
@@ -86,6 +86,7 @@ class Druid(CharClass):
|
||||
_wild_shapes = ()
|
||||
_circle = ''
|
||||
hit_dice_faces = 8
|
||||
subclass_select_level = 2
|
||||
saving_throw_proficiencies = ('intelligence', 'wisdom')
|
||||
primary_abilities = ('wisdom',)
|
||||
languages = 'Druidic'
|
||||
|
||||
@@ -165,6 +165,7 @@ class Gunslinger(SubClass):
|
||||
class Fighter(CharClass):
|
||||
name = 'Fighter'
|
||||
hit_dice_faces = 10
|
||||
subclass_select_level = 3
|
||||
saving_throw_proficiencies = ('strength', 'constitution')
|
||||
primary_abilities = ('strength', 'dexterity',)
|
||||
_proficiencies_text = ('All armor', 'shields', 'simple weapons',
|
||||
|
||||
@@ -110,6 +110,7 @@ class KenseiWay(SubClass):
|
||||
class Monk(CharClass):
|
||||
name = 'Monk'
|
||||
hit_dice_faces = 8
|
||||
subclass_select_level = 3
|
||||
saving_throw_proficiencies = ('strength', 'dexterity')
|
||||
primary_abilities = ('dexterity', 'wisdom')
|
||||
_proficiencies_text = (
|
||||
|
||||
@@ -222,6 +222,7 @@ class OathOfRedemption(SubClass):
|
||||
class Paladin(CharClass):
|
||||
name = 'Paladin'
|
||||
hit_dice_faces = 10
|
||||
subclass_select_level = 3
|
||||
saving_throw_proficiencies = ('wisdom', 'charisma')
|
||||
primary_abilities = ('strength', 'charisma')
|
||||
_proficiencies_text = ('All armor', 'shields', 'simple weapons',
|
||||
|
||||
@@ -118,12 +118,13 @@ class Swashbuckler(SubClass):
|
||||
class Rogue(CharClass):
|
||||
name = 'Rogue'
|
||||
hit_dice_faces = 8
|
||||
subclass_select_level = 3
|
||||
saving_throw_proficiencies = ('dexterity', 'intelligence')
|
||||
primary_abilities = ('dexterity',)
|
||||
_proficiencies_text = (
|
||||
'light armor', 'simple weapons', 'hand crossbows', 'longswords',
|
||||
'rapiers', 'shortswords', "thieves' tools")
|
||||
weapon_proficiencies = (weapons,SimpleWeapon, weapons.HandCrossbow,
|
||||
weapon_proficiencies = (weapons.SimpleWeapon, weapons.HandCrossbow,
|
||||
weapons.Longsword, weapons.Rapier,
|
||||
weapons.Shortsword)
|
||||
multiclass_weapon_proficiencies = ()
|
||||
|
||||
@@ -94,6 +94,7 @@ class StormSorcery(SubClass):
|
||||
class Sorceror(CharClass):
|
||||
name = 'Sorceror'
|
||||
hit_dice_faces = 6
|
||||
subclass_select_level = 1
|
||||
saving_throw_proficiencies = ('constitution', 'charisma')
|
||||
primary_abilities = ('charisma',)
|
||||
_proficiencies_text = ('daggers', 'darts', 'slings',
|
||||
|
||||
@@ -115,6 +115,7 @@ class Hexblade(SubClass):
|
||||
class Warlock(CharClass):
|
||||
name = 'Warlock'
|
||||
hit_dice_faces = 8
|
||||
subclass_select_level = 1
|
||||
saving_throw_proficiencies = ('wisdom', 'charisma')
|
||||
primary_abilities = ('charisma',)
|
||||
_proficiencies_text = ("light Armor", "simple weapons")
|
||||
|
||||
@@ -159,6 +159,7 @@ class WarMagic(SubClass):
|
||||
class Wizard(CharClass):
|
||||
name = 'Wizard'
|
||||
hit_dice_faces = 6
|
||||
subclass_select_level = 2
|
||||
saving_throw_proficiencies = ('intelligence', 'wisdom')
|
||||
primary_abilities = ('intelligence',)
|
||||
_proficiencies_text = ('daggers', 'darts', 'slings',
|
||||
|
||||
+129
-105
@@ -30,6 +30,49 @@ races = {r.name: r for r in race.available_races}
|
||||
backgrounds = {b.name: b for b in background.available_backgrounds}
|
||||
|
||||
|
||||
class LinkedListForm(npyscreen.ActionForm):
|
||||
prev_page = None
|
||||
this_page = None
|
||||
next_page = None
|
||||
|
||||
def __init__(self, formid, *args, **kwargs):
|
||||
self.this_page = formid
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_next(self):
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
|
||||
def to_prev(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
|
||||
def add_next(self, next_name):
|
||||
new_next = self.parentApp.getForm(next_name)
|
||||
if self.next_page:
|
||||
current_next = self.parentApp.getForm(self.next_page)
|
||||
current_next.prev_page = next_name
|
||||
new_next.next_page = self.next_page
|
||||
new_next.prev_page = self.this_page
|
||||
self.next_page = next_name
|
||||
|
||||
def add_prev(self, prev_name):
|
||||
new_prev = self.parentApp.getForm(prev_name)
|
||||
if self.prev_page:
|
||||
current_prev = self.parentApp.getForm(self.prev_page)
|
||||
current_prev.next_page = prev_name
|
||||
new_prev.prev_page = self.prev_page
|
||||
new_prev.next_page = self.this_page
|
||||
self.prev_page = prev_name
|
||||
|
||||
def prune(self):
|
||||
if self.next_page:
|
||||
next_form = self.parentApp.getForm(self.next_page)
|
||||
next_form.prev_page = self.prev_page
|
||||
if self.prev_page:
|
||||
prev_form = self.parentApp.getForm(self.prev_page)
|
||||
prev_form.next_page = self.next_page
|
||||
self.parentApp.removeForm(self.this_page)
|
||||
|
||||
|
||||
class App(npyscreen.NPSAppManaged):
|
||||
# STARTING_FORM = 'SKILLS'
|
||||
character = None
|
||||
@@ -63,17 +106,31 @@ class App(npyscreen.NPSAppManaged):
|
||||
|
||||
def onStart(self):
|
||||
self.character = character.Character()
|
||||
self.addForm("MAIN", BasicInfoForm, name="Basic Info:")
|
||||
self.addForm("RACE", RaceForm, name="Select your character's race:")
|
||||
self.addForm("CLASS1", CharacterClassForm, name="Select your character's primary class:")
|
||||
self.addForm("BACKGROUND", BackgroundForm, name="Choose background:")
|
||||
self.addForm("ALIGNMENT", AlignmentForm, name="Select your character's alignment:")
|
||||
self.addForm("ABILITIES", AbilityScoreForm, name="Choose ability scores:")
|
||||
self.addForm("SKILLS", SkillForm, name="Choose skill proficiencies")
|
||||
self.addForm("SAVE", SaveForm, name="Save character:")
|
||||
self.addForm("MAIN", BasicInfoForm, name="Basic Info:", formid='MAIN')
|
||||
self.addForm("RACE", RaceForm, name="Select your character's race:",
|
||||
formid='RACE')
|
||||
self.addForm("CLASS1", CharacterClassForm, name="Select your character's primary class:",
|
||||
formid='CLASS1')
|
||||
self.addForm("BACKGROUND", BackgroundForm, name="Choose background:",
|
||||
formid='BACKGROUND')
|
||||
self.addForm("ALIGNMENT", AlignmentForm,
|
||||
name="Select your character's alignment:",
|
||||
formid='ALIGNMENT')
|
||||
self.addForm("ABILITIES", AbilityScoreForm,
|
||||
name="Choose ability scores:", formid='ABILITIES')
|
||||
self.addForm("SKILLS", SkillForm, name="Choose skill proficiencies",
|
||||
formid='SKILLS')
|
||||
self.addForm("SAVE", SaveForm, name="Save character:", formid='SAVE')
|
||||
|
||||
# Initialized the DoublyLinkedList
|
||||
forms = ['MAIN', 'RACE', 'CLASS1', 'BACKGROUND',
|
||||
'ALIGNMENT', 'ABILITIES', 'SKILLS', 'SAVE']
|
||||
for i in range(len(forms)-1):
|
||||
form = self.getForm(forms[i])
|
||||
form.add_next(forms[i+1])
|
||||
|
||||
|
||||
class BasicInfoForm(npyscreen.ActionForm):
|
||||
class BasicInfoForm(LinkedListForm):
|
||||
def create(self):
|
||||
self.name = self.add(
|
||||
npyscreen.TitleText, name="Character Name:", use_two_lines=False)
|
||||
@@ -91,18 +148,13 @@ class BasicInfoForm(npyscreen.ActionForm):
|
||||
save_form.filename.value = filename
|
||||
self.parentApp.character.name = self.name.value
|
||||
self.parentApp.character.player_name = self.player_name.value
|
||||
# Move to the next form
|
||||
self.parentApp.setNextForm('RACE')
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
|
||||
class RaceForm(npyscreen.ActionForm):
|
||||
prev_page = 'MAIN'
|
||||
this_page = 'RACE'
|
||||
next_page = 'CLASS1'
|
||||
|
||||
class RaceForm(LinkedListForm):
|
||||
def create(self):
|
||||
self.race = self.add(
|
||||
npyscreen.TitleSelectOne, name="Race:", values=tuple(races.keys()))
|
||||
@@ -112,17 +164,14 @@ class RaceForm(npyscreen.ActionForm):
|
||||
selected_race = self.race.get_selected_objects()[0]
|
||||
SelectedRace = races[selected_race]
|
||||
log.debug('Selected character race: %s', SelectedRace.name)
|
||||
self.parentApp.character.race = SelectedRace()
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
self.parentApp.character.race = SelectedRace
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
class CharacterClassForm(npyscreen.ActionForm):
|
||||
prev_page = 'RACE'
|
||||
this_page = 'CLASS1'
|
||||
next_page = 'BACKGROUND'
|
||||
class CharacterClassForm(LinkedListForm):
|
||||
class_num = 1
|
||||
|
||||
def __init__(self, num=1, **kwargs):
|
||||
@@ -153,7 +202,7 @@ class CharacterClassForm(npyscreen.ActionForm):
|
||||
self.class_options = list(char_classes.keys())
|
||||
for c in self.parentApp.character.class_list[:self.class_num-1]:
|
||||
self.class_options.remove(c.name)
|
||||
self.character_class.values = tuple(self.class_options)
|
||||
self.character_class.values = sorted(tuple(self.class_options))
|
||||
self.character_class.update()
|
||||
|
||||
def create(self):
|
||||
@@ -167,14 +216,12 @@ class CharacterClassForm(npyscreen.ActionForm):
|
||||
t = 'Class #{:d}:'.format(self.class_num)
|
||||
for c in self.parentApp.character.class_list:
|
||||
self.class_options.remove(c.name)
|
||||
self.level = self.add(
|
||||
npyscreen.TitleText, name='Level:', value="1", use_two_lines=False)
|
||||
self.subclass = self.add(npyscreen.Checkbox, name="Choose a Subclass?", value=False)
|
||||
if self.class_num == 1:
|
||||
self.multiclass = self.add(npyscreen.Checkbox, name="Add Multiclass?".format(self.class_num + 1), value=False)
|
||||
else:
|
||||
self.multiclass = self.add(npyscreen.Checkbox, name="Add Class #{:d}?".format(self.class_num + 1), value=False)
|
||||
self.this_page = 'CLASS{:d}'.format(self.class_num)
|
||||
self.level = self.add(
|
||||
npyscreen.TitleText, name='Level:', value="1", use_two_lines=False)
|
||||
self.character_class = self.add(
|
||||
npyscreen.TitleSelectOne, name=t, values=tuple(self.class_options))
|
||||
|
||||
@@ -183,11 +230,9 @@ class CharacterClassForm(npyscreen.ActionForm):
|
||||
new_form = self.parentApp.addForm(new_name,
|
||||
CharacterClassForm,
|
||||
name="Select your character's Class #{:d}:".format(self.class_num + 1),
|
||||
num=self.class_num+1)
|
||||
self.parentApp.getForm(self.next_page).prev_page = new_name
|
||||
new_form.next_page = self.next_page
|
||||
new_form.prev_page = self.this_page
|
||||
self.next_page = new_name
|
||||
num=self.class_num+1,
|
||||
formid=new_name)
|
||||
self.add_next(new_name)
|
||||
return new_form
|
||||
|
||||
def add_subclass_page(self, newclass, level):
|
||||
@@ -197,11 +242,9 @@ class CharacterClassForm(npyscreen.ActionForm):
|
||||
name="Select your {:s} Subclass".format(newclass.name),
|
||||
newclass=newclass,
|
||||
level=level,
|
||||
num=self.class_num)
|
||||
self.parentApp.getForm(self.next_page).prev_page = new_name
|
||||
new_form.next_page = self.next_page
|
||||
new_form.prev_page = self.this_page
|
||||
self.next_page = new_name
|
||||
num=self.class_num,
|
||||
formid=new_name)
|
||||
self.add_next(new_name)
|
||||
return new_form
|
||||
|
||||
def on_ok(self):
|
||||
@@ -209,13 +252,11 @@ class CharacterClassForm(npyscreen.ActionForm):
|
||||
selected_class = self.character_class.get_selected_objects()[0]
|
||||
selected_class = char_classes[selected_class]
|
||||
log.debug('Selected character class %s', selected_class.name)
|
||||
new_class = selected_class(level=int(self.level.value),
|
||||
subclass=None)
|
||||
if len(self.parentApp.character.class_list) < self.class_num:
|
||||
self.parentApp.character.class_list.append(new_class)
|
||||
else:
|
||||
# replace existing character if we've backed up
|
||||
self.parentApp.character.class_list[self.class_num-1] = new_class
|
||||
# replace later classes if we've backed up
|
||||
self.parentApp.character.class_list = self.parentApp.character.class_list[:self.class_num-1]
|
||||
self.parentApp.character.add_class(cls=selected_class,
|
||||
level=int(self.level.value),
|
||||
subclass=None)
|
||||
# add multiclass page if not exists yet
|
||||
if self.multiclass.value:
|
||||
if self.next_multiclass_page is None:
|
||||
@@ -224,22 +265,24 @@ class CharacterClassForm(npyscreen.ActionForm):
|
||||
self.next_multiclass_page.update_options()
|
||||
else:
|
||||
# in case returned a page, prune any future multiclasses
|
||||
self.next_page = "BACKGROUND"
|
||||
self.parentApp.getForm("BACKGROUND").prev_page = self.this_page
|
||||
self.parentApp.character.class_list = self.parentApp.character.class_list[:self.class_num]
|
||||
if self.subclass.value:
|
||||
self.add_subclass_page(newclass=selected_class,
|
||||
level=int(self.level.value))
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
while self.next_page != 'BACKGROUND':
|
||||
f = self.parentApp.getForm(self.next_page)
|
||||
f.prune()
|
||||
if int(self.level.value) >= selected_class.subclass_select_level:
|
||||
if not self.subclass_page:
|
||||
self.add_subclass_page(newclass=selected_class,
|
||||
level=int(self.level.value))
|
||||
else:
|
||||
if self.subclass_page is not None:
|
||||
f = self.parentApp.getForm(self.next_page)
|
||||
f.prune()
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
class SubclassForm(npyscreen.ActionForm):
|
||||
prev_page = 'CLASS1'
|
||||
next_page = 'BACKGROUND'
|
||||
|
||||
class SubclassForm(LinkedListForm):
|
||||
def __init__(self, newclass, level, num=1, **kwargs):
|
||||
self.class_num = num
|
||||
self.parent_class = newclass
|
||||
@@ -256,25 +299,21 @@ class SubclassForm(npyscreen.ActionForm):
|
||||
values=tuple(self.subclass_options))
|
||||
|
||||
def on_ok(self):
|
||||
sc = self.subclass.get_selected_objects()[0]
|
||||
if sc in [None, '', 'None']:
|
||||
newclass = self.parent_class(level=self.level,
|
||||
subclass=None)
|
||||
else:
|
||||
newclass = self.parent_class(level=self.level,
|
||||
subclass=sc)
|
||||
self.parentApp.character.class_list[self.class_num-1] = newclass
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
if self.subclass.value is not None:
|
||||
sc = self.subclass.get_selected_objects()[0]
|
||||
if sc in [None, '', 'None']:
|
||||
sc = None
|
||||
self.parentApp.character.class_list = self.parentApp.character.class_list[:self.class_num-1]
|
||||
self.parentApp.character.add_class(cls=self.parent_class,
|
||||
level=self.level,
|
||||
subclass=sc)
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
class BackgroundForm(npyscreen.ActionForm):
|
||||
prev_page = 'CLASS1'
|
||||
this_page = 'BACKGROUND'
|
||||
next_page = 'ALIGNMENT'
|
||||
|
||||
class BackgroundForm(LinkedListForm):
|
||||
def create(self):
|
||||
self.background = self.add(
|
||||
npyscreen.TitleSelectOne,
|
||||
@@ -290,21 +329,17 @@ class BackgroundForm(npyscreen.ActionForm):
|
||||
languages = Background.languages + race_languages
|
||||
self.parentApp.character.languages = ', '.join(languages)
|
||||
log.debug("Selected character background: %s", Background.name)
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
class AlignmentForm(npyscreen.ActionForm):
|
||||
class AlignmentForm(LinkedListForm):
|
||||
"""Choose your character's alignment."""
|
||||
alignments = ('Lawful good', 'Neutral good', 'Chaotic good',
|
||||
'Lawful neutral', 'True neutral', 'Chaotic neutral',
|
||||
'Lawful evil', 'Neutral evil', 'Chaotic evil', )
|
||||
prev_page = 'BACKGROUND'
|
||||
this_page = 'ALIGNMENT'
|
||||
next_page = 'ABILITIES'
|
||||
|
||||
def create(self):
|
||||
self.alignment = self.add(
|
||||
npyscreen.TitleSelectOne, name="Alignment:", values=self.alignments)
|
||||
@@ -317,16 +352,13 @@ class AlignmentForm(npyscreen.ActionForm):
|
||||
# prep additions to abilities page
|
||||
abils = self.parentApp.getForm('ABILITIES')
|
||||
abils.prep()
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
class AbilityScoreForm(npyscreen.ActionForm):
|
||||
prev_page = 'ALIGNMENT'
|
||||
this_page = 'ABILITIES'
|
||||
next_page = 'SKILLS'
|
||||
class AbilityScoreForm(LinkedListForm):
|
||||
num_rolls = 0
|
||||
|
||||
def roll_dice(self):
|
||||
@@ -348,7 +380,7 @@ class AbilityScoreForm(npyscreen.ActionForm):
|
||||
self.score_options.value = str(new_scores)[1:-1]
|
||||
self.score_options.update()
|
||||
self.reroll_button.value = False
|
||||
self.reroll_button.name = 'Reroll ({:d}x):'.format(self.num_rolls)
|
||||
self.reroll_button.name = 'Reroll'
|
||||
self.reroll_button.update()
|
||||
self.default_button.value = False
|
||||
self.default_button.update()
|
||||
@@ -403,7 +435,7 @@ class AbilityScoreForm(npyscreen.ActionForm):
|
||||
name="Use Default Rolls",
|
||||
when_pressed_function=self.set_default)
|
||||
self.reroll_button = self.add(npyscreen.MiniButtonPress,
|
||||
name="Reroll (0x)",
|
||||
name="Reroll",
|
||||
when_pressed_function=self.reroll)
|
||||
|
||||
def prep(self):
|
||||
@@ -414,7 +446,7 @@ class AbilityScoreForm(npyscreen.ActionForm):
|
||||
self.race_text = self.add(npyscreen.FixedText, editable=False,
|
||||
value="Do not add racial bonuses, they will be added for you as listed.")
|
||||
for attr in attrs:
|
||||
if attr in self.parentApp.character.saving_throw_proficiencies:
|
||||
if attr in self.parentApp.character.primary_class.primary_abilities:
|
||||
name = '**' + attr
|
||||
else:
|
||||
name = '' + attr
|
||||
@@ -431,17 +463,13 @@ class AbilityScoreForm(npyscreen.ActionForm):
|
||||
self.max_hp = self.add(npyscreen.TitleText, name="Max HP:")
|
||||
|
||||
def on_ok(self):
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
class SkillForm(npyscreen.ActionForm):
|
||||
prev_page = 'ABILITIES'
|
||||
this_page = 'SKILLS'
|
||||
next_page = 'SAVE'
|
||||
|
||||
class SkillForm(LinkedListForm):
|
||||
def while_editing(self):
|
||||
# Update the static skills for race and background
|
||||
bg_skills = self.parentApp.character.background.skill_proficiencies
|
||||
@@ -454,7 +482,7 @@ class SkillForm(npyscreen.ActionForm):
|
||||
self.parentApp.character.background.skill_choices)
|
||||
static_skills = bg_skills + race_skills
|
||||
choices = set([c for c in choices if c.lower() not in static_skills])
|
||||
self.skill_proficiencies.set_values(tuple(choices))
|
||||
self.skill_proficiencies.set_values(sorted(tuple(choices)))
|
||||
self.update_remaining()
|
||||
|
||||
def update_remaining(self, widget=None):
|
||||
@@ -493,17 +521,13 @@ class SkillForm(npyscreen.ActionForm):
|
||||
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(self.next_page)
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
class SaveForm(npyscreen.ActionForm):
|
||||
prev_page = 'SKILLS'
|
||||
this_page = 'SAVE'
|
||||
next_page = None
|
||||
|
||||
class SaveForm(LinkedListForm):
|
||||
def create(self):
|
||||
self.filename = self.add(
|
||||
npyscreen.TitleText, name='Filename:')
|
||||
@@ -513,10 +537,10 @@ class SaveForm(npyscreen.ActionForm):
|
||||
value="After saving, edit this file to finish your personality, etc.")
|
||||
|
||||
def on_ok(self):
|
||||
self.parentApp.setNextForm(self.next_page)
|
||||
super().to_next()
|
||||
|
||||
def on_cancel(self):
|
||||
self.parentApp.setNextForm(self.prev_page)
|
||||
super().to_prev()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -32,7 +32,7 @@ class Feature():
|
||||
spells_prepared = ()
|
||||
needs_implementation = False # Set to True if need to find way to compute stats
|
||||
|
||||
def __init__(self, owner):
|
||||
def __init__(self, owner=None):
|
||||
self.owner = owner
|
||||
|
||||
def __eq__(self, other):
|
||||
|
||||
@@ -46,12 +46,7 @@ class MartialArts(Feature):
|
||||
|
||||
def __init__(self, owner):
|
||||
self.owner = owner
|
||||
if self.owner.level >= 5:
|
||||
self.die = 'd6'
|
||||
if self.owner.level >= 11:
|
||||
self.die = 'd8'
|
||||
if self.owner.level >= 17:
|
||||
self.die = 'd10'
|
||||
self.level = owner.Monk.level
|
||||
|
||||
def weapon_func(self, weapon: weapons.Weapon, char=None, **kwargs):
|
||||
"""
|
||||
@@ -63,6 +58,13 @@ class MartialArts(Feature):
|
||||
return weapon
|
||||
if char is None:
|
||||
return weapon
|
||||
self.die = 'd4'
|
||||
if self.level >= 5:
|
||||
self.die = 'd6'
|
||||
if self.level >= 11:
|
||||
self.die = 'd8'
|
||||
if self.level >= 17:
|
||||
self.die = 'd10'
|
||||
# check if new damage is better than default
|
||||
if self.die > int(weapon.base_damage.split('d')[-1]):
|
||||
weapon.base_damage = '1d' + str(self.die)
|
||||
@@ -81,15 +83,20 @@ class UnarmoredMovement(Feature):
|
||||
"""
|
||||
name = "Unarmored Movement"
|
||||
source = "Monk"
|
||||
speed_bonus = 10
|
||||
|
||||
def __init__(self, owner):
|
||||
self.owner = owner
|
||||
if self.owner.level >= 6:
|
||||
self.speed_bonus = 15
|
||||
if self.owner.level >= 10:
|
||||
self.speed_bonus = 20
|
||||
if self.owner.level >= 14:
|
||||
self.speed_bonus = 25
|
||||
if self.owner.level >= 18:
|
||||
self.speed_bonus = 30
|
||||
self.level = owner.Monk.level
|
||||
|
||||
@property
|
||||
def speed_bonus(self):
|
||||
_speed_bonus = 10
|
||||
if self.level >= 6:
|
||||
_speed_bonus = 15
|
||||
if self.level >= 10:
|
||||
_speed_bonus = 20
|
||||
if self.level >= 14:
|
||||
_speed_bonus = 25
|
||||
if self.level >= 18:
|
||||
_speed_bonus = 30
|
||||
return _speed_bonus
|
||||
|
||||
@@ -2,18 +2,18 @@ from .features import Feature, FeatureSelector
|
||||
from .. import (weapons, armor)
|
||||
|
||||
|
||||
def select_ranger_fighting_style(feature_choices=[]):
|
||||
def select_ranger_fighting_style(char=None, feature_choices=[]):
|
||||
lower_choices = [fc for fc in map(str.lower, feature_choices)]
|
||||
if 'archery' in lower_choices:
|
||||
return Archery()
|
||||
return Archery(owner=char)
|
||||
elif 'defense' in lower_choices:
|
||||
return Defense()
|
||||
return Defense(owner=char)
|
||||
elif 'dueling' in lower_choices:
|
||||
return Dueling()
|
||||
return Dueling(owner=char)
|
||||
elif 'two-weapon fighting' in lower_choices:
|
||||
return TwoWeaponFighting()
|
||||
return TwoWeaponFighting(owner=char)
|
||||
else:
|
||||
return RangerFightingStyle()
|
||||
return RangerFightingStyle(owner=char)
|
||||
|
||||
|
||||
class Archery(Feature):
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
\usepackage[dvipsnames]{color}
|
||||
\definecolor{mygrey}{gray}{0.7}
|
||||
|
||||
\title{Features and Subclass}
|
||||
\title{Features and Magic Items}
|
||||
\author{[[ character.name ]]}
|
||||
\date{}
|
||||
|
||||
@@ -36,4 +36,20 @@
|
||||
|
||||
[% endfor %]
|
||||
|
||||
[% for mitem in character.magic_items %]
|
||||
|
||||
\section*{[[ mitem.name ]]}
|
||||
|
||||
\noindent
|
||||
\textbf{Requires Attunement:} [[ mitem.requires_attunement ]] \\
|
||||
\textbf{Rarity:} [[ mitem.rarity ]] \\
|
||||
|
||||
[% if mitem.needs_implementation %] %
|
||||
\textbf{**Not included in stats on Character Sheet} %
|
||||
[% endif %] %
|
||||
|
||||
[[ mitem.__doc__|rst_to_latex ]]
|
||||
|
||||
[% endfor %]
|
||||
|
||||
\end{document}
|
||||
|
||||
@@ -6,6 +6,7 @@ class MagicItem():
|
||||
name = ''
|
||||
ac_bonus = 0
|
||||
requires_attunement = False
|
||||
needs_implementation = False
|
||||
rarity = ''
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class Race():
|
||||
spells_known = ()
|
||||
spells_prepared = ()
|
||||
|
||||
def __init__(self, owner):
|
||||
def __init__(self, owner=None):
|
||||
self.owner = owner
|
||||
cls = type(self)
|
||||
# Instantiate the features
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import math
|
||||
from collections import namedtuple
|
||||
from .armor import NoArmor, NoShield, HeavyArmor
|
||||
from . import (weapons)
|
||||
from .features import (UnarmoredDefenseMonk, UnarmoredDefenseBarbarian,
|
||||
DraconicResilience, Defense, FastMovement,
|
||||
UnarmoredMovement)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
from .stats import mod_str
|
||||
|
||||
|
||||
class Weapon():
|
||||
name = ""
|
||||
cost = "0 gp"
|
||||
@@ -17,7 +14,7 @@ class Weapon():
|
||||
def damage(self):
|
||||
dam_str = str(self.base_damage)
|
||||
if self.bonus_damage != 0:
|
||||
dam_str += mod_str(self.bonus_damage)
|
||||
dam_str += '{:+d}'.format(self.bonus_damage)
|
||||
return dam_str
|
||||
|
||||
def __str__(self):
|
||||
|
||||
Reference in New Issue
Block a user