Example #1
0
    def is_valid_classification(self, dbr, dbr_file, result):
        """
        Check if this item is of a valid classification for TQDB.

        """
        itemClass = dbr.get('Class')
        classification = dbr.get('itemClassification', None)

        if (itemClass not in self.ALLOWED and
                classification not in self.CLASSIFICATIONS.keys()):
            raise StopIteration
        elif (classification in self.CLASSIFICATIONS.keys() and
                'classification' not in result):
            # Only add the classification if it doesn't exist yet:
            result['classification'] = texts.get(
                self.CLASSIFICATIONS[classification]).strip()

            # For Monster Infrequents, make sure a drop difficulty exists:
            if classification == 'Rare':
                file_name = os.path.basename(dbr_file).split('_')
                if len(file_name) < 2 or file_name[1] not in DIFFICULTIES:
                    raise StopIteration

                # Set the difficulty for which this MI drops:
                result['dropsIn'] = texts.get(
                    DIFFICULTIES[file_name[1]]).strip()
Example #2
0
    def is_valid_classification(self, dbr, dbr_file, result):
        """
        Check if this item is of a valid classification for TQDB.

        """
        itemClass = dbr.get('Class')
        classification = dbr.get('itemClassification', None)

        if (itemClass not in self.ALLOWED and
                classification not in self.CLASSIFICATIONS.keys()):
            raise InvalidItemError(f"Item {dbr_file} is excluded due to Class "
                                   f"{itemClass} with itemClassification "
                                   f"{classification}.")
        elif (classification in self.CLASSIFICATIONS.keys() and
                'classification' not in result):
            # Only add the classification if it doesn't exist yet:
            result['classification'] = texts.get(
                self.CLASSIFICATIONS[classification]).strip()

            # For Monster Infrequents, make sure a drop difficulty exists:
            if classification == 'Rare':
                file_name = os.path.basename(dbr_file).split('_')
                if len(file_name) < 2 or file_name[1] not in DIFFICULTIES:
                    raise InvalidItemError(f"File name {file_name} does not "
                                           "specify difficulty, or difficulty "
                                           "not recognized.")

                # Set the difficulty for which this MI drops:
                result['dropsIn'] = texts.get(
                    DIFFICULTIES[file_name[1]]).strip()
Example #3
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))
Example #4
0
    def is_valid_classification(self, dbr, dbr_file, result):
        """
        Check if this item is of a valid classification for TQDB.

        """
        itemClass = dbr.get('Class')
        classification = dbr.get('itemClassification', None)

        if (itemClass not in self.ALLOWED
                and classification not in self.CLASSIFICATIONS.keys()):
            raise StopIteration
        elif (classification in self.CLASSIFICATIONS.keys()
              and 'classification' not in result):
            # Only add the classification if it doesn't exist yet:
            result['classification'] = texts.get(
                self.CLASSIFICATIONS[classification]).strip()

            # For Monster Infrequents, make sure a drop difficulty exists:
            if classification == 'Rare':
                file_name = os.path.basename(dbr_file).split('_')
                if len(file_name) < 2 or file_name[1] not in DIFFICULTIES:
                    raise StopIteration

                # Set the difficulty for which this MI drops:
                result['dropsIn'] = texts.get(
                    DIFFICULTIES[file_name[1]]).strip()
Example #5
0
    def parse_global(self, key, chance, xor, all_fields, index):
        """
        Add a global chance for properties.

        This is where multiple properties can be triggered by a chance, like

        10% Chance for one of the following:
            - ...
            - ...
        """
        # Grab the value for this index from the global stores:
        fields = dict((k, v[index]) if index < len(v)
                      # If this value isn't repeated, grab the last value
                      else (k, v[-1]) for k, v in all_fields.items())

        # Check if the XOR was set for any field:
        if xor:
            value = {
                'chance': (texts.get(GLOBAL_XOR_ALL) if chance == 100 else
                           texts.get(GLOBAL_XOR_PCT).format(chance)),
                'properties':
                fields,
            }
        else:
            value = {
                'chance': (texts.get(GLOBAL_ALL).format('') if chance == 100
                           else texts.get(GLOBAL_PCT).format(chance)),
                'properties':
                fields,
            }

        # Insert the value normally
        TQDBParser.insert_value(key, value, self.result)
