def change_equipment_filter(current_equip, filters, modifier): """ Modifies one piece of equipment to another piece available for the given class. :param current_equip: a list of the names of the current equipment the character owns :type current_equip: list :param filters: the dictionary of filters used to make a full character :type filters: dict :param modifier: the modifier number produced, used to help randomise adjustment :type modifier: int :return: newly modified filters """ Db.cursor.execute("SELECT equipOptionId, suboption FROM EquipmentOption WHERE hasChoice = 1 AND classId=" + str(Db.get_id(filters["Class"], "Class"))) options = Db.cursor.fetchall() option, suboption = options[np.random.randint(0, len(options))] itemsToRemove = [] # react to the suboption as appropriate if suboption is not None: Db.cursor.execute("SELECT hasChoice FROM EquipmentOption WHERE equipOptionId =" + str(suboption)) if Db.cursor.fetchone()[0] == 1: option = suboption else: Db.cursor.execute(f"SELECT equipmentName FROM Equipment WHERE equipmentId IN (" f"SELECT equipmentId FROM EquipmentIndivOption WHERE equipmentOptionId = {suboption})") itemsToRemove = list(itertools.chain(*Db.cursor.fetchall())) # remove the appropriate amount of the item being unselected Db.cursor.execute(f"SELECT equipmentName FROM Equipment WHERE equipmentId IN (" f"SELECT equipmentId FROM EquipmentIndivOption WHERE equipmentOptionId = {option})") equipmentItems = list(itertools.chain(*Db.cursor.fetchall())) for equipmentItem in equipmentItems: if equipmentItem in current_equip: Db.cursor.execute("SELECT amnt FROM EquipmentIndivOption WHERE equipmentId=" + str(Db.get_id(equipmentItem, "Equipment")) + " AND equipmentOptionId=" + str(option)) for x in range(Db.cursor.fetchone()[0]): itemsToRemove.append(equipmentItem) equipmentItems.remove(equipmentItem) break # update and return the filters, providing no const-filter collisions have been created if len(set(itemsToRemove).intersection(set(ChromosomeController.constFilters.get("Equipment", [])))) == 0 \ and len(equipmentItems) > 0: modifier = check_modifier(modifier, equipmentItems) for item in itemsToRemove: # this if statement defends against the thin chance they chose 1 of an item when 2 were available if item in filters["Equipment"]: filters["Equipment"].remove(item) filters["Equipment"].append(equipmentItems[modifier]) return filters
def collect_class_option_data(class_name, class_lvl, subclass_id=-1): """ Extracts the class options data from the class database. :param class_name: the name of the class selected :type class_name: str :param class_lvl: the level to build the class at :type class_lvl: int :param subclass_id: the id of the subclass, if appropriate :type subclass_id: int, optional :return: three arrays holding the traits, proficiencies and languages """ if subclass_id == -1: subclassStr = " IS NULL" else: subclassStr = "=" + str(subclass_id) traits, proficiencies, languages = [], [], [] Db.cursor.execute("SELECT classOptionsId FROM ClassOptions WHERE classId=" + str( Db.get_id(class_name, "Class")) + " AND subclassId" + subclassStr) ids = Db.cursor.fetchall() for nextId in ids: metadata, options = DataExtractor.class_options_connections(nextId[0], subclass_id) if metadata[0] <= class_lvl: if metadata[2] == "traits": traits += make_choice(metadata[1], options, class_name) traits = choose_trait_option(traits, class_name) elif metadata[2] == "proficiencies": proficiencies += make_choice(metadata[1], options, class_name) else: languages += make_choice(metadata[1], options, class_name) return traits, proficiencies, languages
def create_class(class_name, class_lvl, subclass=""): """ Creates and returns a class object, given the name of the class to use. :param class_name: the name of the class selected :type class_name: str :param class_lvl: the level to build the class at :type class_lvl: int :param subclass: the subclass to add to this object :type subclass: object, optional :return: a python object representing the class """ classId = Db.get_id(class_name, "Class") Db.cursor.execute("SELECT hitDiceSides, primaryAbility, secondaryAbility, isMagical, savingThrows FROM Class " "WHERE classId=" + str(classId)) hitDice, primaryAbility, secondaryAbility, isMagical, savingThrows = Db.cursor.fetchone() if subclass == "": subclass = None traits, proficiencies, languages = collect_class_option_data(class_name, class_lvl) equipment = create_equipment(class_name) if isMagical: magic = create_class_magic(class_name, class_lvl) return Class.Class(class_name, traits, proficiencies, equipment, primaryAbility, secondaryAbility, savingThrows, hitDice, languages, class_lvl, magic, subclass) else: return Class.Class(class_name, traits, proficiencies, equipment, primaryAbility, secondaryAbility, savingThrows, hitDice, languages, class_lvl, subclass=subclass)
def create_character(chr_lvl, chr_choices=None, ability_scores=None): """ Creates a character object at the given level. :param chr_lvl: the level to build the character at :type chr_lvl: int :param chr_choices: the choices made to apply for this character :type chr_choices: optional, list :param ability_scores: the ability score ranges inputted :type ability_scores: optional, dict :return: a character object """ global selected_results if chr_choices is not None: selected_results = ChoiceStruct.ChoiceStruct(chr_choices) Db.cursor.execute(f"SELECT backgroundName FROM Background") backgrounds = list(itertools.chain(*Db.cursor.fetchall())) background = create_background(make_choice(1, backgrounds, "Background")[0]) Db.cursor.execute(f"SELECT raceName FROM Race") races = list(itertools.chain(*Db.cursor.fetchall())) race = make_choice(1, races, "Race")[0] Db.cursor.execute(f"SELECT subraceName FROM Subrace WHERE raceId={Db.get_id(race, 'Race')}") subraces = list(itertools.chain(*Db.cursor.fetchall())) if len(subraces) > 0: subraceId = Db.get_id(make_choice(1, subraces, "Subrace")[0], "Subrace") race = create_race(race, chr_lvl, subraceId) else: race = create_race(race, chr_lvl) Db.cursor.execute(f"SELECT className FROM Class") classes = list(itertools.chain(*Db.cursor.fetchall())) chrClass = make_choice(1, classes, "Class")[0] Db.cursor.execute(f"SELECT subclassName FROM Subclass WHERE classId={Db.get_id(chrClass, 'Class')}") subclasses = list(itertools.chain(*Db.cursor.fetchall())) if len(subclasses) > 0: subclass = create_subclass(make_choice(1, subclasses, "Subclass")[0], chr_lvl) chrClass = create_class(chrClass, chr_lvl, subclass) else: chrClass = create_class(chrClass, chr_lvl) raceAdditions = dict() if ability_scores is not None: for ability, mod in race.abilityScores.items(): if ability == "ALL": if chrClass.mainAbility not in race.abilityScores.keys(): ability = chrClass.mainAbility.split("/")[0] else: ability = chrClass.secondAbility.split("/")[0] ability_scores[ability][0] -= mod ability_scores[ability][1] -= mod raceAdditions.update({ability: mod}) else: ability_scores = dict() for score in ["STR", "DEX", "CON", "INT", "WIS", "CHA"]: ability_scores.update({score: [8, 15]}) abilityScores = create_ability_scores((chrClass.mainAbility, chrClass.secondAbility), ability_scores) return Character.Character(race, chrClass, background, abilityScores)
def spell_info(spell_name, chr_level=1): """ Get all the appropriate info for a spell from the name. :param spell_name: the name of the spell to fetch :type spell_name: str :param chr_level: the level of the character, relevant for cantrips, and 1 by default :type chr_level: int, optional :return: all the required data for a spell object, in parameter order """ Db.cursor.execute("SELECT * FROM Spell WHERE spellName='" + spell_name.replace("'", "''") + "'") lvl, castingTime, duration, sRange, area, components, atOrSave, school, damOrEffect, desc = Db.cursor.fetchone( )[2:] damage, attack, save = None, None, None # gets the spells' tags tags = [] Db.cursor.execute("SELECT genericTagId FROM SpellTag WHERE spellId=" + str(Db.get_id(spell_name, "Spell"))) for tag in Db.cursor.fetchall(): Db.cursor.execute( "SELECT genericTagName FROM GenericTag where genericTagId=" + str(tag[0])) tags.append(Db.cursor.fetchone()[0]) # separates atOrSave into it's appropriate variable if "Save" in atOrSave: save = atOrSave elif atOrSave is not None: attack = atOrSave return spell_name, lvl, castingTime, duration, sRange, components, school,\ tags, desc, damage, attack, save, area, chr_level
def begin(): """ Begins the use of the data extraction. """ Db.cursor.execute("SELECT * FROM Magic WHERE subclassId=" + str(Db.get_id("Nature Domain", "Subclass"))) print(Db.cursor.fetchall()) Db.cursor.execute( "SELECT classOptionsId, amntToChoose FROM ClassOptions WHERE subclassId=" + str(Db.get_id("Nature Domain", "Subclass"))) res = Db.cursor.fetchall() print(res) call = "SELECT proficiencyName FROM Proficiency WHERE proficiencyId IN " \ "(SELECT proficiencyId FROM ClassProficiency WHERE classOptionsId IN (" for r in res: call += str(r[0]) + ", " call = call[:-2] + "))" Db.cursor.execute(call) print(Db.cursor.fetchall())
def create_equipment(class_name): """ Creates and selects all equipment options for one class. :param class_name: the name of the class to get the equipment for :type class_name: str :return: a list of equipment objects """ optionsData = DataExtractor.equipment_connections(Db.get_id(class_name, "Class")) equipment = [] for option in optionsData: equipment += create_equipment_option(option, class_name)[1] return equipment
def collect_race_option_data(race_name, chr_lvl, subrace_id=-1): """ Extracts the race options data from the race database. :param race_name: the name of the race selected :type race_name: str :param chr_lvl: the level of the character being built :type chr_lvl: int :param subrace_id: the id of the subrace, if appropriate :type subrace_id: int, optional :return: three arrays holding the traits, proficiencies and languages """ if subrace_id == -1: subraceStr = " IS NULL" else: subraceStr = "=" + str(subrace_id) spells, proficiencies, languages = [], [], [] modUsed = "" Db.cursor.execute("SELECT raceOptionsId FROM RaceOptions WHERE raceId=" + str(Db.get_id(race_name, "Race")) + " AND subraceId" + subraceStr) ids = Db.cursor.fetchall() Db.cursor.execute("SELECT raceOptionsId FROM RaceOptions WHERE raceId=" + str(Db.get_id(race_name, "Race"))) for nextId in ids: metadata, options = DataExtractor.race_options_connections(nextId[0], subrace_id) if len(options) > 0: if metadata[1] == "proficiencies": proficiencies += make_choice(metadata[0], options, race_name) elif metadata[1] == "spells": spellNames = [] for spell in options: spellNames.append(spell[0]) spells = make_choice(metadata[0], spellNames, race_name) spells = build_race_spells(options, spells, chr_lvl) modUsed = options[0][3] else: languages += make_choice(metadata[0], options, race_name) return spells, modUsed, proficiencies, languages
def get_names_from_connector(start_table, end_table, input_id=-1, input_name=""): """ Given two tables and an id or name, it gets all possible row names from the second table that are intermediately connected to the first row. This does not operate for EquipmentOption as the intermediary table has useful information. :param start_table: the name of the table the connection travels from :type start_table: str :param end_table: the name of the table the connection travels to :type end_table: str :param input_id: the id of the row to travel from in the start table. This or input_name must not be default :type input_id: int, optional :param input_name: the name of the row to travel from in the start table. This or input_id must not be default :type input_name: str, optional :return: an array of the names assigned within the rows pulled from the end table """ # retrieves the id, if not provided if input_id == -1: input_id = Db.get_id(input_name, start_table) # determines the name of the intermediary table if start_table == "Magic": connTable = "ClassSpell" elif start_table == "Tag": connTable = "ArchTagConn" else: connTable = start_table.replace("Options", "") + end_table.replace( "Generic", "") # determines the required variables for the sql call endId = Db.table_to_id(end_table) nameColumn = endId.replace("Id", "Name") startId = Db.table_to_id(start_table) # joins the tables to extract the names command = f"SELECT {nameColumn} FROM {connTable} INNER JOIN {start_table} ON {start_table}.{startId} = " \ f"{connTable}.{startId} INNER JOIN {end_table} ON {end_table}.{endId} = {connTable}.{endId} " \ f"WHERE {start_table}.{startId}={str(input_id)}" Db.cursor.execute(command) # converts the output into a cleaner, 1D array initOutput = Db.cursor.fetchall() output = [] for item in initOutput: output.append(item[0]) return output
def get_tag_fitness(self, tag): """ Gets the unweighted fitness value of a tag relative to it's character. :param tag: the tag applying to the character :type tag: str :return: the integer weight """ fitness = 0 tagId = Db.get_id(tag, "Tag") fitness += self.proficiencies_total(tagId) Db.cursor.execute( f"SELECT genericTagName FROM GenericTag WHERE genericTagId IN " f"(SELECT genericTagId FROM ArchTagConn WHERE tagId={Db.get_id(tag, 'Tag')})" ) for genericTag in list(itertools.chain(*Db.cursor.fetchall())): fitness += self.generic_tags[genericTag] return fitness
def create_class_magic(class_name, class_lvl, subclass_name=""): """ Retrieves the magic associated with a class at a given level. :param class_name: the name of the class to create the magic for :type class_name: str :param class_lvl: the level to get the classes magic at :type class_lvl: int :param subclass_name: the name of the subclass to receive magic for :type subclass_name: str, optional :return: the data required to choose all of the classes magic, in 2 arrays & a dictionary, with the format: [no of cantrips, no of spells, spells prepared boolean, amount of spells known calculation], [spell names], (spellslot level: num of spellslot) dictionary """ if subclass_name != "": subclassCall = "=" + str(Db.get_id(subclass_name, 'Subclass')) else: subclassCall = " IS NULL" Db.cursor.execute( f"SELECT magicId, spellsPrepared, knownCalc, amntKnown, cantripsKnown FROM Magic WHERE " f"classId={Db.get_id(class_name, 'Class')} AND lvl={class_lvl} AND subclassId{subclassCall}" ) magicId, spellsPrepared, knownCalc, amntKnown, cantripsKnown = Db.cursor.fetchone( ) if subclass_name != "": spellsPrepared = False Db.cursor.execute( f"SELECT spellslotLvl, amount FROM ClassSpellslot WHERE magicId={str(magicId)}" ) spellslots = dict() for pair in Db.cursor.fetchall(): spellslots.update({pair[0]: pair[1]}) spells = get_names_from_connector("Magic", "Spell", magicId) return [cantripsKnown, amntKnown, spellsPrepared, knownCalc], spells, spellslots
def change_core_filter(filters, element, modifier, character, subset=None): """ Modifies a core filter, trying to keep all other filters as similar as possible in the process. A core filter refers to Race, Class and Background - the three top-end elements. Modifies one filter in a dictionary of chromosome filters. :param filters: the dictionary of filters used to make a full character :type filters: dict :param element: the element to modify within this, listed as it's dictionary key :type element: str :param modifier: the modifier number produced, used to help randomise adjustment :type modifier: int :param character: the character that is being adjusted :type character: class: `CharacterElements.Character` :param subset: a subset of options the new change must be from :type subset: list, optional :return: newly modified filters """ # if no subset is provided, set it to all potential options that aren't the current option if subset is None: sqlCall = f"SELECT {element.lower()}Name FROM {element} " \ f"WHERE {element.lower()}Name != '{filters[element]}'" if "Sub" in element: parentTable = element.replace('Sub', '') Db.cursor.execute(f"SELECT {parentTable}Id FROM {element} " f"WHERE {element.lower()}Name = '{filters[element]}'") sqlCall = sqlCall + f" AND {parentTable}Id=" + str(Db.cursor.fetchone()[0]) Db.cursor.execute(sqlCall) subset = list(itertools.chain(*Db.cursor.fetchall())) # get the value at the modifier position if there is one, or choose a random one otherwise # avoids overwriting const filters if len(set(filters[element]) - set(ChromosomeController.constFilters.get(element, []))) == 0: return filters modifier = check_modifier(modifier, subset) choice = subset[modifier] while choice in ChromosomeController.constFilters.get(element, []): modifier = check_modifier(100, subset) # this is set to 100 to guarantee a new random value choice = subset[modifier] if "Sub" not in element: subId = "IS NULL" else: subId = "= " + str(Db.get_id(choice, element)) # gets the data the current core filter provides # subrace is treat the same as race as no race with subraces has a choice outside the subrace oldData = dict() if element in ["Race", "Subrace"]: oldData = character.race.get_data() choice = character.race.raceName elif element == "Subclass": unmodifiedData = character.chrClass.subclassItems oldData["language"] = unmodifiedData.get("languages", []) oldData["proficiency"] = unmodifiedData.get("proficiencies", []) oldData["spell"] = unmodifiedData.get("spells", []) choice = character.chrClass.className elif element == "Class": spells = [] if character.chrClass.magic is not None: spells += character.chrClass.magic.knownSpells + character.chrClass.magic.preparedSpellOptions oldData.update({"language": character.chrClass.languages, "proficiency": character.chrClass.proficiencies, "spell": spells}) element = element.replace("Sub", "").capitalize() newData = dict() Db.cursor.execute(f"SELECT {element.lower()}OptionsId, amntToChoose FROM {element}Options " f"WHERE {element.lower()}Id={Db.get_id(choice, element)} AND sub{element.lower()}Id {subId}") results = Db.cursor.fetchall() for (opId, amnt) in results: for elem in ["language", "proficiency", "spell"]: # find any data linked to the RaceOptions/ClassOptions if elem == "spell" and element == "Class": Db.cursor.execute(f"SELECT spellName FROM Spell WHERE spellId IN (" f"SELECT spellId FROM ClassSpell WHERE magicId IN (" f"SELECT magicId FROM Magic WHERE classId={Db.get_id(choice, 'Class')} " f"AND subclassId {subId}))") else: Db.cursor.execute(f"SELECT {elem}Name FROM {elem.capitalize()} WHERE {elem}Id IN (" f"SELECT {elem}Id FROM {element}{elem.capitalize()} " f"WHERE {element.lower()}OptionsId={opId})") items = list(set(itertools.chain(*Db.cursor.fetchall()))) # if this is the data the results are for, add all items if there's no choice, or all items that the old # one had if there is - all extra choices will be made during construction if len(items) > 0: if amnt is not None and len(items) > amnt: chosenItems = [item for item in items if item in oldData.get(elem, [])] if len(chosenItems) > amnt: chosenItems = chosenItems[:amnt] newData.setdefault(elem, []).extend(chosenItems) else: newData.setdefault(elem, []).extend(items) break # updates every data piece in the filters to match for elem in ["language", "proficiency", "spell"]: filters[elem.capitalize()] = [x for x in filters.get(elem.capitalize(), []) if x not in oldData.get(elem, [])] \ + newData.get(elem, []) filters[element] = choice return filters
def create_race(race_name, chr_lvl, subrace_id=-1, is_subrace=False): """ Creates and returns a race object, given the name and level of the race to use. :param race_name: the name of the race to create :type race_name: str :param chr_lvl: the level of the character being created :type chr_lvl: int :param subrace_id: the id of the subrace being built :type subrace_id: int, optional :param is_subrace: whether the current pass is creating the subrace :type is_subrace: bool, optional :return: a race object of the inputted race """ # recursively builds a subrace from the data if subrace_id > -1 and is_subrace is False: subrace = create_race(race_name, chr_lvl, subrace_id, True) subrace_id = -1 else: subrace = None # get basic variable data if subrace_id == -1: subraceStr = " IS NULL" else: subraceStr = "=" + str(subrace_id) raceId = Db.get_id(race_name, "Race") # get basic racial data Db.cursor.execute("SELECT size FROM Race WHERE raceId=" + str(raceId)) size = Db.cursor.fetchone()[0] if subrace_id == -1: Db.cursor.execute("SELECT speed, darkvision, resistance FROM Race WHERE raceId=" + str(raceId)) else: Db.cursor.execute("SELECT speed, darkvision, resistance FROM Subrace WHERE raceId=" + str(raceId) + " AND subraceId" + subraceStr) speed, darkvision, resistance = Db.cursor.fetchone() # gets the trait data traits = [] traitNames = DataExtractor.get_names_from_connector("Race", "Trait", raceId) for trait in traitNames: Db.cursor.execute("SELECT subraceId FROM RaceTrait WHERE traitId=" + str(Db.get_id(trait, "Trait"))) subId = Db.cursor.fetchone()[0] if (subId is None and subrace_id == -1) or (subId == subrace_id): Db.cursor.execute("SELECT traitDescription FROM Trait WHERE traitName='" + trait.replace("'", "''") + "'") traits.append((trait, Db.cursor.fetchone()[0])) traits = choose_trait_option(traits, race_name) # extracts data from options spells, modUsed, proficiencies, languages = collect_race_option_data(race_name, chr_lvl, subrace_id) # gets the ability score data Db.cursor.execute("SELECT abilityScore, scoreIncrease FROM RaceAbilityScore WHERE raceId=" + str(raceId) + " AND subraceId" + subraceStr) abilityScores = dict() for scoreName, scoreIncrease in Db.cursor.fetchall(): abilityScores.update({scoreName: scoreIncrease}) # converts data into the required formats darkvision = darkvision == 1 if len(spells) == 0: spells = None modUsed = None if is_subrace: Db.cursor.execute("SELECT subraceName FROM Subrace WHERE subraceId=" + str(subrace_id)) race_name = Db.cursor.fetchone()[0] return Race.Race(race_name, languages, proficiencies, abilityScores, traits, speed, size, darkvision, spells, modUsed, resistance, subrace)