def __init__(self, ggpk, relational_reader, translation_file_cache): self.relational_reader = relational_reader self.translation_file_cache = translation_file_cache self.gepls = {} for gepl in self.relational_reader['GrantedEffectsPerLevel.dat']: ge_id = gepl['GrantedEffectsKey']['Id'] if ge_id not in self.gepls: self.gepls[ge_id] = [] self.gepls[ge_id].append(gepl) self.tags = {} for tag in self.relational_reader['GemTags.dat']: name = tag['Tag'] self.tags[tag['Id']] = name if name != '' else None self.max_levels = {} for row in self.relational_reader['ItemExperiencePerLevel.dat']: base_item = row['BaseItemTypesKey']['Id'] level = row['ItemCurrentLevel'] if base_item not in self.max_levels: self.max_levels[base_item] = level elif self.max_levels[base_item] < level: self.max_levels[base_item] = level self.max_totem_id = relational_reader['SkillTotems.dat'].table_rows self._skill_totem_life_multipliers = {} for row in self.relational_reader['SkillTotemVariations.dat']: self._skill_totem_life_multipliers[row['SkillTotemsKey'].rowid] = \ row['MonsterVarietiesKey']['LifeMultiplier'] / 100 self.skill_stat_filter = StatFilterFile() self.skill_stat_filter.read(ggpk['Metadata/StatDescriptions/skillpopup_stat_filters.txt'].record.extract())
def __init__(self, file_system, relational_reader): self.relational_reader = relational_reader self.gepls = {} for gepl in self.relational_reader["GrantedEffectsPerLevel.dat"]: ge_id = gepl["GrantedEffectsKey"]["Id"] if ge_id not in self.gepls: self.gepls[ge_id] = [] self.gepls[ge_id].append(gepl) self.granted_effect_quality_stats = {} for geq in self.relational_reader["GrantedEffectQualityStats.dat"]: ge_id = geq["GrantedEffectsKey"]["Id"] if ge_id not in self.granted_effect_quality_stats: self.granted_effect_quality_stats[ge_id] = [] self.granted_effect_quality_stats[ge_id].append(geq) self.tags = {} for tag in self.relational_reader["GemTags.dat"]: name = tag["Tag"] self.tags[tag["Id"]] = name if name != "" else None self.max_levels = {} for row in self.relational_reader["ItemExperiencePerLevel.dat"]: base_item = row["BaseItemTypesKey"]["Id"] level = row["ItemCurrentLevel"] if base_item not in self.max_levels: self.max_levels[base_item] = level elif self.max_levels[base_item] < level: self.max_levels[base_item] = level self.max_totem_id = relational_reader["SkillTotems.dat"].table_rows self._skill_totem_life_multipliers = {} for row in self.relational_reader["SkillTotemVariations.dat"]: self._skill_totem_life_multipliers[row["SkillTotemsKey"].rowid] = ( row["MonsterVarietiesKey"]["LifeMultiplier"] / 100) self.skill_stat_filter = StatFilterFile() self.skill_stat_filter.read( file_system.get_file( "Metadata/StatDescriptions/skillpopup_stat_filters.txt"))
class GemConverter: regex_number = re.compile(r"-?\d+(\.\d+)?") def __init__(self, file_system, relational_reader): self.relational_reader = relational_reader self.gepls = {} for gepl in self.relational_reader["GrantedEffectsPerLevel.dat"]: ge_id = gepl["GrantedEffectsKey"]["Id"] if ge_id not in self.gepls: self.gepls[ge_id] = [] self.gepls[ge_id].append(gepl) self.granted_effect_quality_stats = {} for geq in self.relational_reader["GrantedEffectQualityStats.dat"]: ge_id = geq["GrantedEffectsKey"]["Id"] if ge_id not in self.granted_effect_quality_stats: self.granted_effect_quality_stats[ge_id] = [] self.granted_effect_quality_stats[ge_id].append(geq) self.tags = {} for tag in self.relational_reader["GemTags.dat"]: name = tag["Tag"] self.tags[tag["Id"]] = name if name != "" else None self.max_levels = {} for row in self.relational_reader["ItemExperiencePerLevel.dat"]: base_item = row["BaseItemTypesKey"]["Id"] level = row["ItemCurrentLevel"] if base_item not in self.max_levels: self.max_levels[base_item] = level elif self.max_levels[base_item] < level: self.max_levels[base_item] = level self.max_totem_id = relational_reader["SkillTotems.dat"].table_rows self._skill_totem_life_multipliers = {} for row in self.relational_reader["SkillTotemVariations.dat"]: self._skill_totem_life_multipliers[row["SkillTotemsKey"].rowid] = ( row["MonsterVarietiesKey"]["LifeMultiplier"] / 100) self.skill_stat_filter = StatFilterFile() self.skill_stat_filter.read( file_system.get_file( "Metadata/StatDescriptions/skillpopup_stat_filters.txt")) def _convert_active_skill(self, active_skill): stat_conversions = {} for in_stat, out_stat in zip(active_skill["Input_StatKeys"], active_skill["Output_StatKeys"]): stat_conversions[in_stat["Id"]] = out_stat["Id"] is_skill_totem = active_skill["SkillTotemId"] <= self.max_totem_id r = { "id": active_skill["Id"], "display_name": active_skill["DisplayedName"], "description": active_skill["Description"], "types": self._select_active_skill_types(active_skill["ActiveSkillTypes"]), "weapon_restrictions": [ ic["Id"] for ic in active_skill["WeaponRestriction_ItemClassesKeys"] ], "is_skill_totem": is_skill_totem, "is_manually_casted": active_skill["IsManuallyCasted"], "stat_conversions": stat_conversions, } if is_skill_totem: r["skill_totem_life_multiplier"] = self._skill_totem_life_multipliers[ active_skill["SkillTotemId"] - 1] if active_skill["MinionActiveSkillTypes"]: r["minion_types"] = self._select_active_skill_types( active_skill["MinionActiveSkillTypes"]) return r @classmethod def _convert_support_gem_specific(cls, granted_effect): return { "letter": granted_effect["SupportGemLetter"], "supports_gems_only": granted_effect["SupportsGemsOnly"], "allowed_types": cls._select_active_skill_types( granted_effect["AllowedActiveSkillTypes"]), "excluded_types": cls._select_active_skill_types( granted_effect["ExcludedActiveSkillTypes"]), "added_types": cls._select_active_skill_types( granted_effect["AddedActiveSkillTypes"]), } @staticmethod def _select_active_skill_types(type_ids): return [ActiveSkillType(t).name for t in type_ids] def _convert_gepl(self, gepl, multipliers, is_support): r = { "required_level": gepl["LevelRequirement"], } if gepl["Cooldown"] > 0: r["cooldown"] = gepl["Cooldown"] cooldown_bypass_type = CooldownBypassType( gepl["CooldownBypassType"]) if cooldown_bypass_type is not CooldownBypassType.none: r["cooldown_bypass_type"] = cooldown_bypass_type.name if gepl["StoredUses"] > 0: r["stored_uses"] = gepl["StoredUses"] if is_support: r["mana_multiplier"] = gepl["ManaMultiplier"] else: r["mana_cost"] = gepl["ManaCost"] if gepl["DamageEffectiveness"] != 0: r["damage_effectiveness"] = gepl["DamageEffectiveness"] if gepl["DamageMultiplier"] != 0: r["damage_multiplier"] = gepl["DamageMultiplier"] if gepl["CriticalStrikeChance"] > 0: r["crit_chance"] = gepl["CriticalStrikeChance"] if gepl["AttackSpeedMultiplier"] != 0: r["attack_speed_multiplier"] = gepl["AttackSpeedMultiplier"] if gepl["VaalSouls"] > 0: r["vaal"] = { "souls": gepl["VaalSouls"], "stored_uses": gepl["VaalStoredUses"] } mana_reservation_override = gepl["ManaReservationOverride"] if mana_reservation_override > 0: r["mana_reservation_override"] = mana_reservation_override stats = [] for k, v in gepl["Stats"]: stats.append({"id": k["Id"], "value": v}) for k in gepl["StatsKeys2"]: stats.append({"id": k["Id"], "value": 1}) r["stats"] = stats q_stats = [] if gepl["GrantedEffectsKey"][ "Id"] in self.granted_effect_quality_stats: for geq in self.granted_effect_quality_stats[ gepl["GrantedEffectsKey"]["Id"]]: for k, v in zip(geq["StatsKeys"], geq["StatsValuesPermille"]): q_stats.append({ "id": k["Id"], "value": v, "set": geq["SetId"] }) r["quality_stats"] = q_stats if multipliers is not None: stat_requirements = {} gtype = GemTypes.support if is_support else GemTypes.active for stat_type, multi in multipliers.items(): if multi == 0 or multi == 33 or multi == 34 or multi == 50: # 33 and 34 are from white gems (Portal, Vaal Breach, Detonate Mine), which have no requirements req = 0 elif multi == 50: # 50 is from SupportTutorial ("Lesser Reduced Mana Cost Support"), for which I # have no idea what the requirements are. print("Unknown multiplier (50) for " + gepl["GrantedEffectsKey"]["Id"]) req = 0 else: req = gem_stat_requirement(gepl["LevelRequirement"], gtype, multi) stat_requirements[stat_type] = req r["stat_requirements"] = stat_requirements return r def _convert_base_item_specific(self, base_item_type, obj): if base_item_type is None: obj["base_item"] = None return obj["base_item"] = { "id": base_item_type["Id"], "display_name": base_item_type["Name"], "release_state": get_release_state(base_item_type["Id"]).name, } key = SkillParserShared._SKILL_ID_TO_PROJECTILE_MAP.get( base_item_type["Name"]) if key: obj["projectile_speed"] = self.relational_reader[ "Projectiles.dat"].index["Id"]["Metadata/Projectiles/" + key]["ProjectileSpeed"] def convert(self, base_item_type, granted_effect, secondary_granted_effect, gem_tags, multipliers): is_support = granted_effect["IsSupport"] obj = {"is_support": is_support} if gem_tags is None: obj["tags"] = None else: obj["tags"] = [tag["Id"] for tag in gem_tags] if is_support: obj["support_gem"] = self._convert_support_gem_specific( granted_effect) else: obj["cast_time"] = granted_effect["CastTime"] obj["active_skill"] = self._convert_active_skill( granted_effect["ActiveSkillsKey"]) game_file_name = self._get_translation_file_name( obj.get("active_skill")) obj["stat_translation_file"] = STAT_TRANSLATION_DICT[game_file_name] self._convert_base_item_specific(base_item_type, obj) if secondary_granted_effect: obj["secondary_granted_effect"] = secondary_granted_effect["Id"] # GrantedEffectsPerLevel gepls = self.gepls[granted_effect["Id"]] gepls.sort(key=lambda g: g["Level"]) gepls_dict = {} for gepl in gepls: gepl_converted = self._convert_gepl(gepl, multipliers, is_support) gepls_dict[gepl["Level"]] = gepl_converted obj["per_level"] = gepls_dict # GrantedEffectsPerLevel that do not change with level # makes using the json harder, but makes the json *a lot* smaller (would be like 3 times larger) obj["static"] = {} if len(gepls) >= 1: representative = gepls_dict[gepls[0]["Level"]] static, _ = _handle_dict(representative, gepls_dict.values()) if static is not None: obj["static"] = static return obj @staticmethod def _normalize_stat_arrays(values): # normalize arrays for each level so they all contain the same stats (set to None if missing for a level) i = 0 while i < max(len(pl["stats"]) for pl in values): id_map = {None: 0} for pl in values: stats = pl["stats"] if i >= len(stats): stats.append(None) if stats[i] is None: id_map[None] += 1 continue if stats[i]["id"] not in id_map: id_map[stats[i]["id"]] = 1 else: id_map[stats[i]["id"]] += 1 if (id_map[None] > 0 and len(id_map) > 1) or len(id_map) > 2: # Not all are the same stat. # Take the most often occurring stat except None and insert None when pl has a # different stat. del id_map[None] taken = max(id_map, key=lambda k: id_map[k]) taken_text = None for pl in values: stats = pl["stats"] if stats[i] is not None: if stats[i]["id"] != taken: stats.insert(i, None) else: taken_text = stats[i]["text"] for pl in values: stats = pl["stats"] if stats[i] is None: stats[i] = { "id": taken, "text": taken_text, "values": None } i += 1 def _get_translation_file_name(self, active_skill): if active_skill is None: return "gem_stat_descriptions.txt" stat_filter_group = self.skill_stat_filter.skills.get( active_skill["id"]) if stat_filter_group is not None: return stat_filter_group.translation_file_path.replace( "Metadata/StatDescriptions/", "") else: return "skill_stat_descriptions.txt"
class GemConverter: regex_number = re.compile(r'-?\d+(\.\d+)?') def __init__(self, ggpk, relational_reader, translation_file_cache): self.relational_reader = relational_reader self.translation_file_cache = translation_file_cache self.gepls = {} for gepl in self.relational_reader['GrantedEffectsPerLevel.dat']: ge_id = gepl['GrantedEffectsKey']['Id'] if ge_id not in self.gepls: self.gepls[ge_id] = [] self.gepls[ge_id].append(gepl) self.tags = {} for tag in self.relational_reader['GemTags.dat']: name = tag['Tag'] self.tags[tag['Id']] = name if name != '' else None self.max_levels = {} for row in self.relational_reader['ItemExperiencePerLevel.dat']: base_item = row['BaseItemTypesKey']['Id'] level = row['ItemCurrentLevel'] if base_item not in self.max_levels: self.max_levels[base_item] = level elif self.max_levels[base_item] < level: self.max_levels[base_item] = level self.max_totem_id = relational_reader['SkillTotems.dat'].table_rows self._skill_totem_life_multipliers = {} for row in self.relational_reader['SkillTotemVariations.dat']: self._skill_totem_life_multipliers[row['SkillTotemsKey'].rowid] = \ row['MonsterVarietiesKey']['LifeMultiplier'] / 100 self.skill_stat_filter = StatFilterFile() self.skill_stat_filter.read(ggpk['Metadata/StatDescriptions/skillpopup_stat_filters.txt'].record.extract()) def _convert_active_skill(self, active_skill): stat_conversions = {} for in_stat, out_stat in zip(active_skill['Input_StatKeys'], active_skill['Output_StatKeys']): stat_conversions[in_stat['Id']] = out_stat['Id'] is_skill_totem = (active_skill['SkillTotemId'] <= self.max_totem_id) r = { 'id': active_skill['Id'], 'display_name': active_skill['DisplayedName'], 'description': active_skill['Description'], 'types': self._select_active_skill_types(active_skill['ActiveSkillTypes']), 'weapon_restrictions': [ic['Id'] for ic in active_skill['WeaponRestriction_ItemClassesKeys']], 'is_skill_totem': is_skill_totem, 'is_manually_casted': active_skill['IsManuallyCasted'], 'stat_conversions': stat_conversions } if is_skill_totem: r['skill_totem_life_multiplier'] = self._skill_totem_life_multipliers[active_skill['SkillTotemId'] - 1] if active_skill['MinionActiveSkillTypes']: r['minion_types'] = self._select_active_skill_types(active_skill['MinionActiveSkillTypes']) return r @classmethod def _convert_support_gem_specific(cls, granted_effect): return { 'letter': granted_effect['SupportGemLetter'], 'supports_gems_only': granted_effect['SupportsGemsOnly'], 'allowed_types': cls._select_active_skill_types(granted_effect['AllowedActiveSkillTypes']), 'excluded_types': cls._select_active_skill_types(granted_effect['ExcludedActiveSkillTypes']), 'added_types': cls._select_active_skill_types(granted_effect['AddedActiveSkillTypes']), } @staticmethod def _select_active_skill_types(type_ids): return [ActiveSkillType(t).name for t in type_ids] def _convert_gepl(self, gepl, multipliers, is_support): r = { 'required_level': gepl['LevelRequirement'], } if gepl['Cooldown'] > 0: r['cooldown'] = gepl['Cooldown'] cooldown_bypass_type = CooldownBypassType(gepl['CooldownBypassType']) if cooldown_bypass_type is not CooldownBypassType.none: r['cooldown_bypass_type'] = cooldown_bypass_type.name if gepl['StoredUses'] > 0: r['stored_uses'] = gepl['StoredUses'] if is_support: r['mana_multiplier'] = gepl['ManaMultiplier'] else: r['mana_cost'] = gepl['ManaCost'] if gepl['DamageEffectiveness'] != 0: r['damage_effectiveness'] = gepl['DamageEffectiveness'] if gepl['DamageMultiplier'] != 0: r['damage_multiplier'] = gepl['DamageMultiplier'] if gepl['CriticalStrikeChance'] > 0: r['crit_chance'] = gepl['CriticalStrikeChance'] if gepl['VaalSouls'] > 0: r['vaal'] = { 'souls': gepl['VaalSouls'], 'stored_uses': gepl['VaalStoredUses'] } mana_reservation_override = gepl['ManaReservationOverride'] if mana_reservation_override > 0: r['mana_reservation_override'] = mana_reservation_override stats = [] for k, v in gepl['Stats']: stats.append({ 'id': k['Id'], 'value': v }) for k in gepl['StatsKeys2']: stats.append({ 'id': k['Id'], 'value': 1 }) r['stats'] = stats q_stats = [] for k, v in gepl['QualityStats']: q_stats.append({ 'id': k['Id'], 'value': v }) r['quality_stats'] = q_stats if multipliers is not None: stat_requirements = {} gtype = GemTypes.support if is_support else GemTypes.active for stat_type, multi in multipliers.items(): if multi == 0 or multi == 33 or multi == 34 or multi == 50: # 33 and 34 are from white gems (Portal, Vaal Breach, Detonate Mine), which have no requirements req = 0 elif multi == 50: # 50 is from SupportTutorial ("Lesser Reduced Mana Cost Support"), for which I # have no idea what the requirements are. print("Unknown multiplier (50) for " + gepl['GrantedEffectsKey']['Id']) req = 0 else: req = gem_stat_requirement(gepl['LevelRequirement'], gtype, multi) stat_requirements[stat_type] = req r['stat_requirements'] = stat_requirements return r def _convert_base_item_specific(self, base_item_type, obj): if base_item_type is None: obj['base_item'] = None return obj['base_item'] = { 'id': base_item_type['Id'], 'display_name': base_item_type['Name'], 'release_state': get_release_state(base_item_type['Id']).name, } key = SkillParserShared._SKILL_ID_TO_PROJECTILE_MAP.get(base_item_type['Name']) if key: obj['projectile_speed'] = \ self.relational_reader['Projectiles.dat'].index['Id']['Metadata/Projectiles/' + key]['ProjectileSpeed'] def convert(self, base_item_type, granted_effect, secondary_granted_effect, gem_tags, multipliers): is_support = granted_effect['IsSupport'] obj = { 'is_support': is_support } if gem_tags is None: obj['tags'] = None else: obj['tags'] = [tag['Id'] for tag in gem_tags] if is_support: obj['support_gem'] = self._convert_support_gem_specific(granted_effect) else: obj['cast_time'] = granted_effect['CastTime'] obj['active_skill'] = self._convert_active_skill(granted_effect['ActiveSkillsKey']) game_file_name = self._get_translation_file_name(obj.get('active_skill')) obj['stat_translation_file'] = STAT_TRANSLATION_DICT[game_file_name] self._convert_base_item_specific(base_item_type, obj) if secondary_granted_effect: obj['secondary_granted_effect'] = secondary_granted_effect['Id'] # GrantedEffectsPerLevel gepls = self.gepls[granted_effect['Id']] gepls.sort(key=lambda g: g['Level']) gepls_dict = {} for gepl in gepls: gepl_converted = self._convert_gepl(gepl, multipliers, is_support) gepls_dict[gepl['Level']] = gepl_converted obj['per_level'] = gepls_dict tp_per_level = {level: self._to_tooltip(obj, level, gepls_dict.values(), for_level) for level, for_level in gepls_dict.items()} tooltip = { 'per_level': tp_per_level } if len(gepls) > 0: self._normalize_stat_arrays(tp_per_level.values()) # 'id' was only there for normalizing the arrays and is not needed for the tooltip for pl in tp_per_level.values(): stats = pl['stats'] for i in range(len(stats)): del stats[i]['id'] # GrantedEffectsPerLevel that do not change with level # makes using the json harder, but makes the json *a lot* smaller (would be like 3 times larger) obj['static'] = {} tooltip['static'] = {} if len(gepls) >= 1: representative = gepls_dict[gepls[0]['Level']] static, _ = _handle_dict(representative, gepls_dict.values()) if static is not None: obj['static'] = static representative = next(iter(tooltip['per_level'].values())) static, _ = _handle_dict(representative, tp_per_level.values()) if static is not None: tooltip['static'] = static for stats in (pl['stats'] for pl in tp_per_level.values() if 'stats' in pl): for i in range(len(stats)): if stats[i] is not None and 'values' in stats[i] and stats[i]['values'] is None: del stats[i]['values'] return obj, tooltip @staticmethod def _normalize_stat_arrays(values): # normalize arrays for each level so they all contain the same stats (set to None if missing for a level) i = 0 while i < max(len(pl['stats']) for pl in values): id_map = {None: 0} for pl in values: stats = pl['stats'] if i >= len(stats): stats.append(None) if stats[i] is None: id_map[None] += 1 continue if stats[i]['id'] not in id_map: id_map[stats[i]['id']] = 1 else: id_map[stats[i]['id']] += 1 if (id_map[None] > 0 and len(id_map) > 1) or len(id_map) > 2: # Not all are the same stat. # Take the most often occurring stat except None and insert None when pl has a # different stat. del id_map[None] taken = max(id_map, key=lambda k: id_map[k]) taken_text = None for pl in values: stats = pl['stats'] if stats[i] is not None: if stats[i]['id'] != taken: stats.insert(i, None) else: taken_text = stats[i]['text'] for pl in values: stats = pl['stats'] if stats[i] is None: stats[i] = { 'id': taken, 'text': taken_text, 'values': None } i += 1 def _to_tooltip(self, gem, level, all_levels, for_level): has_base_item = gem.get('base_item') is not None has_active_skill = gem.get('active_skill') is not None # name if has_base_item: name = gem['base_item']['display_name'] elif has_active_skill: name = gem['active_skill']['display_name'] else: name = "Unknown" properties = [] # tags if gem['tags'] is not None: ts = (self.tags[t] for t in gem['tags'] if self.tags[t] is not None) properties.append(", ".join(ts)) else: properties.append("") # level p = "Level: {0}" if has_base_item: max_level = self.max_levels.get(gem['base_item']['id']) if max_level is None or level >= max_level: p += " (Max)" properties.append({ "text": p, "value": level }) if 'mana_multiplier' in for_level \ and any(l['mana_multiplier'] != 100 for l in all_levels): properties.append({ "text": "Mana Multiplier: {0}%", "value": for_level['mana_multiplier'] }) if 'mana_cost' in for_level and for_level['mana_cost'] > 0: p = "Mana Cost: {0}" if has_active_skill: types = gem['active_skill']['types'] if ActiveSkillType.mana_cost_is_reservation.name in types \ and ActiveSkillType.totem.name not in types: p = "Mana Reserved: {0}" if ActiveSkillType.mana_cost_is_percentage.name in types: p += "%" properties.append({ "text": p, "value": for_level['mana_cost'] }) if 'mana_reservation_override' in for_level: properties.append({ "text": "Mana Reservation Override: {0}%", "value": for_level['mana_reservation_override'] }) if 'vaal' in for_level: properties.append({ "text": "Souls Per Use: {0}", "value": for_level['vaal']['souls'] }) properties.append({ "text": "Can Store {0} Use", "value": for_level['vaal']['stored_uses'] }) if 'cooldown' in for_level: p = "Cooldown Time: {0} sec" vs = [float(for_level['cooldown'] / 1000)] if 'stored_uses' in for_level and for_level['stored_uses'] > 1: p += " ({1} uses)" vs.append(for_level['stored_uses']) properties.append({ "text": p, "values": vs }) if 'cast_time' in gem: properties.append({ "text": "Cast Time: {0} sec", "value": float(gem['cast_time'] / 1000) }) if 'crit_chance' in for_level: properties.append({ "text": "Critical Strike Chance: {0}%", "value": float(for_level['crit_chance'] / 100) }) if any('damage_effectiveness' in l for l in all_levels): damage_effectiveness = for_level['damage_effectiveness'] \ if 'damage_effectiveness' in for_level else 0 properties.append({ "text": "Damage Effectiveness: {0}%", "value": damage_effectiveness + 100 }) requirements = [] p = "Requires Level {0}" vs = [for_level['required_level']] if 'stat_requirements' in for_level: i = 1 sr = for_level['stat_requirements'] if 'str' in sr and sr['str'] > 0: p += ", {%i} Str" % i vs.append(sr['str']) i += 1 if 'dex' in sr and sr['dex'] > 0: p += ", {%i} Dex" % i vs.append(sr['dex']) i += 1 if 'int' in sr and sr['int'] > 0: p += ", {%i} Int" % i vs.append(sr['int']) i += 1 requirements.append({ "text": p, "values": vs }) description = [] if has_active_skill and gem['active_skill']['description'] is not None: description.append(gem['active_skill']['description']) tf = self.translation_file_cache[self._get_translation_file_name(gem.get('active_skill'))] stats = [] if any('damage_multiplier' in l for l in all_levels): damage_multiplier = for_level['damage_multiplier'] if 'damage_multiplier' in for_level else 0 stats.append({ "id": ' ', # spaces can't appear in stat ids "text": "Deals {0}% of Base Attack Damage", "value": (damage_multiplier / 100) + 100 }) stats.extend(self._tooltip_stats(tf, for_level['stats'], are_quality_stats=False)) quality_stats = list(self._tooltip_stats(tf, for_level['quality_stats'], are_quality_stats=True)) return { 'name': name, 'properties': properties, 'requirements': requirements, 'description': description, 'stats': stats, 'quality_stats': quality_stats } def _get_translation_file_name(self, active_skill): if active_skill is None: return 'gem_stat_descriptions.txt' stat_filter_group = self.skill_stat_filter.skills.get(active_skill['id']) if stat_filter_group is not None: return stat_filter_group.translation_file_path.replace('Metadata/StatDescriptions/', '') else: return 'skill_stat_descriptions.txt' def _tooltip_stats(self, tf, for_level, are_quality_stats): divisor = 50 if are_quality_stats else 1 tr = tf.get_translation( tags=[s['id'] for s in for_level], values=[s['value'] / divisor for s in for_level], full_result=True ) divisor = 20 if are_quality_stats else 1 for i, line in enumerate(tr.lines): line = line.strip() vs = [] for match in self.regex_number.finditer(line): string = match.group(0) if '.' in string or divisor != 1: vs.append(float(string) / divisor) else: vs.append(int(string)) match_i = [-1] def repl(_): match_i[0] += 1 return "{%d}" % match_i[0] line = self.regex_number.sub(repl, line) stat = { "text": line, "values": vs } if not are_quality_stats: stat["id"] = ','.join(tr.found_ids[i]) yield stat