예제 #1
0
 def dev_btn_pressed(self):
     """
     Sets up the threading necessary to access the dev menu.
     """
     QApplication.quit()
     self.window.close()
     CoreDatabase.complete_setup()
     self.thread = threading.Thread(target=self.dev_btn)
     self.thread.start()
예제 #2
0
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 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
예제 #4
0
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)
예제 #5
0
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
예제 #6
0
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())
예제 #9
0
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
예제 #10
0
 def dev_btn():
     """
     Calls the systems reaction to the Dev Menu button being pressed.
     """
     CoreDatabase.connection = sql.connect(CoreDatabase.dir_path +
                                           "/Resources/ChrDatabase.db")
     CoreDatabase.cursor = CoreDatabase.connection.cursor()
     print("Enter which service you'd like to use:\n"
           "1. Database Setup\n"
           "2. View Tables\n"
           "3. General testing\n"
           "4. Exit")
     menu = CoreDatabase.int_input("> ")
     if menu == 1:
         DatabaseSetup.begin()
     elif menu == 2:
         CoreDatabase.view_tables()
     elif menu == 3:
         # replace me with whatever needs testing!
         DataExtractor.begin()
     else:
         SystemExit(0)
     CoreDatabase.complete_setup()
예제 #11
0
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
예제 #12
0
def manual_output(num_of_choices, choices):
    """
    Gets a manual output for a choice.
    :param num_of_choices: the amount of choices to make
    :type num_of_choices: int
    :param choices: the choices to select from
    :type choices: list
    :return: a list of the choices selected
    """
    output = []
    choiceCount = []
    choiceDict = dict()

    # converts any list choices into a single string, directing a dictionary to the list of objects
    for x in range(0, len(choices)):
        if type(choices[x]) is list:
            fullStr = ""
            for y in list({i: choices[x].count(i) for i in choices[x]}.items()):
                fullStr += f"({str(y[1])}x) {str(y[0])}\n"
            choiceDict.update({fullStr: choices[x]})
        else:
            choiceCount.append(choices[x])

    # puts all single-object choices into the dictionary,
    # linking their string to the appropriate amount of the object
    choiceCount = list({i: choiceCount.count(i) for i in choiceCount}.items())
    for z in range(0, len(choiceCount)):
        objects = []
        for s in range(0, choiceCount[z][1]):
            objects.append(choiceCount[z][0])
        choiceDict.update({f"({choiceCount[z][1]}x) {str(choiceCount[z][0])}": objects})

    # lists their options
    while len(output) < num_of_choices:
        print("Choose one from: ")
        counter = 0
        for key in list(choiceDict.keys()):
            counter += 1
            print(str(counter) + ".\n" + key)

        nextAddition = Db.int_input(">") - 1
        if nextAddition < len(choiceDict.keys()):
            output.append(list(choiceDict.values())[nextAddition])
            choiceDict.pop(list(choiceDict.keys())[nextAddition])

    return output
예제 #13
0
    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
예제 #15
0
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
예제 #16
0
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)