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_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 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_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(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): """ 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_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 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
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