Exemplo n.º 1
0
    def parse(self, dbr, dbr_file, result):
        # Skip formula without artifacts
        if self.ARTIFACT not in dbr:
            raise StopIteration

        artifact = DBRParser.parse(dbr[self.ARTIFACT])

        # Update the result with the artifact:
        result['tag'] = artifact['tag']
        result['name'] = artifact['name']
        result['classification'] = artifact['classification']

        if self.BITMAP in dbr:
            result['bitmap'] = dbr[self.BITMAP]

        # Grab the reagents (ingredients):
        for reagent_key in ['reagent1', 'reagent2', 'reagent3']:
            # For some reason reagent DBRs are of type array, so grab [0]:
            reagent = DBRParser.parse(dbr[reagent_key + 'BaseName'][0])

            # Add the reagent (relic, scroll or artifact)
            result[reagent_key] = reagent['tag']

        # Add the potential completion bonuses
        bonuses = DBRParser.parse(dbr['artifactBonusTableName'])
        result['bonus'] = bonuses.get('table', [])

        # Last but not least, pop the 'properties' from this result, since
        # formula don't have the properties themselves, but their respective
        # artifacts do.
        result.pop('properties')
Exemplo n.º 2
0
    def parse(self, dbr, dbr_file, result):
        # Skip formula without artifacts
        if self.ARTIFACT not in dbr:
            raise StopIteration

        artifact = DBRParser.parse(dbr[self.ARTIFACT])

        # Update the result with the artifact:
        result['tag'] = artifact['tag']
        result['name'] = artifact['name']
        result['classification'] = artifact['classification']

        if self.BITMAP in dbr:
            result['bitmap'] = dbr[self.BITMAP]

        # Grab the reagents (ingredients):
        for reagent_key in ['reagent1', 'reagent2', 'reagent3']:
            # For some reason reagent DBRs are of type array, so grab [0]:
            reagent = DBRParser.parse(dbr[reagent_key + 'BaseName'][0])

            # Add the reagent (relic, scroll or artifact)
            result[reagent_key] = reagent['tag']

        # Add the potential completion bonuses
        bonuses = DBRParser.parse(dbr['artifactBonusTableName'])
        result['bonus'] = bonuses.get('table', [])

        # Last but not least, pop the 'properties' from this result, since
        # formula don't have the properties themselves, but their respective
        # artifacts do.
        result.pop('properties')
Exemplo n.º 3
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse a possible skill bonus for a pet.

        These bonuses are things like:
            - 15 Vitality Damage
            - +5% Vitality Damage

        """
        if self.NAME in dbr:
            # Parse the pet bonus and add it:
            pet_bonus = DBRParser.parse(dbr[self.NAME])

            properties = (
                # If a tiered property set is found, return the first entry
                pet_bonus['properties'][0]
                if isinstance(pet_bonus['properties'], list)
                # Otherwise just return the list
                else pet_bonus['properties'])

            # Don't allow nested petBonus properties
            # One example of this is the Spear of Nemetona
            if 'petBonus' in properties:
                properties.pop('petBonus')

            # Set the properties of the bonus as the value for this field:
            result['properties']['petBonus'] = properties
Exemplo n.º 4
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse a possible skill bonus for a pet.

        These bonuses are things like:
            - 15 Vitality Damage
            - +5% Vitality Damage

        """
        if self.NAME in dbr:
            # Parse the pet bonus and add it:
            pet_bonus = DBRParser.parse(dbr[self.NAME])

            properties = (
                # If a tiered property set is found, return the first entry
                pet_bonus['properties'][0] if isinstance(
                    pet_bonus['properties'], list)
                # Otherwise just return the list
                else pet_bonus['properties'])

            # Don't allow nested petBonus properties
            # One example of this is the Spear of Nemetona
            if 'petBonus' in properties:
                properties.pop('petBonus')

            # Set the properties of the bonus as the value for this field:
            result['properties']['petBonus'] = properties