Example #6
0
    def parse_creature(self, dbr, dbr_file, result):
        """
        Parse the creature and its properties and skills.

        """
        # Grab the first set of properties:
        classification = dbr.get('monsterClassification', 'Common')
        tag = dbr.get('description', None)

        # Set the known properties for this creature
        if tag:
            race = dbr.get('characterRacialProfile', None)
            result.update({
                'classification':
                classification,
                'name':
                texts.get(tag),
                'race':
                race[0] if race else None,
                'level': [level for level in dbr.get('charLevel', [])],
                'tag':
                tag,
            })

        # Manually parse the defensive properties, since there's no template
        # tied for it for monsters:
        parsers.ParametersDefensiveParser().parse(dbr, dbr_file, result)

        # Iterate over the properties for each difficulty:
        properties = []
        for i in range(3):
            properties.append({})
            itr = TQDBParser.extract_values(dbr, '', i)

            # Set this creature's HP and MP as stats, not as bonuses:
            if self.HP in itr:
                hp = itr[self.HP]
                properties[i][self.HP] = texts.get('LifeText').format(hp)
            if self.MP in itr:
                mp = itr[self.MP]
                properties[i][self.MP] = texts.get('ManaText').format(mp)

            # Add non-character properties:
            for k, v in result['properties'].items():
                if k.startswith('character'):
                    continue

                # Add the property to the correct difficulty index:
                if isinstance(v, list):
                    # The property changes per difficulty:
                    properties[i][k] = v[i] if i < len(v) else v[-1]
                else:
                    # The property is constant:
                    properties[i][k] = v

        # Add the base damage, stats, regens, and resistances:
        result['properties'] = properties
Example #7
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)
Example #8
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)
Example #9
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)
Example #10
0
 def parse(self, dbr, dbr_file, result):
     # Set the block chance and value:
     result['properties'][self.BLOCK] = texts.get(self.TEXT).format(
         # Block chance
         dbr.get(f'{self.BLOCK}Chance', 0),
         # Blocked damage
         dbr.get(self.BLOCK, 0))
Example #11
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']])
        })
Example #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']])})
Example #13
0
 def parse(self, dbr, dbr_file, result):
     if 'lootRandomizerName' in dbr:
         result['tag'] = dbr['lootRandomizerName']
         # Some names had inline comments, so strip the spaces:
         result['name'] = texts.get(result['tag']).strip()
         # Add the level requirement:
         result['levelRequirement'] = dbr['levelRequirement']
Example #14
0
 def parse(self, dbr, dbr_file, result):
     # Set the block chance and value:
     result['properties'][self.BLOCK] = texts.get(self.TEXT).format(
         # Block chance
         dbr.get(f'{self.BLOCK}Chance', 0),
         # Blocked damage
         dbr.get(self.BLOCK, 0))
Example #15
0
 def parse(self, dbr, dbr_file, result):
     if 'lootRandomizerName' in dbr:
         result['tag'] = dbr['lootRandomizerName']
         # Some names had inline comments, so strip the spaces:
         result['name'] = texts.get(result['tag']).strip()
         # Add the level requirement:
         result['levelRequirement'] = dbr['levelRequirement']
Example #16
0
    def parse_modifier(self, field, field_type, dbr):
        """
        Parse a percentage increase in an offensive attribute.

        """
        field_mod = f'{field}Modifier'

        chance = dbr.get(f'{field_mod}Chance', 0)
        mod = dbr.get(field_mod, 0)
        is_xor = dbr.get(f'{field}XOR', False)
        is_global = dbr.get(f'{field}Global', False)

        # Optional pre/suffix for chance and duration damage/effects:
        prefix = ''
        suffix = ''

        if field_type in [self.DOT, self.EOT]:
            # Add the possible duration field:
            duration_mod = dbr.get(f'{field}DurationModifier', 0)

            if duration_mod:
                suffix = texts.get(IMPRV_TIME).format(duration_mod)

        value = texts.get(field_mod).format(mod)
        if chance and not is_xor:
            prefix = texts.get(CHANCE).format(chance)

        if not is_global:
            # Insert the value normally
            TQDBParser.insert_value(
                field_mod,
                f'{prefix}{value}{suffix}',
                self.result)
        elif field.startswith('offensive'):
            # Add this field to the global offensive list
            self.offensive[field_mod] = (
                [f'{prefix}{value}{suffix}']
                if field_mod not in self.offensive
                else self.offensive[field_mod] + [f'{prefix}{value}{suffix}']
            )
        elif field.startswith('retaliation'):
            # Add this field to the global retaliation list
            self.retaliation[field_mod] = (
                [f'{prefix}{value}{suffix}']
                if field_mod not in self.retaliation
                else self.retaliation[field_mod] + [f'{prefix}{value}{suffix}']
            )
Example #17
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
Example #18
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]
Example #19
0
    def parse(self, dbr, dbr_file, result):
        file_name = os.path.basename(dbr_file).split('_')

        # Skip artifacts with unknown difficulties in which they drop:
        if file_name[0] not in DIFFICULTIES:
            raise StopIteration

        result.update({
            # Bitmap has a different key name than items here.
            'bitmap': dbr.get('artifactBitmap', None),
            # Classification is either Lesser, Greater or Divine
            'classification': dbr.get('artifactClassification', None),
            # Difficulty it starts dropping is based on the file name
            'dropsIn': texts.get(DIFFICULTIES[file_name[0]]).strip(),
            # For artifacts the tag is in the Actor.tpl variable 'description'
            'name': texts.get(dbr['description']),
            'tag': dbr['description'],
        })
