import random import json #TODO 5 AC? Or Gold Start no AC? #TODO 6 Sub Classes? 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"] 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.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 = (f"Your new character is {self.name}, {self.article} {self.race} {self.job}, with the " f"{self.background} background.\n") 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}\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: {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) 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) #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}")