mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 21:23:31 +02:00
create-character now works for multiclass and subclasses
This commit is contained in:
@@ -6,6 +6,8 @@ class Background():
|
|||||||
skill_proficiencies = ()
|
skill_proficiencies = ()
|
||||||
weapon_proficiencies = ()
|
weapon_proficiencies = ()
|
||||||
proficiencies_text = ()
|
proficiencies_text = ()
|
||||||
|
skill_choices = ()
|
||||||
|
num_skill_choices = 0
|
||||||
features = ()
|
features = ()
|
||||||
languages = ()
|
languages = ()
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ class Spy(Criminal):
|
|||||||
|
|
||||||
class Entertainer(Background):
|
class Entertainer(Background):
|
||||||
name = "Entertainer"
|
name = "Entertainer"
|
||||||
skill_proficiencies = ('acrobatic', 'performance')
|
skill_proficiencies = ('acrobatics', 'performance')
|
||||||
|
|
||||||
|
|
||||||
class Gladiator(Entertainer):
|
class Gladiator(Entertainer):
|
||||||
@@ -106,10 +108,20 @@ class Urchin(Background):
|
|||||||
|
|
||||||
class UrbanBountyHunter(Background):
|
class UrbanBountyHunter(Background):
|
||||||
name = 'Urban Bounty Hunter'
|
name = 'Urban Bounty Hunter'
|
||||||
skill_proficiencies = ('[choose one]', '[choose one]')
|
skill_proficiencies = ()
|
||||||
|
skill_choices = ('Deception', 'Insight', 'Persuasion', 'Stealth')
|
||||||
|
num_skill_choices = 2
|
||||||
|
|
||||||
|
|
||||||
class FarTraveler(Background):
|
class FarTraveler(Background):
|
||||||
name = 'Far Traveler'
|
name = 'Far Traveler'
|
||||||
skill_proficiencies = ('insight', 'perception')
|
skill_proficiencies = ('insight', 'perception')
|
||||||
languages = ('[choose one]',)
|
languages = ('[choose one]',)
|
||||||
|
|
||||||
|
|
||||||
|
available_backgrounds = [Acolyte, Charlatan, Criminal, Spy, Entertainer,
|
||||||
|
Gladiator, FolkHero, GuildArtisan, GuildMerchant,
|
||||||
|
Hermit, Noble, Knight, Outlander, Sage, Sailor,
|
||||||
|
Pirate, Soldier, Urchin, UrbanBountyHunter,
|
||||||
|
FarTraveler]
|
||||||
|
|
||||||
|
|||||||
+25
-23
@@ -74,24 +74,24 @@ class Character():
|
|||||||
proficiencies_extra = tuple()
|
proficiencies_extra = tuple()
|
||||||
languages = ""
|
languages = ""
|
||||||
# Skills
|
# Skills
|
||||||
acrobatics = Skill(ability='dexterity')
|
acrobatics = Skill(ability='dexterity', name='acrobatics')
|
||||||
animal_handling = Skill(ability='wisdom')
|
animal_handling = Skill(ability='wisdom', name='animal handling')
|
||||||
arcana = Skill(ability='intelligence')
|
arcana = Skill(ability='intelligence', name='arcana')
|
||||||
athletics = Skill(ability='strength')
|
athletics = Skill(ability='strength', name='athletics')
|
||||||
deception = Skill(ability='charisma')
|
deception = Skill(ability='charisma', name='deception')
|
||||||
history = Skill(ability='intelligence')
|
history = Skill(ability='intelligence', name='history')
|
||||||
insight = Skill(ability='wisdom')
|
insight = Skill(ability='wisdom', name='insight')
|
||||||
intimidation = Skill(ability='charisma')
|
intimidation = Skill(ability='charisma', name='intimidation')
|
||||||
investigation = Skill(ability='intelligence')
|
investigation = Skill(ability='intelligence', name='investigation')
|
||||||
medicine = Skill(ability='wisdom')
|
medicine = Skill(ability='wisdom', name='medicine')
|
||||||
nature = Skill(ability='intelligence')
|
nature = Skill(ability='intelligence', name='nature')
|
||||||
perception = Skill(ability='wisdom')
|
perception = Skill(ability='wisdom', name='perception')
|
||||||
performance = Skill(ability='charisma')
|
performance = Skill(ability='charisma', name='performance')
|
||||||
persuasion = Skill(ability='charisma')
|
persuasion = Skill(ability='charisma', name='persuasion')
|
||||||
religion = Skill(ability='intelligence')
|
religion = Skill(ability='intelligence', name='religion')
|
||||||
sleight_of_hand = Skill(ability='dexterity')
|
sleight_of_hand = Skill(ability='dexterity', name='sleight of hand')
|
||||||
stealth = Skill(ability='dexterity')
|
stealth = Skill(ability='dexterity', name='stealth')
|
||||||
survival = Skill(ability='wisdom')
|
survival = Skill(ability='wisdom', name='survival')
|
||||||
# Characteristics
|
# Characteristics
|
||||||
attacks_and_spellcasting = ""
|
attacks_and_spellcasting = ""
|
||||||
personality_traits = ""
|
personality_traits = ""
|
||||||
@@ -255,11 +255,13 @@ class Character():
|
|||||||
self.other_weapon_proficiencies = tuple([findattr(weapons, w)
|
self.other_weapon_proficiencies = tuple([findattr(weapons, w)
|
||||||
for w in val])
|
for w in val])
|
||||||
elif attr == 'race':
|
elif attr == 'race':
|
||||||
MyRace = findattr(race, val)
|
if val is not None:
|
||||||
self.race = MyRace()
|
MyRace = findattr(race, val)
|
||||||
|
self.race = MyRace()
|
||||||
elif attr == 'background':
|
elif attr == 'background':
|
||||||
MyBackground = findattr(background, val)
|
if val is not None:
|
||||||
self.background = MyBackground()
|
MyBackground = findattr(background, val)
|
||||||
|
self.background = MyBackground()
|
||||||
elif attr == 'armor':
|
elif attr == 'armor':
|
||||||
self.wear_armor(val)
|
self.wear_armor(val)
|
||||||
elif attr == 'shield':
|
elif attr == 'shield':
|
||||||
@@ -397,7 +399,7 @@ class Character():
|
|||||||
directly.
|
directly.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if new_armor not in ('', None):
|
if new_armor not in ('', 'None', None):
|
||||||
if isinstance(new_armor, armor.Armor):
|
if isinstance(new_armor, armor.Armor):
|
||||||
new_armor = new_armor
|
new_armor = new_armor
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -18,11 +18,15 @@ class CharClass():
|
|||||||
spellcasting_ability = None
|
spellcasting_ability = None
|
||||||
spell_slots_by_level = None
|
spell_slots_by_level = None
|
||||||
subclass = None
|
subclass = None
|
||||||
|
subclasses_available = ()
|
||||||
features_by_level = defaultdict(list)
|
features_by_level = defaultdict(list)
|
||||||
|
|
||||||
def __init__(self, level, subclass=None, **params):
|
def __init__(self, level, subclass=None, **params):
|
||||||
self.class_level = level
|
self.class_level = level
|
||||||
self.subclass = subclass
|
if subclass in [None, '', 'None']:
|
||||||
|
self.subclass = None
|
||||||
|
else:
|
||||||
|
self.subclass = subclass
|
||||||
for k, v in params.items():
|
for k, v in params.items():
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Monk(CharClass):
|
|||||||
weapon_proficiencies = (weapons.Shortsword, weapons.Unarmed) + weapons.simple_weapons
|
weapon_proficiencies = (weapons.Shortsword, weapons.Unarmed) + weapons.simple_weapons
|
||||||
class_skill_choices = ('Acrobatics', 'Athletics', 'History', 'Insight',
|
class_skill_choices = ('Acrobatics', 'Athletics', 'History', 'Insight',
|
||||||
'Religion', 'Stealth')
|
'Religion', 'Stealth')
|
||||||
|
subclasses_available = ('SunSoul', 'OpenHand')
|
||||||
features_by_level = defaultdict(list)
|
features_by_level = defaultdict(list)
|
||||||
martial_arts = features.MartialArts()
|
martial_arts = features.MartialArts()
|
||||||
features_by_level[1] = [features.UnarmoredDefense(),
|
features_by_level[1] = [features.UnarmoredDefense(),
|
||||||
|
|||||||
+252
-185
@@ -58,15 +58,7 @@ races = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
backgrounds = (background.Acolyte, background.Charlatan,
|
backgrounds = background.available_backgrounds
|
||||||
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}
|
backgrounds = {bg.name: bg for bg in backgrounds}
|
||||||
|
|
||||||
|
|
||||||
@@ -75,119 +67,240 @@ class App(npyscreen.NPSAppManaged):
|
|||||||
character = None
|
character = None
|
||||||
|
|
||||||
def save_character(self):
|
def save_character(self):
|
||||||
# Create the template context
|
|
||||||
context = dict(
|
|
||||||
char=self.character,
|
|
||||||
dungeonsheets_version=read_version(),
|
|
||||||
)
|
|
||||||
# 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
|
# Save the file
|
||||||
filename = self.getForm("SAVE").filename.value
|
filename = self.getForm("SAVE").filename.value
|
||||||
with open(filename, mode='w') as f:
|
self.character.save(filename)
|
||||||
f.write(text)
|
|
||||||
# Create the PDF character sheet
|
# Create the PDF character sheet
|
||||||
if self.getForm('SAVE').make_pdf.value:
|
if self.getForm('SAVE').make_pdf.value:
|
||||||
log.debug("Creating PDF")
|
log.debug("Creating PDF")
|
||||||
subprocess.call(['makesheets', filename])
|
subprocess.call(['makesheets', filename])
|
||||||
|
|
||||||
@property
|
def add_class(self, NewClass):
|
||||||
def character_class(self, *args, **kwargs):
|
log.debug("Adding Class: {:s}".format(NewClass.class_name))
|
||||||
return self.character_class
|
# basic_info = self.getForm('MAIN')
|
||||||
|
# self.character = NewClass(
|
||||||
@character_class.setter
|
# name=basic_info.name.value,
|
||||||
def character_class(self, NewClass):
|
# player_name=basic_info.player_name.value,
|
||||||
log.debug("Replacing character")
|
# level=int(basic_info.level.value),
|
||||||
basic_info = self.getForm('MAIN')
|
# strength=-1, dexterity=-1, constitution=-1,
|
||||||
self.character = NewClass(
|
# intelligence=-1, wisdom=-1, charisma=-1)
|
||||||
name=basic_info.name.value,
|
self.character.class_list.append(NewClass)
|
||||||
player_name=basic_info.player_name.value,
|
|
||||||
level=int(basic_info.level.value),
|
|
||||||
strength=-1, dexterity=-1, constitution=-1,
|
|
||||||
intelligence=-1, wisdom=-1, charisma=-1)
|
|
||||||
self.update_max_hp()
|
self.update_max_hp()
|
||||||
# Reset form widgets
|
# Reset form widgets
|
||||||
log.debug("Resetting forms")
|
log.debug("Resetting forms")
|
||||||
self.getForm('ABILITIES').reset()
|
self.getForm('ABILITIES').reset()
|
||||||
|
|
||||||
|
# @property
|
||||||
|
# def character_class(self, *args, **kwargs):
|
||||||
|
# return self.character_class
|
||||||
|
|
||||||
|
# @character_class.setter
|
||||||
|
# def character_class(self, NewClass):
|
||||||
|
# log.debug("Adding Class: {:s}".format(NewClass.class_name))
|
||||||
|
# # basic_info = self.getForm('MAIN')
|
||||||
|
# # self.character = NewClass(
|
||||||
|
# # name=basic_info.name.value,
|
||||||
|
# # player_name=basic_info.player_name.value,
|
||||||
|
# # level=int(basic_info.level.value),
|
||||||
|
# # strength=-1, dexterity=-1, constitution=-1,
|
||||||
|
# # intelligence=-1, wisdom=-1, charisma=-1)
|
||||||
|
# self.character.class_list.append(NewClass)
|
||||||
|
# self.update_max_hp()
|
||||||
|
# # Reset form widgets
|
||||||
|
# log.debug("Resetting forms")
|
||||||
|
# self.getForm('ABILITIES').reset()
|
||||||
|
|
||||||
def update_max_hp(self):
|
def update_max_hp(self):
|
||||||
# Update max HP based on the class
|
# Update max HP based on the class
|
||||||
max_hp_fld = self.getForm('ABILITIES').max_hp
|
max_hp_fld = self.getForm('ABILITIES').max_hp
|
||||||
if max_hp_fld.value == '':
|
if max_hp_fld.value == '':
|
||||||
# Calculate the new value
|
# Calculate the new value
|
||||||
hit_dice = dice.read_dice_str(self.character.hit_dice)
|
hit_dice = [dice.read_dice_str(d)
|
||||||
|
for d in self.character.hit_dice.split(' + ')]
|
||||||
const = self.character.constitution.modifier
|
const = self.character.constitution.modifier
|
||||||
max_hp = hit_dice.faces + const
|
# Assume first hd given is from primary class
|
||||||
for d in range(hit_dice.num - 1):
|
max_hp = math.floor(hit_dice[0].faces/2) + const
|
||||||
max_hp += math.ceil(hit_dice.faces/2) + const
|
for hd in hit_dice:
|
||||||
|
for d in range(hd.num - 1):
|
||||||
|
max_hp += math.ceil(hd.faces/2) + const
|
||||||
log.debug("Updating max hp: %d", max_hp)
|
log.debug("Updating max hp: %d", max_hp)
|
||||||
max_hp_fld.value = str(max_hp)
|
max_hp_fld.value = str(max_hp)
|
||||||
|
|
||||||
def onStart(self):
|
def onStart(self):
|
||||||
self.character = character.Character()
|
self.character = character.Character()
|
||||||
self.addForm("MAIN", BasicInfoForm, name="Basic Info:")
|
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("RACE", RaceForm, name="Select your character's race:")
|
||||||
|
self.addForm("CLASS", CharacterClassForm, name="Select your character's primary class:")
|
||||||
|
self.addForm("SUBCLASS", SubclassForm, name="Select your subclass:")
|
||||||
|
self.addForm("BACKGROUND", BackgroundForm, name="Choose background:")
|
||||||
self.addForm("ALIGNMENT", AlignmentForm, name="Select your character's alignment:")
|
self.addForm("ALIGNMENT", AlignmentForm, name="Select your character's alignment:")
|
||||||
self.addForm("ABILITIES", AbilityScoreForm, name="Choose ability scores:")
|
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("SKILLS", SkillForm, name="Choose skill proficiencies")
|
||||||
self.addForm("SAVE", SaveForm, name="Save character:")
|
self.addForm("SAVE", SaveForm, name="Save character:")
|
||||||
|
|
||||||
|
|
||||||
class SkillForm(npyscreen.ActionForm):
|
class BasicInfoForm(npyscreen.ActionForm):
|
||||||
def while_editing(self):
|
|
||||||
# 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):
|
|
||||||
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):
|
def create(self):
|
||||||
self.bg_skills = self.add(
|
self.name = self.add(
|
||||||
npyscreen.TitleText, name="Background:",
|
npyscreen.TitleText, name="Character Name:", use_two_lines=False)
|
||||||
value="", editable=False)
|
self.player_name = self.add(
|
||||||
self.race_skills = self.add(
|
npyscreen.TitleText, name="Player Name:", use_two_lines=False)
|
||||||
npyscreen.TitleText, name="Racial:",
|
|
||||||
value="", editable=False)
|
|
||||||
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):
|
def on_ok(self):
|
||||||
new_skills = self.skill_proficiencies.get_selected_objects()
|
# Update the default filename
|
||||||
if new_skills is not None:
|
name = self.name.value
|
||||||
new_skills = tuple(s.lower() for s in new_skills)
|
if name == '':
|
||||||
|
filename = 'new_character.py'
|
||||||
else:
|
else:
|
||||||
new_skills = ()
|
filename = f'{name.split(" ")[0].lower()}.py'
|
||||||
bg_skills = tuple(self.parentApp.character.background.skill_proficiencies)
|
save_form = self.parentApp.getForm('SAVE')
|
||||||
race_skills = tuple(self.parentApp.character.race.skill_proficiencies)
|
save_form.filename.value = filename
|
||||||
all_skills = new_skills + bg_skills + race_skills
|
# Move to the next form
|
||||||
self.parentApp.character.skill_proficiencies = all_skills
|
self.parentApp.setNextForm('RACE')
|
||||||
log.debug(f"Skill proficiencies: {all_skills}")
|
|
||||||
self.parentApp.setNextForm('SAVE')
|
def on_cancel(self):
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
|
||||||
|
class RaceForm(npyscreen.ActionForm):
|
||||||
|
def create(self):
|
||||||
|
self.race = self.add(
|
||||||
|
npyscreen.TitleMultiLine, name="Race:", values=tuple(races.keys()))
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
if self.race.value is not None:
|
||||||
|
selected_race = self.race.values[self.race.value]
|
||||||
|
SelectedRace = races[selected_race]
|
||||||
|
log.debug('Selected character race: %s', SelectedRace.name)
|
||||||
|
self.parentApp.character.race = SelectedRace()
|
||||||
|
self.parentApp.setNextForm('CLASS')
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.parentApp.setNextForm('MAIN')
|
||||||
|
|
||||||
|
|
||||||
|
class CharacterClassForm(npyscreen.ActionForm):
|
||||||
|
char_options = list(char_classes.keys())
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
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)
|
||||||
|
self.multiclass = self.add(npyscreen.Checkbox, name="Multiclass?", value=False)
|
||||||
|
self.character_class = self.add(
|
||||||
|
npyscreen.TitleMultiLine, name="Class:", values=tuple(self.char_options))
|
||||||
|
|
||||||
|
def setup_multiclass(self):
|
||||||
|
self.character_class.values = self.char_options
|
||||||
|
self.name = "Select your character's class #{:d}".format(1 + self.parentApp.character.num_classes)
|
||||||
|
self.multiclass.name = "Add another class?"
|
||||||
|
self.level.value = '1'
|
||||||
|
self.subclass.value = False
|
||||||
|
self.multiclass.value = False
|
||||||
|
self.character_class.value = None
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
if self.character_class.value is not None:
|
||||||
|
# make sure this option can't be selected again
|
||||||
|
selected_class = self.character_class.values[self.character_class.value]
|
||||||
|
self.char_options.remove(selected_class)
|
||||||
|
selected_class = char_classes[selected_class]
|
||||||
|
log.debug('Selected character class %s', selected_class.class_name)
|
||||||
|
if self.subclass.value:
|
||||||
|
sc = self.parentApp.getForm('SUBCLASS')
|
||||||
|
sc.when_done = 'CLASS' if self.multiclass.value else 'BACKGROUND'
|
||||||
|
if self.multiclass:
|
||||||
|
self.setup_multiclass()
|
||||||
|
sc.prepare_for_class(selected_class, int(self.level.value))
|
||||||
|
self.parentApp.setNextForm('SUBCLASS')
|
||||||
|
else:
|
||||||
|
self.parentApp.add_class(selected_class(level=int(self.level.value),
|
||||||
|
subclass=None))
|
||||||
|
if self.multiclass.value:
|
||||||
|
self.setup_multiclass()
|
||||||
|
self.parentApp.setNextForm('CLASS')
|
||||||
|
else:
|
||||||
|
self.parentApp.setNextForm('BACKGROUND')
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
if self.parentApp.character.num_classes > 1:
|
||||||
|
self.parentApp.setNextForm('BACKGROUND')
|
||||||
|
self.parentApp.setNextForm('RACE')
|
||||||
|
|
||||||
|
|
||||||
|
class SubclassForm(npyscreen.ActionForm):
|
||||||
|
subclass_options = ('None',)
|
||||||
|
parent_class = None
|
||||||
|
level = 1
|
||||||
|
when_done = 'BACKGROUND'
|
||||||
|
name_formatter = "Select your {:s} subclass:"
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
self.subclass = self.add(
|
||||||
|
npyscreen.TitleMultiLine, name="Subclass:",
|
||||||
|
values=tuple(self.subclass_options))
|
||||||
|
|
||||||
|
def prepare_for_class(self, parent_class, level):
|
||||||
|
self.subclass_options = parent_class.subclasses_available or ('None',)
|
||||||
|
self.parent_class = parent_class
|
||||||
|
self.level = level
|
||||||
|
self.name = self.name_formatter.format(parent_class.class_name)
|
||||||
|
self._clear_all_widgets()
|
||||||
|
self.create()
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
if self.subclass.value in [None, '', 'None']:
|
||||||
|
newclass = self.parent_class(level=self.level,
|
||||||
|
subclass=None)
|
||||||
|
else:
|
||||||
|
newclass = self.parent_class(level=self.level,
|
||||||
|
subclass=self.subclass.value)
|
||||||
|
self.parentApp.add_class(newclass)
|
||||||
|
self.parentApp.setNextForm(self.when_done)
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.parentApp.setNextForm('CLASS')
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundForm(npyscreen.ActionForm):
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
self.background = self.add(
|
||||||
|
npyscreen.TitleMultiLine,
|
||||||
|
name="Background:", values=tuple(backgrounds.keys()))
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
if self.background.value is not None:
|
||||||
|
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('ALIGNMENT')
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.parentApp.setNextForm('CLASS')
|
||||||
|
|
||||||
|
|
||||||
|
class AlignmentForm(npyscreen.ActionForm):
|
||||||
|
"""Choose your character's alignment."""
|
||||||
|
alignments = ('Lawful good', 'Neutral good', 'Chaotic good',
|
||||||
|
'Lawful neutral', 'True neutral', 'Chaotic neutral',
|
||||||
|
'Lawful evil', 'Neutral evil', 'Chaotic evil', )
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
self.alignment = self.add(
|
||||||
|
npyscreen.TitleMultiLine, name="Alignment:", values=self.alignments)
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
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):
|
def on_cancel(self):
|
||||||
self.parentApp.setNextForm('BACKGROUND')
|
self.parentApp.setNextForm('BACKGROUND')
|
||||||
@@ -255,116 +368,70 @@ class AbilityScoreForm(npyscreen.ActionForm):
|
|||||||
self.max_hp = self.add(npyscreen.TitleText, name="Max HP:")
|
self.max_hp = self.add(npyscreen.TitleText, name="Max HP:")
|
||||||
|
|
||||||
def on_ok(self):
|
def on_ok(self):
|
||||||
self.parentApp.setNextForm('BACKGROUND')
|
self.parentApp.setNextForm('SKILLS')
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.parentApp.setNextForm('ALIGNMENT')
|
self.parentApp.setNextForm('ALIGNMENT')
|
||||||
|
|
||||||
|
|
||||||
class CharacterClassForm(npyscreen.ActionForm):
|
class SkillForm(npyscreen.ActionForm):
|
||||||
|
def while_editing(self):
|
||||||
|
# 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.primary_class.class_skill_choices +
|
||||||
|
self.parentApp.character.race.skill_choices +
|
||||||
|
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.update_remaining()
|
||||||
|
|
||||||
|
def update_remaining(self, widget=None):
|
||||||
|
num_choices = (self.parentApp.character.num_skill_choices +
|
||||||
|
self.parentApp.character.race.num_skill_choices +
|
||||||
|
self.parentApp.character.background.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):
|
def create(self):
|
||||||
self.character_class = self.add(
|
self.bg_skills = self.add(
|
||||||
npyscreen.TitleMultiLine, name="Class:", values=tuple(char_classes.keys()))
|
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)
|
||||||
|
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):
|
def on_ok(self):
|
||||||
if self.character_class.value is not None:
|
new_skills = self.skill_proficiencies.get_selected_objects()
|
||||||
selected_class = self.character_class.values[self.character_class.value]
|
if new_skills is not None:
|
||||||
selected_class = char_classes[selected_class]
|
new_skills = tuple(s.lower() for s in new_skills)
|
||||||
log.debug('Selected character class %s', selected_class.class_name)
|
else:
|
||||||
self.parentApp.character_class = selected_class
|
new_skills = ()
|
||||||
self.parentApp.setNextForm('RACE')
|
bg_skills = tuple(self.parentApp.character.background.skill_proficiencies)
|
||||||
|
race_skills = tuple(self.parentApp.character.race.skill_proficiencies)
|
||||||
def on_cancel(self):
|
all_skills = new_skills + bg_skills + race_skills
|
||||||
self.parentApp.setNextForm('MAIN')
|
self.parentApp.character.skill_proficiencies = all_skills
|
||||||
|
log.debug(f"Skill proficiencies: {all_skills}")
|
||||||
|
self.parentApp.setNextForm('SAVE')
|
||||||
class BackgroundForm(npyscreen.ActionForm):
|
|
||||||
|
|
||||||
def create(self):
|
|
||||||
self.background = self.add(
|
|
||||||
npyscreen.TitleMultiLine,
|
|
||||||
name="Background:", values=tuple(backgrounds.keys()))
|
|
||||||
|
|
||||||
def on_ok(self):
|
|
||||||
if self.background.value is not None:
|
|
||||||
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):
|
def on_cancel(self):
|
||||||
self.parentApp.setNextForm('ABILITIES')
|
self.parentApp.setNextForm('ABILITIES')
|
||||||
|
|
||||||
|
|
||||||
class RaceForm(npyscreen.ActionForm):
|
|
||||||
def create(self):
|
|
||||||
self.race = self.add(
|
|
||||||
npyscreen.TitleMultiLine, name="Race:", values=tuple(races.keys()))
|
|
||||||
|
|
||||||
def on_ok(self):
|
|
||||||
if self.race.value is not None:
|
|
||||||
selected_race = self.race.values[self.race.value]
|
|
||||||
SelectedRace = races[selected_race]
|
|
||||||
log.debug('Selected character race: %s', SelectedRace.name)
|
|
||||||
self.parentApp.character.race = SelectedRace()
|
|
||||||
self.parentApp.setNextForm('ALIGNMENT')
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
self.parentApp.setNextForm('CLASS')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AlignmentForm(npyscreen.ActionForm):
|
|
||||||
"""Choose your character's alignment."""
|
|
||||||
alignments = ('Lawful good', 'Neutral good', 'Chaotic good',
|
|
||||||
'Lawful neutral', 'True neutral', 'Chaotic neutral',
|
|
||||||
'Lawful evil', 'Neutral evil', 'Chaotic evil', )
|
|
||||||
|
|
||||||
def create(self):
|
|
||||||
self.alignment = self.add(
|
|
||||||
npyscreen.TitleMultiLine, name="Alignment:", values=self.alignments)
|
|
||||||
|
|
||||||
def on_ok(self):
|
|
||||||
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):
|
|
||||||
self.parentApp.setNextForm('RACE')
|
|
||||||
|
|
||||||
|
|
||||||
class BasicInfoForm(npyscreen.ActionForm):
|
|
||||||
def create(self):
|
|
||||||
self.name = self.add(
|
|
||||||
npyscreen.TitleText, name="Character Name:", use_two_lines=False)
|
|
||||||
self.player_name = self.add(
|
|
||||||
npyscreen.TitleText, name="Player Name:", use_two_lines=False)
|
|
||||||
self.level = self.add(
|
|
||||||
npyscreen.TitleText, name='Level:', value="1")
|
|
||||||
|
|
||||||
def on_ok(self):
|
|
||||||
# Update the default filename
|
|
||||||
name = self.name.value
|
|
||||||
if name == '':
|
|
||||||
filename = 'new_character.py'
|
|
||||||
else:
|
|
||||||
filename = f'{name.split(" ")[0].lower()}.py'
|
|
||||||
save_form = self.parentApp.getForm('SAVE')
|
|
||||||
save_form.filename.value = filename
|
|
||||||
# Move to the next form
|
|
||||||
self.parentApp.setNextForm('CLASS')
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
raise KeyboardInterrupt
|
|
||||||
|
|
||||||
|
|
||||||
class SaveForm(npyscreen.ActionForm):
|
class SaveForm(npyscreen.ActionForm):
|
||||||
def create(self):
|
def create(self):
|
||||||
self.filename = self.add(
|
self.filename = self.add(
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ def rst_to_latex(rst):
|
|||||||
tex = ""
|
tex = ""
|
||||||
else:
|
else:
|
||||||
tex = rst
|
tex = rst
|
||||||
|
for c in ['\\']:
|
||||||
|
tex = tex.replace(c, '\\' + c)
|
||||||
tex = bold_re.sub(r'\\textbf{\1}', tex)
|
tex = bold_re.sub(r'\\textbf{\1}', tex)
|
||||||
tex = it_re.sub(r'\\textit{\1}', tex)
|
tex = it_re.sub(r'\\textit{\1}', tex)
|
||||||
tex = dice_re.sub(r'\\texttt{\1}', tex)
|
tex = dice_re.sub(r'\\texttt{\1}', tex)
|
||||||
tex = tt_re.sub(r'\\texttt{\1}', tex)
|
tex = tt_re.sub(r'\\texttt{\1}', tex)
|
||||||
for c in ['\\', '#', '$', '%', '&', '~', '_', '^',
|
for c in ['#', '$', '%', '&', '~', '_', '^']:
|
||||||
'{', '}', '(', ')', '[', ']']:
|
|
||||||
tex = tex.replace(c, '\\' + c)
|
tex = tex.replace(c, '\\' + c)
|
||||||
return tex
|
return tex
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ class Race():
|
|||||||
proficiencies_text = tuple()
|
proficiencies_text = tuple()
|
||||||
weapon_proficiences = tuple()
|
weapon_proficiences = tuple()
|
||||||
skill_proficiencies = ()
|
skill_proficiencies = ()
|
||||||
|
skill_choices = ()
|
||||||
|
num_skill_choices = 0
|
||||||
features = tuple()
|
features = tuple()
|
||||||
strength_bonus = 0
|
strength_bonus = 0
|
||||||
dexterity_bonus = 0
|
dexterity_bonus = 0
|
||||||
|
|||||||
@@ -74,12 +74,13 @@ class Ability():
|
|||||||
class Skill():
|
class Skill():
|
||||||
"""An ability-based skill, such as athletics."""
|
"""An ability-based skill, such as athletics."""
|
||||||
|
|
||||||
def __init__(self, ability):
|
def __init__(self, ability, name):
|
||||||
self.ability_name = ability
|
self.ability_name = ability
|
||||||
|
|
||||||
def __set_name__(self, character, name):
|
|
||||||
self.skill_name = name
|
self.skill_name = name
|
||||||
self.character = character
|
|
||||||
|
# def __set_name__(self, character, name):
|
||||||
|
# self.skill_name = name
|
||||||
|
# self.character = character
|
||||||
|
|
||||||
def __get__(self, character, owner):
|
def __get__(self, character, owner):
|
||||||
ability = getattr(character, self.ability_name)
|
ability = getattr(character, self.ability_name)
|
||||||
|
|||||||
Reference in New Issue
Block a user