Exemplo n.º 5
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse skill augments, mastery augments, and item granted skills.

        """
        self._parse_skill_grant(dbr, result)

        # Parse skills that are augmented:
        for name, level in self.SKILL_AUGMENTS.items():
            # Skip skills without both the name and level set:
            if name not in dbr or level not in dbr:
                continue

            skill = DBRParser.parse(dbr[name])
            # Store the skill, which will ensure a unique tag:
            skill_tag = storage.store_skill(skill)
            level = dbr[level]

            # Skill format is either ItemSkillIncrement or ItemMasteryIncrement
            skill_format = (self.TXT_SKILL_INC
                            if 'Mastery' not in skill['name']
                            else self.TXT_MASTERY_INC)

            result['properties'][name] = {
                'tag': skill_tag,
                'name': texts.get(skill_format).format(level, skill['name']),
            }

        # Parse augment to all skills:
        if self.AUGMENT_ALL in dbr:
            level = dbr[self.AUGMENT_ALL]
            result['properties'][self.AUGMENT_ALL] = (
                texts.get(self.TXT_ALL_INC).format(level))
Exemplo n.º 6
0
def parse_sets():
    """
    Parse the Titan Quest equipment sets.

    The equipment sets are indexed and their properties are the set
    bonuses you receive for wearing multiple set pieces at once.

    """
    timer = time.clock()

    files = []
    for resource in resources.SETS:
        set_files = resources.DB / resource
        files.extend(glob.glob(str(set_files), recursive=True))

    sets = {}
    for dbr in files:
        parsed = parse(dbr)

        try:
            # Add the set by its tag to the dictionary of sets:
            sets[parsed['tag']] = parsed
        except KeyError:
            # Skip sets with no tag:
            continue

    # Log the timer:
    logging.info(f'Parsed sets in {time.clock() - timer} seconds.')

    return sets
Exemplo n.º 7
0
def parse_sets():
    """
    Parse the Titan Quest equipment sets.

    The equipment sets are indexed and their properties are the set
    bonuses you receive for wearing multiple set pieces at once.

    """
    timer = time.clock()

    files = []
    for resource in resources.SETS:
        set_files = resources.DB / resource
        files.extend(glob.glob(str(set_files), recursive=True))

    sets = {}
    for dbr in files:
        parsed = parse(dbr)

        try:
            # Add the set by its tag to the dictionary of sets:
            sets[parsed['tag']] = parsed
        except KeyError:
            # Skip sets with no tag:
            continue

    # Log the timer:
    logging.info(f'Parsed sets in {time.clock() - timer} seconds.')

    return sets
Exemplo n.º 8
0
    def _parse_skill_grant(self, dbr, result):
        """
        Parse a granted skill.

        """
        # Skip files without both granted skill name and level:
        if self.SKILL_NAME not in dbr or self.SKILL_LEVEL not in dbr:
            return

        level = dbr[self.SKILL_LEVEL]
        skill = DBRParser.parse(dbr[self.SKILL_NAME])

        if 'name' not in skill:
            return

        # Store the skill, which will ensure a unique tag:
        skill_tag = storage.store_skill(skill)

        # Now add the granted skill to the item:
        result['properties'][self.SKILL_NAME] = {
            'tag': skill_tag,
            'level': level,
        }

        if self.CONTROLLER not in dbr:
            return

        # Grab the auto controller to see if this skill is activated:
        controller = DBRParser.read(dbr[self.CONTROLLER])

        # The property 'triggerType' can be converted to a useful text suffix
        # like 'Activated on attack':
        result['properties'][self.SKILL_NAME].update(
            {'trigger': texts.get(self.TRIGGERS[controller['triggerType']])})
Exemplo n.º 9
0
    def parse(self, dbr, dbr_file, result):
        if 'level' in result['references']:
            # This camelCased variable is required for the level equations.
            # Grab the level from the references which has been passed:
            averagePlayerLevel = result['references']['level']  # noqa
            parentLevel = result['references']['level']  # noqa

        # Calculate the minimum and maximum levels:
        try:
            min_level = numexpr.evaluate(dbr['minItemLevelEquation']).item()
            max_level = numexpr.evaluate(dbr['maxItemLevelEquation']).item()
            target_level = numexpr.evaluate(dbr['targetLevelEquation']).item()
        except KeyError:
            # Log the missing variable:
            logging.info(f'Missing parentLevel in {dbr_file}')
            return

        # Grab the slope and defaultWeight, to use for adjusting values later:
        slope = dbr['bellSlope']
        weight = dbr['defaultWeight']

        # Store the drop and their adjusted weights in this dictionary:
        drops = {}

        for index, loot_file in enumerate(dbr.get('itemNames', [])):
            # Grab the item and its chance
            try:
                item = DBRParser.parse(loot_file)
            except InvalidItemError as e:
                logging.debug(
                    f'Invalid loot file {loot_file} in {dbr_file}. {e}')
                continue

            if 'tag' not in item:
                logging.debug(f'No tag for {loot_file} in {dbr_file}')
                continue

            level = item['itemLevel']

            # Skip all items outside the range
            if level is None or level < min_level or level > max_level:
                continue

            # Next compare the item's level to the target level
            target = int(level - target_level)

            # Grab the adjustment from the slope (or the last one)
            adjustment = slope[target] if len(slope) > target else slope[-1]

            # The adjusted weight is the default multiplied by the adjustment:
            drops[item['tag']] = weight * adjustment

        # The sum of all weights can now be calculated
        summed = sum(v for v in drops.values())

        # Store the chance of this item by its tag:
        result['loot_table'] = {
            tag: float('{0:.5f}'.format(item_weight / summed))
            for tag, item_weight in drops.items()
        }
Exemplo n.º 10
0
    def parse(self, dbr, dbr_file, result):
        items = {}

        # Add up all the loot weights:
        summed = sum(v for k, v in dbr.items()
                     if k.startswith('lootWeight'))

        # Run through all the loot chances and parse them:
        for i in range(1, 31):
            weight = dbr.get(f'lootWeight{i}', 0)

            # Skip items with no chance:
            if not weight:
                continue

            try:
                # Grab the item and its chance
                item = DBRParser.parse(dbr[f'lootName{i}'])
                # Store the chance of this item by its tag:
                items[item['tag']] = float('{0:.5f}'.format(weight / summed))
            except KeyError:
                # Skip items that have no tag:
                continue

        result['loot_table'] = items
Exemplo n.º 11
0
Arquivo: main.py Projeto: tnm-l/tqdb
def parse_sets():
    """
    Parse the Titan Quest equipment sets.

    The equipment sets are indexed and their properties are the set
    bonuses you receive for wearing multiple set pieces at once.

    """
    start_time = time.time()

    files = []
    for resource in resources.SETS:
        set_files = resources.DB / resource
        files.extend(glob.glob(str(set_files), recursive=True))

    sets = {}
    for dbr in files:
        try:
            parsed = parse(dbr)
        except InvalidItemError as e:
            exception_messages = exception_messages_with_causes(e)
            logging.debug(f"Ignoring item in {dbr}. {exception_messages}")
            continue

        try:
            # Add the set by its tag to the dictionary of sets:
            sets[parsed['tag']] = parsed
        except KeyError:
            # Skip sets with no tag:
            continue

    # Log the timer:
    logging.info(f'Parsed sets in {time.time() - start_time:.2f} seconds.')

    return sets
Exemplo n.º 12
0
    def _parse_skill_grant(self, dbr, result):
        """
        Parse a granted skill.

        """
        # Skip files without both granted skill name and level:
        if self.SKILL_NAME not in dbr or self.SKILL_LEVEL not in dbr:
            return

        level = dbr[self.SKILL_LEVEL]
        skill = DBRParser.parse(dbr[self.SKILL_NAME])

        if 'name' not in skill:
            return

        # Store the skill, which will ensure a unique tag:
        skill_tag = storage.store_skill(skill)

        # Now add the granted skill to the item:
        result['properties'][self.SKILL_NAME] = {
            'tag': skill_tag,
            'level': level,
        }

        if self.CONTROLLER not in dbr:
            return

        # Grab the auto controller to see if this skill is activated:
        controller = DBRParser.read(dbr[self.CONTROLLER])

        # The property 'triggerType' can be converted to a useful text suffix
        # like 'Activated on attack':
        result['properties'][self.SKILL_NAME].update({
            'trigger': texts.get(self.TRIGGERS[controller['triggerType']])
        })
Exemplo n.º 13
0
def parse_equipment():
    """
    Parse all wearable Titan Quest equipment.

    The wearable equipment is indexed and sorted by equipment type. These
    categories are defined by the Class property of each piece of equipment
    which is mapped to the 'category' key in the parsed result.

    """
    timer = time.clock()

    files = []
    for resource in resources.EQUIPMENT:
        equipment_files = resources.DB / resource

        # Exclude all files in 'old' and 'default'
        files.extend([
            equipment_file
            for equipment_file
            in glob.glob(str(equipment_files), recursive=True)
            if not (
                '\\old' in equipment_file or
                '\\default' in equipment_file or
                # Rhodian and Electrum sling don't drop:
                '\\1hranged\\u_e_02.dbr' in equipment_file or
                '\\1hranged\\u_n_05.dbr' in equipment_file
            )
        ])

    items = {}
    for dbr in files:
        parsed = parse(dbr)
        try:
            # Organize the equipment based on the category
            category = parsed.pop('category')

            # Skip items without rarities
            if 'classification' not in parsed:
                continue

            # Save the bitmap and remove the bitmap key
            images.save_bitmap(parsed, category, 'output/graphics/')
        except KeyError:
            # Skip equipment that couldn't be parsed:
            continue

        # Pop off the properties key off any item without properties:
        if 'properties' in parsed and not parsed['properties']:
            parsed.pop('properties')

        # Now save the parsed item in the category:
        if category and category in items:
            items[category].append(parsed)
        elif category:
            items[category] = [parsed]

    # Log the timer:
    logging.info(f'Parsed equipment in {time.clock() - timer} seconds.')

    return items
Exemplo n.º 14
0
    def parse(self, dbr, dbr_file, result):
        items = {}

        # Add up all the loot weights:
        summed = sum(v for k, v in dbr.items()
                     if k.startswith('lootWeight'))

        # Run through all the loot chances and parse them:
        for i in range(1, 31):
            weight = dbr.get(f'lootWeight{i}', 0)

            # Skip items with no chance:
            if not weight:
                continue

            try:
                # Grab the item and its chance
                item = DBRParser.parse(dbr[f'lootName{i}'])
                # Store the chance of this item by its tag:
                items[item['tag']] = float('{0:.5f}'.format(weight / summed))
            except KeyError:
                # Skip items that have no tag:
                continue

        result['loot_table'] = items
Exemplo n.º 15
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the referenced pet skill, and pass that back as this result.

        """
        if self.PET_SKILL in dbr:
            # Now set our result as the result of the pet skill being parsed:
            result.update(DBRParser.parse(dbr[self.PET_SKILL]))
Exemplo n.º 16
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the referenced buff skill, and pass that back as this result.

        """
        if self.BUFF in dbr:
            # Now set our result as the result of the buff being parsed:
            result.update(DBRParser.parse(dbr[self.BUFF]))
Exemplo n.º 17
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the referenced buff skill, and pass that back as this result.

        """
        if self.BUFF in dbr:
            # Now set our result as the result of the buff being parsed:
            result.update(DBRParser.parse(dbr[self.BUFF]))