Example #20
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the refresh cooldown property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]]
Example #21
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the "Chance to be used" property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]]
Example #22
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the "activated when health drops below ...%" property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]]
Example #23
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the "% Chance to pass through enemies" property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]]
Example #24
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the "% Chance to pass through enemies" property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]
            ]
Example #25
0
    def parse_modifier(self, field, field_type, dbr):
        """
        Parse a percentage increase in an offensive attribute.

        """
        field_mod = f'{field}Modifier'

        chance = dbr.get(f'{field_mod}Chance', 0)
        mod = dbr.get(field_mod, 0)
        is_xor = dbr.get(f'{field}XOR', False)
        is_global = dbr.get(f'{field}Global', False)

        # Optional pre/suffix for chance and duration damage/effects:
        prefix = ''
        suffix = ''

        if field_type in [self.DOT, self.EOT]:
            # Add the possible duration field:
            duration_mod = dbr.get(f'{field}DurationModifier', 0)

            if duration_mod:
                suffix = texts.get(IMPRV_TIME).format(duration_mod)

        value = texts.get(field_mod).format(mod)
        if chance and not is_xor:
            prefix = texts.get(CHANCE).format(chance)

        if not is_global:
            # Insert the value normally
            TQDBParser.insert_value(field_mod, f'{prefix}{value}{suffix}',
                                    self.result)
        elif field.startswith('offensive'):
            # Add this field to the global offensive list
            self.offensive[field_mod] = ([f'{prefix}{value}{suffix}']
                                         if field_mod not in self.offensive
                                         else self.offensive[field_mod] +
                                         [f'{prefix}{value}{suffix}'])
        elif field.startswith('retaliation'):
            # Add this field to the global retaliation list
            self.retaliation[field_mod] = ([f'{prefix}{value}{suffix}']
                                           if field_mod not in self.retaliation
                                           else self.retaliation[field_mod] +
                                           [f'{prefix}{value}{suffix}'])
Example #26
0
    def parse(self, dbr, dbr_file, result):
        dbr_class = dbr['Class']

        # Skip shields:
        if (dbr_class.startswith('Weapon') and 'Shield' in dbr_class):
            return

        # Set the attack speed
        result['properties']['characterAttackSpeed'] = texts.get(
            dbr['characterBaseAttackSpeedTag'])
Example #27
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the "Chance to be used" property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]
            ]
Example #28
0
    def parse(self, dbr, dbr_file, result):
        dbr_class = dbr['Class']

        # Skip shields:
        if (dbr_class.startswith('Weapon') and 'Shield' in dbr_class):
            return

        # Set the attack speed
        result['properties']['characterAttackSpeed'] = texts.get(
            dbr['characterBaseAttackSpeedTag'])
Example #29
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the "activated when health drops below ...%" property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]
            ]
Example #30
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the refresh cooldown property.

        """
        if self.FIELD in dbr:
            result['properties'][self.FIELD] = [
                texts.get(self.FIELD).format(value)
                for value in dbr[self.FIELD]
            ]
Example #31
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
Example #32
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the projectile properties.

        """
        for field in self.FIELDS:
            if field not in dbr:
                continue

            result['properties'][field] = [
                texts.get(field).format(value)
                for value in dbr[field]]
Example #33
0
    def parse_field(self, field, dbr, result):
        """
        Parse all values for a given character field that is in the DBR.

        """
        for value in dbr[field]:
            # Skip 0 values
            if not value:
                continue

            formatted = texts.get(field).format(value)
            TQDBParser.insert_value(field, formatted, result)
