You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
20 KiB
421 lines
20 KiB
import random |
|
import json |
|
|
|
# TODO AC? Or Gold Start no AC? |
|
# TODO Feats? |
|
|
|
VOWELS = ["A", "E", "I", "O", "U"] |
|
|
|
SKILL_LIST = ["acrobatics", "animal_handling", "arcana", "athletics", "deception", "history", "insight", "intimidation", |
|
"investigation", "medicine", "nature", "perception", "performance", "persuasion", "religion", |
|
"sleight_of_hand", "stealth", "survival"] |
|
|
|
ATTRIBUTE_LIST = ["strength", "dexterity", "wisdom", "constitution", "intelligence", "charisma"] |
|
|
|
CLASS_LIST = ["Artificer", "Barbarian", "Bard", "Cleric", "Druid", "Fighter", "Monk", "Paladin", |
|
"Ranger", "Rogue", "Sorcerer", "Warlock", "Wizard", "Blood Hunter"] |
|
|
|
BACKGROUND_LIST = ["Acolyte", "Charlatan", "Criminal / Spy", "Entertainer", "Folk Hero", "Gladiator", |
|
"Guild Artisan / Guild Merchant", "Hermit", "Knight", "Noble", "Outlander", "Pirate", "Sage", |
|
"Sailor", "Soldier", "Urchin", ] |
|
|
|
instrument_list = ["Bagpipes", "Birdpipes", "Clarinet", "Drum", "Dulcimer", "Fiddle", "Flute", "Glaur", "Hand Drum", |
|
"Harp", "Horn", "Longhorn", "Lute", "Lyre", "Pan Flute", "Shawm", "Songhorn", "Tantan", "Thelarr", |
|
"Tocken", "Trumpet", "Viol", "Wargong", "Yarting", "Zulkoon"] |
|
|
|
languages = ["Aarakocra", "Abyssal", "Aquan", "Auran", "Celestial", "Deep Speech", "Draconic", "Dwarvish", "Elvish", |
|
"Giant", |
|
"Gith", "Gnomish", "Goblin", "Halfling", "Infernal", "Orc", "Primordial", "Sylvan", "Undercommon", |
|
"Vedalken"] |
|
|
|
known_language_list = ["Common"] |
|
|
|
GAMING_SETS = ["Bowls", "Darts", "Dice Set", "Dragonchess Set", "Playing Card Set", "Quoits", "Three-Dragon Ante Set"] |
|
|
|
ARTISANS_TOOLS = ["Alchemist's Supplies", "Brewer's Supplies", "Calligrapher's Supplies", "Carpenter's Tools", |
|
"Cartographer's Tools", "Cobbler's Tools", "Cook's Utensils", "Glassblower's Tools", |
|
"Jeweler's Tools", "Leatherworker's Tools", "Mason's Tools", "Painter's Supplies", "Potter's Tools", |
|
"Smith's Tools", "Tinker's Tools", "Weaver's Tools", "Woodcarver's Tools"] |
|
|
|
CLERIC_SUBCLASSES = ["Life Domain", "Knowledge Domain", "Light Domain", "Nature Domain", "Tempest Domain", |
|
"Trickery Domain", "War Domain", "Death Domain", "Arcana Domain", "Forge Domain", "Grave Domain", |
|
"Twilight Domain", "Peace Domain", "Order Domain"] |
|
|
|
SORCERER_SUBCLASSES = ["Aberrant Mind", "Clockwork Soul", "Divine Soul", "Draconic Bloodline", "Lunar Sorcery", |
|
"Shadow Magic", "Storm Sorcery", "Wild Magic"] |
|
|
|
WIZARD_SUBCLASSES = ["Bladesinging", "Chronurgy Magic", "Graviturgy Magic", "Order of Scribes", "School of Abjuration", |
|
"School of Conjuration", "School of Divination", "School of Enchantment", "School of Evocation", |
|
"School of Illusion", "School of Necromancy", "School of Transmutation", "War Magic"] |
|
|
|
class HeroCreation: |
|
def __init__(self): |
|
self.name_generation() |
|
self.race_selection() |
|
self.race = self.race_string["Race"] |
|
# 'job' is used in place of 'class' to avoid a function conflict |
|
self.job = random.choice(CLASS_LIST) |
|
self.job_characteristics() |
|
self.background = random.choice(BACKGROUND_LIST) |
|
self.stat_rolls() |
|
self.racial_bonuses() |
|
self.speed = self.race_string["Speed"] |
|
self.article = self.grammar() |
|
self.skill_generation() |
|
self.skill_proficiency() |
|
self.instruments = "None" |
|
self.artisan_tools = "None" |
|
self.additional_tools = "None" |
|
self.armor_proficiency = "None" |
|
self.race_proficiencies() |
|
self.job_proficiencies() |
|
self.background_proficiencies() |
|
self.finalize_languages() |
|
self.apply_skill_modifiers("dexterity", "initiative") |
|
self.whoami() |
|
self.stat_block = (f"Stat Block\n----------\nCharisma: {self.charisma}\nConstitution: {self.constitution}\n" |
|
f"Dexterity: {self.dexterity}\nIntelligence: {self.intelligence}\n" |
|
f"Strength: {self.strength}\nWisdom: {self.wisdom}\n") |
|
self.saves = (f"Saving Throws\n----------\nCharisma Save: {self.charisma_save}\n" |
|
f"Constitution Save: {self.constitution_save}\n" |
|
f"Dexterity Save: {self.dexterity_save}\n" |
|
f"Intelligence Save: {self.intelligence_save}\n" |
|
f"Strength Save: {self.strength_save}\n" |
|
f"Wisdom Save: {self.wisdom_save}\n") |
|
self.additional_stats = ( |
|
f"Additional Stats\n----------\nStarting HP: {self.starting_hp}\nHit Die: {self.hit_die}\n" |
|
f"Initiative Bonus: {self.initiative}\nSpeed: {self.speed}\n") |
|
self.skill_bonuses = ( |
|
f"Skill Bonuses\n----------\nAcrobatics: {self.acrobatics}\nAnimal Handling: {self.animal_handling}" |
|
f"\nArcana: {self.arcana}\nAthletics: {self.athletics}\nDeception: {self.deception}" |
|
f"\nHistory: {self.history}\nInsight: {self.insight}\nIntimidation: {self.intimidation}" |
|
f"\nInvestigation: {self.investigation}\nMedicine: {self.medicine}\nNature: {self.nature}" |
|
f"\nPerception: {self.perception}\nPerformance: {self.performance}\nPersuasion: {self.persuasion}" |
|
f"\nReligion: {self.religion}\nSleight of Hand: {self.sleight_of_hand}" |
|
f"\nStealth: {self.stealth}\nSurvival: {self.survival}\n") |
|
self.other_proficiencies = (f"Additional Proficiencies\n----------\n" |
|
f"Languages: {self.languages}\n" |
|
f"Instruments: {self.instruments}\n" |
|
f"Artisan's Tools: {self.artisan_tools}\nAdditional Tools: " |
|
f"{self.additional_tools}\n" |
|
f"Weapon Proficiencies: {self.weapon_proficiency}\n" |
|
f"Armor Proficiencies: {self.armor_proficiency}\n") |
|
|
|
# Assign the correct a/an article in the 'whoami' attr |
|
def grammar(self): |
|
if self.race[0] in VOWELS: |
|
return "an" |
|
else: |
|
return "a" |
|
|
|
def race_selection(self): |
|
with open('races.json') as json_file: |
|
race_data = json.load(json_file) |
|
chosen_race = random.choice(race_data) |
|
setattr(self, "race_string", chosen_race) |
|
|
|
def stat_rolls(self): |
|
# Configured to "Re-roll" 1s |
|
# To include 1s, increase the randint range to 1,6 |
|
for attribute in ATTRIBUTE_LIST: |
|
roll_list = [] |
|
for each in range(4): |
|
roll_list.append(random.randint(2, 6)) |
|
roll_list.remove(min(roll_list)) |
|
stat_total = sum(roll_list) |
|
setattr(self, f"{attribute}", stat_total) |
|
|
|
# Assign racial stat bonuses |
|
def racial_bonuses(self): |
|
self.charisma += self.race_string["Charisma"] |
|
self.constitution += self.race_string["Constitution"] |
|
self.dexterity += self.race_string["Dexterity"] |
|
self.intelligence += self.race_string["Intelligence"] |
|
self.strength += self.race_string["Strength"] |
|
self.wisdom += self.race_string["Wisdom"] |
|
|
|
def name_generation(self): |
|
with open("names.txt", "r") as f: |
|
name_list = f.read().splitlines() |
|
first_name = random.choice(name_list) |
|
sur_name = random.choice(name_list) |
|
combined_name = f"{first_name} {sur_name}" |
|
setattr(self, "name", combined_name) |
|
|
|
# Created skill attributes and save attributes with a base value of +0 to the roll |
|
def skill_generation(self): |
|
for skill in SKILL_LIST: |
|
setattr(self, skill, 0) |
|
if skill == "athletics": |
|
self.apply_skill_modifiers("strength", skill) |
|
elif skill in ["acrobatics", "sleight_of_hand", "stealth"]: |
|
self.apply_skill_modifiers("dexterity", skill) |
|
elif skill in ["arcana", "history", "investigation", "nature", "religion"]: |
|
self.apply_skill_modifiers("intelligence", skill) |
|
elif skill in ["animal_handling", "insight", "medicine", "perception", "survival"]: |
|
self.apply_skill_modifiers("wisdom", skill) |
|
elif skill in ["deception", "intimidation", "performance", "persuasion"]: |
|
self.apply_skill_modifiers("charisma", skill) |
|
# Initialization of saving throw modifiers |
|
for attribute in ATTRIBUTE_LIST: |
|
save_name = f"{attribute}_save" |
|
setattr(self, save_name, 0) |
|
self.apply_skill_modifiers(attribute, save_name) |
|
|
|
def apply_skill_modifiers(self, attribute_name, skill_name): |
|
attribute = getattr(self, attribute_name) |
|
if attribute == 1: |
|
modifier = -5 |
|
elif attribute in [2, 3]: |
|
modifier = -4 |
|
elif attribute in [4, 5]: |
|
modifier = -3 |
|
elif attribute in [6, 7]: |
|
modifier = -2 |
|
elif attribute in [8, 9]: |
|
modifier = -1 |
|
elif attribute in [10, 11]: |
|
modifier = 0 |
|
elif attribute in [12, 13]: |
|
modifier = 1 |
|
elif attribute in [14, 15]: |
|
modifier = 2 |
|
elif attribute in [16, 17]: |
|
modifier = 3 |
|
elif attribute in [18, 19]: |
|
modifier = 4 |
|
elif attribute in [20, 21]: |
|
modifier = 5 |
|
else: |
|
modifier = 0 |
|
setattr(self, skill_name, modifier) |
|
|
|
def skill_proficiency(self): |
|
with open('backgrounds.json') as json_file: |
|
background_data = json.load(json_file) |
|
# Is this actually a dict? |
|
background_dict = background_data[self.background]["Skills"] |
|
for skill in background_dict: |
|
current_score = getattr(self, skill) |
|
new_score = str(current_score + 2) |
|
setattr(self, skill, f"{new_score} (Prof)") |
|
with open('jobs.json') as json_file: |
|
job_data = json.load(json_file) |
|
# Is this actually a dict? |
|
job_dict = job_data[self.job] |
|
proficiencies = job_dict["Proficiencies"] |
|
job_skill_list = job_dict["Skills"] |
|
for skill in background_dict: |
|
if skill in job_skill_list: |
|
job_skill_list.remove(skill) |
|
# rogue list for expertise calculation |
|
rogue_proficient_list = [] |
|
for each in range(proficiencies): |
|
proficient_skill = random.choice(job_skill_list) |
|
if self.job == "Rogue": |
|
rogue_proficient_list.append(proficient_skill) |
|
job_skill_list.remove(proficient_skill) |
|
current_score = getattr(self, proficient_skill) |
|
setattr(self, f"{proficient_skill}_base_value", current_score) |
|
new_score = str(current_score + 2) |
|
setattr(self, proficient_skill, f"{new_score} (Prof)") |
|
if self.job == "Rogue": |
|
expertise_skill = random.choice(rogue_proficient_list) |
|
score_value = getattr(self, f"{expertise_skill}_base_value") |
|
new_score = score_value + 4 |
|
setattr(self, expertise_skill, f"{new_score} (Expertise)") |
|
|
|
def job_characteristics(self): |
|
with open('jobs.json') as json_file: |
|
job_data = json.load(json_file) |
|
hit_die_value = job_data[self.job]["Hit Die"] |
|
setattr(self, "hit_die", hit_die_value) |
|
starting_hp_value = job_data[self.job]["Starting HP"] |
|
setattr(self, "starting_hp", starting_hp_value) |
|
|
|
def job_proficiencies(self): |
|
with open('jobs.json') as json_file: |
|
job_data = json.load(json_file) |
|
if self.job == "Monk": |
|
tool_choice = random.randint(0, 1) |
|
if tool_choice == 0: |
|
instrument_value = 1 |
|
else: |
|
artisan_tool_value = 1 |
|
else: |
|
try: |
|
instrument_value = job_data[self.job]["Instruments"] |
|
except KeyError: |
|
pass |
|
try: |
|
artisan_tool_value = job_data[self.job]["Artisan's Tools"] |
|
except KeyError: |
|
pass |
|
instrument_prof_list = [] |
|
artisan_prof_list = [] |
|
try: |
|
for value in range(0, instrument_value): |
|
new_instrument = random.choice(instrument_list) |
|
instrument_list.remove(new_instrument) |
|
instrument_prof_list.append(new_instrument) |
|
instrument_string = ", ".join(instrument_prof_list) |
|
setattr(self, "instruments", str(instrument_string)) |
|
except UnboundLocalError: |
|
pass |
|
try: |
|
for value in range(0, artisan_tool_value): |
|
new_tool = random.choice(ARTISANS_TOOLS) |
|
ARTISANS_TOOLS.remove(new_tool) |
|
artisan_prof_list.append(new_tool) |
|
artisan_tool_string = ", ".join(artisan_prof_list) |
|
setattr(self, "artisan_tools", str(artisan_tool_string)) |
|
except UnboundLocalError: |
|
pass |
|
try: |
|
additional_tool_list = job_data[self.job]["Additional Tools"] |
|
additional_tool_string = ", ".join(additional_tool_list) |
|
setattr(self, "additional_tools", str(additional_tool_string)) |
|
except KeyError: |
|
pass |
|
armor_prof_list = job_data[self.job]["Armor Proficiency"] |
|
if not armor_prof_list: |
|
setattr(self, "armor_proficiency", "None") |
|
else: |
|
armor_prof_string = ", ".join(armor_prof_list) |
|
setattr(self, "armor_proficiency", armor_prof_string) |
|
weapon_prof_list = job_data[self.job]["Weapon Proficiency"] |
|
weapon_prof_string = ", ".join(weapon_prof_list) |
|
setattr(self, "weapon_proficiency", weapon_prof_string) |
|
for attribute in job_data[self.job]["Saving Throws"]: |
|
current_score = getattr(self, f"{attribute}_save") |
|
new_score = str(current_score + 2) |
|
setattr(self, f"{attribute}_save", f"{new_score} (Prof)") |
|
|
|
def background_proficiencies(self): |
|
global known_language_list |
|
with open('backgrounds.json') as json_file: |
|
background_data = json.load(json_file) |
|
# background instrument proficiencies |
|
try: |
|
instrument_value = background_data[self.background]["Additional Proficiencies"]["Instrument"] |
|
except KeyError or UnboundLocalError: |
|
pass |
|
for value in range(0, instrument_value): |
|
new_instrument = random.choice(instrument_list) |
|
current_instruments = getattr(self, "instruments") |
|
if current_instruments == "None": |
|
setattr(self, "instruments", f"{new_instrument}") |
|
else: |
|
setattr(self, "instruments", current_instruments + f", {new_instrument}") |
|
# background artisan's tools proficiencies |
|
try: |
|
tool_value = background_data[self.background]["Additional Proficiencies"]["Artisan's Tool"] |
|
except KeyError or UnboundLocalError: |
|
pass |
|
for value in range(0, tool_value): |
|
new_tool = random.choice(ARTISANS_TOOLS) |
|
current_tools = getattr(self, "artisan_tools") |
|
if current_tools == "None": |
|
setattr(self, "artisan_tools", f"{new_tool}") |
|
else: |
|
setattr(self, "artisan_tools", current_tools + f", {new_tool}") |
|
# language proficiencies |
|
try: |
|
language_value = background_data[self.background]["Additional Proficiencies"]["Languages"] |
|
except UnboundLocalError: |
|
pass |
|
for value in range(0, language_value): |
|
try: |
|
new_language = random.choice(languages) |
|
known_language_list.append(new_language) |
|
languages.remove(new_language) |
|
except IndexError: |
|
pass |
|
# additional tool proficiencies |
|
additional_tool_list = background_data[self.background]["Additional Proficiencies"]["Additional Tools"] |
|
if not additional_tool_list: |
|
pass |
|
else: |
|
current_tools = getattr(self, "additional_tools") |
|
if current_tools == "None": |
|
setattr(self, "additional_tools", ", ".join(additional_tool_list)) |
|
else: |
|
for tool in additional_tool_list: |
|
if tool in current_tools: |
|
pass |
|
else: |
|
new_tool_list = getattr(self, "additional_tools") + f", {tool}" |
|
setattr(self, "additional_tools", new_tool_list) |
|
|
|
def race_proficiencies(self): |
|
global known_language_list |
|
try: |
|
known_languages = self.race_string["Languages"] |
|
for language in known_languages: |
|
known_language_list.append(language) |
|
print(language) |
|
languages.remove(language) |
|
except KeyError: |
|
pass |
|
for value in range(0, self.race_string["AdditionalLanguages"]): |
|
try: |
|
new_language = random.choice(languages) |
|
known_language_list.append(new_language) |
|
languages.remove(new_language) |
|
except IndexError: |
|
pass |
|
|
|
def finalize_languages(self): |
|
global known_language_list |
|
language_string = ", ".join(known_language_list) |
|
setattr(self, "languages", language_string) |
|
|
|
def whoami(self): |
|
if self.job == "Cleric": |
|
subclass = random.choice(CLERIC_SUBCLASSES) |
|
setattr(self,"subclass",subclass) |
|
print(f"Your new character is {self.name}, {self.article} {self.race} {self.job}, with the " |
|
f"{self.background} background and the {self.subclass} subclass.\n") |
|
elif self.job == "Sorcerer": |
|
subclass = random.choice(SORCERER_SUBCLASSES) |
|
setattr(self, "subclass", subclass) |
|
print(f"Your new character is {self.name}, {self.article} {self.race} {self.job}, with the " |
|
f"{self.background} background and the {self.subclass} subclass.\n") |
|
elif self.job == "Wizard": |
|
subclass = random.choice(WIZARD_SUBCLASSES) |
|
setattr(self, "subclass", subclass) |
|
print(f"Your new character is {self.name}, {self.article} {self.race} {self.job}, with the " |
|
f"{self.background} background and the {self.subclass} subclass.\n") |
|
else: |
|
print(f"Your new character is {self.name}, {self.article} {self.race} {self.job}, with the " |
|
f"{self.background} background.\n") |
|
|
|
|
|
|
|
# Used for interactively moving around stats in case of a bad allocation. |
|
# Disabling for now, as this project is intended to be one-command and self-contained |
|
# def stat_moves(self): |
|
# finished = False |
|
# while not finished: |
|
# move_request = input("Would you like to move any of these stats around? (y/n)").lower() |
|
# if move_request == "y": |
|
# stat_1 = input("What is the first stat you would like to move? Please enter the full stat |
|
# name.").lower() |
|
# stat_2 = input("What is the second stat you would like to move? Please enter the full stat |
|
# name.").lower() |
|
# if hasattr(self, stat_1) and hasattr(self, stat_2): |
|
# stat_1_value = getattr(self, stat_1) |
|
# stat_2_value = getattr(self, stat_2) |
|
# setattr(self, stat_1, stat_2_value) |
|
# setattr(self, stat_2, stat_1_value) |
|
# self.update_stat_block() |
|
# print(self.whoami) |
|
# print(self.stat_block) |
|
# elif move_request == "n": |
|
# finished = True |
|
|
|
# Currently only used for the stat_moves functionality. Disabling for now |
|
# def update_stat_block(self): |
|
# self.stat_block = (f"Stat Block\n---------\nCharisma: {self.charisma}\nConstitution: {self.constitution}\n" |
|
# f"Dexterity: {self.dexterity}\nIntelligence: {self.intelligence}\n" |
|
# f"Strength: {self.strength}\nWisdom: {self.wisdom}")
|
|
|