Exemplo n.º 18
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the referenced pet skill, and pass that back as this result.

        """
        if self.PET_SKILL in dbr:
            # Now set our result as the result of the pet skill being parsed:
            result.update(DBRParser.parse(dbr[self.PET_SKILL]))
Exemplo n.º 19
0
def parse_equipment():
    """
    Parse all wearable Titan Quest equipment.

    The wearable equipment is indexed and sorted by equipment type. These
    categories are defined by the Class property of each piece of equipment
    which is mapped to the 'category' key in the parsed result.

    """
    timer = time.clock()

    files = []
    for resource in resources.EQUIPMENT:
        equipment_files = resources.DB / resource

        # Exclude all files in 'old' and 'default'
        files.extend([
            equipment_file
            for equipment_file in glob.glob(str(equipment_files),
                                            recursive=True)
            if not (
                '\\old' in equipment_file or '\\default' in equipment_file or
                # Rhodian and Electrum sling don't drop:
                '\\1hranged\\u_e_02.dbr' in equipment_file
                or '\\1hranged\\u_n_05.dbr' in equipment_file)
        ])

    items = {}
    for dbr in files:
        parsed = parse(dbr)
        try:
            # Organize the equipment based on the category
            category = parsed.pop('category')

            # Skip items without rarities
            if 'classification' not in parsed:
                continue

            # Save the bitmap and remove the bitmap key
            images.save_bitmap(parsed, category, 'output/graphics/')
        except KeyError:
            # Skip equipment that couldn't be parsed:
            continue

        # Pop off the properties key off any item without properties:
        if 'properties' in parsed and not parsed['properties']:
            parsed.pop('properties')

        # Now save the parsed item in the category:
        if category and category in items:
            items[category].append(parsed)
        elif category:
            items[category] = [parsed]

    # Log the timer:
    logging.info(f'Parsed equipment in {time.clock() - timer} seconds.')

    return items
Exemplo n.º 20
0
    def parse_difficulty(self, dbr, dbr_file):
        """
        Parse a difficulty of equipable loot.

        """
        result = {}

        # Parse all equipable loot:
        for equipment in self.EQUIPMENT_SLOTS:
            equip_key = f'chanceToEquip{equipment}'
            equip_chance = dbr.get(equip_key, 0)

            # Skip equipment that has 0 chance to be equiped
            if not equip_chance:
                continue

            equip_key = f'{equip_key}Item'

            # Iterate over all the possibilities and sum up the weights:
            summed = sum(v for k, v in dbr.items()
                         if k.startswith(equip_key))

            for i in range(1, 7):
                weight = dbr.get(f'{equip_key}{i}', 0)

                # Skip slots that have 0 chance
                if not weight:
                    continue

                chance = float('{0:.5f}'.format(weight / summed))

                # Grab the loot table holding the equipment list:
                loot_key = f'loot{equipment}Item{i}'
                loot_file = dbr.get(loot_key)
                if not loot_file or not loot_file.is_file():
                    logging.debug(f'No {loot_key} in {dbr_file}')
                    continue

                loot = DBRParser.parse(loot_file, {'level': dbr['charLevel']})
                if 'tag' in loot:
                    # Add a single item that was found:
                    self.add_items(
                        result,
                        {loot['tag']: chance * equip_chance})
                elif 'loot_table' in loot:
                    # Add all the items (and multiply their chances)
                    items = dict(
                        (k, v * chance * equip_chance)
                        for k, v in loot['loot_table'].items())
                    self.add_items(result, items)

        # Convert all item chances to 4 point precision:
        result = dict(
            (k, float('{0:.4f}'.format(v))) for k, v
            in result.items())

        return result
Exemplo n.º 21
0
    def parse(self, dbr, dbr_file, result):
        if 'level' in result['references']:
            # This camelCased variable is required for the level equations.
            # Grab the level from the references which has been passed:
            averagePlayerLevel = result['references']['level']  # noqa
            parentLevel = result['references']['level']  # noqa

        # Calculate the minimum and maximum levels:
        try:
            min_level = numexpr.evaluate(dbr['minItemLevelEquation']).item()
            max_level = numexpr.evaluate(dbr['maxItemLevelEquation']).item()
            target_level = numexpr.evaluate(dbr['targetLevelEquation']).item()
        except KeyError:
            # Log the missing variable:
            logging.info(f'Missing parentLevel in {dbr_file}')
            return

        # Grab the slope and defaultWeight, to use for adjusting values later:
        slope = dbr['bellSlope']
        weight = dbr['defaultWeight']

        # Store the drop and their adjusted weights in this dictionary:
        drops = {}

        for index, loot_file in enumerate(dbr.get('itemNames', [])):
            # Grab the item and its chance
            item = DBRParser.parse(loot_file)

            if 'tag' not in item:
                logging.debug(f'No tag for {loot_file} in {dbr_file}')
                continue

            level = item['itemLevel']

            # Skip all items outside the range
            if level < min_level or level > max_level:
                continue

            # Next compare the item's level to the target level
            target = int(level - target_level)

            # Grab the adjustment from the slope (or the last one)
            adjustment = slope[target] if len(slope) > target else slope[-1]

            # The adjusted weight is the default multiplied by the adjustment:
            drops[item['tag']] = weight * adjustment

        # The sum of all weights can now be calculated
        summed = sum(v for v in drops.values())

        # Store the chance of this item by its tag:
        result['loot_table'] = {
            tag: float('{0:.5f}'.format(item_weight / summed))
            for tag, item_weight in drops.items()
        }
Exemplo n.º 22
0
    def parse(self, dbr, dbr_file, result):
        tag = dbr.get(self.NAME, None)

        if not tag or texts.get(tag) == tag:
            logging.warning(f'No tag or name for set found in {dbr_file}.')
            raise StopIteration

        result.update({
            # Prepare the list of set items
            'items': [],
            'name': texts.get(tag),
            'tag': tag,
        })

        # Add the set members:
        for set_member_path in dbr['setMembers']:
            # Parse the set member:
            set_member = DBRParser.parse(set_member_path)

            # Add the tag to the items list:
            result['items'].append(set_member['tag'])

        # The number of set bonuses is equal to the number of set items minus 1
        bonus_number = len(result['items']) - 1

        # Because this parser has the lowest priority, all properties will
        # already have been parsed, so they can now be reconstructed to match
        # the set bonuses. Begin by initializing the properties for each set
        # bonus tier to an empty dict:
        properties = [{} for i in range(bonus_number)]

        # Insert the existing properties by adding them to the correct tier:
        for field, values in result['properties'].items():
            if not isinstance(values, list):
                properties[bonus_number - 1][field] = values

                # Don't parse any further
                continue

            # The starting tier is determined by the highest tier
            starting_index = bonus_number - len(values)

            # Now just iterate and add the properties to each tier:
            for index, value in enumerate(values):
                properties[starting_index + index][field] = value

        # Now set the tiered set bonuses:
        result['properties'] = properties

        # Pop off the first element of the properties, if it's empty:
        if len(result['properties']) > 1:
            if not result['properties'][0]:
                result['properties'].pop(0)
Exemplo n.º 23
0
    def parse(self, dbr, dbr_file, result):
        if 'tables' not in dbr:
            logging.debug(f'No table found in {dbr_file}')
            raise StopIteration

        # Parse the references 'tables' file and set the result:
        loot = DBRParser.parse(
            dbr['tables'][0],
            # Always pass along any references that were set:
            result['references'],
        )
        result['loot_table'] = loot['loot_table']
Exemplo n.º 24
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the skill that spawns a pet.

        """
        # Only set time to live it's set (otherwise it's infinite)
        ttl_list = dbr.get(self.TTL, None)

        # Parse all the summons and set them as a list:
        result['summons'] = []
        for index, spawn_file in enumerate(dbr['spawnObjects']):
            spawn = DBRParser.parse(spawn_file)

            # Keep track of the original properties this summon had:
            original_properties = {}
            if 'properties' in spawn:
                if isinstance(spawn['properties'], list):
                    original_properties = spawn['properties'][0].copy()
                else:
                    original_properties = spawn['properties'].copy()

            # We need the raw values from the spawn DBR for hp/mp
            spawn['properties'] = {}
            spawn_dbr = DBRParser.read(spawn_file)

            if 'characterLife' in spawn_dbr:
                hp_list = spawn_dbr['characterLife']
                hp = (hp_list[index]
                      if index < len(hp_list) else hp_list[len(hp_list) - 1])
                TQDBParser.insert_value('characterLife',
                                        texts.get('LifeText').format(hp),
                                        spawn)
            if 'characterMana' in spawn_dbr:
                mp_list = spawn_dbr['characterMana']
                mp = (mp_list[index]
                      if index < len(mp_list) else mp_list[len(mp_list) - 1])
                TQDBParser.insert_value('characterMana',
                                        texts.get('ManaText').format(mp),
                                        spawn)
            if ttl_list:
                ttl = (ttl_list[index] if index < len(ttl_list) else
                       ttl_list[len(ttl_list) - 1])
                TQDBParser.insert_value(self.TTL,
                                        texts.get(self.TTL).format(ttl), spawn)

            # Iterate over the original properties and add some whitelisted
            # properties to the final result:
            for key, value in original_properties.items():
                if key.startswith('character'):
                    continue
                spawn['properties'][key] = value

            result['summons'].append(spawn)