Example #34
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the projectile properties.

        """
        for field in self.FIELDS:
            if field not in dbr:
                continue

            result['properties'][field] = [
                texts.get(field).format(value) for value in dbr[field]
            ]
Example #35
0
    def parse_field(self, field, dbr, result):
        """
        Parse all values for a given character field that is in the DBR.

        """
        for value in dbr[field]:
            # Skip 0 values
            if not value:
                continue

            formatted = texts.get(field).format(value)
            TQDBParser.insert_value(field, formatted, result)
Example #36
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))
Example #37
0
    def parse(self, dbr, dbr_file, result):
        # If no tag exists, skip parsing:
        tag = dbr.get('itemNameTag', None)
        if not tag:
            raise StopIteration

        # Set the known item properties:
        result.update({
            'bitmap': dbr.get('bitmap', None),
            'itemLevel': dbr.get('itemLevel', None),
            'name': texts.get(tag),
            'tag': tag,
        })

        # Check if this item is part of a set:
        item_set_path = dbr.get('itemSetName', None)
        if item_set_path:
            # Read (don't parse to avoid recursion) the set to get the tag:
            item_set = DBRParser.read(item_set_path)

            # Only add the set if it has a tag:
            result['set'] = item_set.get(ItemSetParser.NAME, None)

        # Stop parsing here if requirement parsing isn't necessary
        if not self.should_parse_requirements(dbr, result):
            return

        # Cost prefix of this props is determined by its class
        cost_prefix = dbr['Class'].split('_')[1]
        cost_prefix = cost_prefix[:1].lower() + cost_prefix[1:]

        # Read cost file
        cost_properties = DBRParser.read(dbr.get(
            'itemCostName', self.REQUIREMENT_FALLBACK))

        # Grab the props level (it's a variable in the equations)
        for requirement in self.REQUIREMENTS:
            # Create the equation key
            equation_key = cost_prefix + requirement + 'Equation'
            req = requirement.lower() + 'Requirement'

            # Existing requirements shouldn't be overriden:
            if equation_key in cost_properties and req not in result:
                equation = cost_properties[equation_key]

                # camelCased variables are required for the equations:
                itemLevel = dbr['itemLevel']  # noqa
                totalAttCount = len(result['properties'])  # noqa

                # Eval the equation:
                result[req] = math.ceil(numexpr.evaluate(equation).item())
Example #38
0
    def parse(self, dbr, dbr_file, result):
        # If no tag exists, skip parsing:
        tag = dbr.get('itemNameTag', None)
        if not tag:
            raise StopIteration

        # Set the known item properties:
        result.update({
            'bitmap': dbr.get('bitmap', None),
            'itemLevel': dbr.get('itemLevel', None),
            'name': texts.get(tag),
            'tag': tag,
        })

        # Check if this item is part of a set:
        item_set_path = dbr.get('itemSetName', None)
        if item_set_path:
            # Read (don't parse to avoid recursion) the set to get the tag:
            item_set = DBRParser.read(item_set_path)

            # Only add the set if it has a tag:
            result['set'] = item_set.get(ItemSetParser.NAME, None)

        # Stop parsing here if requirement parsing isn't necessary
        if not self.should_parse_requirements(dbr, result):
            return

        # Cost prefix of this props is determined by its class
        cost_prefix = dbr['Class'].split('_')[1]
        cost_prefix = cost_prefix[:1].lower() + cost_prefix[1:]

        # Read cost file
        cost_properties = DBRParser.read(
            dbr.get('itemCostName', self.REQUIREMENT_FALLBACK))

        # Grab the props level (it's a variable in the equations)
        for requirement in self.REQUIREMENTS:
            # Create the equation key
            equation_key = cost_prefix + requirement + 'Equation'
            req = requirement.lower() + 'Requirement'

            # Existing requirements shouldn't be overriden:
            if equation_key in cost_properties and req not in result:
                equation = cost_properties[equation_key]

                # camelCased variables are required for the equations:
                itemLevel = dbr['itemLevel']  # noqa
                totalAttCount = len(result['properties'])  # noqa

                # Eval the equation:
                result[req] = math.ceil(numexpr.evaluate(equation).item())
Example #39
0
    def parse_global(self, key, chance, xor, all_fields, index):
        """
        Add a global chance for properties.

        This is where multiple properties can be triggered by a chance, like

        10% Chance for one of the following:
            - ...
            - ...
        """
        # Grab the value for this index from the global stores:
        fields = dict(
            (k, v[index])
            if index < len(v)
            # If this value isn't repeated, grab the last value
            else (k, v[-1])
            for k, v in all_fields.items())

        # Check if the XOR was set for any field:
        if xor:
            value = {
                'chance': (
                    texts.get(GLOBAL_XOR_ALL)
                    if chance == 100
                    else texts.get(GLOBAL_XOR_PCT).format(chance)),
                'properties': fields,
            }
        else:
            value = {
                'chance': (
                    texts.get(GLOBAL_ALL).format('')
                    if chance == 100
                    else texts.get(GLOBAL_PCT).format(chance)),
                'properties': fields,
            }

        # Insert the value normally
        TQDBParser.insert_value(key, value, self.result)
Example #40
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]
Example #41
0
    def parse(self, dbr, dbr_file, result):
        file_name = os.path.basename(dbr_file).split('_')

        # Skip artifacts with unknown difficulties in which they drop:
        if file_name[0] not in DIFFICULTIES:
            raise StopIteration

        # Artifact classification value (always Lesser, Greater or Divine)
        ac_value = dbr.get('artifactClassification', None)
        # Translation tag for this classification
        ac_tag = ARTIFACT_CLASSIFICATIONS[ac_value]

        result.update({
            # Bitmap has a different key name than items here.
            'bitmap': dbr.get('artifactBitmap', None),
            # Classification is either Lesser, Greater or Divine (translated)
            'classification': texts.get(ac_tag),
            # Difficulty it starts dropping is based on the file name
            'dropsIn': texts.get(DIFFICULTIES[file_name[0]]).strip(),
            # For artifacts the tag is in the Actor.tpl variable 'description'
            'name': texts.get(dbr['description']),
            'tag': dbr['description'],
        })
Example #42
0
    def parse_field(self, field, dbr, result):
        """
        Parse all values for a given defensive field that is in the DBR.

        """
        for i in range(len(dbr[field])):
            iteration = TQDBParser.extract_values(dbr, field, i)

            # It's possible some values aren't set in this iteration:
            if field not in iteration:
                continue

            chance = iteration.get(f'{field}Chance', 0)
            value = iteration[field]

            # Format the value using the texts:
            formatted = texts.get(field).format(value)

            # Prepend a chance if it's set:
            if chance:
                formatted = texts.get(CHANCE).format(chance) + formatted

            TQDBParser.insert_value(field, formatted, result)
Example #43
0
    def parse(self, dbr, dbr_file, result):
        file_name = os.path.basename(dbr_file).split('_')

        # Skip artifacts with unknown difficulties in which they drop:
        if file_name[0] not in DIFFICULTIES:
            raise StopIteration

        # Artifact classification value (always Lesser, Greater or Divine)
        ac_value = dbr.get('artifactClassification', None)
        # Translation tag for this classification
        ac_tag = ARTIFACT_CLASSIFICATIONS[ac_value]

        result.update({
            # Bitmap has a different key name than items here.
            'bitmap': dbr.get('artifactBitmap', None),
            # Classification is either Lesser, Greater or Divine (translated)
            'classification': texts.get(ac_tag).strip(),
            # Difficulty it starts dropping is based on the file name
            'dropsIn': texts.get(DIFFICULTIES[file_name[0]]).strip(),
            # For artifacts the tag is in the Actor.tpl variable 'description'
            'name': texts.get(dbr['description']),
            'tag': dbr['description'],
        })
Example #44
0
    def parse_field(self, field, dbr, result):
        """
        Parse all values for a given defensive field that is in the DBR.

        """
        for i in range(len(dbr[field])):
            iteration = TQDBParser.extract_values(dbr, field, i)

            # It's possible some values aren't set in this iteration:
            if field not in iteration:
                continue

            chance = iteration.get(f'{field}Chance', 0)
            value = iteration[field]

            # Format the value using the texts:
            formatted = texts.get(field).format(value)

            # Prepend a chance if it's set:
            if chance:
                formatted = texts.get(CHANCE).format(chance) + formatted

            TQDBParser.insert_value(field, formatted, result)
Example #45
0
    def format(cls, field_type, field, min, max):
        """
        Format a specific field.

        A field is formatted by determining the numeric format and appending
        the text specific for that field.

        """
        if max > min:
            if field_type == cls.EOT:
                # Effect damage is done in seconds, so add a decimal
                value = texts.get(DMG_RANGE_DECIMAL).format(min, max)
            else:
                # DOT and regular damage is flat, so no decimals:
                value = texts.get(DMG_RANGE).format(min, max)
        else:
            if field_type == cls.EOT:
                # Effect damage is done in seconds, so add a decimal
                value = texts.get(DMG_SINGLE_DECIMAL).format(min)
            else:
                # DOT and regular damage is flat, so no decimals:
                value = texts.get(DMG_SINGLE).format(min)

        return f'{value}{texts.get(field)}'
Example #46
0
    def format(cls, field_type, field, min, max):
        """
        Format a specific field.

        A field is formatted by determining the numeric format and appending
        the text specific for that field.

        """
        if max > min:
            if field_type == cls.EOT:
                # Effect damage is done in seconds, so add a decimal
                value = texts.get(DMG_RANGE_DECIMAL).format(min, max)
            else:
                # DOT and regular damage is flat, so no decimals:
                value = texts.get(DMG_RANGE).format(min, max)
        else:
            if field_type == cls.EOT:
                # Effect damage is done in seconds, so add a decimal
                value = texts.get(DMG_SINGLE_DECIMAL).format(min)
            else:
                # DOT and regular damage is flat, so no decimals:
                value = texts.get(DMG_SINGLE).format(min)

        return f'{value}{texts.get(field)}'
Example #47
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse skill properties.

        """
        for field in self.FIELDS:
            if field not in dbr:
                continue

            # Now iterate as many times as is necessary for this field:
            for index, value in enumerate(dbr[field]):
                # Skip any field that has a negligible value
                if value <= 0.01:
                    continue

                formatted = texts.get(field).format(value)

                # Grab the chance and add a prefix with it:
                if f'{field}Chance' in dbr:
                    chance = dbr[f'{field}Chance'][index]
                    formatted = texts.get(CHANCE).format(chance) + formatted

                # Insert the value:
                TQDBParser.insert_value(field, formatted, result)
