Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

User:Sairon/Python example for working with .lsx: Difference between revisions

From bg3.wiki
Sairon (talk | contribs)
Python script example for making sweeping changes to the progressions
 
Sairon (talk | contribs)
Formating
Line 5: Line 5:
     import uuid
     import uuid
     from pathlib import Path
     from pathlib import Path
 
     # This function takes an xpath, which is a query language for xml structures, and tries to respect the load order to give you the most up to date result
     # This function takes an xpath, which is a query language for xml structures, and tries to respect the load order to give you the most up to date result
     def run_xpath_group( xpath, progressions ):
     def run_xpath_group( xpath, progressions ):
Line 13: Line 13:
                 ret[item.xpath('attribute[@id="UUID"]')[0].attrib['value']] = item
                 ret[item.xpath('attribute[@id="UUID"]')[0].attrib['value']] = item
         return ret.values()
         return ret.values()
 
     # This is just an helper function used later for conditionally extending a list
     # This is just an helper function used later for conditionally extending a list
     def or_add( target, add ):
     def or_add( target, add ):
         target.extend(add)
         target.extend(add)
         return add
         return add
 
     # This sets up how we can interact with this script via the command line to actually get the work done
     # This sets up how we can interact with this script via the command line to actually get the work done
     arg_parser = argparse.ArgumentParser(description='Progressions manipulation for BG3')
     arg_parser = argparse.ArgumentParser(description='Progressions manipulation for BG3')
Line 27: Line 27:
     run.add_argument('output_template')
     run.add_argument('output_template')
     run.add_argument('output')
     run.add_argument('output')
 
     parsed_args = arg_parser.parse_args()
     parsed_args = arg_parser.parse_args()
 
     if parsed_args.command == 'run':
     if parsed_args.command == 'run':
         # Given a path to where you've extracted the .pak files we'll collect all versions of Progressions.lsx
         # Given a path to where you've extracted the .pak files we'll collect all versions of Progressions.lsx
Line 38: Line 38:
         base_classes = run_xpath_group( '//node[@id="ClassDescription" and not(attribute[@id="ParentGuid"])]',class_descriptions)
         base_classes = run_xpath_group( '//node[@id="ClassDescription" and not(attribute[@id="ParentGuid"])]',class_descriptions)
         sub_classes = run_xpath_group( '//node[@id="ClassDescription" and attribute[@id="ParentGuid"]]',class_descriptions)
         sub_classes = run_xpath_group( '//node[@id="ClassDescription" and attribute[@id="ParentGuid"]]',class_descriptions)
 
         progressions.sort( key=lambda x: x['tree'].xpath('//version')[0].attrib['build'] ) # Sort list such as the versions with highest build number is last
         progressions.sort( key=lambda x: x['tree'].xpath('//version')[0].attrib['build'] ) # Sort list such as the versions with highest build number is last
         xp = '//node[@id="Progression" and attribute[@id="ProgressionType" and @value="0"] and attribute[@id="Name" and ( @value="MulticlassSpellSlots" or '+' or '.join( '@value="'+x+'"' for x in (x.xpath('attribute[@id="Name"]')[0].attrib['value'] for x in base_classes) )+ ' ) ] ]'  
         xp = '//node[@id="Progression" and attribute[@id="ProgressionType" and @value="0"] and attribute[@id="Name" and ( @value="MulticlassSpellSlots" or '+' or '.join( '@value="'+x+'"' for x in (x.xpath('attribute[@id="Name"]')[0].attrib['value'] for x in base_classes) )+ ' ) ] ]'  
Line 49: Line 49:
             node.xpath('attribute[@id="UUID"]')[0].attrib['value'] = str(uuid.uuid4()) # The copies needs a new UUID  
             node.xpath('attribute[@id="UUID"]')[0].attrib['value'] = str(uuid.uuid4()) # The copies needs a new UUID  
         nodes_base_classes += level_13s
         nodes_base_classes += level_13s
 
         for progress in nodes_base_classes: # Give perk to all levels for base classes  
         for progress in nodes_base_classes: # Give perk to all levels for base classes  
             allow_feat = progress.xpath('attribute[@id="AllowImprovement"]') or or_add( progress, [ etree.Element('attribute', {'id':'AllowImprovement', 'value' : 'true','type' : 'bool' }) ] )
             allow_feat = progress.xpath('attribute[@id="AllowImprovement"]') or or_add( progress, [ etree.Element('attribute', {'id':'AllowImprovement', 'value' : 'true','type' : 'bool' }) ] )
             allow_feat[0].attrib['value'] = 'true'
             allow_feat[0].attrib['value'] = 'true'
 
         xp = '//node[@id="Progression" and attribute[@id="ProgressionType" and @value="1"] and attribute[@id="Name" and ( '+' or '.join( '@value="'+x+'"' for x in (x.xpath('attribute[@id="Name"]')[0].attrib['value'] for x in sub_classes) )+ ' ) ] ]'  
         xp = '//node[@id="Progression" and attribute[@id="ProgressionType" and @value="1"] and attribute[@id="Name" and ( '+' or '.join( '@value="'+x+'"' for x in (x.xpath('attribute[@id="Name"]')[0].attrib['value'] for x in sub_classes) )+ ' ) ] ]'  
         nodes_sub_classes = [ copy.deepcopy(x) for x in run_xpath_group( xp, progressions ) ]
         nodes_sub_classes = [ copy.deepcopy(x) for x in run_xpath_group( xp, progressions ) ]
 
         # Get every class & sub class level which has a Boosts child, which contains the point improvements for that level
         # Get every class & sub class level which has a Boosts child, which contains the point improvements for that level
         for check_item in [item for sublist in [ *(x.xpath('//attribute[@id="Boosts"]') for x in nodes_base_classes), *(x.xpath('//attribute[@id="Boosts"]') for x in nodes_sub_classes ) ] for item in sublist]:
         for check_item in [item for sublist in [ *(x.xpath('//attribute[@id="Boosts"]') for x in nodes_base_classes), *(x.xpath('//attribute[@id="Boosts"]') for x in nodes_sub_classes ) ] for item in sublist]:
Line 64: Line 64:
                 cur_val = re.sub( rf"(ActionResource\({res},)(\d)", lambda x: x.group(1)+str(int(x.group(2))*3), cur_val )
                 cur_val = re.sub( rf"(ActionResource\({res},)(\d)", lambda x: x.group(1)+str(int(x.group(2))*3), cur_val )
             check_item.attrib['value'] = cur_val
             check_item.attrib['value'] = cur_val
 
         # Parse the xml template provided
         # Parse the xml template provided
         output = etree.parse(parsed_args.output_template, etree.XMLParser(remove_blank_text=True))
         output = etree.parse(parsed_args.output_template, etree.XMLParser(remove_blank_text=True))