Exemplo n.º 25
0
    def parse(self, dbr, dbr_file, result):
        tag = dbr.get(self.NAME, None)

        if not tag or texts.get(tag) == tag:
            logging.warning(f'No tag or name for set found in {dbr_file}.')
            raise StopIteration

        result.update({
            # Prepare the list of set items
            'items': [],
            'name': texts.get(tag),
            'tag': tag,
        })

        # Add the set members:
        for set_member_path in dbr['setMembers']:
            # Parse the set member:
            set_member = DBRParser.parse(set_member_path)

            # Add the tag to the items list:
            result['items'].append(set_member['tag'])

        # The number of set bonuses is equal to the number of set items minus 1
        bonus_number = len(result['items']) - 1

        # Because this parser has the lowest priority, all properties will
        # already have been parsed, so they can now be reconstructed to match
        # the set bonuses. Begin by initializing the properties for each set
        # bonus tier to an empty dict:
        properties = [{} for i in range(bonus_number)]

        # Insert the existing properties by adding them to the correct tier:
        for field, values in result['properties'].items():
            if not isinstance(values, list):
                properties[bonus_number - 1][field] = values

                # Don't parse any further
                continue

            # The starting tier is determined by the highest tier
            starting_index = bonus_number - len(values)

            # Now just iterate and add the properties to each tier:
            for index, value in enumerate(values):
                properties[starting_index + index][field] = value

        # Now set the tiered set bonuses:
        result['properties'] = properties

        # Pop off the first element of the properties, if it's empty:
        if len(result['properties']) > 1:
            if not result['properties'][0]:
                result['properties'].pop(0)
Exemplo n.º 26
0
    def parse(self, dbr, dbr_file, result):
        if 'tables' not in dbr:
            logging.debug(f'No table found in {dbr_file}')
            raise StopIteration

        # Parse the references 'tables' file and set the result:
        loot = DBRParser.parse(
            dbr['tables'][0],
            # Always pass along any references that were set:
            result['references'],
        )
        result['loot_table'] = loot['loot_table']
Exemplo n.º 27
0
    def parse(self, dbr, dbr_file, result):
        items = {}

        # Add up all the loot weights:
        summed = sum(v for k, v in dbr.items() if k.startswith('lootWeight'))

        # Run through all the loot entries and parse them:
        for i in range(1, 31):
            weight = dbr.get(f'lootWeight{i}', 0)

            # Skip items with no chance:
            if not weight:
                continue

            chance = float('{0:.5f}'.format(weight / summed))

            try:
                # Try to parse the referenced loot file
                loot_file = dbr[f'lootName{i}']
            except KeyError:
                logging.debug(f'No lootName{i} not found in {dbr_file}.')
                continue

            # Parse the loot file
            try:
                loot = DBRParser.parse(
                    loot_file,
                    # Always pass along any references that were set:
                    result['references'],
                )
            except InvalidItemError as e:
                logging.debug(
                    f"Invalid lootName{i} in {loot_file} referenced by {dbr_file}."
                )
                continue

            # e.g. xpack2\quests\rewards\loottables\generic_rareweapon_n.dbr
            # The entry lootName15 has two entries separated by ';'
            if 'loot_table' not in loot:
                logging.debug(f'Invalid lootName{i} in {dbr_file}.')
                continue

            # Loot entries will be in 'table', add those:
            for k, v in loot['loot_table'].items():
                if k in items:
                    items[k] += (v * chance)
                else:
                    items[k] = (v * chance)

        # Add the parsed loot table
        result['loot_table'] = items
Exemplo n.º 28
0
    def parse(self, dbr, dbr_file, result):
        # Skip formula without artifacts
        if self.ARTIFACT not in dbr:
            raise InvalidItemError(f"Artifact {dbr_file} has no {self.ARTIFACT}.")

        artifact = DBRParser.parse(dbr[self.ARTIFACT])

        # Update the result with the artifact:
        result['tag'] = artifact['tag']
        result['name'] = artifact['name']
        result['classification'] = artifact['classification']

        if self.BITMAP in dbr:
            result['bitmap'] = dbr[self.BITMAP]

        # Grab the reagents (ingredients):
        for reagent_key in ['reagent1', 'reagent2', 'reagent3']:
            # For some reason reagent DBRs are of type array, so grab [0]:
            reagent = DBRParser.parse(dbr[reagent_key + 'BaseName'][0])

            # Add the reagent (relic, scroll or artifact)
            result[reagent_key] = reagent['tag']

        # Add the potential completion bonuses
        bonuses = {}
        try:
            bonuses = DBRParser.parse(dbr['artifactBonusTableName'])
        except InvalidItemError as e:
            logging.debug("Could not parse artifact completion bonus "
                          f"information for {result['name']} in {dbr_file}. "
                          f"{e}")

        result['bonus'] = bonuses.get('table', [])

        # Last but not least, pop the 'properties' from this result, since
        # formula don't have the properties themselves, but their respective
        # artifacts do.
        result.pop('properties')
Exemplo n.º 29
0
Arquivo: main.py Projeto: tnm-l/tqdb
def parse_creatures():
    """
    Parse all creatures (bosses and heroes) in Titan Quest.

    Parsing the bosses and heroes is mostly about parsing their loot tables
    to create an index of what they can drop. This index will work two ways,
    the first being a complete list of items that the monster can drop and the
    reverse being added to each individual item's loot table so it can be
    sorted.

    """
    start_time = time.time()

    files = []
    for resource in resources.CREATURES:
        boss_files = resources.DB / resource
        files.extend(glob.glob(str(boss_files), recursive=True))

    logging.info(f"Found {len(files)} creature files.")

    creatures = {}
    for dbr in files:
        try:
            logging.debug(f"Attempting to parse creature in {dbr}.")
            parsed = parse(dbr)
        except InvalidItemError as e:
            logging.debug(f"Ignoring creature in {dbr}. {e}")
            continue

        try:
            # Don't include common monsters
            # XXX - Should 'Champion' be added?
            # Should this be moved to MonsterParser to save work? The equipment
            # parser does that.
            if parsed['classification'] not in ['Quest', 'Hero', 'Boss']:
                continue

            # Store the monster by its tag:
            creatures[parsed['tag']] = parsed
        except KeyError:
            # Skip creatures without tags
            logging.debug(f"Ignoring creature in {dbr}. No classification "
                          "present.")
            continue

    # Log the timer:
    logging.info(
        f'Parsed creatures in {time.time() - start_time:.2f} seconds.')

    return creatures
Exemplo n.º 30
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse all the abilities a monster has.

        """
        # Initialize the abilities (to be indexed per level)
        abilities = []

        # Parse all the normal skills (17 max):
        for i in range(1, 18):
            nameTag = f'skillName{i}'
            levelTag = f'skillLevel{i}'

            # Skip unset skills or skills that are to be ignored:
            if (nameTag not in dbr or levelTag not in dbr
                    or str(dbr[nameTag]).lower() in self.IGNORE_SKILLS):
                continue

            try:
                skill = DBRParser.parse(dbr[nameTag])
            except InvalidItemError as e:
                logging.debug(
                    f"Skipping creature skill {nameTag} in {dbr_file} because it's invalid. {e}"
                )
                continue
            if not skill['properties']:
                continue

            # Store the skill, which will ensure a unique tag:
            skill_tag = storage.store_skill(skill)

            # Iterate over the difficulties:
            for difficulty in range(3):
                itr = TQDBParser.extract_values(dbr, 'skill', difficulty)

                # If a level is set to 0 for a difficulty, it won't be in the
                # extracted result, so use the KeyError safe 'get' method:
                level = itr.get(levelTag, 0)

                if not level:
                    continue

                if len(abilities) - 1 < difficulty:
                    # Create missing tiers:
                    abilities += ([{}] * (difficulty - len(abilities) + 1))

                abilities[difficulty][skill_tag] = level

        result['abilities'] = abilities
Exemplo n.º 31
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the scroll.

        """
        # The new Potion XP items are also considered "scrolls":
        if 'potion' in str(dbr_file):
            return result

        # Use the file name to determine the difficulty:
        file_name = os.path.basename(dbr_file).split('_')[0][1:]
        # Strip all but digits from the string, then cast to int:
        difficulty = self.DIFFICULTIES_LIST[int(
            ''.join(filter(lambda x: x.isdigit(), file_name))) - 1]

        result.update({
            'tag': dbr['description'],
            'name': texts.get(dbr['description']),
            'classification': texts.get(difficulty).strip(),
            'description': texts.get(dbr['itemText']),
        })

        # Greater scroll of svefnthorn is incorrectly referenced as its Divine
        # variant. Manual fix required for now:
        if '02_svefnthorn.dbr' in str(dbr_file):
            result['tag'] = 'x2tagScrollName06'
            result['name'] = texts.get('x2tagScrollName06')

        # Set the bitmap if it exists
        if 'bitmap' in dbr:
            result['bitmap'] = dbr['bitmap']

        # Grab the skill file:
        skill = {}
        try:
            skill = DBRParser.parse(dbr['skillName'])
        except InvalidItemError as e:
            logging.debug(f"Could not parse skill {dbr['skillName']} from "
                          f"scroll {result['name']}. {e}")

        # Add the first tier of properties if there are any:
        if 'properties' in skill and skill['properties']:
            result['properties'] = skill['properties'][0]

        # Add any summon (just the first one)
        if 'summons' in skill:
            result['summons'] = skill['summons'][0]
