From 363e5879e13f1df3a7527f13d97bf5a957c64578 Mon Sep 17 00:00:00 2001 From: Wires77 Date: Thu, 3 Mar 2022 01:31:13 -0600 Subject: [PATCH] Adding script to parse additional timeless jewel notables --- .../wiki/parsers/alternatePassives.py | 372 ++++++++++++++++++ PyPoE/poe/file/specification/data/stable.py | 10 +- 2 files changed, 377 insertions(+), 5 deletions(-) create mode 100644 PyPoE/cli/exporter/wiki/parsers/alternatePassives.py diff --git a/PyPoE/cli/exporter/wiki/parsers/alternatePassives.py b/PyPoE/cli/exporter/wiki/parsers/alternatePassives.py new file mode 100644 index 00000000..be0c9eaa --- /dev/null +++ b/PyPoE/cli/exporter/wiki/parsers/alternatePassives.py @@ -0,0 +1,372 @@ +""" +Overview +=============================================================================== + ++----------+------------------------------------------------------------------+ +| Path | PyPoE/cli/exporter/wiki/parsers/passives.py | ++----------+------------------------------------------------------------------+ +| Version | 1.0.0a0 | ++----------+------------------------------------------------------------------+ +| Revision | $Id$ | ++----------+------------------------------------------------------------------+ +| Author | Omega_K2 | ++----------+------------------------------------------------------------------+ + +Description +=============================================================================== + + + +Agreement +=============================================================================== + +See PyPoE/LICENSE + +Documentation +=============================================================================== + +Public API +------------------------------------------------------------------------------- + +Internal API +------------------------------------------------------------------------------- +""" + +# ============================================================================= +# Imports +# ============================================================================= + +# Python +import re +import os.path +import warnings +from functools import partialmethod +from collections import OrderedDict + +# 3rd-party + +# self +from PyPoE.cli.core import console, Msg +from PyPoE.cli.exporter.wiki import parser +from PyPoE.cli.exporter.wiki.handler import ExporterHandler, ExporterResult +from PyPoE.poe.file.psg import PSGFile + +# ============================================================================= +# Globals +# ============================================================================= + +__all__ = [] + + +# ============================================================================= +# Classes +# ============================================================================= + + +class WikiCondition(parser.WikiCondition): + COPY_KEYS = ( + 'main_page', + ) + + NAME = 'Passive skill' + ADD_INCLUDE = False + INDENT = 36 + + +class AlternatePassiveSkillCommandHandler(ExporterHandler): + def __init__(self, sub_parser): + self.parser = sub_parser.add_parser( + 'alt-passive', + help='Passive skill exporter', + ) + self.parser.set_defaults(func=lambda args: self.parser.print_help()) + + self.add_default_subparser_filters( + sub_parser=self.parser.add_subparsers(), + cls=AlternatePassiveSkillParser, + ) + + # filtering + '''a_filter = sub.add_parser( + 'filter', + help='Extract passives using filters.' + ) + self.add_default_parsers( + parser=a_filter, + cls=PassiveSkillParser, + func=PassiveSkillParser.by_filter, + ) + + a_filter.add_argument( + '-ft-id', '--filter-id', '--filter-metadata-id', + help='Regular expression on the id', + type=str, + dest='re_id', + )''' + + def add_default_parsers(self, *args, **kwargs): + super().add_default_parsers(*args, **kwargs) + self.add_format_argument(kwargs['parser']) + self.add_image_arguments(kwargs['parser']) + kwargs['parser'].add_argument( + '-ft-id', '--filter-id', '--filter-metadata-id', + help='Regular expression on the id', + type=str, + dest='re_id', + ) + + +class AlternatePassiveSkillParser(parser.BaseParser): + _files = [ + 'AlternatePassiveSkills.dat', + ] + + _passive_column_index_filter = partialmethod( + parser.BaseParser._column_index_filter, + dat_file_name='AlternatePassiveSkills.dat', + error_msg='Several passives have not been found:\n%s', + ) + + _MAX_STAT_ID = 4 + + _COPY_KEYS = OrderedDict(( + ('Id', { + 'template': 'id', + }), + ('Name', { + 'template': 'name', + }), + ('FlavourText', { + 'template': 'flavour_text', + 'default': '', + }), + ('AlternateTreeVersionsKey', { + 'template': 'jewel_type', + 'format': lambda value: [x for x in value][0], + 'condition': lambda passive: passive['AlternateTreeVersionsKey'] + }), + )) + + def _apply_filter(self, parsed_args, passives): + if parsed_args.re_id: + parsed_args.re_id = re.compile(parsed_args.re_id, flags=re.UNICODE) + else: + return passives + + new = [] + + for passive in passives: + if parsed_args.re_id and not \ + parsed_args.re_id.match(passive['Id']): + continue + + new.append(passive) + + return new + + def by_rowid(self, parsed_args): + return self.export( + parsed_args, + self.rr['AlternatePassiveSkills.dat'][parsed_args.start:parsed_args.end], + ) + + def by_id(self, parsed_args): + return self.export(parsed_args, self._passive_column_index_filter( + column_id='Id', arg_list=parsed_args.id + )) + + def by_name(self, parsed_args): + return self.export(parsed_args, self._passive_column_index_filter( + column_id='Name', arg_list=parsed_args.name + )) + + def export(self, parsed_args, passives): + r = ExporterResult() + + passives = self._apply_filter(parsed_args, passives) + + console(f'Found {len(passives)} passives. Removing Royale passives...') + passives = [ + passive for passive in passives + if not passive['Id'].startswith('royale') + ] + console(f'{len(passives)} passives left for processing.') + + if not passives: + console( + 'No passives found for the specified parameters. Quitting.', + msg=Msg.warning, + ) + return r + + console('Accessing additional data...') + + psg = PSGFile() + psg.read( + file_path_or_raw=self.file_system.get_file( + 'Metadata/PassiveSkillGraph.psg' + ), + ) + + node_index = {} + for group in psg.groups: + for node in group.nodes: + node_index[node.passive_skill] = node + # Connections are one-way, make them two way + for psg_id, node in node_index.items(): + for other_psg_id in node.connections: + node_index[other_psg_id].connections.append(psg_id) + + self._image_init(parsed_args) + + console('Found %s, parsing...' % len(passives)) + + for passive in passives: + data = OrderedDict() + + # Copy over simple fields from the .dat + for row_key, copy_data in self._COPY_KEYS.items(): + value = passive[row_key] + + condition = copy_data.get('condition') + if condition is not None and not condition(passive): + continue + + # Skip default values to reduce size of template + if value == copy_data.get('default'): + continue + + fmt = copy_data.get('format') + if fmt: + value = fmt(value) + data[copy_data['template']] = value + + # Flag if it's an atlas skill + if passive['Id'].startswith('atlas'): + data['is_atlas_passive'] = True + + for i in range(len(passive['PassiveType'])): + if passive['PassiveType'][i] == 4: + data['is_keystone'] = True + elif passive['PassiveType'][i] == 3: + data['is_notable'] = True + + data['int_id'] = 0 + + # Handle icon paths + if passive['Icon_DDSFile']: + icon = passive['Icon_DDSFile'].split('/') + if passive['Icon_DDSFile'].startswith( + 'Art/2DArt/SkillIcons/passives/'): + if icon[-2] == 'passives': + data['icon'] = icon[-1] + else: + data['icon'] = '%s (%s)' % (icon[-1], icon[-2]) + else: + data['icon'] = icon[-1] + # atlas_start_node doesn't have an icon path + else: + data['icon'] = '' + warnings.warn(f"Icon path file not found for {passive['Id']}: {passive['Name']}") + + data['icon'] = data['icon'].replace('.dds', '') + + # Handle Stats + stat_ids = [] + values = [] + + j = 0 + for i in range(0, self._MAX_STAT_ID): + try: + stat = passive['StatsKeys'][i] + except IndexError: + break + j = i + 1 + stat_ids.append(stat['Id']) + data['stat%s_id' % j] = stat['Id'] + values.append(passive['Stat%sValue' % j]) + data['stat%s_value' % j] = passive['Stat%sValue' % j] + + data['stat_text'] = '
'.join(self._get_stats( + stat_ids, values, + translation_file=get_translation_file(passive['Id']) + )) + + # For now this is being added to the stat text + # for ps_buff in passive['PassiveSkillBuffsKeys']: + # stat_ids = [stat['Id'] for stat in + # ps_buff['BuffDefinitionsKey']['StatsKeys']] + # values = ps_buff['Buff_StatValues'] + # # if passive['Id'] == 'AscendancyChampion7': + # # index = stat_ids.index('damage_taken_+%_from_hits') + # # del stat_ids[index] + # # del values[index] + # for i, (sid, val) in enumerate(zip(stat_ids, values)): + # j += 1 + # data['stat%s_id' % j] = sid + # data['stat%s_value' % j] = val + # + # text = '
'.join(self._get_stats( + # stat_ids, values, + # translation_file='passive_skill_aura_stat_descriptions.txt' + # )) + # + # if data['stat_text']: + # data['stat_text'] += '
' + text + # else: + # data['stat_text'] = text + + # node = node_index.get(passive['PassiveSkillGraphId']) + # if node and node.connections: + # data['connections'] = ','.join([ + # self.rr['AlternatePassiveSkills.dat'].index['PassiveSkillGraphId'][ + # psg_id]['Id'] for psg_id in node.connections]) + + # extract icons if specified + if parsed_args.store_images and data['icon'] != '': + fn = data['icon'] + ' passive skill icon' + dds = os.path.join(self._img_path, fn + '.dds') + png = os.path.join(self._img_path, fn + '.png') + if not (os.path.exists(dds) or os.path.exists(png)): + self._write_dds( + data=self.file_system.get_file(passive['Icon_DDSFile']), + out_path=dds, + parsed_args=parsed_args, + ) + + cond = WikiCondition( + data=data, + cmdargs=parsed_args, + ) + + r.add_result( + text=cond, + out_file='passive_skill_%s.txt' % data['id'], + wiki_page=[ + { + 'page': 'Passive Skill:' + self._format_wiki_title(data['id']), + 'condition': cond, + }, + ], + wiki_message='Passive skill updater', + ) + + return r + + +# ============================================================================= +# Functions +# ============================================================================= + +def get_translation_file(passive_id: str): + ''' + Determines which translation file should be used based on the passive skill ID. + + Parameters + ---------- + passive_id: the Id of the passive skill + ''' + if passive_id.startswith('atlas'): + return 'atlas_stat_descriptions.txt' + else: + return 'passive_skill_stat_descriptions.txt' diff --git a/PyPoE/poe/file/specification/data/stable.py b/PyPoE/poe/file/specification/data/stable.py index b688ff92..3ed08d67 100644 --- a/PyPoE/poe/file/specification/data/stable.py +++ b/PyPoE/poe/file/specification/data/stable.py @@ -888,19 +888,19 @@ key='Stats.dat', ), Field( - name='Stat1Min', + name='Stat1Value', type='int', ), Field( - name='Stat1Max', + name='Stat2Value', type='int', ), Field( - name='Stat2Min', + name='Stat3Value', type='int', ), Field( - name='Stat2Max', + name='Stat4Value', type='int', ), Field( @@ -956,7 +956,7 @@ type='ref|string', ), Field( - name='DDSIcon', + name='Icon_DDSFile', type='ref|string', file_path=True, file_ext='.dds',