Example #48
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse skill properties.

        """
        for field in self.FIELDS:
            if field not in dbr:
                continue

            # Now iterate as many times as is necessary for this field:
            for index, value in enumerate(dbr[field]):
                # Skip any field that has a negligible value
                if value <= 0.01:
                    continue

                formatted = texts.get(field).format(value)

                # Grab the chance and add a prefix with it:
                if f'{field}Chance' in dbr:
                    chance = dbr[f'{field}Chance'][index]
                    formatted = texts.get(CHANCE).format(chance) + formatted

                # Insert the value:
                TQDBParser.insert_value(field, formatted, result)
Example #49
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']])})
Example #50
0
    def parse_field(self, field, dbr, result):
        """
        Parse all values for a given racial bonus field that is in the DBR.

        """
        if field not in dbr:
            return

        races = []
        for race in dbr[self.RACE]:
            if race == 'Beastman':
                races.append('Beastmen')
            elif race != 'Undead' and race != 'Magical':
                races.append(race + 's')
            else:
                races.append(race)

        for value in dbr[field]:
            TQDBParser.insert_value(
                field,
                # Create a list of all racial bonuses
                [texts.get(field).format(value, race) for race in races],
                result)
Example #51
0
    def parse_field(self, field, dbr, result):
        """
        Parse all values for a given racial bonus field that is in the DBR.

        """
        if field not in dbr:
            return

        races = []
        for race in dbr[self.RACE]:
            if race == 'Beastman':
                races.append('Beastmen')
            elif race != 'Undead' and race != 'Magical':
                races.append(race + 's')
            else:
                races.append(race)

        for value in dbr[field]:
            TQDBParser.insert_value(
                field,
                # Create a list of all racial bonuses
                [texts.get(field).format(value, race) for race in races],
                result)
Example #52
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)
Example #53
0
    def parse_flat(self, field, field_type, dbr):
        """
        Parse a flat increase in an offensive attribute.

        """
        # Prepare some keys that will determine the flat or flat range:
        chance = dbr.get(f'{field}Chance', 0)
        min = dbr.get(f'{field}Min', 0)
        max = dbr.get(f'{field}Max', min)
        is_xor = dbr.get(f'{field}XOR', False)
        is_global = dbr.get(f'{field}Global', False)

        # Optional pre/suffix for chance and duration damage/effects:
        prefix = ''
        suffix = ''

        if field_type == self.MANA:
            # The mana burn suffixes are kind of derpy:
            chance = dbr.get('offensiveManaBurnChance', 0)
            min = dbr.get('offensiveManaBurnDrainMin', 0)
            max = dbr.get('offensiveManaBurnDrainMax', 0)
            is_xor = dbr.get(f'{field}XOR', False)
            ratio = dbr.get('offensiveManaBurnDamageRatio', 0)

            if ratio:
                suffix = texts.get('offensiveManaBurnRatio').format(ratio)

            # Reset the field to what's used in texts.
            field = 'offensiveManaDrain'
        elif field_type == self.DOT:
            duration_min = dbr.get(f'{field}DurationMin', 0)
            duration_max = dbr.get(f'{field}DurationMax', 0)

            if duration_min:
                min *= duration_min
                suffix = texts.get(DOT_SINGLE).format(duration_min)

                if duration_max and max == min:
                    max = min * duration_max
                elif max:
                    max *= duration_min
        elif field_type == self.EOT:
            duration_min = dbr.get(f'{field}DurationMin', 0)

            if duration_min:
                suffix = texts.get(DFT_SINGLE).format(duration_min)

        # Pierce ratio is a singular exception for flat properties:
        if field == 'offensivePierceRatio':
            # Pierce ratio already has its formatter in the flat property:
            # "{0:.0f}% Pierce Ratio" instead of "% Pierce Ratio"
            value = texts.get(field).format(min)
        else:
            # Format the value based on its field type and values:
            value = self.format(field_type, field, min, max)

        if chance and not is_xor:
            prefix = texts.get(CHANCE).format(chance)

        if not is_global:
            # Insert the value normally
            TQDBParser.insert_value(
                field,
                f'{prefix}{value}{suffix}',
                self.result)
        elif field.startswith('offensive'):
            # Add this field to the global offensive list
            self.offensive[field] = (
                [f'{prefix}{value}{suffix}']
                if field not in self.offensive
                else self.offensive[field] + [f'{prefix}{value}{suffix}'])
            if is_xor:
                self.offensiveXOR = True
        elif field.startswith('retaliation'):
            # Add this field to the global retaliation list
            self.retaliation[field] = (
                [f'{prefix}{value}{suffix}']
                if field not in self.retaliation
                else self.retaliation[field] + [f'{prefix}{value}{suffix}'])
            if is_xor:
                self.retaliationXOR = True
Example #54
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the base properties of a skill.

        These properties include the skillDisplayName, its friendly display
        name (the property returns a tag), and the maximum level of a skill.

        """
        # Store the path to this skill, it is used in tqdb.storage to ensure
        # all tags are unique.
        result['path'] = dbr_file

        if self.NAME in dbr:
            # The tag is the skillDisplayName property
            result['tag'] = self.FORMATTER.sub('', dbr[self.NAME])

            # Now try to find a friendly name for the tag:
            result['name'] = texts.get(result['tag'])

            if result['name'] == result['tag']:
                # If the tag wasn't returned, a friendly name weas found:
                logging.debug(f'No skill name found for {result["tag"]}')
        else:
            logging.debug(f'No skillDisplayName found in {dbr_file}')

        if self.DESC in dbr and texts.has(dbr[self.DESC]):
            # Also load the description, if it's known:
            result['description'] = texts.get(dbr[self.DESC])
        elif self.FILE in dbr:
            # Use the FileDescription instead:
            result['description'] = dbr['FileDescription']

        # Check the skill base fields:
        base_tiers = TQDBParser.highest_tier(dbr, self.FIELDS)

        for field in self.FIELDS:
            for index in range(base_tiers):
                itr_dbr = TQDBParser.extract_values(dbr, field, index)

                if field not in itr_dbr or itr_dbr[field] <= 0.01:
                    continue

                # Insert this skill property
                value = texts.get(field).format(itr_dbr[field])
                TQDBParser.insert_value(field, value, result)

        # Check the damage absorption skill properties:
        abs_tiers = TQDBParser.highest_tier(dbr, self.ABSORPTIONS)

        for field in self.ABSORPTIONS:
            for index in range(abs_tiers):
                itr_dbr = TQDBParser.extract_values(dbr, field, index)

                if field not in itr_dbr:
                    continue

                # Add 'skill' prefix and capitalize first letter:
                field_prefixed = 'skill' + field[:1].upper() + field[1:]
                value = itr_dbr[field]

                # Find qualifier damage type(s):
                damage_types = ', '.join([
                    texts.get(text_key)
                    for dmg_type, text_key in self.QUALIFIERS.items()
                    if dmg_type in dbr])

                if damage_types:
                    TQDBParser.insert_value(
                        field_prefixed,
                        f'{texts.get(field_prefixed).format(value)} '
                        f'({damage_types})',
                        result)
                else:
                    # If there is no qualifier, it's all damage:
                    TQDBParser.insert_value(
                        field_prefixed,
                        texts.get(field_prefixed).format(value),
                        result)

        # Prepare two variables to determine the max number of tiers:
        skill_cap = dbr.get('skillUltimateLevel', dbr.get('skillMaxLevel'))
        props = result['properties']

        # The maximum number of properties is now the minimum between the skill
        # cap and the highest number of tiers available in the properties:
        max_tiers = min(
            TQDBParser.highest_tier(props, props.keys()),
            skill_cap)

        # After all skill properties have been set, index them by level:
        properties = [{} for i in range(max_tiers)]

        # Insert the existing properties by adding them to the correct tier:
        for field, values in result['properties'].items():
            for index in range(max_tiers):
                # Each value is either a list or a flat value to repeat:
                if isinstance(values, list):
                    # Properties that are capped before this tier repeat their
                    # last value:
                    if index >= len(values):
                        properties[index][field] = values[len(values) - 1]
                    else:
                        properties[index][field] = values[index]
                else:
                    properties[index][field] = values

        # For summoned skills it's very likely a lot of extraneous empty
        # property tiers were added, filter those out:
        properties = [tier for tier in properties if tier]

        # Now set the reindexed properties:
        result['properties'] = properties