Exemplo n.º 32
0
    def parse(self, dbr, dbr_file, result):
        file_name = os.path.basename(dbr_file).split('_')
        difficulty = self.DIFFICULTIES_LIST[int(file_name[0][1:]) - 1]

        result.update({
            # The act it starts dropping in is also listed in the file name
            'act': file_name[1],
            # Bitmap has a different key name than items here.
            'bitmap': dbr.get('relicBitmap', None),
            # Difficulty classification is based on the file name
            'classification': texts.get(difficulty).strip(),
            # Ironically the itemText holds the actual description tag
            'description': texts.get(dbr['itemText']),
            # For relics the tag is in the Actor.tpl variable 'description'
            'name': texts.get(dbr['description']),
            'tag': dbr['description'],
        })

        # The possible completion bonuses are in bonusTableName:
        bonuses = {}
        try:
            bonuses = DBRParser.parse(dbr['bonusTableName'])
        except InvalidItemError as e:
            logging.debug("Could not parse relic completion bonus information "
                          f"for {result['name']} in {dbr_file}. {e}")

        result['bonus'] = bonuses.get('table', [])

        # Find how many pieces this relic has
        max_pieces = TQDBParser.highest_tier(
            result['properties'], result['properties'].keys())

        # Initialize a list of tiers
        properties = [{} for i in range(max_pieces)]

        # Setup properties as list to correspond to adding pieces of a relic:
        for key, values in result['properties'].items():
            if not isinstance(values, list):
                # This property is just repeated for all tiers:
                for i in range(max_pieces):
                    properties[i][key] = values
                continue

            for index, value in enumerate(values):
                properties[index][key] = value

        result['properties'] = properties
Exemplo n.º 33
0
    def parse(self, dbr, dbr_file, result):
        items = {}

        # Add up all the loot weights:
        summed = sum(v for k, v in dbr.items() if k.startswith('lootWeight'))

        # Run through all the loot entries and parse them:
        for i in range(1, 31):
            weight = dbr.get(f'lootWeight{i}', 0)

            # Skip items with no chance:
            if not weight:
                continue

            chance = float('{0:.5f}'.format(weight / summed))

            try:
                # Try to parse the referenced loot file
                loot_file = dbr[f'lootName{i}']
            except KeyError:
                logging.debug(f'No lootName{i} not found in {dbr_file}.')
                continue

            # Parse the loot file
            loot = DBRParser.parse(
                loot_file,
                # Always pass along any references that were set:
                result['references'],
            )

            # e.g. xpack2\quests\rewards\loottables\generic_rareweapon_n.dbr
            # The entry lootName15 has two entries separated by ';'
            if 'loot_table' not in loot:
                logging.debug(f'Invalid lootName{i} in {dbr_file}.')
                continue

            # Loot entries will be in 'table', add those:
            for k, v in loot['loot_table'].items():
                if k in items:
                    items[k] += (v * chance)
                else:
                    items[k] = (v * chance)

        # Add the parsed loot table
        result['loot_table'] = items
Exemplo n.º 34
0
    def parse(self, dbr, dbr_file, result):
        file_name = os.path.basename(dbr_file).split('_')
        difficulty = self.DIFFICULTIES_LIST[int(file_name[0][1:]) - 1]

        result.update({
            # The act it starts dropping in is also listed in the file name
            'act': file_name[1],
            # Bitmap has a different key name than items here.
            'bitmap': dbr.get('relicBitmap', None),
            # Difficulty classification is based on the file name
            'classification': texts.get(difficulty).strip(),
            # Ironically the itemText holds the actual description tag
            'description': texts.get(dbr['itemText']),
            # For relics the tag is in the Actor.tpl variable 'description'
            'name': texts.get(dbr['description']),
            'tag': dbr['description'],
        })

        # The possible completion bonuses are in bonusTableName:
        bonuses = DBRParser.parse(dbr['bonusTableName'])
        result['bonus'] = bonuses.get('table', [])

        # Find how many pieces this relic has
        max_pieces = TQDBParser.highest_tier(
            result['properties'], result['properties'].keys())

        # Initialize a list of tiers
        properties = [{} for i in range(max_pieces)]

        # Setup properties as list to correspond to adding pieces of a relic:
        for key, values in result['properties'].items():
            if not isinstance(values, list):
                # This property is just repeated for all tiers:
                for i in range(max_pieces):
                    properties[i][key] = values
                continue

            for index, value in enumerate(values):
                properties[index][key] = value

        result['properties'] = properties
Exemplo n.º 35
0
    def parse_loot(self, loot_key, spawn_number, dbr, result):
        chance = dbr.get(f'{loot_key}Chance', 0)

        # Skip slots that have 0 chance to drop
        if not chance:
            return

        # Add up all the loot weights:
        summed = sum(v for k, v in dbr.items()
                     if k.startswith(f'{loot_key}Weight'))

        # Run through all the loot possibilities and parse them:
        for i in range(1, 7):
            weight = dbr.get(f'{loot_key}Weight{i}', 0)

            # Skip items with no chance:
            if not weight:
                continue

            try:
                loot = DBRParser.parse(
                    dbr[f'{loot_key}Name{i}'][0],
                    # Always pass along any references that were set:
                    result['references'],
                )

                # Parse the table and multiply the values by the chance:
                loot_chance = float('{0:.5f}'.format(weight / summed))
                new_items = dict(
                    (k, v * loot_chance * chance * spawn_number)
                    for k, v in loot['loot_table'].items()
                )
            except KeyError:
                # Skip files that weren't found/parsed (no loot_table)
                continue

            for k, v in new_items.items():
                if k in self.items:
                    self.items[k] += v
                else:
                    self.items[k] = v
Exemplo n.º 36
0
    def parse_loot(self, loot_key, spawn_number, dbr, result):
        chance = dbr.get(f'{loot_key}Chance', 0)

        # Skip slots that have 0 chance to drop
        if not chance:
            return

        # Add up all the loot weights:
        summed = sum(v for k, v in dbr.items()
                     if k.startswith(f'{loot_key}Weight'))

        # Run through all the loot possibilities and parse them:
        for i in range(1, 7):
            weight = dbr.get(f'{loot_key}Weight{i}', 0)

            # Skip items with no chance:
            if not weight:
                continue

            try:
                loot = DBRParser.parse(
                    dbr[f'{loot_key}Name{i}'][0],
                    # Always pass along any references that were set:
                    result['references'],
                )

                # Parse the table and multiply the values by the chance:
                loot_chance = float('{0:.5f}'.format(weight / summed))
                new_items = dict(
                    (k, v * loot_chance * chance * spawn_number)
                    for k, v in loot['loot_table'].items()
                )
            except KeyError:
                # Skip files that weren't found/parsed (no loot_table)
                continue

            for k, v in new_items.items():
                if k in self.items:
                    self.items[k] += v
                else:
                    self.items[k] = v
Exemplo n.º 37
0
    def _parse_skill_grant(self, dbr, result):
        """
        Parse a granted skill.

        """
        # If it doesn't have a granted skill name and level, it grants no skills:
        if self.SKILL_NAME not in dbr or self.SKILL_LEVEL not in dbr:
            return

        level = dbr[self.SKILL_LEVEL]
        try:
            skill = DBRParser.parse(dbr[self.SKILL_NAME])
        except InvalidItemError as e:
            logging.debug(
                f"Skipping granted skill record in {dbr[self.SKILL_NAME]}. {e}"
            )
            return

        if 'name' not in skill:
            return

        # Store the skill, which will ensure a unique tag:
        skill_tag = storage.store_skill(skill)

        # Now add the granted skill to the item:
        result['properties'][self.SKILL_NAME] = {
            'tag': skill_tag,
            'level': level,
        }

        if self.CONTROLLER not in dbr:
            return

        # Grab the auto controller to see if this skill is activated:
        controller = DBRParser.read(dbr[self.CONTROLLER])

        # The property 'triggerType' can be converted to a useful text suffix
        # like 'Activated on attack':
        result['properties'][self.SKILL_NAME].update(
            {'trigger': texts.get(self.TRIGGERS[controller['triggerType']])})
