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)
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)
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}'] )
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
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}'])
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)
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)
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)
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
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)
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
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)
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
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
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
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)
def parse(self, dbr, dbr_file, result): """ Parse an offensive/retaliation field. For each field, depending on the group it's in, this function will need to check both the absolute (flat increase) and modifier (% increase) versions as well as possible effects or damage durations. Args: field (str): Field name, as listed in the FIELDS list """ # Set the result and prepare the global stores: self.result = result self.offensive = {} self.offensiveXOR = False self.retaliation = {} self.retaliationXOR = False for field, field_type in self.FIELDS.items(): # Find whether the flat, modifier, or both fields are present: min = (f'{field}Min' if field != 'offensiveManaBurn' # Mana burn is the only field that differs from the rest else 'offensiveManaBurnDrainMin') mod = f'{field}Modifier' iterations = max( len(dbr[min]) if min in dbr else 0, len(dbr[mod]) if mod in dbr else 0, 0) # Now iterate as many times as is necessary for this field: for index in range(iterations): # Create a new copy of the DBR with the values for this index: iteration = TQDBParser.extract_values(dbr, field, index) if min in iteration: # Parse the flat (+...) version: self.parse_flat(field, field_type, iteration) if mod in iteration: # Parse the modifier (+...%) version self.parse_modifier(field, field_type, iteration) # Now add the global chance tags if they're set: offensive_key = 'offensiveGlobalChance' if offensive_key in dbr and self.offensive: # Skip 0 chance globals altogether chances = [chance for chance in dbr[offensive_key] if chance] for index, chance in enumerate(chances): self.parse_global( offensive_key, # The global chance for the offensive properties chance, # If any global offensive properties are XOR-ed: self.offensiveXOR, # The dictionary of global offensive properties self.offensive, # Index of this global chance index) retaliation_key = 'retaliationGlobalChance' if retaliation_key in dbr and self.retaliation: # Skip 0 chance globals altogether chances = [chance for chance in dbr[retaliation_key] if chance] for index, chance in enumerate(chances): self.parse_global( retaliation_key, # The global chance for the offensive properties chance, # If any global offensive properties are XOR-ed: self.retaliationXOR, # The dictionary of global offensive properties self.retaliation, # Index of this global chance index)
def parse(self, dbr, dbr_file, result): """ Parse an offensive/retaliation field. For each field, depending on the group it's in, this function will need to check both the absolute (flat increase) and modifier (% increase) versions as well as possible effects or damage durations. Args: field (str): Field name, as listed in the FIELDS list """ # Set the result and prepare the global stores: self.result = result self.offensive = {} self.offensiveXOR = False self.retaliation = {} self.retaliationXOR = False for field, field_type in self.FIELDS.items(): # Find whether the flat, modifier, or both fields are present: min = ( f'{field}Min' if field != 'offensiveManaBurn' # Mana burn is the only field that differs from the rest else 'offensiveManaBurnDrainMin') mod = f'{field}Modifier' iterations = max( len(dbr[min]) if min in dbr else 0, len(dbr[mod]) if mod in dbr else 0, 0) # Now iterate as many times as is necessary for this field: for index in range(iterations): # Create a new copy of the DBR with the values for this index: iteration = TQDBParser.extract_values(dbr, field, index) if min in iteration: # Parse the flat (+...) version: self.parse_flat(field, field_type, iteration) if mod in iteration: # Parse the modifier (+...%) version self.parse_modifier(field, field_type, iteration) # Now add the global chance tags if they're set: offensive_key = 'offensiveGlobalChance' if offensive_key in dbr and self.offensive: # Skip 0 chance globals altogether chances = [chance for chance in dbr[offensive_key] if chance] for index, chance in enumerate(chances): self.parse_global( offensive_key, # The global chance for the offensive properties chance, # If any global offensive properties are XOR-ed: self.offensiveXOR, # The dictionary of global offensive properties self.offensive, # Index of this global chance index) retaliation_key = 'retaliationGlobalChance' if retaliation_key in dbr and self.retaliation: # Skip 0 chance globals altogether chances = [chance for chance in dbr[retaliation_key] if chance] for index, chance in enumerate(chances): self.parse_global( retaliation_key, # The global chance for the offensive properties chance, # If any global offensive properties are XOR-ed: self.retaliationXOR, # The dictionary of global offensive properties self.retaliation, # Index of this global chance index)
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
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
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