Example #55
0
    def parse_flat(self, field, field_type, dbr):
        """
        Parse a flat increase in an offensive attribute.

        """
        # Prepare some keys that will determine the flat or flat range:
        chance = dbr.get(f'{field}Chance', 0)
        min = dbr.get(f'{field}Min', 0)
        max = dbr.get(f'{field}Max', min)
        is_xor = dbr.get(f'{field}XOR', False)
        is_global = dbr.get(f'{field}Global', False)

        # Optional pre/suffix for chance and duration damage/effects:
        prefix = ''
        suffix = ''

        if field_type == self.MANA:
            # The mana burn suffixes are kind of derpy:
            chance = dbr.get('offensiveManaBurnChance', 0)
            min = dbr.get('offensiveManaBurnDrainMin', 0)
            max = dbr.get('offensiveManaBurnDrainMax', 0)
            is_xor = dbr.get(f'{field}XOR', False)
            ratio = dbr.get('offensiveManaBurnDamageRatio', 0)

            if ratio:
                suffix = texts.get('offensiveManaBurnRatio').format(ratio)

            # Reset the field to what's used in texts.
            field = 'offensiveManaDrain'
        elif field_type == self.DOT:
            duration_min = dbr.get(f'{field}DurationMin', 0)
            duration_max = dbr.get(f'{field}DurationMax', 0)

            if duration_min:
                min *= duration_min
                suffix = texts.get(DOT_SINGLE).format(duration_min)

                if duration_max and max == min:
                    max = min * duration_max
                elif max:
                    max *= duration_min
        elif field_type == self.EOT:
            duration_min = dbr.get(f'{field}DurationMin', 0)

            if duration_min:
                suffix = texts.get(DFT_SINGLE).format(duration_min)

        # Pierce ratio is a singular exception for flat properties:
        if field == 'offensivePierceRatio':
            # Pierce ratio already has its formatter in the flat property:
            # "{0:.0f}% Pierce Ratio" instead of "% Pierce Ratio"
            value = texts.get(field).format(min)
        else:
            # Format the value based on its field type and values:
            value = self.format(field_type, field, min, max)

        if chance and not is_xor:
            prefix = texts.get(CHANCE).format(chance)

        if not is_global:
            # Insert the value normally
            TQDBParser.insert_value(field, f'{prefix}{value}{suffix}',
                                    self.result)
        elif field.startswith('offensive'):
            # Add this field to the global offensive list
            self.offensive[field] = ([
                f'{prefix}{value}{suffix}'
            ] if field not in self.offensive else self.offensive[field] +
                                     [f'{prefix}{value}{suffix}'])
            if is_xor:
                self.offensiveXOR = True
        elif field.startswith('retaliation'):
            # Add this field to the global retaliation list
            self.retaliation[field] = ([
                f'{prefix}{value}{suffix}'
            ] if field not in self.retaliation else self.retaliation[field] +
                                       [f'{prefix}{value}{suffix}'])
            if is_xor:
                self.retaliationXOR = True