Exemplo n.º 38
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse skill augments, mastery augments, and item granted skills.

        """
        self._parse_skill_grant(dbr, result)

        # Parse skills that are augmented:
        for name, level in self.SKILL_AUGMENTS.items():
            # Skip skills without both the name and level set:
            if name not in dbr or level not in dbr:
                continue

            if 'skilltree' in str(dbr[name]):
                # The atlantis expansion references skilltree instead of
                # skillmastery, so rebuild the path:
                mastery_file = str(dbr[name]).replace('skilltree', 'mastery')
            else:
                mastery_file = dbr[name]

            # Parse the skill:
            skill = DBRParser.parse(mastery_file)
            # Store the skill, which will ensure a unique tag:
            skill_tag = storage.store_skill(skill)
            level = dbr[level]

            # Skill format is either ItemSkillIncrement or ItemMasteryIncrement
            skill_format = (self.TXT_SKILL_INC if 'Mastery'
                            not in skill['name'] else self.TXT_MASTERY_INC)

            result['properties'][name] = {
                'tag': skill_tag,
                'name': texts.get(skill_format).format(level, skill['name']),
            }

        # Parse augment to all skills:
        if self.AUGMENT_ALL in dbr:
            level = dbr[self.AUGMENT_ALL]
            result['properties'][self.AUGMENT_ALL] = (texts.get(
                self.TXT_ALL_INC).format(level))
Exemplo n.º 39
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the scroll.

        """
        # Use the file name to determine the difficulty:
        file_name = os.path.basename(dbr_file).split('_')[0][1:]
        # Strip all but digits from the string, then cast to int:
        difficulty = self.DIFFICULTIES_LIST[int(
            ''.join(filter(lambda x: x.isdigit(), file_name))) - 1]

        result.update({
            'tag': dbr['description'],
            'name': texts.get(dbr['description']),
            'classification': texts.get(difficulty).strip(),
            'description': texts.get(dbr['itemText']),
        })

        # Greater scroll of svefnthorn is incorrectly referenced as its Divine
        # variant. Manual fix required for now:
        if '02_svefnthorn.dbr' in str(dbr_file):
            result['tag'] = 'x2tagScrollName06'
            result['name'] = texts.get('x2tagScrollName06')

        # Set the bitmap if it exists
        if 'bitmap' in dbr:
            result['bitmap'] = dbr['bitmap']

        # Grab the skill file:
        skill = DBRParser.parse(dbr['skillName'])

        # Add the first tier of properties if there are any:
        if 'properties' in skill and skill['properties']:
            result['properties'] = skill['properties'][0]

        # Add any summon (just the first one)
        if 'summons' in skill:
            result['summons'] = skill['summons'][0]
Exemplo n.º 40
0
def parse_creatures():
    """
    Parse all creatures (bosses and heroes) in Titan Quest.

    Parsing the bosses and heroes is mostly about parsing their loot tables
    to create an index of what they can drop. This index will work two ways,
    the first being a complete list of items that the monster can drop and the
    reverse being added to each individual item's loot table so it can be
    sorted.

    """
    timer = time.clock()

    files = []
    for resource in resources.CREATURES:
        boss_files = resources.DB / resource
        files.extend(glob.glob(str(boss_files), recursive=True))

    creatures = {}
    for dbr in files:
        parsed = parse(dbr)

        try:
            # Don't include common monsters
            # XXX - Should 'Champion' be added?
            if parsed['classification'] not in ['Quest', 'Hero', 'Boss']:
                continue

            # Store the monster by its tag:
            creatures[parsed['tag']] = parsed
        except KeyError:
            # Skip creatures without tags
            continue

    # Log the timer:
    logging.info(f'Parsed creatures in {time.clock() - timer} seconds.')

    return creatures
Exemplo n.º 41
0
def parse_creatures():
    """
    Parse all creatures (bosses and heroes) in Titan Quest.

    Parsing the bosses and heroes is mostly about parsing their loot tables
    to create an index of what they can drop. This index will work two ways,
    the first being a complete list of items that the monster can drop and the
    reverse being added to each individual item's loot table so it can be
    sorted.

    """
    timer = time.clock()

    files = []
    for resource in resources.CREATURES:
        boss_files = resources.DB / resource
        files.extend(glob.glob(str(boss_files), recursive=True))

    creatures = {}
    for dbr in files:
        parsed = parse(dbr)

        try:
            # Don't include common monsters
            # XXX - Should 'Champion' be added?
            if parsed['classification'] not in ['Quest', 'Hero', 'Boss']:
                continue

            # Store the monster by its tag:
            creatures[parsed['tag']] = parsed
        except KeyError:
            # Skip creatures without tags
            continue

    # Log the timer:
    logging.info(f'Parsed creatures in {time.clock() - timer} seconds.')

    return creatures
Exemplo n.º 42
0
    def parse(self, dbr, dbr_file, result):
        tables = {}
        weights = {}

        # Initialize the results table:
        result['table'] = []

        # Parse all available entries
        for field, value in dbr.items():
            if field.startswith('randomizerName'):
                # Grab the number suffix (1-70)
                number = re.search(r'\d+', field).group()
                # Store the DBR reference in the table
                tables[number] = value
            if field.startswith('randomizerWeight'):
                # Grab the number suffix (1-70)
                number = re.search(r'\d+', field).group()
                # Store the weight reference in the table
                weights[number] = value

        # Add all the weights together to determined % later
        total_weight = sum(weights.values())
        for key, dbr_file in tables.items():
            # Skip entries without chance or without a file
            if key not in weights or not os.path.exists(dbr_file):
                continue

            # Parse the table entry
            randomizer = DBRParser.parse(dbr_file)

            # Append the parsed bonus with its chance:
            result['table'].append({
                'chance':
                float('{0:.2f}'.format((weights[key] / total_weight) * 100)),
                'option':
                randomizer['properties']
            })
Exemplo n.º 43
0
    def parse(self, dbr, dbr_file, result):
        tables = {}
        weights = {}

        # Initialize the results table:
        result['table'] = []

        # Parse all available entries
        for field, value in dbr.items():
            if field.startswith('randomizerName'):
                # Grab the number suffix (1-70)
                number = re.search(r'\d+', field).group()
                # Store the DBR reference in the table
                tables[number] = value
            if field.startswith('randomizerWeight'):
                # Grab the number suffix (1-70)
                number = re.search(r'\d+', field).group()
                # Store the weight reference in the table
                weights[number] = value

        # Add all the weights together to determined % later
        total_weight = sum(weights.values())
        for key, dbr_file in tables.items():
            # Skip entries without chance or without a file
            if key not in weights or not os.path.exists(dbr_file):
                continue

            # Parse the table entry
            randomizer = DBRParser.parse(dbr_file)

            # Append the parsed bonus with its chance:
            result['table'].append({
                'chance': float(
                    '{0:.2f}'.format((weights[key] / total_weight) * 100)),
                'option': randomizer['properties']
            })
