1
0
forked from 0ad/0ad

Use PEP 8 naming conventions for templatesanalyzer

This commit is contained in:
Dunedan 2024-09-04 08:28:35 +02:00
parent 5bea0a0f97
commit ccb1d747f0
Signed by untrusted user: Dunedan
GPG Key ID: 885B16854284E0B2
3 changed files with 273 additions and 280 deletions

View File

@ -55,7 +55,7 @@ pipeline {
stage("Template Analyzer") { stage("Template Analyzer") {
steps { steps {
ws("/zpool0/entity-docs"){ ws("/zpool0/entity-docs"){
sh "cd source/tools/templatesanalyzer/ && python3 unitTables.py" sh "cd source/tools/templatesanalyzer/ && python3 unit_tables.py"
} }
} }
} }

View File

@ -4,7 +4,7 @@ This python tool has been written by wraitii and updated to 0ad A25 by hyiltiz.
Its purpose is to help with unit and civ balancing by allowing quick comparison Its purpose is to help with unit and civ balancing by allowing quick comparison
between important template data. between important template data.
Run it using `python unitTables.py` or `pypy unitTables.py` (if you have pypy Run it using `python unit_tables.py` or `pypy unit_tables.py` (if you have pypy
installed). The output will be located in an HTML file called installed). The output will be located in an HTML file called
`unit_summary_table.html` in this folder. `unit_summary_table.html` in this folder.
@ -17,15 +17,15 @@ The script generates 3 informative tables:
You can customize the script by changing the units to include (loading all units You can customize the script by changing the units to include (loading all units
might make it slightly unreadable). To change this, change the might make it slightly unreadable). To change this, change the
`LoadTemplatesIfParent` variable. You can also consider only some civilizations. `LOAD_TEMPLATES_IF_PARENT` constant. You can also consider only some civilizations.
You may also filter some templates based on their name, if you want to remove You may also filter some templates based on their name, if you want to remove
specific templates. By default it loads all citizen soldiers and all champions, specific templates. By default it loads all citizen soldiers and all champions,
and ignores non-interesting units for the comparison/efficienicy table (2nd and ignores non-interesting units for the comparison/efficiency table (2nd
table). table).
The HTML page comes with a JavaScript extension that allows to filter and sort The HTML page comes with a JavaScript extension that allows to filter and sort
in-place, to help with comparisons. You can disable this by disabling javascript in-place, to help with comparisons. You can disable this by disabling javascript
or by changing the `AddSortingOverlay` parameter in the script. This JS or by changing the `ADD_SORTING_OVERLAY` constant in the script. This JS
extension, called TableFilter, is released under the MIT license. The version extension, called TableFilter, is released under the MIT license. The version
used can be found at https://github.com/koalyptus/TableFilter/ used can be found at https://github.com/koalyptus/TableFilter/
@ -62,13 +62,13 @@ getting familiarized with the analyzer. Note that you'll need `dot` engine provi
by the `graphviz` package. You can install `graphviz` using your system's package manager. by the `graphviz` package. You can install `graphviz` using your system's package manager.
pip3 install pyan3==1.1.1 pip3 install pyan3==1.1.1
python3 -m pyan unitTables.py --uses --no-defines --colored \ python3 -m pyan unit_tables.py --uses --no-defines --colored \
--grouped --annotated --html > fundeps.html --grouped --annotated --html > fundeps.html
Alternatively, only create the `.dot` file using the following line, and render Alternatively, only create the `.dot` file using the following line, and render
it with an online renderer like http://viz-js.com/ it with an online renderer like http://viz-js.com/
python3 -m pyan unitTables.py --uses --no-defines --colored \ python3 -m pyan unit_tables.py --uses --no-defines --colored \
--grouped --annotated --dot > fundeps.dot --grouped --annotated --dot > fundeps.dot
Enjoy! Enjoy!

View File

@ -34,14 +34,14 @@ sys.path.append("../entity")
from scriptlib import SimulTemplateEntity from scriptlib import SimulTemplateEntity
AttackTypes = ["Hack", "Pierce", "Crush", "Poison", "Fire"] ATTACK_TYPES = ["Hack", "Pierce", "Crush", "Poison", "Fire"]
Resources = ["food", "wood", "stone", "metal"] RESOURCES = ["food", "wood", "stone", "metal"]
# Generic templates to load # Generic templates to load
# The way this works is it tries all generic templates # The way this works is it tries all generic templates
# But only loads those who have one of the following parents # But only loads those who have one of the following parents
# EG adding "template_unit.xml" will load all units. # EG adding "template_unit.xml" will load all units.
LoadTemplatesIfParent = [ LOAD_TEMPLATES_IF_PARENT = [
"template_unit_infantry.xml", "template_unit_infantry.xml",
"template_unit_cavalry.xml", "template_unit_cavalry.xml",
"template_unit_champion.xml", "template_unit_champion.xml",
@ -51,7 +51,7 @@ LoadTemplatesIfParent = [
# Those describe Civs to analyze. # Those describe Civs to analyze.
# The script will load all entities that derive (to the nth degree) from one of # The script will load all entities that derive (to the nth degree) from one of
# the above templates. # the above templates.
Civs = [ CIVS = [
"athen", "athen",
"brit", "brit",
"cart", "cart",
@ -70,16 +70,16 @@ Civs = [
] ]
# Remote Civ templates with those strings in their name. # Remote Civ templates with those strings in their name.
FilterOut = ["marian", "thureophoros", "thorakites", "kardakes"] FILTER_OUT = ["marian", "thureophoros", "thorakites", "kardakes"]
# In the Civilization specific units table, do you want to only show the units # In the Civilization specific units table, do you want to only show the units
# that are different from the generic templates? # that are different from the generic templates?
showChangedOnly = True SHOW_CHANGED_ONLY = True
# Sorting parameters for the "roster variety" table # Sorting parameters for the "roster variety" table
ComparativeSortByCav = True COMPARATIVE_SORT_BY_CAV = True
ComparativeSortByChamp = True COMPARATIVE_SORT_BY_CHAMP = True
ClassesUsedForSort = [ CLASSES_USED_FOR_SORT = [
"Support", "Support",
"Pike", "Pike",
"Spear", "Spear",
@ -92,16 +92,16 @@ ClassesUsedForSort = [
# Disable if you want the more compact basic data. Enable to allow filtering and # Disable if you want the more compact basic data. Enable to allow filtering and
# sorting in-place. # sorting in-place.
AddSortingOverlay = True ADD_SORTING_OVERLAY = True
# This is the path to the /templates/ folder to consider. Change this for mod # This is the path to the /templates/ folder to consider. Change this for mod
# support. # support.
modsFolder = Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods" mods_folder = Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods"
basePath = modsFolder / "public" / "simulation" / "templates" base_path = mods_folder / "public" / "simulation" / "templates"
# For performance purposes, cache opened templates files. # For performance purposes, cache opened templates files.
globalTemplatesList = {} global_templates_list = {}
sim_entity = SimulTemplateEntity(modsFolder, None) sim_entity = SimulTemplateEntity(mods_folder, None)
def htbout(file, balise, value): def htbout(file, balise, value):
@ -112,27 +112,27 @@ def htout(file, value):
file.write("<p>" + value + "</p>\n") file.write("<p>" + value + "</p>\n")
def fastParse(template_name): def fast_parse(template_name):
"""Run ET.parse() with memoising in a global table.""" """Run ET.parse() with memoising in a global table."""
if template_name in globalTemplatesList: if template_name in global_templates_list:
return globalTemplatesList[template_name] return global_templates_list[template_name]
parent_string = ET.parse(template_name).getroot().get("parent") parent_string = ET.parse(template_name).getroot().get("parent")
globalTemplatesList[template_name] = sim_entity.load_inherited( global_templates_list[template_name] = sim_entity.load_inherited(
"simulation/templates/", str(template_name), ["public"] "simulation/templates/", str(template_name), ["public"]
) )
globalTemplatesList[template_name].set("parent", parent_string) global_templates_list[template_name].set("parent", parent_string)
return globalTemplatesList[template_name] return global_templates_list[template_name]
def getParents(template_name): def get_parents(template_name):
template_data = fastParse(template_name) template_data = fast_parse(template_name)
parents_string = template_data.get("parent") parents_string = template_data.get("parent")
if parents_string is None: if parents_string is None:
return set() return set()
parents = set() parents = set()
for parent in parents_string.split("|"): for parent in parents_string.split("|"):
parents.add(parent) parents.add(parent)
for element in getParents( for element in get_parents(
sim_entity.get_file("simulation/templates/", parent + ".xml", "public") sim_entity.get_file("simulation/templates/", parent + ".xml", "public")
): ):
parents.add(element) parents.add(element)
@ -140,18 +140,18 @@ def getParents(template_name):
return parents return parents
def ExtractValue(value): def extract_value(value):
return float(value.text) if value is not None else 0.0 return float(value.text) if value is not None else 0.0
# This function checks that a template has the given parent. # This function checks that a template has the given parent.
def hasParentTemplate(template_name, parentName): def has_parent_template(template_name, parent_name):
return any(parentName == parent + ".xml" for parent in getParents(template_name)) return any(parent_name == parent + ".xml" for parent in get_parents(template_name))
def CalcUnit(UnitName, existingUnit=None): def calc_unit(unit_name, existing_unit=None):
if existingUnit is not None: if existing_unit is not None:
unit = existingUnit unit = existing_unit
else: else:
unit = { unit = {
"HP": 0, "HP": 0,
@ -180,177 +180,177 @@ def CalcUnit(UnitName, existingUnit=None):
"Civ": None, "Civ": None,
} }
Template = fastParse(UnitName) template = fast_parse(unit_name)
# 0ad started using unit class/category prefixed to the unit name # 0ad started using unit class/category prefixed to the unit name
# separated by |, known as mixins since A25 (rP25223) # separated by |, known as mixins since A25 (rP25223)
# We strip these categories for now # We strip these categories for now
# This can be used later for classification # This can be used later for classification
unit["Parent"] = Template.get("parent").split("|")[-1] + ".xml" unit["Parent"] = template.get("parent").split("|")[-1] + ".xml"
unit["Civ"] = Template.find("./Identity/Civ").text unit["Civ"] = template.find("./Identity/Civ").text
unit["HP"] = ExtractValue(Template.find("./Health/Max")) unit["HP"] = extract_value(template.find("./Health/Max"))
unit["BuildTime"] = ExtractValue(Template.find("./Cost/BuildTime")) unit["BuildTime"] = extract_value(template.find("./Cost/BuildTime"))
unit["Cost"]["population"] = ExtractValue(Template.find("./Cost/Population")) unit["Cost"]["population"] = extract_value(template.find("./Cost/Population"))
resource_cost = Template.find("./Cost/Resources") resource_cost = template.find("./Cost/Resources")
if resource_cost is not None: if resource_cost is not None:
for resource_type in list(resource_cost): for resource_type in list(resource_cost):
unit["Cost"][resource_type.tag] = ExtractValue(resource_type) unit["Cost"][resource_type.tag] = extract_value(resource_type)
if Template.find("./Attack/Melee") is not None: if template.find("./Attack/Melee") is not None:
unit["RepeatRate"]["Melee"] = ExtractValue(Template.find("./Attack/Melee/RepeatTime")) unit["RepeatRate"]["Melee"] = extract_value(template.find("./Attack/Melee/RepeatTime"))
unit["PrepRate"]["Melee"] = ExtractValue(Template.find("./Attack/Melee/PrepareTime")) unit["PrepRate"]["Melee"] = extract_value(template.find("./Attack/Melee/PrepareTime"))
for atttype in AttackTypes: for atttype in ATTACK_TYPES:
unit["Attack"]["Melee"][atttype] = ExtractValue( unit["Attack"]["Melee"][atttype] = extract_value(
Template.find("./Attack/Melee/Damage/" + atttype) template.find("./Attack/Melee/Damage/" + atttype)
) )
attack_melee_bonus = Template.find("./Attack/Melee/Bonuses") attack_melee_bonus = template.find("./Attack/Melee/Bonuses")
if attack_melee_bonus is not None: if attack_melee_bonus is not None:
for Bonus in attack_melee_bonus: for bonus in attack_melee_bonus:
Against = [] against = []
CivAg = [] civ_ag = []
if Bonus.find("Classes") is not None and Bonus.find("Classes").text is not None: if bonus.find("Classes") is not None and bonus.find("Classes").text is not None:
Against = Bonus.find("Classes").text.split(" ") against = bonus.find("Classes").text.split(" ")
if Bonus.find("Civ") is not None and Bonus.find("Civ").text is not None: if bonus.find("Civ") is not None and bonus.find("Civ").text is not None:
CivAg = Bonus.find("Civ").text.split(" ") civ_ag = bonus.find("Civ").text.split(" ")
Val = float(Bonus.find("Multiplier").text) val = float(bonus.find("Multiplier").text)
unit["AttackBonuses"][Bonus.tag] = { unit["AttackBonuses"][bonus.tag] = {
"Classes": Against, "Classes": against,
"Civs": CivAg, "Civs": civ_ag,
"Multiplier": Val, "Multiplier": val,
} }
attack_restricted_classes = Template.find("./Attack/Melee/RestrictedClasses") attack_restricted_classes = template.find("./Attack/Melee/RestrictedClasses")
if attack_restricted_classes is not None: if attack_restricted_classes is not None:
newClasses = attack_restricted_classes.text.split(" ") new_classes = attack_restricted_classes.text.split(" ")
for elem in newClasses: for elem in new_classes:
if elem.find("-") != -1: if elem.find("-") != -1:
newClasses.pop(newClasses.index(elem)) new_classes.pop(new_classes.index(elem))
if elem in unit["Restricted"]: if elem in unit["Restricted"]:
unit["Restricted"].pop(newClasses.index(elem)) unit["Restricted"].pop(new_classes.index(elem))
unit["Restricted"] += newClasses unit["Restricted"] += new_classes
elif Template.find("./Attack/Ranged") is not None: elif template.find("./Attack/Ranged") is not None:
unit["Ranged"] = True unit["Ranged"] = True
unit["Range"] = ExtractValue(Template.find("./Attack/Ranged/MaxRange")) unit["Range"] = extract_value(template.find("./Attack/Ranged/MaxRange"))
unit["Spread"] = ExtractValue(Template.find("./Attack/Ranged/Projectile/Spread")) unit["Spread"] = extract_value(template.find("./Attack/Ranged/Projectile/Spread"))
unit["RepeatRate"]["Ranged"] = ExtractValue(Template.find("./Attack/Ranged/RepeatTime")) unit["RepeatRate"]["Ranged"] = extract_value(template.find("./Attack/Ranged/RepeatTime"))
unit["PrepRate"]["Ranged"] = ExtractValue(Template.find("./Attack/Ranged/PrepareTime")) unit["PrepRate"]["Ranged"] = extract_value(template.find("./Attack/Ranged/PrepareTime"))
for atttype in AttackTypes: for atttype in ATTACK_TYPES:
unit["Attack"]["Ranged"][atttype] = ExtractValue( unit["Attack"]["Ranged"][atttype] = extract_value(
Template.find("./Attack/Ranged/Damage/" + atttype) template.find("./Attack/Ranged/Damage/" + atttype)
) )
if Template.find("./Attack/Ranged/Bonuses") is not None: if template.find("./Attack/Ranged/Bonuses") is not None:
for Bonus in Template.find("./Attack/Ranged/Bonuses"): for bonus in template.find("./Attack/Ranged/Bonuses"):
Against = [] against = []
CivAg = [] civ_ag = []
if Bonus.find("Classes") is not None and Bonus.find("Classes").text is not None: if bonus.find("Classes") is not None and bonus.find("Classes").text is not None:
Against = Bonus.find("Classes").text.split(" ") against = bonus.find("Classes").text.split(" ")
if Bonus.find("Civ") is not None and Bonus.find("Civ").text is not None: if bonus.find("Civ") is not None and bonus.find("Civ").text is not None:
CivAg = Bonus.find("Civ").text.split(" ") civ_ag = bonus.find("Civ").text.split(" ")
Val = float(Bonus.find("Multiplier").text) val = float(bonus.find("Multiplier").text)
unit["AttackBonuses"][Bonus.tag] = { unit["AttackBonuses"][bonus.tag] = {
"Classes": Against, "Classes": against,
"Civs": CivAg, "Civs": civ_ag,
"Multiplier": Val, "Multiplier": val,
} }
if Template.find("./Attack/Melee/RestrictedClasses") is not None: if template.find("./Attack/Melee/RestrictedClasses") is not None:
newClasses = Template.find("./Attack/Melee/RestrictedClasses").text.split(" ") new_classes = template.find("./Attack/Melee/RestrictedClasses").text.split(" ")
for elem in newClasses: for elem in new_classes:
if elem.find("-") != -1: if elem.find("-") != -1:
newClasses.pop(newClasses.index(elem)) new_classes.pop(new_classes.index(elem))
if elem in unit["Restricted"]: if elem in unit["Restricted"]:
unit["Restricted"].pop(newClasses.index(elem)) unit["Restricted"].pop(new_classes.index(elem))
unit["Restricted"] += newClasses unit["Restricted"] += new_classes
if Template.find("Resistance") is not None: if template.find("Resistance") is not None:
for atttype in AttackTypes: for atttype in ATTACK_TYPES:
unit["Resistance"][atttype] = ExtractValue( unit["Resistance"][atttype] = extract_value(
Template.find("./Resistance/Entity/Damage/" + atttype) template.find("./Resistance/Entity/Damage/" + atttype)
) )
if ( if (
Template.find("./UnitMotion") is not None template.find("./UnitMotion") is not None
and Template.find("./UnitMotion/WalkSpeed") is not None and template.find("./UnitMotion/WalkSpeed") is not None
): ):
unit["WalkSpeed"] = ExtractValue(Template.find("./UnitMotion/WalkSpeed")) unit["WalkSpeed"] = extract_value(template.find("./UnitMotion/WalkSpeed"))
if Template.find("./Identity/VisibleClasses") is not None: if template.find("./Identity/VisibleClasses") is not None:
newClasses = Template.find("./Identity/VisibleClasses").text.split(" ") new_classes = template.find("./Identity/VisibleClasses").text.split(" ")
for elem in newClasses: for elem in new_classes:
if elem.find("-") != -1: if elem.find("-") != -1:
newClasses.pop(newClasses.index(elem)) new_classes.pop(new_classes.index(elem))
if elem in unit["Classes"]: if elem in unit["Classes"]:
unit["Classes"].pop(newClasses.index(elem)) unit["Classes"].pop(new_classes.index(elem))
unit["Classes"] += newClasses unit["Classes"] += new_classes
if Template.find("./Identity/Classes") is not None: if template.find("./Identity/Classes") is not None:
newClasses = Template.find("./Identity/Classes").text.split(" ") new_classes = template.find("./Identity/Classes").text.split(" ")
for elem in newClasses: for elem in new_classes:
if elem.find("-") != -1: if elem.find("-") != -1:
newClasses.pop(newClasses.index(elem)) new_classes.pop(new_classes.index(elem))
if elem in unit["Classes"]: if elem in unit["Classes"]:
unit["Classes"].pop(newClasses.index(elem)) unit["Classes"].pop(new_classes.index(elem))
unit["Classes"] += newClasses unit["Classes"] += new_classes
return unit return unit
def WriteUnit(Name, UnitDict): def write_unit(name, unit_dict):
ret = "<tr>" ret = "<tr>"
ret += '<td class="Sub">' + Name + "</td>" ret += '<td class="Sub">' + name + "</td>"
ret += "<td>" + str("{:.0f}".format(float(UnitDict["HP"]))) + "</td>" ret += "<td>" + str("{:.0f}".format(float(unit_dict["HP"]))) + "</td>"
ret += "<td>" + str("{:.0f}".format(float(UnitDict["BuildTime"]))) + "</td>" ret += "<td>" + str("{:.0f}".format(float(unit_dict["BuildTime"]))) + "</td>"
ret += "<td>" + str("{:.1f}".format(float(UnitDict["WalkSpeed"]))) + "</td>" ret += "<td>" + str("{:.1f}".format(float(unit_dict["WalkSpeed"]))) + "</td>"
for atype in AttackTypes: for atype in ATTACK_TYPES:
PercentValue = 1.0 - (0.9 ** float(UnitDict["Resistance"][atype])) percent_value = 1.0 - (0.9 ** float(unit_dict["Resistance"][atype]))
ret += ( ret += (
"<td>" "<td>"
+ str("{:.0f}".format(float(UnitDict["Resistance"][atype]))) + str("{:.0f}".format(float(unit_dict["Resistance"][atype])))
+ " / " + " / "
+ str("%.0f" % (PercentValue * 100.0)) + str("%.0f" % (percent_value * 100.0))
+ "%</td>" + "%</td>"
) )
attType = "Ranged" if UnitDict["Ranged"] is True else "Melee" att_type = "Ranged" if unit_dict["Ranged"] is True else "Melee"
if UnitDict["RepeatRate"][attType] != "0": if unit_dict["RepeatRate"][att_type] != "0":
for atype in AttackTypes: for atype in ATTACK_TYPES:
repeatTime = float(UnitDict["RepeatRate"][attType]) / 1000.0 repeat_time = float(unit_dict["RepeatRate"][att_type]) / 1000.0
ret += ( ret += (
"<td>" "<td>"
+ str("%.1f" % (float(UnitDict["Attack"][attType][atype]) / repeatTime)) + str("%.1f" % (float(unit_dict["Attack"][att_type][atype]) / repeat_time))
+ "</td>" + "</td>"
) )
ret += "<td>" + str("%.1f" % (float(UnitDict["RepeatRate"][attType]) / 1000.0)) + "</td>" ret += "<td>" + str("%.1f" % (float(unit_dict["RepeatRate"][att_type]) / 1000.0)) + "</td>"
else: else:
for _ in AttackTypes: for _ in ATTACK_TYPES:
ret += "<td> - </td>" ret += "<td> - </td>"
ret += "<td> - </td>" ret += "<td> - </td>"
if UnitDict["Ranged"] is True and UnitDict["Range"] > 0: if unit_dict["Ranged"] is True and unit_dict["Range"] > 0:
ret += "<td>" + str("{:.1f}".format(float(UnitDict["Range"]))) + "</td>" ret += "<td>" + str("{:.1f}".format(float(unit_dict["Range"]))) + "</td>"
spread = float(UnitDict["Spread"]) spread = float(unit_dict["Spread"])
ret += "<td>" + str(f"{spread:.1f}") + "</td>" ret += "<td>" + str(f"{spread:.1f}") + "</td>"
else: else:
ret += "<td> - </td><td> - </td>" ret += "<td> - </td><td> - </td>"
for rtype in Resources: for rtype in RESOURCES:
ret += "<td>" + str("{:.0f}".format(float(UnitDict["Cost"][rtype]))) + "</td>" ret += "<td>" + str("{:.0f}".format(float(unit_dict["Cost"][rtype]))) + "</td>"
ret += "<td>" + str("{:.0f}".format(float(UnitDict["Cost"]["population"]))) + "</td>" ret += "<td>" + str("{:.0f}".format(float(unit_dict["Cost"]["population"]))) + "</td>"
ret += '<td style="text-align:left;">' ret += '<td style="text-align:left;">'
for Bonus in UnitDict["AttackBonuses"]: for bonus in unit_dict["AttackBonuses"]:
ret += "[" ret += "["
for classe in UnitDict["AttackBonuses"][Bonus]["Classes"]: for classe in unit_dict["AttackBonuses"][bonus]["Classes"]:
ret += classe + " " ret += classe + " "
ret += ": {}] ".format(UnitDict["AttackBonuses"][Bonus]["Multiplier"]) ret += ": {}] ".format(unit_dict["AttackBonuses"][bonus]["Multiplier"])
ret += "</td>" ret += "</td>"
ret += "</tr>\n" ret += "</tr>\n"
@ -358,37 +358,37 @@ def WriteUnit(Name, UnitDict):
# Sort the templates dictionary. # Sort the templates dictionary.
def SortFn(A): def sort_fn(a):
sortVal = 0 sort_val = 0
for classe in ClassesUsedForSort: for classe in CLASSES_USED_FOR_SORT:
sortVal += 1 sort_val += 1
if classe in A[1]["Classes"]: if classe in a[1]["Classes"]:
break break
if ComparativeSortByChamp is True and A[0].find("champion") == -1: if COMPARATIVE_SORT_BY_CHAMP is True and a[0].find("champion") == -1:
sortVal -= 20 sort_val -= 20
if ComparativeSortByCav is True and A[0].find("cavalry") == -1: if COMPARATIVE_SORT_BY_CAV is True and a[0].find("cavalry") == -1:
sortVal -= 10 sort_val -= 10
if A[1]["Civ"] is not None and A[1]["Civ"] in Civs: if a[1]["Civ"] is not None and a[1]["Civ"] in CIVS:
sortVal += 100 * Civs.index(A[1]["Civ"]) sort_val += 100 * CIVS.index(a[1]["Civ"])
return sortVal return sort_val
def WriteColouredDiff(file, diff, isChanged): def write_coloured_diff(file, diff, is_changed):
"""Help to write coloured text. """Help to write coloured text.
diff value must always be computed as a unit_spec - unit_generic. diff value must always be computed as a unit_spec - unit_generic.
A positive imaginary part represents advantageous trait. A positive imaginary part represents advantageous trait.
""" """
def cleverParse(diff): def clever_parse(diff):
if float(diff) - int(diff) < 0.001: if float(diff) - int(diff) < 0.001:
return str(int(diff)) return str(int(diff))
return str(f"{float(diff):.1f}") return str(f"{float(diff):.1f}")
isAdvantageous = diff.imag > 0 is_advantageous = diff.imag > 0
diff = diff.real diff = diff.real
if diff != 0: if diff != 0:
isChanged = True is_changed = True
else: else:
# do not change its value if one parameter is not changed (yet) # do not change its value if one parameter is not changed (yet)
# some other parameter might be different # some other parameter might be different
@ -396,32 +396,32 @@ def WriteColouredDiff(file, diff, isChanged):
if diff == 0: if diff == 0:
rgb_str = "200,200,200" rgb_str = "200,200,200"
elif isAdvantageous and diff > 0 or (not isAdvantageous) and diff < 0: elif is_advantageous and diff > 0 or (not is_advantageous) and diff < 0:
rgb_str = "180,0,0" rgb_str = "180,0,0"
else: else:
rgb_str = "0,150,0" rgb_str = "0,150,0"
file.write( file.write(
f"""<td><span style="color:rgb({rgb_str});">{cleverParse(diff)}</span></td> f"""<td><span style="color:rgb({rgb_str});">{clever_parse(diff)}</span></td>
""" """
) )
return isChanged return is_changed
def computeUnitEfficiencyDiff(TemplatesByParent, Civs): def compute_unit_efficiency_diff(templates_by_parent, civs):
efficiency_table = {} efficiency_table = {}
for parent in TemplatesByParent: for parent in templates_by_parent:
for template in [ for template in [
template for template in TemplatesByParent[parent] if template[1]["Civ"] not in Civs template for template in templates_by_parent[parent] if template[1]["Civ"] not in civs
]: ]:
print(template) print(template)
TemplatesByParent[parent] = [ templates_by_parent[parent] = [
template for template in TemplatesByParent[parent] if template[1]["Civ"] in Civs template for template in templates_by_parent[parent] if template[1]["Civ"] in civs
] ]
TemplatesByParent[parent].sort(key=lambda x: Civs.index(x[1]["Civ"])) templates_by_parent[parent].sort(key=lambda x: civs.index(x[1]["Civ"]))
for tp in TemplatesByParent[parent]: for tp in templates_by_parent[parent]:
# HP # HP
diff = -1j + (int(tp[1]["HP"]) - int(templates[parent]["HP"])) diff = -1j + (int(tp[1]["HP"]) - int(templates[parent]["HP"]))
efficiency_table[(parent, tp[0], "HP")] = diff efficiency_table[(parent, tp[0], "HP")] = diff
@ -436,7 +436,7 @@ def computeUnitEfficiencyDiff(TemplatesByParent, Civs):
efficiency_table[(parent, tp[0], "WalkSpeed")] = diff efficiency_table[(parent, tp[0], "WalkSpeed")] = diff
# Resistance # Resistance
for atype in AttackTypes: for atype in ATTACK_TYPES:
diff = -1j + ( diff = -1j + (
float(tp[1]["Resistance"][atype]) float(tp[1]["Resistance"][atype])
- float(templates[parent]["Resistance"][atype]) - float(templates[parent]["Resistance"][atype])
@ -444,35 +444,37 @@ def computeUnitEfficiencyDiff(TemplatesByParent, Civs):
efficiency_table[(parent, tp[0], "Resistance/" + atype)] = diff efficiency_table[(parent, tp[0], "Resistance/" + atype)] = diff
# Attack types (DPS) and rate. # Attack types (DPS) and rate.
attType = "Ranged" if tp[1]["Ranged"] is True else "Melee" att_type = "Ranged" if tp[1]["Ranged"] is True else "Melee"
if tp[1]["RepeatRate"][attType] != "0": if tp[1]["RepeatRate"][att_type] != "0":
for atype in AttackTypes: for atype in ATTACK_TYPES:
myDPS = float(tp[1]["Attack"][attType][atype]) / ( my_dps = float(tp[1]["Attack"][att_type][atype]) / (
float(tp[1]["RepeatRate"][attType]) / 1000.0 float(tp[1]["RepeatRate"][att_type]) / 1000.0
) )
parentDPS = float(templates[parent]["Attack"][attType][atype]) / ( parent_dps = float(templates[parent]["Attack"][att_type][atype]) / (
float(templates[parent]["RepeatRate"][attType]) / 1000.0 float(templates[parent]["RepeatRate"][att_type]) / 1000.0
) )
diff = -1j + (myDPS - parentDPS) diff = -1j + (my_dps - parent_dps)
efficiency_table[(parent, tp[0], "Attack/" + attType + "/" + atype)] = diff efficiency_table[(parent, tp[0], "Attack/" + att_type + "/" + atype)] = diff
diff = -1j + ( diff = -1j + (
float(tp[1]["RepeatRate"][attType]) / 1000.0 float(tp[1]["RepeatRate"][att_type]) / 1000.0
- float(templates[parent]["RepeatRate"][attType]) / 1000.0 - float(templates[parent]["RepeatRate"][att_type]) / 1000.0
) )
efficiency_table[ efficiency_table[
(parent, tp[0], "Attack/" + attType + "/" + atype + "/RepeatRate") (parent, tp[0], "Attack/" + att_type + "/" + atype + "/RepeatRate")
] = diff ] = diff
# range and spread # range and spread
if tp[1]["Ranged"] is True: if tp[1]["Ranged"] is True:
diff = -1j + (float(tp[1]["Range"]) - float(templates[parent]["Range"])) diff = -1j + (float(tp[1]["Range"]) - float(templates[parent]["Range"]))
efficiency_table[(parent, tp[0], "Attack/" + attType + "/Ranged/Range")] = diff efficiency_table[(parent, tp[0], "Attack/" + att_type + "/Ranged/Range")] = (
diff = float(tp[1]["Spread"]) - float(templates[parent]["Spread"])
efficiency_table[(parent, tp[0], "Attack/" + attType + "/Ranged/Spread")] = (
diff diff
) )
for rtype in Resources: diff = float(tp[1]["Spread"]) - float(templates[parent]["Spread"])
efficiency_table[(parent, tp[0], "Attack/" + att_type + "/Ranged/Spread")] = (
diff
)
for rtype in RESOURCES:
diff = +1j + ( diff = +1j + (
float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype]) float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])
) )
@ -486,25 +488,25 @@ def computeUnitEfficiencyDiff(TemplatesByParent, Civs):
return efficiency_table return efficiency_table
def computeTemplates(LoadTemplatesIfParent): def compute_templates(load_templates_if_parent):
"""Loops over template XMLs and selectively insert into templates dict.""" """Loops over template XMLs and selectively insert into templates dict."""
pwd = os.getcwd() pwd = os.getcwd()
os.chdir(basePath) os.chdir(base_path)
templates = {} templates = {}
for template in list(glob.glob("template_*.xml")): for template in list(glob.glob("template_*.xml")):
if os.path.isfile(template): if os.path.isfile(template):
found = False found = False
for possParent in LoadTemplatesIfParent: for poss_parent in load_templates_if_parent:
if hasParentTemplate(template, possParent): if has_parent_template(template, poss_parent):
found = True found = True
break break
if found is True: if found is True:
templates[template] = CalcUnit(template) templates[template] = calc_unit(template)
os.chdir(pwd) os.chdir(pwd)
return templates return templates
def computeCivTemplates(Civs: list): def compute_civ_templates(civs: list):
"""Load Civ specific templates. """Load Civ specific templates.
NOTE: whether a Civ can train a certain unit is not recorded in the unit NOTE: whether a Civ can train a certain unit is not recorded in the unit
@ -518,87 +520,83 @@ def computeCivTemplates(Civs: list):
up with the game engine. up with the game engine.
""" """
pwd = os.getcwd() pwd = os.getcwd()
os.chdir(basePath) os.chdir(base_path)
CivTemplates = {} civ_templates = {}
for Civ in Civs: for civ in civs:
CivTemplates[Civ] = {} civ_templates[civ] = {}
# Load all templates that start with that civ indicator # Load all templates that start with that civ indicator
# TODO: consider adding mixin/civs here too # TODO: consider adding mixin/civs here too
civ_list = list(glob.glob("units/" + Civ + "/*.xml")) civ_list = list(glob.glob("units/" + civ + "/*.xml"))
for template in civ_list: for template in civ_list:
if os.path.isfile(template): if os.path.isfile(template):
# filter based on FilterOut # filter based on FilterOut
breakIt = False break_it = False
for civ_filter in FilterOut: for civ_filter in FILTER_OUT:
if template.find(civ_filter) != -1: if template.find(civ_filter) != -1:
breakIt = True break_it = True
if breakIt: if break_it:
continue continue
# filter based on loaded generic templates # filter based on loaded generic templates
breakIt = True break_it = True
for possParent in LoadTemplatesIfParent: for poss_parent in LOAD_TEMPLATES_IF_PARENT:
if hasParentTemplate(template, possParent): if has_parent_template(template, poss_parent):
breakIt = False break_it = False
break break
if breakIt: if break_it:
continue continue
unit = CalcUnit(template) unit = calc_unit(template)
# Remove variants for now # Remove variants for now
if unit["Parent"].find("template_") == -1: if unit["Parent"].find("template_") == -1:
continue continue
# load template # load template
CivTemplates[Civ][template] = unit civ_templates[civ][template] = unit
os.chdir(pwd) os.chdir(pwd)
return CivTemplates return civ_templates
def computeTemplatesByParent(templates: dict, Civs: list, CivTemplates: dict): def compute_templates_by_parent(templates: dict, civs: list, civ_templates: dict):
"""Get them in the array.""" """Get them in the array."""
# Civs:list -> CivTemplates:dict -> templates:dict -> TemplatesByParent # civs:list -> civ_templates:dict -> templates:dict -> templates_by_parent
TemplatesByParent = {} templates_by_parent = {}
for Civ in Civs: for civ in civs:
for CivUnitTemplate in CivTemplates[Civ]: for civ_unit_template in civ_templates[civ]:
parent = CivTemplates[Civ][CivUnitTemplate]["Parent"] parent = civ_templates[civ][civ_unit_template]["Parent"]
# We have the following constant equality # We have the following constant equality
# templates[*]["Civ"] === gaia # templates[*]["Civ"] === gaia
# if parent in templates and templates[parent]["Civ"] == None: # if parent in templates and templates[parent]["Civ"] == None:
if parent in templates: if parent in templates:
if parent not in TemplatesByParent: if parent not in templates_by_parent:
TemplatesByParent[parent] = [] templates_by_parent[parent] = []
TemplatesByParent[parent].append( templates_by_parent[parent].append(
(CivUnitTemplate, CivTemplates[Civ][CivUnitTemplate]) (civ_unit_template, civ_templates[civ][civ_unit_template])
) )
# debug after CivTemplates are non-empty # debug after CivTemplates are non-empty
return TemplatesByParent return templates_by_parent
############################################################ ############################################################
## Pre-compute all tables ## Pre-compute all tables
templates = computeTemplates(LoadTemplatesIfParent) templates = compute_templates(LOAD_TEMPLATES_IF_PARENT)
CivTemplates = computeCivTemplates(Civs) CivTemplates = compute_civ_templates(CIVS)
TemplatesByParent = computeTemplatesByParent(templates, Civs, CivTemplates) TemplatesByParent = compute_templates_by_parent(templates, CIVS, CivTemplates)
# Not used; use it for your own custom analysis # Not used; use it for your own custom analysis
efficiencyTable = computeUnitEfficiencyDiff(TemplatesByParent, Civs) efficiency_table = compute_unit_efficiency_diff(TemplatesByParent, CIVS)
############################################################ ############################################################
def writeHTML(): def write_html():
"""Create the HTML file.""" """Create the HTML file."""
f = open( f = open(Path(__file__).parent / "unit_summary_table.html", "w", encoding="utf8")
os.path.realpath(__file__).replace("unitTables.py", "") + "unit_summary_table.html",
"w",
encoding="utf-8",
)
f.write( f.write(
""" """
@ -639,7 +637,7 @@ def writeHTML():
""" """
) )
for template in templates: for template in templates:
f.write(WriteUnit(template, templates[template])) f.write(write_unit(template, templates[template]))
f.write("</table>") f.write("</table>")
# Write unit specialization # Write unit specialization
@ -681,14 +679,10 @@ differences between the two.
""" """
) )
for parent in TemplatesByParent: for parent in TemplatesByParent:
TemplatesByParent[parent].sort(key=lambda x: Civs.index(x[1]["Civ"])) TemplatesByParent[parent].sort(key=lambda x: CIVS.index(x[1]["Civ"]))
for tp in TemplatesByParent[parent]: for tp in TemplatesByParent[parent]:
isChanged = False is_changed = False
ff = open( ff = open(Path(__file__).parent / ".cache", "w", encoding="utf8")
os.path.realpath(__file__).replace("unitTables.py", "") + ".cache",
"w",
encoding="utf-8",
)
ff.write("<tr>") ff.write("<tr>")
ff.write( ff.write(
@ -702,54 +696,56 @@ differences between the two.
# HP # HP
diff = -1j + (int(tp[1]["HP"]) - int(templates[parent]["HP"])) diff = -1j + (int(tp[1]["HP"]) - int(templates[parent]["HP"]))
isChanged = WriteColouredDiff(ff, diff, isChanged) is_changed = write_coloured_diff(ff, diff, is_changed)
# Build Time # Build Time
diff = +1j + (int(tp[1]["BuildTime"]) - int(templates[parent]["BuildTime"])) diff = +1j + (int(tp[1]["BuildTime"]) - int(templates[parent]["BuildTime"]))
isChanged = WriteColouredDiff(ff, diff, isChanged) is_changed = write_coloured_diff(ff, diff, is_changed)
# walk speed # walk speed
diff = -1j + (float(tp[1]["WalkSpeed"]) - float(templates[parent]["WalkSpeed"])) diff = -1j + (float(tp[1]["WalkSpeed"]) - float(templates[parent]["WalkSpeed"]))
isChanged = WriteColouredDiff(ff, diff, isChanged) is_changed = write_coloured_diff(ff, diff, is_changed)
# Resistance # Resistance
for atype in AttackTypes: for atype in ATTACK_TYPES:
diff = -1j + ( diff = -1j + (
float(tp[1]["Resistance"][atype]) float(tp[1]["Resistance"][atype])
- float(templates[parent]["Resistance"][atype]) - float(templates[parent]["Resistance"][atype])
) )
isChanged = WriteColouredDiff(ff, diff, isChanged) is_changed = write_coloured_diff(ff, diff, is_changed)
# Attack types (DPS) and rate. # Attack types (DPS) and rate.
attType = "Ranged" if tp[1]["Ranged"] is True else "Melee" att_type = "Ranged" if tp[1]["Ranged"] is True else "Melee"
if tp[1]["RepeatRate"][attType] != "0": if tp[1]["RepeatRate"][att_type] != "0":
for atype in AttackTypes: for atype in ATTACK_TYPES:
myDPS = float(tp[1]["Attack"][attType][atype]) / ( my_dps = float(tp[1]["Attack"][att_type][atype]) / (
float(tp[1]["RepeatRate"][attType]) / 1000.0 float(tp[1]["RepeatRate"][att_type]) / 1000.0
) )
parentDPS = float(templates[parent]["Attack"][attType][atype]) / ( parent_dps = float(templates[parent]["Attack"][att_type][atype]) / (
float(templates[parent]["RepeatRate"][attType]) / 1000.0 float(templates[parent]["RepeatRate"][att_type]) / 1000.0
) )
isChanged = WriteColouredDiff(ff, -1j + (myDPS - parentDPS), isChanged) is_changed = write_coloured_diff(ff, -1j + (my_dps - parent_dps), is_changed)
isChanged = WriteColouredDiff( is_changed = write_coloured_diff(
ff, ff,
-1j -1j
+ ( + (
float(tp[1]["RepeatRate"][attType]) / 1000.0 float(tp[1]["RepeatRate"][att_type]) / 1000.0
- float(templates[parent]["RepeatRate"][attType]) / 1000.0 - float(templates[parent]["RepeatRate"][att_type]) / 1000.0
), ),
isChanged, is_changed,
) )
# range and spread # range and spread
if tp[1]["Ranged"] is True: if tp[1]["Ranged"] is True:
isChanged = WriteColouredDiff( is_changed = write_coloured_diff(
ff, ff,
-1j + (float(tp[1]["Range"]) - float(templates[parent]["Range"])), -1j + (float(tp[1]["Range"]) - float(templates[parent]["Range"])),
isChanged, is_changed,
)
my_spread = float(tp[1]["Spread"])
parent_spread = float(templates[parent]["Spread"])
is_changed = write_coloured_diff(
ff, +1j + (my_spread - parent_spread), is_changed
) )
mySpread = float(tp[1]["Spread"])
parentSpread = float(templates[parent]["Spread"])
isChanged = WriteColouredDiff(ff, +1j + (mySpread - parentSpread), isChanged)
else: else:
ff.write( ff.write(
"<td><span style='color:rgb(200,200,200);'>-</span></td><td>" "<td><span style='color:rgb(200,200,200);'>-</span></td><td>"
@ -758,39 +754,36 @@ differences between the two.
else: else:
ff.write("<td></td><td></td><td></td><td></td><td></td><td></td>") ff.write("<td></td><td></td><td></td><td></td><td></td><td></td>")
for rtype in Resources: for rtype in RESOURCES:
isChanged = WriteColouredDiff( is_changed = write_coloured_diff(
ff, ff,
+1j + (float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])), +1j + (float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])),
isChanged, is_changed,
) )
isChanged = WriteColouredDiff( is_changed = write_coloured_diff(
ff, ff,
+1j +1j
+ ( + (
float(tp[1]["Cost"]["population"]) float(tp[1]["Cost"]["population"])
- float(templates[parent]["Cost"]["population"]) - float(templates[parent]["Cost"]["population"])
), ),
isChanged, is_changed,
) )
ff.write("<td>" + tp[1]["Civ"] + "</td>") ff.write("<td>" + tp[1]["Civ"] + "</td>")
ff.write("</tr>\n") ff.write("</tr>\n")
ff.close() # to actually write into the file ff.close() # to actually write into the file
with open( with open(Path(__file__).parent / ".cache", encoding="utf-8") as ff:
os.path.realpath(__file__).replace("unitTables.py", "") + ".cache", unit_str = ff.read()
encoding="utf-8",
) as ff:
unitStr = ff.read()
if showChangedOnly: if SHOW_CHANGED_ONLY:
if isChanged: if is_changed:
f.write(unitStr) f.write(unit_str)
else: else:
# print the full table if showChangedOnly is false # print the full table if SHOW_CHANGED_ONLY is false
f.write(unitStr) f.write(unit_str)
f.write("<table/>") f.write("<table/>")
@ -813,17 +806,17 @@ each loaded generic template.
<th>Template </th> <th>Template </th>
""" """
) )
for civ in Civs: for civ in CIVS:
f.write('<td class="vertical-text">' + civ + "</td>\n") f.write('<td class="vertical-text">' + civ + "</td>\n")
f.write("</tr>\n") f.write("</tr>\n")
sortedDict = sorted(templates.items(), key=SortFn) sorted_dict = sorted(templates.items(), key=sort_fn)
for tp in sortedDict: for tp in sorted_dict:
if tp[0] not in TemplatesByParent: if tp[0] not in TemplatesByParent:
continue continue
f.write("<tr><td>" + tp[0] + "</td>\n") f.write("<tr><td>" + tp[0] + "</td>\n")
for civ in Civs: for civ in CIVS:
found = 0 found = 0
for temp in TemplatesByParent[tp[0]]: for temp in TemplatesByParent[tp[0]]:
if temp[1]["Civ"] == civ: if temp[1]["Civ"] == civ:
@ -841,7 +834,7 @@ each loaded generic template.
'<tr style="margin-top:2px;border-top:2px #aaa solid;">\ '<tr style="margin-top:2px;border-top:2px #aaa solid;">\
<th style="text-align:right; padding-right:10px;">Total:</th>\n' <th style="text-align:right; padding-right:10px;">Total:</th>\n'
) )
for civ in Civs: for civ in CIVS:
count = 0 count = 0
for _units in CivTemplates[civ]: for _units in CivTemplates[civ]:
count += 1 count += 1
@ -853,7 +846,7 @@ each loaded generic template.
# Add a simple script to allow filtering on sorting directly in the HTML # Add a simple script to allow filtering on sorting directly in the HTML
# page. # page.
if AddSortingOverlay: if ADD_SORTING_OVERLAY:
f.write( f.write(
""" """
<script src="tablefilter/tablefilter.js"></script> <script src="tablefilter/tablefilter.js"></script>
@ -941,4 +934,4 @@ tf2.init();
if __name__ == "__main__": if __name__ == "__main__":
writeHTML() write_html()