From 675591324cff3cbf16135212bd315a03cecf9e22 Mon Sep 17 00:00:00 2001 From: Stanislas Daniel Claude Dolcini Date: Mon, 16 Jan 2023 23:39:45 +0100 Subject: [PATCH] Add A25 to A26 scripts --- .normalized_ao_public | 0 A25ToA26.py | 68 +++++++++++++ A25_A26/ElevationBonusFixer.py | 54 +++++++++++ A25_A26/FancyGrassRemover.py | 43 ++++++++ A25_A26/FixNewPlayerParent.py | 23 +++++ A25_A26/FormationFixer.py | 40 ++++++++ A25_A26/P256.py | 113 ++++++++++++++++++++++ A25_A26/P259.py | 52 ++++++++++ A25_A26/P261.py | 70 ++++++++++++++ A25_A26/PlayerXMLFixer.py | 91 +++++++++++++++++ A25_A26/PlayerXMLFormationFixer.py | 60 ++++++++++++ A25_A26/SessionIconPathFixer.py | 35 +++++++ utils/PMPMap.py | 95 ++++++++++++++++++ utils/fixers/BaseFixer.py | 97 +++++++++++++++++++ utils/logger_utils/InterceptableLogger.py | 35 +++++++ utils/logger_utils/SingleLevelFilter.py | 19 ++++ 16 files changed, 895 insertions(+) create mode 100644 .normalized_ao_public create mode 100644 A25ToA26.py create mode 100644 A25_A26/ElevationBonusFixer.py create mode 100644 A25_A26/FancyGrassRemover.py create mode 100644 A25_A26/FixNewPlayerParent.py create mode 100644 A25_A26/FormationFixer.py create mode 100644 A25_A26/P256.py create mode 100644 A25_A26/P259.py create mode 100644 A25_A26/P261.py create mode 100644 A25_A26/PlayerXMLFixer.py create mode 100644 A25_A26/PlayerXMLFormationFixer.py create mode 100644 A25_A26/SessionIconPathFixer.py create mode 100644 utils/PMPMap.py create mode 100644 utils/fixers/BaseFixer.py create mode 100644 utils/logger_utils/InterceptableLogger.py create mode 100644 utils/logger_utils/SingleLevelFilter.py diff --git a/.normalized_ao_public b/.normalized_ao_public new file mode 100644 index 0000000..e69de29 diff --git a/A25ToA26.py b/A25ToA26.py new file mode 100644 index 0000000..3f57123 --- /dev/null +++ b/A25ToA26.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from A25_A26.ElevationBonusFixer import ElevationBonusFixer +from A25_A26.PlayerXMLFixer import PlayerXMLFixer +from A25_A26.SessionIconPathFixer import SessionIconPathFixer +from A25_A26.FixNewPlayerParent import FixNewPlayerParent +from A25_A26.FormationFixer import FormationFixer +from A25_A26.PlayerXMLFormationFixer import PlayerXMLFormationFixer +from A25_A26.P256 import TemplateFixer, ModifiersFixer +from A25_A26.P259 import convert_recursively +from A25_A26.P261 import TemplateFixer as TemplateFixerP261, SedLike +from A25_A26.FancyGrassRemover import FancyGrassRemover + +from argparse import ArgumentParser +from pathlib import Path +import os + +if __name__ == '__main__': + parser = ArgumentParser(description='A25 to A26 converter.') + parser.add_argument('-r', '--root', action='store', dest='root', default=os.path.dirname(os.path.realpath(__file__))) + parser.add_argument('-m', '--mod', action='store', dest='mod', default='public') + parser.add_argument('-v', '--verbose', action='store_true', default=False, help="Be verbose.") + args = parser.parse_args() + script_dir = args.root + mod_name = args.mod + path = Path(script_dir) / mod_name + print(f"Running in {path}") + print("Running P256...") + template_fixer = TemplateFixer(path) + template_fixer.run() + modifier_fixer = ModifiersFixer(path) + modifier_fixer.run() + if not os.path.exists('.normalized_ao_' + mod_name) and os.path.isdir(path): + print("Running P259...") + convert_recursively(path) + (Path('.normalized_ao_' + mod_name)).touch() + else: + print("Skipping P259...") + print("Fixing r26074...") + elevationFixer = ElevationBonusFixer(path, args.verbose) + elevationFixer.run() + print("Running P261...") + template_fixer = TemplateFixerP261(path) + template_fixer.run() + print("Running FancyGrassRemover...") + remover = FancyGrassRemover(path, args.verbose) + remover.run() + print("Fixing r26298...") + fixer = PlayerXMLFixer(path, args.verbose) + fixer.run() + print("Fixing r26299...") + fixer = PlayerXMLFormationFixer(path, args.verbose) + fixer.run() + print("Fixing r26317...") + fixer = SessionIconPathFixer(path, args.verbose) + fixer.run() + print("Fixing r26458 ...") + fixer = FixNewPlayerParent(path, args.verbose) + fixer.run() + print("Fixing r26476...") + fixer = FormationFixer(path, args.verbose) + fixer.run() + diff --git a/A25_A26/ElevationBonusFixer.py b/A25_A26/ElevationBonusFixer.py new file mode 100644 index 0000000..46c7f66 --- /dev/null +++ b/A25_A26/ElevationBonusFixer.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from utils.fixers.BaseFixer import BaseFixer + +import xml.etree.ElementTree as ET +import os + +class ElevationBonusFixer(BaseFixer): + def __init__(self, vfs_root, verbose=False): + BaseFixer.__init__(self, vfs_root, verbose, __name__) + self.add_files(os.path.join('simulation', 'templates'), tuple(".xml")) + + def fixEffectDelay(self, root): + cmpAttack = root.find('Attack') + changed = False + + if cmpAttack is not None: + for attack_type in cmpAttack: + delay = attack_type.find('Delay') + if delay is not None: + delay.tag = 'EffectDelay' + changed = True + + return changed + + def fixElevationBonus(self, root): + cmpAttack = root.find('Attack') + changed = False + + if cmpAttack is not None: + for attack_type in cmpAttack: + bonus = attack_type.find('ElevationBonus') + if bonus is not None: + ET.SubElement(bonus, 'X').text = '0' + ET.SubElement(bonus, 'Y').text = bonus.text + ET.SubElement(bonus, 'Z').text = '0' + bonus.tag = 'Origin' + bonus.text = '' + changed = True + + return changed + + def run(self): + for file in self.files: + tree = ET.parse(file) + root = tree.getroot() + if self.fixElevationBonus(root) or self.fixEffectDelay(root): + self.logger.info(f'Saving {file}') + self.save_xml_file(tree, root, file) diff --git a/A25_A26/FancyGrassRemover.py b/A25_A26/FancyGrassRemover.py new file mode 100644 index 0000000..0eeaf56 --- /dev/null +++ b/A25_A26/FancyGrassRemover.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from utils.fixers.BaseFixer import BaseFixer +from utils.PMPMap import PmpMap + +import os +from pathlib import Path + +class FancyGrassRemover(BaseFixer): + def __init__(self, vfs_root, verbose=False): + BaseFixer.__init__(self, vfs_root, verbose, __name__) + self.vfs_root = Path(vfs_root) + self.verbose = verbose + self.add_files(os.path.join("maps"), tuple(".pmp")) + + def run(self): + for filePath in self.files: + with open(filePath, "r+b") as f1: + pmpMap = PmpMap(f1) + hasChanged = False + if (self.verbose): + self.logger.info(f'Parsing {filePath}') + for textureIndex in range(0, len(pmpMap.textures)): + texture = pmpMap.textures[textureIndex] + if '_fancy' in texture: + pmpMap.header.data_size -= len(texture) + if (self.verbose): + self.logger.info(f'Replacing {texture} by {texture.replace("_fancy", "")}') + pmpMap.textures[textureIndex] = texture.replace('_fancy', '') + pmpMap.header.data_size += len(texture) + hasChanged = True + if hasChanged: + self.logger.info(f"Patching {filePath}...") + f1.seek(0) + pmpMap.write_to_stream(f1) + else: + f1.close() + diff --git a/A25_A26/FixNewPlayerParent.py b/A25_A26/FixNewPlayerParent.py new file mode 100644 index 0000000..da71856 --- /dev/null +++ b/A25_A26/FixNewPlayerParent.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from utils.fixers.BaseFixer import BaseFixer + +import xml.etree.ElementTree as ET +import os + +class FixNewPlayerParent(BaseFixer): + def __init__(self, vfs_root, verbose=False): + BaseFixer.__init__(self, vfs_root, verbose, __name__) + self.add_files(os.path.join('simulation', 'templates', 'special', 'players'), tuple(".xml")) + + def run(self): + for file in self.files: + tree = ET.parse(file) + root = tree.getroot() + root.set('parent', "template_player") + self.save_xml_file(tree, root, file) diff --git a/A25_A26/FormationFixer.py b/A25_A26/FormationFixer.py new file mode 100644 index 0000000..95cfdc2 --- /dev/null +++ b/A25_A26/FormationFixer.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from utils.fixers.BaseFixer import BaseFixer + +import xml.etree.ElementTree as ET +import os + +class FormationFixer(BaseFixer): + def __init__(self, vfs_root, verbose=False): + BaseFixer.__init__(self, vfs_root, verbose, __name__) + self.add_files(os.path.join('simulation', 'templates'), tuple(".xml")) + + def run(self): + for file in self.files: + tree = ET.parse(file) + root = tree.getroot() + cmp_formation = root.find('Formation') + if cmp_formation is None: + continue + + cmp_identity = self.create_tag_if_not_exist(root, 'Identity') + formation_icon_tag = cmp_formation.find('Icon') + if formation_icon_tag is not None: + icon_tag = self.create_tag_if_not_exist(cmp_identity, 'Icon') + icon_tag.text = formation_icon_tag.text + + cmp_formation.remove(formation_icon_tag) + + formation_name_tag = cmp_formation.find('FormationName') + if formation_name_tag is not None: + generic_name_tag = self.create_tag_if_not_exist(cmp_identity, 'GenericName') + generic_name_tag.text = formation_name_tag.text + cmp_formation.remove(formation_name_tag) + + self.save_xml_file(tree, root, file) diff --git a/A25_A26/P256.py b/A25_A26/P256.py new file mode 100644 index 0000000..20cd1fe --- /dev/null +++ b/A25_A26/P256.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Freagarach + +import fileinput +import glob +import os +import xml.etree.ElementTree as ET + +class SedLike: + def sed(path, changes): + for line in fileinput.input(path, inplace=True, encoding='utf-8'): + for change in changes: + line = line.replace(change[0], change[1]) + print(line, end="") + +class TemplateFixer: + def __init__(self, vfs_root): + self.template_folder = os.path.join(vfs_root, 'simulation', 'templates') + + def fix_template(self, template_path): + tree = ET.parse(template_path) + root = tree.getroot() + production_queue = root.find('ProductionQueue') + if production_queue == None: + return False + + technologies = production_queue.find('Technologies') + tech_cost_multiplier = production_queue.find('TechCostMultiplier') + if technologies != None or tech_cost_multiplier != None: + researcher = ET.Element('Researcher') + if (technologies != None): + researcher.append(technologies) + production_queue.remove(technologies) + if (tech_cost_multiplier != None): + researcher.append(tech_cost_multiplier) + production_queue.remove(tech_cost_multiplier) + researcher[:] = sorted(researcher, key=lambda x: x.tag) + root.append(researcher) + + entities = production_queue.find('Entities') + batch_time_modifier = production_queue.find('BatchTimeModifier') + if entities != None or batch_time_modifier != None: + trainer = ET.Element('Trainer') + if (entities != None): + trainer.append(entities) + production_queue.remove(entities) + if (batch_time_modifier != None): + trainer.append(batch_time_modifier) + production_queue.remove(batch_time_modifier) + trainer[:] = sorted(trainer, key=lambda x: x.tag) + root.append(trainer) + + if production_queue.get('disable') != None: + for element in ["Researcher", "Trainer"]: + existing_element = root.find(element) + if existing_element is None: + functionality = ET.Element(element) + functionality.set('disable', "") + root.append(functionality) + else: + existing_element.set('disable', "") + else: + root.remove(production_queue) + + root[:] = sorted(root, key=lambda x: x.tag) + ET.indent(tree) + + tree.write(template_path, xml_declaration=True, encoding='utf-8') + return True + + def fix_style(self, template_path): + self.changes = [ + [' />', '/>'], + ["version='1.0'", 'version="1.0"'], + ["'utf-8'", '"utf-8"'] + ] + SedLike.sed(template_path, self.changes) + with open(template_path, 'a' , encoding='utf-8') as file: + file.write('\n') + + def run(self): + for template in glob.iglob(self.template_folder + '/**/*.xml', recursive=True): + if self.fix_template(template): + print(template) + self.fix_style(template) + + +class ModifiersFixer: + def __init__(self, vfs_root): + self.data_folder = os.path.join(vfs_root, 'simulation', 'data') + self.changes = [ + ["ProductionQueue/Batch", "Trainer/Batch"], + ["ProductionQueue/Ent", "Trainer/Ent"], + ["ProductionQueue/Train", "Trainer/Train"], + ["ProductionQueue/Tech", "Researcher/Tech"], + ] + + def run(self): + for modification in glob.iglob(self.data_folder + '/**/*.json', recursive=True): + SedLike.sed(modification, self.changes) + + +if __name__ == '__main__': + script_dir = os.path.dirname(os.path.realpath(__file__)) + template_fixer = TemplateFixer(script_dir) + template_fixer.run() + + modifier_fixer = ModifiersFixer(script_dir) + modifier_fixer.run() diff --git a/A25_A26/P259.py b/A25_A26/P259.py new file mode 100644 index 0000000..ef42cf8 --- /dev/null +++ b/A25_A26/P259.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Vladislav Belov + +import os +import sys + +from PIL import Image, ImageDraw + +def convert_ao(path): + if 'ao' not in path.lower(): + return + print(path) + + image = Image.open(path) + image_out = Image.new('L', image.size) + for y in range(image.size[1]): + for x in range(image.size[0]): + if image.mode in ['1', 'L', 'P']: + ao = float(image.getpixel((x, y))) / 255.0 + else: + ao = float(image.getpixel((x, y))[0]) / 255.0 + ao = 0.3 + ao * 2.0 * 0.7 # Might be adjusted for a particular mod. + gray = int(ao * 255) + if gray < 0: + gray = 0 + elif gray > 255: + gray = 255 + image_out.putpixel((x, y), (gray)) + image_out.save(path) + +def convert_recursively(path): + for file_name in os.listdir(path): + file_path = os.path.join(path, file_name) + if os.path.isfile(file_path): + name, ext = os.path.splitext(file_name) + if ext.lower() in ['.png']: + convert_ao(file_path) + elif os.path.isdir(file_path): + convert_recursively(file_path) + +if __name__ == '__main__': + paths = sys.argv[1:] + for path in paths: + if os.path.isfile(path): + convert_ao(path) + elif os.path.isdir(path): + convert_recursively(path) + diff --git a/A25_A26/P261.py b/A25_A26/P261.py new file mode 100644 index 0000000..3d74be2 --- /dev/null +++ b/A25_A26/P261.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Freagarach + +import fileinput +import glob +import os +import xml.etree.ElementTree as ET + +class SedLike: + def sed(path, changes): + for line in fileinput.input(path, inplace=True, encoding='utf-8'): + for change in changes: + line = line.replace(change[0], change[1]) + print(line, end="") + +class TemplateFixer: + def __init__(self, vfs_root): + self.template_folder = os.path.join(vfs_root, 'simulation', 'templates') + + def fix_template(self, template_path): + tree = ET.parse(template_path) + root = tree.getroot() + cmp_identity = root.find('Identity') + if cmp_identity == None: + return False + + formations = cmp_identity.find('Formations') + if formations == None: + return False + + cmp_unitai = root.find('UnitAI') + if cmp_unitai == None: + cmp_unitai = ET.Element('UnitAI') + root.append(cmp_unitai) + cmp_unitai.append(formations) + cmp_identity.remove(formations) + + if cmp_identity.__len__() == 0: + root.remove(cmp_identity) + + root[:] = sorted(root, key=lambda x: x.tag) + ET.indent(tree) + + tree.write(template_path, xml_declaration=True, encoding='utf-8') + return True + + def fix_style(self, template_path): + self.changes = [ + [' />', '/>'], + ["version='1.0'", 'version="1.0"'], + ["'utf-8'", '"utf-8"'] + ] + SedLike.sed(template_path, self.changes) + with open(template_path, 'a', encoding='utf-8') as file: + file.write('\n') + + def run(self): + for template in glob.iglob(self.template_folder + '/**/*.xml', recursive=True): + if self.fix_template(template): + print(template) + self.fix_style(template) + +if __name__ == '__main__': + script_dir = os.path.dirname(os.path.realpath(__file__)) + template_fixer = TemplateFixer(script_dir) + template_fixer.run() diff --git a/A25_A26/PlayerXMLFixer.py b/A25_A26/PlayerXMLFixer.py new file mode 100644 index 0000000..591550f --- /dev/null +++ b/A25_A26/PlayerXMLFixer.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from utils.fixers.BaseFixer import BaseFixer + +from pathlib import Path +import os +import json +import xml.etree.ElementTree as ET + +class PlayerXMLFixer(BaseFixer): + + def __init__(self, vfs_root, verbose=False, name=__name__): + BaseFixer.__init__(self, vfs_root, verbose, name) + self.add_files(os.path.join('simulation', 'templates', 'special', 'player'), tuple(".xml")) + + def list_civs(self): + + civs = [] + for root, _, files in os.walk(str(self.vfs_root)): + for name in files: + file_path = os.path.join(root, name) + if os.path.isfile(file_path) and os.path.join('simulation', 'data', 'civs') in file_path and '.json' in name: + civs.append({"Path" : file_path , "Code" : name.split('.')[0]}) + + return civs + + def create_player_file(self): + root = ET.Element("Entity ") + root.set('parent', "special/player") + return root + + def run(self): + for civ in self.list_civs(): + tree = None + root = None + xml_file = None + for file in self.files: + if civ["Code"] in file: + xml_file = file + break + + if xml_file is not None: + tree = ET.parse(xml_file) + root = tree.getroot() + root.set('parent', "special/player") + else: + root = self.create_player_file() + tree = ET.ElementTree(root) + + + data = None + with open(civ["Path"], 'r', encoding='utf-8') as f: + data = json.load(f) + + cmp_identity = self.create_tag_if_not_exist(root, 'Identity') + + if "History" in data.keys() and data["History"] is not None: + history_tag = self.create_tag_if_not_exist(cmp_identity, 'History') + history_tag.text = data["History"] + del data["History"] + + if "Code" in data.keys(): + civ_tag = self.create_tag_if_not_exist(cmp_identity, 'Civ') + civ_tag.text = data["Code"] + + if "Name" in data.keys(): + generic_name_tag = self.create_tag_if_not_exist(cmp_identity, 'GenericName') + generic_name_tag.text = data["Name"] + del data["Name"] + + if "Emblem" in data.keys(): + icon_tag = self.create_tag_if_not_exist(cmp_identity, 'Icon') + icon_tag.text = data["Emblem"] + del data["Emblem"] + + outputFolder = Path(self.vfs_root) / 'simulation' / 'templates' / 'special' / 'players' + os.makedirs(outputFolder, exist_ok=True) + outputFile = outputFolder / (civ["Code"] + '.xml') + outputFile.touch() + self.save_xml_file(tree, root, outputFile) + with open(civ["Path"], 'w', encoding='utf-8') as outfile: + json.dump(data, outfile, indent='\t') + + oldPlayerFolder = Path(self.vfs_root) / 'simulation' / 'templates' / 'special' / 'player' + if oldPlayerFolder.exists(): + shutil.rmtree(oldPlayerFolder) diff --git a/A25_A26/PlayerXMLFormationFixer.py b/A25_A26/PlayerXMLFormationFixer.py new file mode 100644 index 0000000..caed33c --- /dev/null +++ b/A25_A26/PlayerXMLFormationFixer.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from .PlayerXMLFixer import PlayerXMLFixer + +from pathlib import Path +import xml.etree.ElementTree as ET +import json +import os + +class PlayerXMLFormationFixer(PlayerXMLFixer): + + def __init__(self, vfs_root, verbose=False): + super(PlayerXMLFixer, self).__init__(vfs_root, verbose, __name__) + self.add_files(os.path.join('simulation', 'templates', 'special', 'players'), tuple(".xml")) + + def run(self): + for civ in self.list_civs(): + tree = None + root = None + xml_file = None + for file in self.files: + if civ["Code"] in file: + xml_file = file + break + + if xml_file is not None: + tree = ET.parse(xml_file) + root = tree.getroot() + root.set('parent', "special/player") + else: + root = self.create_player_file() + tree = ET.ElementTree(root) + + + data = None + with open(civ["Path"], 'r', encoding='utf-8') as f: + data = json.load(f) + + + + cmp_player = self.create_tag_if_not_exist(root, 'Player') + if "Formations" in data.keys() and data["Formations"] is not None: + formations_tag = self.create_tag_if_not_exist(cmp_player, 'Formations') + formations_tag.text = " ".join(data["Formations"]) + + del data["Formations"] + + + outputFolder = Path(self.vfs_root) / 'simulation' / 'templates' / 'special' / 'players' + os.makedirs(outputFolder, exist_ok=True) + outputFile = outputFolder / (civ["Code"] + '.xml') + outputFile.touch() + self.save_xml_file(tree, root, outputFile) + with open(civ["Path"], 'w', encoding='utf-8') as outfile: + json.dump(data, outfile, indent='\t', ensure_ascii=False) diff --git a/A25_A26/SessionIconPathFixer.py b/A25_A26/SessionIconPathFixer.py new file mode 100644 index 0000000..a0ae50c --- /dev/null +++ b/A25_A26/SessionIconPathFixer.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from utils.fixers.BaseFixer import BaseFixer + +import os +import xml.etree.ElementTree as ET + +class SessionIconPathFixer(BaseFixer): + + def __init__(self, vfs_root, verbose=False): + BaseFixer.__init__(self, vfs_root, verbose, __name__) + self.add_files(os.path.join('simulation', 'templates', 'special', 'players'), tuple(".xml")) + + def run(self): + for file in self.files: + + tree = ET.parse(file) + root = tree.getroot() + + cmp_identity = root.find('Identity') + if cmp_identity is None: + continue + + icon_tag = cmp_identity.find('Icon') + if icon_tag is None: + continue + + icon_tag.text = str(icon_tag.text).replace("session/portraits/", "") + self.save_xml_file(tree, root, file) + self.fix_style(file) diff --git a/utils/PMPMap.py b/utils/PMPMap.py new file mode 100644 index 0000000..3be9331 --- /dev/null +++ b/utils/PMPMap.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from io import BufferedReader, BufferedWriter + +class PmpHeader(): + def __init__(self, stream : BufferedReader): + self.magic = int.from_bytes(stream.read(4), byteorder='little') + self.version = int.from_bytes(stream.read(4), byteorder='little') + self.data_size = int.from_bytes(stream.read(4), byteorder='little') + self.map_size = int.from_bytes(stream.read(4), byteorder='little') + + def write_to_stream(self, stream : BufferedWriter): + stream.write(self.magic.to_bytes(4, 'little')) + stream.write(self.version.to_bytes(4, 'little')) + stream.write(self.data_size.to_bytes(4, 'little')) + stream.write(self.map_size.to_bytes(4, 'little')) + +class PmpHeightMap(list): + def __init__(self, stream : BufferedReader, width : int, height : int): + self.capacity = (width * 16 + 1) * (height * 16 + 1) + for _ in range (0, self.capacity): + self.append(int.from_bytes(stream.read(2), byteorder='little')) + + def write_to_stream(self, stream : BufferedWriter): + for height_data in self: + stream.write(height_data.to_bytes(2, byteorder='little')) + +class PmpTextures(list): + def __init__(self, stream : BufferedReader): + self.capacity = int.from_bytes(stream.read(4), byteorder='little') + for _ in range (0, self.capacity): + length = int.from_bytes(stream.read(4), byteorder='little') + self.append(stream.read(length).decode()) + + def write_to_stream(self, stream : BufferedWriter): + stream.write((len(self)).to_bytes(4, byteorder='little')) + for texture in self: + stream.write(len(texture).to_bytes(4, byteorder='little')) + stream.write(texture.encode()) + +class PmpTiles(list): + def __init__(self, stream : BufferedReader, capacity : int): + for _ in range(0, capacity): + self.append(PmpTile(stream)) + + def write_to_stream(self, stream : BufferedWriter): + for tile in self: + tile.write_to_stream(stream) + +class PmpTile(): + def __init__(self, stream : BufferedReader): + self.texture1 = int.from_bytes(stream.read(2), byteorder='little') + self.texture2 = int.from_bytes(stream.read(2), byteorder='little') + self.priority = int.from_bytes(stream.read(4), byteorder='little') + + def write_to_stream(self, stream : BufferedWriter): + stream.write(self.texture1.to_bytes(2, byteorder='little')) + stream.write(self.texture2.to_bytes(2, byteorder='little')) + stream.write(self.priority.to_bytes(4, byteorder='little')) + +class PmpPatch(): + def __init__(self, stream : BufferedReader): + self.TILE_SIZE = 16 * 16 + self.tiles = PmpTiles(stream, self.TILE_SIZE) + def write_to_stream(self, stream : BufferedWriter): + self.tiles.write_to_stream(stream) + +class PmpPatches(list): + def __init__(self, stream, width, height): + self.capacity = width * height + for _ in range (0, self.capacity): + self.append(PmpPatch(stream)) + def write_to_stream(self, stream : BufferedWriter): + for patch in self: + patch.write_to_stream(stream) + +class PmpMap(): + def __init__(self, stream : BufferedReader): + self.header = PmpHeader(stream) + self.heightMap = PmpHeightMap(stream, self.header.map_size, self.header.map_size) + self.textures = PmpTextures(stream) + self.patches = PmpPatches(stream, self.header.map_size, self.header.map_size) + + def write_to_stream(self, stream : BufferedWriter): + self.header.write_to_stream(stream) + self.heightMap.write_to_stream(stream) + self.textures.write_to_stream(stream) + self.patches.write_to_stream(stream) + + diff --git a/utils/fixers/BaseFixer.py b/utils/fixers/BaseFixer.py new file mode 100644 index 0000000..f15577d --- /dev/null +++ b/utils/fixers/BaseFixer.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from ..logger_utils.InterceptableLogger import InterceptableLogger + +from pathlib import Path +import fileinput +import os + +class BaseFixer(): + def __init__(self, vfs_root, verbose=False, name=__name__): + self.vfs_root = Path(vfs_root) + self.verbose = verbose + self.files = [] + self.logger = InterceptableLogger(name) + + def fix_style(self, xml_path): + changes = [ + [' />', '/>'], + ["version='1.0'", 'version="1.0"'], + ["'utf-8'", '"utf-8"'] + ] + for line in fileinput.input(xml_path, inplace=True): + for change in changes: + line = line.replace(change[0], change[1]) + print(line, end="") + + with open(xml_path, 'a', encoding='utf-8') as file: + file.write('\n') + + def indent(self, elem, level=0, more_sibs=False): + i = "\n" + if level: + i += (level-1) * ' ' + num_kids = len(elem) + if num_kids: + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if level: + elem.text += ' ' + count = 0 + for kid in elem: + self.indent(kid, level+1, count < num_kids - 1) + count += 1 + if not elem.tail or not elem.tail.strip(): + elem.tail = i + if more_sibs: + elem.tail += ' ' + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + if more_sibs: + elem.tail += ' ' + def sort(self, root): + # sort the first layer + root[:] = sorted(root, key=lambda child: (child.tag,child.get('name'))) + + # sort the second layer + for c in root: + c[:] = sorted(c, key=lambda child: (child.tag,child.get('name'))) + for cp in c: + cp[:] = sorted(cp, key=lambda child: (child.tag,child.get('name'))) + for scp in cp: + scp[:] = sorted(scp, key=lambda child: (child.tag,child.get('name'))) + + def save_xml_file(self, tree, root, xml_file, sort=True): + if sort: + self.sort(root) + self.indent(root) + tree.write(xml_file, xml_declaration=True, encoding='utf-8') + self.fix_style(xml_file) + + def create_tag_if_not_exist(self, parent, name): + tag = parent.find(name) + if tag is None: + tag = ET.SubElement(parent, name) + return tag + + def add_files(self, path, extensions : tuple[str]): + self.files = [] + if os.path.isfile(str(self.vfs_root)): + self.files.append(self.vfs_root) + elif os.path.isdir(str(self.vfs_root)): + for root, _, files in os.walk(str(self.vfs_root)): + for name in files: + file_path = os.path.join(root, name) + if os.path.isfile(file_path) and path in file_path and name.endswith(extensions): + self.files.append(file_path) + if self.verbose: + if len(self.files) > 0: + self.logger.info(f"Found {len(self.files)} file(s).") + else: + self.logger.info(f"No files were found.") diff --git a/utils/logger_utils/InterceptableLogger.py b/utils/logger_utils/InterceptableLogger.py new file mode 100644 index 0000000..ba4f84a --- /dev/null +++ b/utils/logger_utils/InterceptableLogger.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from .SingleLevelFilter import SingleLevelFilter + +from logging import getLogger, StreamHandler, INFO, WARNING, Formatter +from sys import stdout, stderr + +class InterceptableLogger(): + def __init__(self, logger_name : str): + self.logger = getLogger(logger_name) + self.logger.setLevel(INFO) + ch = StreamHandler(stdout) + ch.setLevel(INFO) + ch.setFormatter(Formatter('%(levelname)s - %(message)s')) + f1 = SingleLevelFilter(INFO, False) + ch.addFilter(f1) + self.logger.addHandler(ch) + errorch = StreamHandler(stderr) + errorch.setLevel(WARNING) + errorch.setFormatter(Formatter('%(levelname)s - %(message)s')) + self.logger.addHandler(errorch) + + def info(self, message): + self.logger.info(message) + + def warn(self, message): + self.logger.warn(message) + + def error(self, message): + self.logger.error(message) diff --git a/utils/logger_utils/SingleLevelFilter.py b/utils/logger_utils/SingleLevelFilter.py new file mode 100644 index 0000000..a35bc74 --- /dev/null +++ b/utils/logger_utils/SingleLevelFilter.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# -*- mode: python-mode; python-indent-offset: 4; -*- +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: © 2023 Wildfire Games +# SPDX-FileCopyrightText: © 2023 Stanislas Daniel Claude Dolcini + +from logging import Filter + +class SingleLevelFilter(Filter): + def __init__(self, passlevel, reject): + self.passlevel = passlevel + self.reject = reject + + def filter(self, record): + if self.reject: + return (record.levelno != self.passlevel) + else: + return (record.levelno == self.passlevel)