Exemplo n.º 44
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the monster.

        """
        self.parse_creature(dbr, dbr_file, result)

        # Don't parse any further for tagless or level-less creatures:
        if 'tag' not in result or not result['level']:
            return

        # Iterate over normal, epic & legendary version of the boss:
        loot = []
        for index in range(3):
            # Initialize an empty result:
            loot.append({})

            # Create a DBR that only has the equipment for this difficulty:
            difficulty_dbr = TQDBParser.extract_values(dbr, '', index)

            # Parse all the equipment in this difficulty
            difficulty_dbr = self.parse_difficulty(difficulty_dbr, dbr_file)

            # Only store the equipment if there was any:
            if difficulty_dbr:
                loot[index] = difficulty_dbr

        # If there is any tiered data to store, store it:
        if any(tier for tier in loot if tier):
            result['loot'] = loot

        chests = []
        tag = result['tag']

        # Find the chest for each difficulty:
        for index in range(3):
            # Initialize an empty result:
            chests.append({})

            # Grab the chest to parse:
            if tag in CHESTS and CHESTS[tag][index]:
                # Grab the level for this index, or the last one:
                level = (
                    result['level'][index]
                    if len(result['level']) > index
                    else result['level'][-1])

                # Parse the chest and pass the monsters level as a reference:
                loot = DBRParser.parse(
                    DB / CHESTS[tag][index],
                    {'level': level},
                )

                # Convert all item chances to 4 point precision:
                chests[index] = dict(
                    (k, float('{0:.4f}'.format(v))) for k, v
                    in loot['loot_table'].items())

        # If there is any tiered data to store, store it:
        if any(tier for tier in chests if tier):
            result['chest'] = chests

        # Check if this monster is limited to a difficulty:
        if len(result['level']) != len(set(result['level'])):
            # If a level is repeated, it means a creature doesn't spawn in
            # some difficulties. The 'normal' difficulty level is either
            # repeated in Epic and Legendary, so find the index and subtract
            # 1 from that to get all difficulties that should be removed:
            for i in range(result['level'].count(result['level'][0]) - 1):
                result['properties'][i] = {}
                result['abilities'][i] = {}
                result['level'][i] = None
Exemplo n.º 45
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the skill that spawns a pet.

        """
        # Only set time to live it's set (otherwise it's infinite)
        ttl_list = dbr.get(self.TTL, None)

        # Parse all the summons and set them as a list:
        result['summons'] = []
        for index, spawn_file in enumerate(dbr['spawnObjects']):
            spawn = DBRParser.parse(spawn_file)

            # Keep track of the original properties this summon had:
            original_properties = {}
            if 'properties' in spawn:
                if isinstance(spawn['properties'], list):
                    original_properties = spawn['properties'][0].copy()
                else:
                    original_properties = spawn['properties'].copy()

            # We need the raw values from the spawn DBR for hp/mp
            spawn['properties'] = {}
            spawn_dbr = DBRParser.read(spawn_file)

            if 'characterLife' in spawn_dbr:
                hp_list = spawn_dbr['characterLife']
                hp = (
                    hp_list[index]
                    if index < len(hp_list)
                    else hp_list[len(hp_list) - 1])
                TQDBParser.insert_value(
                    'characterLife',
                    texts.get('LifeText').format(hp),
                    spawn)
            if 'characterMana' in spawn_dbr:
                mp_list = spawn_dbr['characterMana']
                mp = (
                    mp_list[index]
                    if index < len(mp_list)
                    else mp_list[len(mp_list) - 1])
                TQDBParser.insert_value(
                    'characterMana',
                    texts.get('ManaText').format(mp),
                    spawn)
            if ttl_list:
                ttl = (
                    ttl_list[index]
                    if index < len(ttl_list)
                    else ttl_list[len(ttl_list) - 1])
                TQDBParser.insert_value(
                    self.TTL,
                    texts.get(self.TTL).format(ttl),
                    spawn)

            # Iterate over the original properties and add some whitelisted
            # properties to the final result:
            for key, value in original_properties.items():
                if key.startswith('character'):
                    continue
                spawn['properties'][key] = value

            result['summons'].append(spawn)
Exemplo n.º 46
0
def parse_affixes():
    """
    Parse all the Titan Quest affixes.

    Affixes are the pre- and suffixes that are applied to weapons.
    These affixes add properties to the equipment, these properties,
    the affix names and the equipment they can be applied to is
    indexed and parsed in this function.

    """
    timer = time.clock()

    files = []
    for resource in resources.AFFIX_TABLES:
        table_files = resources.DB / resource
        files.extend(glob.glob(str(table_files), recursive=True))

    # The affix tables will determine what gear an affix can be applied to.
    affix_tables = {}
    for dbr in files:
        table = read(dbr)

        # Use the filename to determine what equipment this table is for:
        file_name = os.path.basename(dbr).split('_')
        table_type = get_affix_table_type(file_name[0])

        # For each affix in this table, create an entry:
        for field, affix_dbr in table.items():
            if not field.startswith('randomizerName') or not table_type:
                continue

            affix_dbr = str(affix_dbr)
            if affix_dbr not in affix_tables:
                affix_tables[affix_dbr] = [table_type]
            elif table_type not in affix_tables[affix_dbr]:
                affix_tables[affix_dbr].append(table_type)

    files = []
    for resource in resources.AFFIXES:
        affix_files = resources.DB / resource
        files.extend(glob.glob(str(affix_files), recursive=True))

    affixes = {'prefixes': {}, 'suffixes': {}}
    for dbr in files:
        affix = parse(dbr)

        # Skip affixes without properties (first one will be empty):
        if not affix['properties']:
            continue

        # Skip the incorrect 'of the Mammoth' prefix entry:
        if 'prefix' in dbr and affix['tag'] == 'tagPrefix145':
            continue

        # Assign the table types to this affix:
        if dbr not in affix_tables:
            # Affix can occur on all equipment:
            affix['equipment'] = 'none'
        else:
            affix['equipment'] = ','.join(affix_tables[dbr])

        # Add affixes to their respective pre- or suffix list.
        if 'Prefix' in affix['tag'] and 'suffix' not in dbr:
            affixType = 'prefixes'
        else:
            affixType = 'suffixes'

        affixTag = affix.pop('tag')

        # Either add the affix or add its properties as an alternative
        if affixTag in affixes[affixType]:
            # Skip duplicate affix properties:
            if is_duplicate_affix(affixes[affixType][affixTag], affix):
                continue
            affixes[affixType][affixTag]['properties'].append(
                affix['properties'])
        else:
            # Place the affix properties into a list that can be extended by
            # alternatives during this parsing.
            affix['properties'] = [affix['properties']]
            affixes[affixType][affixTag] = affix

    # Log and reset the timer:
    logging.info(f'Parsed affixes in {time.clock() - timer} seconds.')

    return affixes
Exemplo n.º 47
0
def parse_quests():
    """
    Parse the Titan Quest quest rewards.

    The quest rewards are indexed by creating a text readable version of the
    QST files located in the Resources/Quests.arc file. The rewards are
    extracted by only retrieving rewards prefixed with item[] tags.

    """
    timer = time.clock()

    # Regex to find item rewards
    REWARD = re.compile(
        r'item\[(?P<index>[0-9])\](.{0,1})'
        r'(?P<file>'
        'records'
        r'[\\||\/]'
        r'(xpack[2]?[\\||\/])?'
        'quests'
        r'[\\||\/]'
        'rewards'
        r'[\\||\/]'
        r'([^.]+)\.dbr'
        r')'
    )

    # Regex to find the title tag
    TITLE = re.compile(r'titletag(?P<tag>[^\s]*)')

    files = glob.glob(resources.QUESTS)
    quests = {}

    for qst in files:
        with open(qst, 'rb') as quest:
            # Read the content as printable characters only:
            content = ''.join(
                c for c in
                # Lower case and convert to utf-8
                quest.read().decode('utf-8', errors='ignore').lower()
                if c in string.printable
            )

        # Find the title and skip this file if none is found:
        title_tag = TITLE.search(content)
        if not title_tag or not title_tag.group('tag'):
            continue

        # Grab the quest title tag
        tag = title_tag.group('tag')
        if tag not in quests:
            # Initialize three difficulties:
            quests[tag] = {
                'name': texts.get(tag),
                'rewards': [{}, {}, {}],
            }

        # Parsed reward files (so we don't duplicate):
        parsed = []

        # Add all the rewards to the quest:
        for match in REWARD.finditer(content):
            # The index in the item[index] tag determines the difficulty:
            difficulty = int(match.group('index'))
            reward_file = match.group('file')

            # Store the file or move on if we've already parsed it
            if reward_file not in parsed:
                parsed.append(reward_file)
            else:
                continue

            # Prepend the path with the database path:
            rewards = parse(resources.DB / reward_file)

            # Skip quests where the rewards aren't items:
            if 'loot_table' not in rewards:
                continue

            # Either set the chance or add it to a previous chance:
            for item, chance in rewards['loot_table'].items():
                if item in quests[tag]['rewards'][difficulty]:
                    quests[tag]['rewards'][difficulty][item] += chance
                else:
                    quests[tag]['rewards'][difficulty][item] = chance

        # Don't save quests without item rewards:
        if not any(reward for reward in quests[tag]['rewards']):
            quests.pop(tag)

    # Turn all chances into percentages:
    for tag, quest in quests.items():
        for index, difficulty in enumerate(quest['rewards']):
            for item, chance in difficulty.items():
                # Format into 4 point precision percentages:
                quests[tag]['rewards'][index][item] = (
                    float('{0:.4f}'.format(chance * 100)))

    # Log the timer:
    logging.info(f'Parsed quest rewards in {time.clock() - timer} seconds.')

    return quests
