diff --git a/creation_engine.py b/creation_engine.py index 6d909da..ebcc4fd 100644 --- a/creation_engine.py +++ b/creation_engine.py @@ -1,9 +1,7 @@ import random import json -# TODO AC? Or Gold Start no AC? -# TODO Feats? - +# Constants Lists VOWELS = ["A", "E", "I", "O", "U"] SKILL_LIST = ["acrobatics", "animal_handling", "arcana", "athletics", "deception", "history", "insight", "intimidation", @@ -12,24 +10,13 @@ SKILL_LIST = ["acrobatics", "animal_handling", "arcana", "athletics", "deception ATTRIBUTE_LIST = ["strength", "dexterity", "wisdom", "constitution", "intelligence", "charisma"] -CLASS_LIST = ["Artificer", "Barbarian", "Bard", "Cleric", "Druid", "Fighter", "Monk", "Paladin", +JOB_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", @@ -48,21 +35,33 @@ WIZARD_SUBCLASSES = ["Bladesinging", "Chronurgy Magic", "Graviturgy Magic", "Ord "School of Conjuration", "School of Divination", "School of Enchantment", "School of Evocation", "School of Illusion", "School of Necromancy", "School of Transmutation", "War Magic"] +# Variable/Functional Lists +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"] + +# Begin the class for creation class HeroCreation: def __init__(self): self.name_generation() self.race_selection() - self.race = self.race_string["Race"] + self.race = self.race_dict["Race"] # 'job' is used in place of 'class' to avoid a function conflict - self.job = random.choice(CLASS_LIST) + self.job = random.choice(JOB_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.speed = self.race_dict["Speed"] self.skill_generation() self.skill_proficiency() + #Initial configuration of tool proficiencies. These are left alone if none are selected. self.instruments = "None" self.artisan_tools = "None" self.additional_tools = "None" @@ -72,51 +71,29 @@ class HeroCreation: 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" + self.print_generator() + self.output() + + # Selects two random entries from a large text file containing fantasy names. + 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) + # Chooses race at random. Each race has a dictionary with other assigned values/proficiencies + # Things like attribute bonuses or language proficiencies come from this dictionary 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) + setattr(self, "race_dict", chosen_race) + # Rolls the 6 main attributes in the style of roll for d6, drop the lowest. + # Configured to "Re-roll" 1s. To include 1s, increase the randint range to 1,6 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): @@ -125,22 +102,14 @@ class HeroCreation: stat_total = sum(roll_list) setattr(self, f"{attribute}", stat_total) - # Assign racial stat bonuses + # Assign racial stat bonuses from the race_dict pulled from races.json 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) + self.charisma += self.race_dict["Charisma"] + self.constitution += self.race_dict["Constitution"] + self.dexterity += self.race_dict["Dexterity"] + self.intelligence += self.race_dict["Intelligence"] + self.strength += self.race_dict["Strength"] + self.wisdom += self.race_dict["Wisdom"] # Created skill attributes and save attributes with a base value of +0 to the roll def skill_generation(self): @@ -162,6 +131,8 @@ class HeroCreation: setattr(self, save_name, 0) self.apply_skill_modifiers(attribute, save_name) + # Receives input for all skills and saves and applies a modifier to the roll based on the associated + # attribute value def apply_skill_modifiers(self, attribute_name, skill_name): attribute = getattr(self, attribute_name) if attribute == 1: @@ -191,27 +162,32 @@ class HeroCreation: setattr(self, skill_name, modifier) def skill_proficiency(self): + # Pulls the available skills that each background is proficient in and applies a +2 proficiency modifier 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: + background_list = background_data[self.background]["Skills"] + for skill in background_list: current_score = getattr(self, skill) new_score = str(current_score + 2) setattr(self, skill, f"{new_score} (Prof)") + # Pulls the available skills that each job can choose to be proficient in with open('jobs.json') as json_file: job_data = json.load(json_file) - # Is this actually a dict? job_dict = job_data[self.job] + # Pulls the number of proficiencies able to be chosen proficiencies = job_dict["Proficiencies"] + # Pulls available skills to be proficient in job_skill_list = job_dict["Skills"] - for skill in background_dict: + # Removes any skills chosen by background proficiencies, so they can not be chosen as double proficient. + for skill in background_list: if skill in job_skill_list: job_skill_list.remove(skill) - # rogue list for expertise calculation + # Rogue list for expertise calculation (Rogues get one double proficiency at level 1) rogue_proficient_list = [] + # Choose proficient skills and add a +2 modifier for the level 1 proficiency bonus. for each in range(proficiencies): proficient_skill = random.choice(job_skill_list) + # Chooses rogue proficiencies and saves them to a list for expertise selection if self.job == "Rogue": rogue_proficient_list.append(proficient_skill) job_skill_list.remove(proficient_skill) @@ -219,12 +195,14 @@ class HeroCreation: setattr(self, f"{proficient_skill}_base_value", current_score) new_score = str(current_score + 2) setattr(self, proficient_skill, f"{new_score} (Prof)") + # Selects one proficient skill to be chosen for expertise for rogues. 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)") + # Assigns additional stats from the job dictionary def job_characteristics(self): with open('jobs.json') as json_file: job_data = json.load(json_file) @@ -233,9 +211,11 @@ class HeroCreation: starting_hp_value = job_data[self.job]["Starting HP"] setattr(self, "starting_hp", starting_hp_value) + #Assigns language and tool proficiencies based on job def job_proficiencies(self): with open('jobs.json') as json_file: job_data = json.load(json_file) + # Monk receives a choice of instrument tool or artisan tool. This selects one at random. if self.job == "Monk": tool_choice = random.randint(0, 1) if tool_choice == 0: @@ -243,6 +223,7 @@ class HeroCreation: else: artisan_tool_value = 1 else: + # Attempt to pull number of proficiencies for each job. Some have none in these values. try: instrument_value = job_data[self.job]["Instruments"] except KeyError: @@ -251,8 +232,10 @@ class HeroCreation: artisan_tool_value = job_data[self.job]["Artisan's Tools"] except KeyError: pass + # Empty list creation for tools and instruments. May not be populated. instrument_prof_list = [] artisan_prof_list = [] + # Attempts to assign random tool values, assuming the job has any proficiencies available try: for value in range(0, instrument_value): new_instrument = random.choice(instrument_list) @@ -277,25 +260,30 @@ class HeroCreation: setattr(self, "additional_tools", str(additional_tool_string)) except KeyError: pass + # Set Armor proficiencies and update the attribute 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) + # Set Weapon proficiencies and update the attribute weapon_prof_list = job_data[self.job]["Weapon Proficiency"] weapon_prof_string = ", ".join(weapon_prof_list) setattr(self, "weapon_proficiency", weapon_prof_string) + #Add proficiency modifier of +2 to both saving throws that each job is proficient with. 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)") + # Assigns tool and instrument proficiencies based on background def background_proficiencies(self): + # Importing list that starts with only "Common" and will be added to global known_language_list with open('backgrounds.json') as json_file: background_data = json.load(json_file) - # background instrument proficiencies + # Attempt instrument proficiencies (if any are available) try: instrument_value = background_data[self.background]["Additional Proficiencies"]["Instrument"] except KeyError or UnboundLocalError: @@ -307,7 +295,7 @@ class HeroCreation: setattr(self, "instruments", f"{new_instrument}") else: setattr(self, "instruments", current_instruments + f", {new_instrument}") - # background artisan's tools proficiencies + # Attempt artisan's tool proficiencies (if any are available) try: tool_value = background_data[self.background]["Additional Proficiencies"]["Artisan's Tool"] except KeyError or UnboundLocalError: @@ -319,7 +307,7 @@ class HeroCreation: setattr(self, "artisan_tools", f"{new_tool}") else: setattr(self, "artisan_tools", current_tools + f", {new_tool}") - # language proficiencies + # Attempt language proficiencies (if any are available) try: language_value = background_data[self.background]["Additional Proficiencies"]["Languages"] except UnboundLocalError: @@ -331,7 +319,7 @@ class HeroCreation: languages.remove(new_language) except IndexError: pass - # additional tool proficiencies + # Attempt additional tool proficiencies (if any are available) additional_tool_list = background_data[self.background]["Additional Proficiencies"]["Additional Tools"] if not additional_tool_list: pass @@ -347,17 +335,20 @@ class HeroCreation: new_tool_list = getattr(self, "additional_tools") + f", {tool}" setattr(self, "additional_tools", new_tool_list) + # Assigns tool and instrument proficiencies based on race def race_proficiencies(self): + # Importing list that starts with only "Common" and will be added to global known_language_list + # Adds racial language proficiencies try: - known_languages = self.race_string["Languages"] + known_languages = self.race_dict["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"]): + # Attempt additional language proficiencies (if the race is able to learn additional languages) + for value in range(0, self.race_dict["AdditionalLanguages"]): try: new_language = random.choice(languages) known_language_list.append(new_language) @@ -365,30 +356,89 @@ class HeroCreation: except IndexError: pass + # Creates and joins a string of languages using the known_language_list updated by background and race def finalize_languages(self): global known_language_list language_string = ", ".join(known_language_list) setattr(self, "languages", language_string) - def whoami(self): + # Creates attributes for the different text blocks that will be printed as an output + def print_generator(self): + # Grammar check for using "a" or "an" when describing which race was chosen + if self.race[0] in VOWELS: + setattr(self,"article","an") + else: + setattr(self,"article","a") + # Chooses subclass for Cleric, as they get one at level 1. Creates the "whoami" attribute 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") + setattr(self, "subclass", subclass) + setattr(self, "whoami", + 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") + # Chooses subclass for Sorcerer, as they get one at level 1. Creates the "whoami" attribute 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") + setattr(self, "whoami", + 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") + # Chooses subclass for Wizard, as they get one at level 1. Creates the "whoami" attribute 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") + setattr(self, "whoami", + 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") + # For all other classes, creates the "whoami" attribute else: - print(f"Your new character is {self.name}, {self.article} {self.race} {self.job}, with the " - f"{self.background} background.\n") + setattr(self, "whoami", f"Your new character is {self.name}, {self.article} {self.race} {self.job}, with the " + f"{self.background} background.\n") + # Create the stat_block attribute + setattr(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") + # Create the saving throw attribute + setattr(self, "saves", "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") + # Create the additional stats attribute + setattr(self, "additional_stats", f"Additional Stats\n----------\nStarting HP: {self.starting_hp}\n" + f"Hit Die: {self.hit_die}\n" + f"Initiative Bonus: {self.initiative}\nSpeed: {self.speed}\n") + # Create the skill bonuses attribute + setattr(self, "skill_bonuses", f"Skill Bonuses\n----------\nAcrobatics: {self.acrobatics}\n" + f"Animal Handling: {self.animal_handling}" + f"\nArcana: {self.arcana}\nAthletics: {self.athletics}\nDeception: " + f"{self.deception}" + f"\nHistory: {self.history}\nInsight: {self.insight}\nIntimidation: " + f"{self.intimidation}" + f"\nInvestigation: {self.investigation}\nMedicine: {self.medicine}\nNature: " + f"{self.nature}" + f"\nPerception: {self.perception}\nPerformance: " + f"{self.performance}\nPersuasion: {self.persuasion}" + f"\nReligion: {self.religion}\nSleight of Hand: {self.sleight_of_hand}" + f"\nStealth: {self.stealth}\nSurvival: {self.survival}\n") + # Create the additional proficiencies attribute + setattr(self, "additional_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") + + # Prints all stats, bonuses, and character info + def output(self): + print(self.whoami) + print(self.stat_block) + print(self.saves) + print(self.additional_stats) + print(self.skill_bonuses) + print(self.additional_proficiencies) diff --git a/main.py b/main.py index 499dbcd..be53305 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,3 @@ import creation_engine -hero = creation_engine.HeroCreation() - -print(hero.stat_block) -print(hero.saves) -print(hero.additional_stats) -print(hero.skill_bonuses) -print(hero.other_proficiencies) -print(hero.job) \ No newline at end of file +hero = creation_engine.HeroCreation() \ No newline at end of file