#!/usr/bin/env python3
from lxml import etree as ET
import fileinput
import glob
import json
import os
import pysvn
import sys
class StyleFixer:
def fix_template_style(template_path):
changes = [
["version='1.0'", 'version="1.0"'],
["'UTF-8'", '"utf-8"']
]
StyleFixer.sed(template_path, changes)
def sed(path, changes):
for line in fileinput.input(path, inplace=True):
for change in changes:
line = line.replace(change[0], change[1])
print(line, end="")
def simplify_requirements(reqs):
if len(reqs) != 1:
return
all_req = reqs.find("All")
if all_req == None:
return
reqs.remove(all_req)
for child in all_req:
reqs.append(child)
def sort_xml(node):
node[:] = sorted(node, key=lambda child: child.tag)
for child in node:
StyleFixer.sort_xml(child)
class TechnologyConverter:
def __init__(self, vfs_root):
self.scc = pysvn.Client()
self.tech_folder = os.path.join(vfs_root, 'simulation', 'data', 'technologies')
self.template_folder = os.path.join(vfs_root, 'simulation', 'templates')
self.tech_template_folder = os.path.join(self.template_folder, 'technologies')
self.civs = []
os.makedirs(self.tech_template_folder, exist_ok=True)
self.scc.add(self.tech_template_folder, force=True)
with os.scandir(os.path.join(self.template_folder, 'special', 'players')) as civ_files:
for civ_file in civ_files:
if civ_file.is_file():
civ = civ_file.name.removesuffix(".xml")
if civ == 'gaia':
continue
self.civs.append(civ)
civ_folder = os.path.join(self.tech_template_folder, civ)
os.makedirs(civ_folder, exist_ok=True)
self.scc.add(civ_folder, force=True)
def add_requirements(self, req_json, reqs):
if 'tech' in req_json:
ET.SubElement(reqs, "Techs").text = req_json['tech']
if 'entity' in req_json:
ent_req = ET.SubElement(ET.SubElement(reqs, "Entities"), req_json['entity']['class'])
if 'number' in req_json['entity']:
ET.SubElement(ent_req, "Number").text = str(req_json['entity']['number'])
if 'variants' in req_json['entity']:
ET.SubElement(ent_req, "Variants").text = str(req_json['entity']['variants'])
if 'all' in req_json:
all_req = ET.SubElement(reqs, "All")
for req in req_json['all']:
self.add_requirements(req, all_req)
if len(all_req) == 0:
reqs.remove(all_req)
if 'any' in req_json:
any_req = ET.SubElement(reqs, "Any")
for req in req_json['any']:
self.add_requirements(req, any_req)
if len(any_req) == 0:
reqs.remove(any_req)
def add_modifications(self, cmp_tech, tech_json):
mods = ET.SubElement(cmp_tech, "Modifiers")
i = 1
for mod in tech_json['modifications']:
modifier = ET.SubElement(mods, "modifier" + str(i))
i += 1
ET.SubElement(modifier, "Paths").text = mod['value']
if 'add' in mod:
ET.SubElement(modifier, "Add").text = str(mod['add'])
elif 'multiply' in mod:
ET.SubElement(modifier, "Multiply").text = str(mod['multiply'])
elif 'replace' in mod:
ET.SubElement(modifier, "Replace").text = str(mod['replace'])
if 'affects' in tech_json:
affects = ET.SubElement(modifier, "Affects")
affects.set("datatype", "tokens")
affects.text = tech_json['affects'][0]
for affect in tech_json['affects'][1:]:
affects.text = affects.text + " " + affect
if 'affects' in modifier:
for affect in modifier['affects']:
affects.text = affects.text + " " + affect
def create_civ_tech(self, tech_json, name, civ):
file_path = os.path.join(self.tech_template_folder, civ, name) + ".xml"
root = ET.Element("Technology")
root.set("parent", "technologies/" + name)
cmp_identity = ET.SubElement(root, "Identity")
ET.SubElement(cmp_identity, "Civ").text = civ
if 'specificName' in tech_json and civ in tech_json['specificName']:
ET.SubElement(cmp_identity, "SpecificName").text = tech_json['specificName'][civ]
StyleFixer.sort_xml(root)
ET.ElementTree(root).write(file_path, xml_declaration=True, pretty_print=True, encoding='utf-8')
StyleFixer.fix_template_style(file_path)
self.scc.add(file_path)
def convert_tech_to_template(self, tech_path, name):
tech_json = json.load(tech_path)
root = ET.Element("Technology")
cmp_tech = ET.SubElement(root, "Technology")
ET.SubElement(cmp_tech, "ID").text = name
if 'top' in tech_json:
ET.SubElement(cmp_tech, "Choice").text = "technologies/{civ}/" + tech_json['top'] + " " + "technologies/{civ}/" + tech_json['bottom']
cmp_identity = ET.SubElement(root, "Identity")
ET.SubElement(cmp_identity, "GenericName").text = tech_json['genericName']
ET.SubElement(cmp_identity, "History").text = tech_json['description'] if 'description' in tech_json else ""
ET.SubElement(cmp_identity, "Icon").text = tech_json['icon']
ET.SubElement(cmp_identity, "Tooltip").text = tech_json['tooltip']
# The identity spec requires this.
ET.SubElement(cmp_identity, "Undeletable").text = "true"
# It would be nice to not have this dual relationship.
if 'pair' in tech_json:
ET.SubElement(cmp_tech, "ChoiceRoot").text = tech_json['pair']
if 'phase' in name:
classes = ET.SubElement(cmp_identity, "Classes")
classes.set("datatype", "tokens")
classes.text = "Phase"
if 'supersedes' in tech_json:
ET.SubElement(cmp_tech, "Supersedes").text = "technologies/{civ}/" + tech_json['supersedes']
if 'autoResearch' in tech_json:
ET.SubElement(cmp_tech, "AutoResearched")
allowed_civs = []
disallowed_civs = []
if 'requirements' in tech_json:
reqs = ET.Element("Requirements")
self.add_requirements(tech_json['requirements'], reqs)
if 'all' in tech_json['requirements']:
for req in tech_json['requirements']['all']:
if 'civ' in req:
allowed_civs.append(req['civ'])
if 'notciv' in req:
disallowed_civs.append(req['notciv'])
if 'any' in req:
for civ_req in req['any']:
allowed_civs.append(civ_req['civ'])
elif 'any' in tech_json['requirements']:
for req in tech_json['requirements']['any']:
if 'civ' in req:
allowed_civs.append(req['civ'])
elif 'civ' in tech_json['requirements']:
allowed_civs.append(tech_json['requirements']['civ'])
StyleFixer.simplify_requirements(reqs)
if 'requirementsTooltip' in tech_json:
techs = reqs.find('Techs')
if len(reqs) != 1 or techs == None:
ET.SubElement(reqs, "Tooltip").text = tech_json['requirementsTooltip']
if len(reqs) > 0:
cmp_identity.append(reqs)
if 'cost' in tech_json:
cmp_cost = ET.SubElement(root, "Cost")
for element in tech_json['cost']:
ET.SubElement(cmp_cost, element).text = str(tech_json['cost'][element])
if 'researchTime' in tech_json:
ET.SubElement(cmp_cost, "BuildTime").text = str(tech_json['researchTime'])
if 'soundComplete' in tech_json:
ET.SubElement(ET.SubElement(root, "Sound"), "completed").text = tech_json['soundComplete']
if 'modifications' in tech_json:
self.add_modifications(cmp_tech, tech_json)
if len(allowed_civs) > 0 and len(disallowed_civs) > 0:
print("Warning: complex (dis)allowed civs: " + name)
if len(disallowed_civs) > 0:
for civ in self.civs:
if not civ in disallowed_civs:
allowed_civs.append(civ)
elif len(allowed_civs) == 0:
allowed_civs = self.civs[:]
if len(allowed_civs) == 1:
civ = allowed_civs[0]
ET.SubElement(cmp_identity, "Civ").text = civ
if 'specificName' in tech_json and civ in tech_json['specificName']:
ET.SubElement(cmp_identity, "SpecificName").text = tech_json['specificName'][civ]
else:
for civ in allowed_civs:
self.create_civ_tech(tech_json, name, civ)
StyleFixer.sort_xml(root)
ET.ElementTree(root).write(tech_path.name, xml_declaration=True, pretty_print=True, encoding='utf-8')
return allowed_civs
def run(self):
for tech_path in glob.iglob(self.tech_folder + '/*.json', recursive=True):
name = os.path.basename(tech_path).removesuffix(".json")
if 'phase' in name:
if 'generic' in name:
name = name.removesuffix("_generic")
else:
continue
tech_template_path = os.path.join(self.tech_template_folder, name) + ".xml"
self.scc.move(tech_path, tech_template_path)
with open(tech_template_path, 'r') as file:
try:
allowed_civs = self.convert_tech_to_template(file, name)
except Exception as e:
print("Error: Failed conversion: " + name)
print(e)
self.scc.move(tech_template_path, tech_path)
continue
StyleFixer.fix_template_style(tech_template_path)
if len(allowed_civs) == 1:
self.scc.move(tech_template_path, os.path.join(self.tech_template_folder, allowed_civs[0], name) + ".xml")
def convert_input_file(self, relative_path):
new_path = relative_path
if 'json' in relative_path:
new_path = relative_path.removesuffix(".json") + ".xml"
self.scc.move(relative_path, new_path)
name = os.path.basename(new_path).removesuffix(".xml")
with open(new_path, 'r') as file:
try:
self.convert_tech_to_template(file, name)
except Exception as e:
print("Error: Failed conversion: " + relative_path)
print(e)
StyleFixer.fix_template_style(new_path)
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()
changed = False
if self.fix_researcher(root):
changed = True
if self.check_requirements(root):
changed = True
tree.write(template_path, xml_declaration=True, pretty_print=True, encoding='utf-8')
return changed
def fix_researcher(self, root):
cmp_researcher = root.find('Researcher')
if cmp_researcher == None:
return False
technologies = cmp_researcher.find('Technologies')
if technologies == None:
return False
self.fix_technology_names(technologies, ' ')
return True
def fix_technology_names(self, node, indentation):
new_names = []
for technology in technologiesnode.text.split():
prefix = "technologies/{civ}/"
if technology[0] == '!':
prefix = '!' + prefix
technology = technology[1:]
if technology[0] == '-':
if technology[1] == '!':
prefix = '!' + prefix
technology = technology[1:]
prefix = '-' + prefix
technology = technology[1:]
if prefix in technology:
new_names.append(technology.removesuffix("_{civ}"))
else:
new_names.append(prefix + technology.removesuffix("_{civ}"))
technologiesnode.text = "\n " + " + indentation + ('\n '' + indentation).join(sorted(new_names)) + "\n "" + indentation
def check_requirements(self, root):
tree.write(template_pathchanged = False
cmp_identity = root.find('Identity')
if cmp_identity != None:
requirements = cmp_identity.find('Requirements')
if requirements != None and self.fix_requirements(requirements, xml_declaration=' '):
changed = True, pretty_print=True, encoding='utf-8')
cmp_upgrade = root.find('Upgrade')
if cmp_upgrade != None:
for upgrade in cmp_upgrade.findall('./'):
requirements = upgrade.find('Requirements')
if requirements != None and self.fix_requirements(requirements, ' '):
changed = True
return changed
def fix_requirements(self, requirements, indentation):
technologies = requirements.find('Techs')
if technologies == None:
return False
self.fix_technology_names(technologies, indentation)
return True
def run(self):
for template_path in glob.iglob(self.template_folder + '/**/*.xml', recursive=True):
if self.fix_template(template_path):
StyleFixer.fix_template_style(template_path)
if __name__ == '__main__':
script_dir = os.path.dirname(os.path.realpath(__file__))
tech_converter = TechnologyConverter(script_dir)
if len(sys.argv) > 1:
for tech_path in sys.argv[1:]:
tech_converter.convert_input_file(tech_path)
else:
tech_converter.run()
template_fixer = TemplateFixer(script_dir)
template_fixer.run()