Example #56
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
Example #57
0
    def parse(self, dbr, dbr_file, result):
        """
        Parse the base properties of a skill.

        These properties include the skillDisplayName, its friendly display
        name (the property returns a tag), and the maximum level of a skill.

        """
        # Store the path to this skill, it is used in tqdb.storage to ensure
        # all tags are unique.
        result['path'] = dbr_file

        if self.NAME in dbr:
            # The tag is the skillDisplayName property
            result['tag'] = self.FORMATTER.sub('', dbr[self.NAME])

            # Now try to find a friendly name for the tag:
            result['name'] = texts.get(result['tag'])

            if result['name'] == result['tag']:
                # If the tag wasn't returned, a friendly name weas found:
                logging.debug(f'No skill name found for {result["tag"]}')
        else:
            logging.debug(f'No skillDisplayName found in {dbr_file}')

        if self.DESC in dbr and texts.has(dbr[self.DESC]):
            # Also load the description, if it's known:
            result['description'] = texts.get(dbr[self.DESC])
        elif self.FILE in dbr:
            # Use the FileDescription instead:
            result['description'] = dbr['FileDescription']

        # Check the skill base fields:
        base_tiers = TQDBParser.highest_tier(dbr, self.FIELDS)

        for field in self.FIELDS:
            for index in range(base_tiers):
                itr_dbr = TQDBParser.extract_values(dbr, field, index)

                if field not in itr_dbr or itr_dbr[field] <= 0.01:
                    continue

                # Insert this skill property
                value = texts.get(field).format(itr_dbr[field])
                TQDBParser.insert_value(field, value, result)

        # Check the damage absorption skill properties:
        abs_tiers = TQDBParser.highest_tier(dbr, self.ABSORPTIONS)

        for field in self.ABSORPTIONS:
            for index in range(abs_tiers):
                itr_dbr = TQDBParser.extract_values(dbr, field, index)

                if field not in itr_dbr:
                    continue

                # Add 'skill' prefix and capitalize first letter:
                field_prefixed = 'skill' + field[:1].upper() + field[1:]
                value = itr_dbr[field]

                # Find qualifier damage type(s):
                damage_types = ', '.join([
                    texts.get(text_key)
                    for dmg_type, text_key in self.QUALIFIERS.items()
                    if dmg_type in dbr
                ])

                if damage_types:
                    TQDBParser.insert_value(
                        field_prefixed,
                        f'{texts.get(field_prefixed).format(value)} '
                        f'({damage_types})', result)
                else:
                    # If there is no qualifier, it's all damage:
                    TQDBParser.insert_value(
                        field_prefixed,
                        texts.get(field_prefixed).format(value), result)

        # Prepare two variables to determine the max number of tiers:
        skill_cap = dbr.get('skillUltimateLevel',
                            dbr.get('skillMaxLevel')) or 99
        props = result['properties']

        # The maximum number of properties is now the minimum between the skill
        # cap and the highest number of tiers available in the properties:
        max_tiers = min(TQDBParser.highest_tier(props, props.keys()),
                        skill_cap)

        # After all skill properties have been set, index them by level:
        properties = [{} for i in range(max_tiers)]

        # Insert the existing properties by adding them to the correct tier:
        for field, values in result['properties'].items():
            for index in range(max_tiers):
                # Each value is either a list or a flat value to repeat:
                if isinstance(values, list):
                    # Properties that are capped before this tier repeat their
                    # last value:
                    if index >= len(values):
                        properties[index][field] = values[len(values) - 1]
                    else:
                        properties[index][field] = values[index]
                else:
                    properties[index][field] = values

        # For summoned skills it's very likely a lot of extraneous empty
        # property tiers were added, filter those out:
        properties = [tier for tier in properties if tier]

        # Now set the reindexed properties:
        result['properties'] = properties