Exemplo n.º 48
0
Arquivo: main.py Projeto: tnm-l/tqdb
def parse_equipment():
    """
    Parse all wearable Titan Quest equipment.

    The wearable equipment is indexed and sorted by equipment type. These
    categories are defined by the Class property of each piece of equipment
    which is mapped to the 'category' key in the parsed result.

    :return: dictionary keyed by equipment category string, value is a list of
        dicts, one for each item in that category. Common items are omitted.

    """
    start_time = time.time()

    files = []
    for resource in resources.EQUIPMENT:
        equipment_files_globpath = resources.DB / resource

        for equipment_filename in glob.glob(str(equipment_files_globpath),
                                            recursive=True):
            equipment_path = Path(equipment_filename)
            posix_path = equipment_path.as_posix()
            if not (
                    # Exclude all files in 'old' and 'default'
                    'old' in equipment_path.parts
                    or 'default' in equipment_path.parts or
                    # Rhodian and Electrum sling don't drop:
                    posix_path.endswith('/1hranged/u_e_02.dbr')
                    or posix_path.endswith('/1hranged/u_n_05.dbr')):
                files.append(equipment_filename)

    logging.info(f"Found {len(files)} equipment files to process.")

    # TODO: add multithreading!

    items = defaultdict(list)
    for dbr in files:
        try:
            parsed = parse(dbr)
        except InvalidItemError as e:
            exception_messages = exception_messages_with_causes(e)

            logging.debug(f"Ignoring item in {dbr}. {exception_messages}")
            continue

        try:
            # Skip items without a category
            if 'category' not in parsed:
                continue

            # Organize the equipment based on its category
            category = parsed.pop('category')

            # Skip items without rarities
            if 'classification' not in parsed:
                continue

            # Save the bitmap and remove the bitmap key
            images.save_bitmap(parsed, category, 'output/graphics/')
        except KeyError as e:
            # Skip equipment that couldn't be parsed:
            logging.warning(
                f"DBR {dbr} parse result unacceptable. Parse result: {parsed}. Error: {e}"
            )
            # raise e
            continue

        # Pop off the properties key off any item without properties:
        if 'properties' in parsed and not parsed['properties']:
            parsed.pop('properties')

        # Now save the parsed item in the category:
        if category:
            items[category].append(parsed)

    # Log the timer:
    logging.info(
        f'Parsed equipment in {time.time() - start_time:.2f} seconds.')

    return items
Exemplo n.º 49
0
def parse_affixes():
    """
    Parse all the Titan Quest affixes.

    Affixes are the pre- and suffixes that are applied to weapons.
    These affixes add properties to the equipment, these properties,
    the affix names and the equipment they can be applied to is
    indexed and parsed in this function.

    """
    timer = time.clock()

    files = []
    for resource in resources.AFFIX_TABLES:
        table_files = resources.DB / resource
        files.extend(glob.glob(str(table_files), recursive=True))

    # The affix tables will determine what gear an affix can be applied to.
    affix_tables = {}
    for dbr in files:
        table = read(dbr)

        # Use the filename to determine what equipment this table is for:
        file_name = os.path.basename(dbr).split('_')
        table_type = get_affix_table_type(file_name[0])

        # For each affix in this table, create an entry:
        for field, affix_dbr in table.items():
            if not field.startswith('randomizerName') or not table_type:
                continue

            affix_dbr = str(affix_dbr)
            if affix_dbr not in affix_tables:
                affix_tables[affix_dbr] = [table_type]
            elif table_type not in affix_tables[affix_dbr]:
                affix_tables[affix_dbr].append(table_type)

    files = []
    for resource in resources.AFFIXES:
        affix_files = resources.DB / resource
        files.extend(glob.glob(str(affix_files), recursive=True))

    affixes = {'prefixes': {}, 'suffixes': {}}
    for dbr in files:
        affix = parse(dbr)

        # Skip affixes without properties (first one will be empty):
        if not affix['properties']:
            continue

        # Skip the incorrect 'of the Mammoth' prefix entry:
        if 'prefix' in dbr and affix['tag'] == 'tagPrefix145':
            continue

        # Assign the table types to this affix:
        if dbr not in affix_tables:
            # Affix can occur on all equipment:
            affix['equipment'] = 'none'
        else:
            affix['equipment'] = ','.join(affix_tables[dbr])

        # Add affixes to their respective pre- or suffix list.
        if 'Prefix' in affix['tag'] and 'suffix' not in dbr:
            affixType = 'prefixes'
        else:
            affixType = 'suffixes'

        affixTag = affix.pop('tag')

        # Either add the affix or add its properties as an alternative
        if affixTag in affixes[affixType]:
            # Skip duplicate affix properties:
            if is_duplicate_affix(affixes[affixType][affixTag], affix):
                continue
            affixes[affixType][affixTag]['properties'].append(
                affix['properties'])
        else:
            # Place the affix properties into a list that can be extended by
            # alternatives during this parsing.
            affix['properties'] = [affix['properties']]
            affixes[affixType][affixTag] = affix

    # Log and reset the timer:
    logging.info(f'Parsed affixes in {time.clock() - timer} seconds.')

    return affixes
Exemplo n.º 50
0
def parse_quests():
    """
    Parse the Titan Quest quest rewards.

    The quest rewards are indexed by creating a text readable version of the
    QST files located in the Resources/Quests.arc file. The rewards are
    extracted by only retrieving rewards prefixed with item[] tags.

    """
    timer = time.clock()

    # Regex to find item rewards
    REWARD = re.compile(r'item\[(?P<index>[0-9])\](.{0,1})'
                        r'(?P<file>'
                        'records'
                        r'[\\||\/]'
                        r'(xpack[2]?[\\||\/])?'
                        'quests'
                        r'[\\||\/]'
                        'rewards'
                        r'[\\||\/]'
                        r'([^.]+)\.dbr'
                        r')')

    # Regex to find the title tag
    TITLE = re.compile(r'titletag(?P<tag>[^\s]*)')

    files = glob.glob(resources.QUESTS)
    quests = {}

    for qst in files:
        with open(qst, 'rb') as quest:
            # Read the content as printable characters only:
            content = ''.join(
                c for c in
                # Lower case and convert to utf-8
                quest.read().decode('utf-8', errors='ignore').lower()
                if c in string.printable)

        # Find the title and skip this file if none is found:
        title_tag = TITLE.search(content)
        if not title_tag or not title_tag.group('tag'):
            continue

        # Grab the quest title tag
        tag = title_tag.group('tag')
        if tag not in quests:
            # Initialize three difficulties:
            quests[tag] = {
                'name': texts.get(tag),
                'rewards': [{}, {}, {}],
            }

        # Parsed reward files (so we don't duplicate):
        parsed = []

        # Add all the rewards to the quest:
        for match in REWARD.finditer(content):
            # The index in the item[index] tag determines the difficulty:
            difficulty = int(match.group('index'))
            reward_file = match.group('file')

            # Store the file or move on if we've already parsed it
            if reward_file not in parsed:
                parsed.append(reward_file)
            else:
                continue

            # Prepend the path with the database path:
            rewards = parse(resources.DB / reward_file)

            # Skip quests where the rewards aren't items:
            if 'loot_table' not in rewards:
                continue

            # Either set the chance or add it to a previous chance:
            for item, chance in rewards['loot_table'].items():
                if item in quests[tag]['rewards'][difficulty]:
                    quests[tag]['rewards'][difficulty][item] += chance
                else:
                    quests[tag]['rewards'][difficulty][item] = chance

        # Don't save quests without item rewards:
        if not any(reward for reward in quests[tag]['rewards']):
            quests.pop(tag)

    # Turn all chances into percentages:
    for tag, quest in quests.items():
        for index, difficulty in enumerate(quest['rewards']):
            for item, chance in difficulty.items():
                # Format into 4 point precision percentages:
                quests[tag]['rewards'][index][item] = (float('{0:.4f}'.format(
                    chance * 100)))

    # Log the timer:
    logging.info(f'Parsed quest rewards in {time.clock() - timer} seconds.')

    return quests