#!/usr/bin/env python3
from lxml import etree as ET
import fileinput
import glob
import json
import os
import pysvn
from lxml import etree as ETt 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 TemplateFixer:
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))
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("Entity")
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]
root[:] = sorted(root, key=lambda x: x.tagStyleFixer.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("Entity")
cmp_tech = ET.SubElement(root, "Technology")
ET.SubElement(cmp_tech, "ID").text = name
if 'top' in tech_json:
ET.SubElement(cmp_tech, "Choice").text = tech_json['top'] + " " + tech_json['bottom']
else:
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 = 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[:]
for civ in allowed_civsif 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)
root[:] = sorted(root, key=lambda x: x.tagStyleFixer.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=False):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)
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(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)
if __name__ == '__main__':
script_dir = os.path.dirname(os.path.realpath(__file__))
template_fixer = TemplateFixer(script_dir)
if len(sys.argv) > 1:
for tech_path in sys.argv[1:]:
template_fixer.convert_input(tech_path)
else:
template_fixer.run()