2021-02-12 13:25:33 -08:00
|
|
|
#!/usr/bin/env python3
|
2022-02-17 14:41:53 -08:00
|
|
|
#
|
2025-05-23 00:30:54 -07:00
|
|
|
# Copyright (C) 2025 Wildfire Games.
|
2022-02-17 14:41:53 -08:00
|
|
|
#
|
2015-12-05 09:02:25 -08:00
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
# in the Software without restriction, including without limitation the rights
|
|
|
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
# furnished to do so, subject to the following conditions:
|
2022-02-17 14:41:53 -08:00
|
|
|
#
|
2015-12-05 09:02:25 -08:00
|
|
|
# The above copyright notice and this permission notice shall be included in
|
|
|
|
|
# all copies or substantial portions of the Software.
|
2022-02-17 14:41:53 -08:00
|
|
|
#
|
2015-12-05 09:02:25 -08:00
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
# THE SOFTWARE.
|
|
|
|
|
|
2025-08-17 06:17:13 -07:00
|
|
|
# ruff: noqa: FURB122,SIM115
|
2024-08-28 22:00:43 -07:00
|
|
|
|
2023-01-18 15:38:44 -08:00
|
|
|
import sys
|
2014-07-11 15:24:58 -07:00
|
|
|
import xml.etree.ElementTree as ET
|
2025-05-23 00:34:06 -07:00
|
|
|
from functools import lru_cache
|
2023-01-18 15:38:44 -08:00
|
|
|
from pathlib import Path
|
2024-08-24 21:29:39 -07:00
|
|
|
|
2014-07-11 15:24:58 -07:00
|
|
|
|
2025-05-23 00:30:54 -07:00
|
|
|
sys.path.append(str(Path(__file__).parent.parent / "entity"))
|
2024-08-24 21:29:39 -07:00
|
|
|
from scriptlib import SimulTemplateEntity
|
2024-08-22 00:18:20 -07:00
|
|
|
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
ATTACK_TYPES = ["Hack", "Pierce", "Crush", "Poison", "Fire"]
|
|
|
|
|
RESOURCES = ["food", "wood", "stone", "metal"]
|
2014-07-12 09:08:35 -07:00
|
|
|
|
2015-11-29 11:19:20 -08:00
|
|
|
# Generic templates to load
|
|
|
|
|
# The way this works is it tries all generic templates
|
|
|
|
|
# But only loads those who have one of the following parents
|
|
|
|
|
# EG adding "template_unit.xml" will load all units.
|
2024-09-03 23:28:35 -07:00
|
|
|
LOAD_TEMPLATES_IF_PARENT = [
|
2022-02-17 14:41:53 -08:00
|
|
|
"template_unit_infantry.xml",
|
|
|
|
|
"template_unit_cavalry.xml",
|
|
|
|
|
"template_unit_champion.xml",
|
|
|
|
|
"template_unit_hero.xml",
|
|
|
|
|
]
|
2015-11-29 11:19:20 -08:00
|
|
|
|
|
|
|
|
# Those describe Civs to analyze.
|
2022-02-17 14:41:53 -08:00
|
|
|
# The script will load all entities that derive (to the nth degree) from one of
|
|
|
|
|
# the above templates.
|
2024-09-03 23:28:35 -07:00
|
|
|
CIVS = [
|
2022-02-17 14:41:53 -08:00
|
|
|
"athen",
|
|
|
|
|
"brit",
|
|
|
|
|
"cart",
|
|
|
|
|
"gaul",
|
|
|
|
|
"iber",
|
|
|
|
|
"kush",
|
2023-01-18 15:38:44 -08:00
|
|
|
"han",
|
2022-02-17 14:41:53 -08:00
|
|
|
"mace",
|
|
|
|
|
"maur",
|
|
|
|
|
"pers",
|
|
|
|
|
"ptol",
|
|
|
|
|
"rome",
|
|
|
|
|
"sele",
|
|
|
|
|
"spart",
|
|
|
|
|
# "gaia",
|
|
|
|
|
]
|
2014-07-12 09:08:35 -07:00
|
|
|
|
|
|
|
|
# Remote Civ templates with those strings in their name.
|
2024-09-03 23:28:35 -07:00
|
|
|
FILTER_OUT = ["marian", "thureophoros", "thorakites", "kardakes"]
|
2014-07-11 15:24:58 -07:00
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
# In the Civilization specific units table, do you want to only show the units
|
|
|
|
|
# that are different from the generic templates?
|
2024-09-03 23:28:35 -07:00
|
|
|
SHOW_CHANGED_ONLY = True
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2015-11-29 11:19:20 -08:00
|
|
|
# Sorting parameters for the "roster variety" table
|
2024-09-03 23:28:35 -07:00
|
|
|
COMPARATIVE_SORT_BY_CAV = True
|
|
|
|
|
COMPARATIVE_SORT_BY_CHAMP = True
|
|
|
|
|
CLASSES_USED_FOR_SORT = [
|
2022-02-17 14:41:53 -08:00
|
|
|
"Support",
|
|
|
|
|
"Pike",
|
|
|
|
|
"Spear",
|
|
|
|
|
"Sword",
|
|
|
|
|
"Archer",
|
|
|
|
|
"Javelin",
|
|
|
|
|
"Sling",
|
|
|
|
|
"Elephant",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Disable if you want the more compact basic data. Enable to allow filtering and
|
|
|
|
|
# sorting in-place.
|
2024-09-03 23:28:35 -07:00
|
|
|
ADD_SORTING_OVERLAY = True
|
2014-07-13 07:22:32 -07:00
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
# This is the path to the /templates/ folder to consider. Change this for mod
|
|
|
|
|
# support.
|
2024-09-03 23:28:35 -07:00
|
|
|
mods_folder = Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods"
|
|
|
|
|
base_path = mods_folder / "public" / "simulation" / "templates"
|
2014-07-13 07:22:32 -07:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
sim_entity = SimulTemplateEntity(mods_folder, None)
|
2015-11-29 11:19:20 -08:00
|
|
|
|
2024-08-22 00:18:20 -07:00
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
def htbout(file, balise, value):
|
|
|
|
|
file.write("<" + balise + ">" + value + "</" + balise + ">\n")
|
2015-11-29 11:19:20 -08:00
|
|
|
|
2014-07-11 15:24:58 -07:00
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
def htout(file, value):
|
|
|
|
|
file.write("<p>" + value + "</p>\n")
|
2014-07-11 15:24:58 -07:00
|
|
|
|
2015-11-29 11:19:20 -08:00
|
|
|
|
2025-05-23 00:34:06 -07:00
|
|
|
@lru_cache
|
2025-05-23 01:41:31 -07:00
|
|
|
def get_parent_names(template_file_name: str) -> list[str]:
|
2025-05-23 00:34:06 -07:00
|
|
|
"""Get the names of the parent templates."""
|
2025-05-23 01:41:31 -07:00
|
|
|
parent_string = ET.parse(base_path / template_file_name).getroot().get("parent")
|
2025-05-23 00:34:06 -07:00
|
|
|
if not parent_string:
|
|
|
|
|
return []
|
|
|
|
|
return parent_string.split("|")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@lru_cache
|
2025-05-23 01:41:31 -07:00
|
|
|
def get_parents(template_file_name: str) -> set[Path]:
|
2025-05-23 00:34:06 -07:00
|
|
|
"""Get the file paths of the parent templates."""
|
2023-01-18 15:38:44 -08:00
|
|
|
parents = set()
|
2025-05-23 01:41:31 -07:00
|
|
|
for parent in get_parent_names(str(base_path / template_file_name)):
|
2023-01-18 15:38:44 -08:00
|
|
|
parents.add(parent)
|
2024-09-03 23:28:35 -07:00
|
|
|
for element in get_parents(
|
2024-08-22 00:18:20 -07:00
|
|
|
sim_entity.get_file("simulation/templates/", parent + ".xml", "public")
|
|
|
|
|
):
|
2023-01-18 15:38:44 -08:00
|
|
|
parents.add(element)
|
2014-07-11 15:24:58 -07:00
|
|
|
|
2023-01-18 15:38:44 -08:00
|
|
|
return parents
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def extract_value(value):
|
2023-01-18 15:38:44 -08:00
|
|
|
return float(value.text) if value is not None else 0.0
|
|
|
|
|
|
2024-08-22 00:18:20 -07:00
|
|
|
|
2025-05-23 01:41:31 -07:00
|
|
|
def has_any_parent_template(template_name: str, parent_names: list[str]) -> bool:
|
|
|
|
|
"""Check whether the template has at least one of the given parents."""
|
|
|
|
|
template_parents = [
|
|
|
|
|
str(template_parent) + ".xml" for template_parent in get_parents(template_name)
|
|
|
|
|
]
|
|
|
|
|
return any(parent_name in template_parents for parent_name in parent_names)
|
2023-01-18 15:38:44 -08:00
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def calc_unit(unit_name, existing_unit=None):
|
|
|
|
|
if existing_unit is not None:
|
|
|
|
|
unit = existing_unit
|
2023-01-18 15:38:44 -08:00
|
|
|
else:
|
|
|
|
|
unit = {
|
|
|
|
|
"HP": 0,
|
|
|
|
|
"BuildTime": 0,
|
|
|
|
|
"Cost": {
|
|
|
|
|
"food": 0,
|
|
|
|
|
"wood": 0,
|
|
|
|
|
"stone": 0,
|
|
|
|
|
"metal": 0,
|
|
|
|
|
"population": 0,
|
|
|
|
|
},
|
|
|
|
|
"Attack": {
|
|
|
|
|
"Melee": {"Hack": 0, "Pierce": 0, "Crush": 0},
|
|
|
|
|
"Ranged": {"Hack": 0, "Pierce": 0, "Crush": 0},
|
|
|
|
|
},
|
|
|
|
|
"RepeatRate": {"Melee": "0", "Ranged": "0"},
|
|
|
|
|
"PrepRate": {"Melee": "0", "Ranged": "0"},
|
|
|
|
|
"Resistance": {"Hack": 0, "Pierce": 0, "Crush": 0},
|
|
|
|
|
"Ranged": False,
|
|
|
|
|
"Classes": [],
|
|
|
|
|
"AttackBonuses": {},
|
|
|
|
|
"Restricted": [],
|
|
|
|
|
"WalkSpeed": 0,
|
|
|
|
|
"Range": 0,
|
|
|
|
|
"Spread": 0,
|
|
|
|
|
"Civ": None,
|
|
|
|
|
}
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2025-05-23 00:34:06 -07:00
|
|
|
template = sim_entity.load_inherited("simulation/templates/", unit_name, ["public"])
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2023-01-18 15:38:44 -08:00
|
|
|
# 0ad started using unit class/category prefixed to the unit name
|
|
|
|
|
# separated by |, known as mixins since A25 (rP25223)
|
|
|
|
|
# We strip these categories for now
|
|
|
|
|
# This can be used later for classification
|
2025-05-23 00:34:06 -07:00
|
|
|
unit["Parent"] = get_parent_names(unit_name)[-1] + ".xml"
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["Civ"] = template.find("./Identity/Civ").text
|
|
|
|
|
unit["HP"] = extract_value(template.find("./Health/Max"))
|
|
|
|
|
unit["BuildTime"] = extract_value(template.find("./Cost/BuildTime"))
|
|
|
|
|
unit["Cost"]["population"] = extract_value(template.find("./Cost/Population"))
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
resource_cost = template.find("./Cost/Resources")
|
2023-01-18 15:38:44 -08:00
|
|
|
if resource_cost is not None:
|
2024-08-27 10:28:11 -07:00
|
|
|
for resource_type in list(resource_cost):
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["Cost"][resource_type.tag] = extract_value(resource_type)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
if template.find("./Attack/Melee") is not None:
|
|
|
|
|
unit["RepeatRate"]["Melee"] = extract_value(template.find("./Attack/Melee/RepeatTime"))
|
|
|
|
|
unit["PrepRate"]["Melee"] = extract_value(template.find("./Attack/Melee/PrepareTime"))
|
2023-01-18 15:38:44 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for atttype in ATTACK_TYPES:
|
|
|
|
|
unit["Attack"]["Melee"][atttype] = extract_value(
|
|
|
|
|
template.find("./Attack/Melee/Damage/" + atttype)
|
2024-08-22 00:18:20 -07:00
|
|
|
)
|
2023-01-18 15:38:44 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
attack_melee_bonus = template.find("./Attack/Melee/Bonuses")
|
2023-01-18 15:38:44 -08:00
|
|
|
if attack_melee_bonus is not None:
|
2024-09-03 23:28:35 -07:00
|
|
|
for bonus in attack_melee_bonus:
|
|
|
|
|
against = []
|
|
|
|
|
civ_ag = []
|
|
|
|
|
if bonus.find("Classes") is not None and bonus.find("Classes").text is not None:
|
|
|
|
|
against = bonus.find("Classes").text.split(" ")
|
|
|
|
|
if bonus.find("Civ") is not None and bonus.find("Civ").text is not None:
|
|
|
|
|
civ_ag = bonus.find("Civ").text.split(" ")
|
|
|
|
|
val = float(bonus.find("Multiplier").text)
|
|
|
|
|
unit["AttackBonuses"][bonus.tag] = {
|
|
|
|
|
"Classes": against,
|
|
|
|
|
"Civs": civ_ag,
|
|
|
|
|
"Multiplier": val,
|
2022-02-17 14:41:53 -08:00
|
|
|
}
|
2023-01-18 15:38:44 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
attack_restricted_classes = template.find("./Attack/Melee/RestrictedClasses")
|
2023-01-18 15:38:44 -08:00
|
|
|
if attack_restricted_classes is not None:
|
2024-09-03 23:28:35 -07:00
|
|
|
new_classes = attack_restricted_classes.text.split(" ")
|
|
|
|
|
for elem in new_classes:
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem.find("-") != -1:
|
2024-09-03 23:28:35 -07:00
|
|
|
new_classes.pop(new_classes.index(elem))
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem in unit["Restricted"]:
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["Restricted"].pop(new_classes.index(elem))
|
|
|
|
|
unit["Restricted"] += new_classes
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
elif template.find("./Attack/Ranged") is not None:
|
2022-02-17 14:41:53 -08:00
|
|
|
unit["Ranged"] = True
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["Range"] = extract_value(template.find("./Attack/Ranged/MaxRange"))
|
|
|
|
|
unit["Spread"] = extract_value(template.find("./Attack/Ranged/Projectile/Spread"))
|
|
|
|
|
unit["RepeatRate"]["Ranged"] = extract_value(template.find("./Attack/Ranged/RepeatTime"))
|
|
|
|
|
unit["PrepRate"]["Ranged"] = extract_value(template.find("./Attack/Ranged/PrepareTime"))
|
|
|
|
|
|
|
|
|
|
for atttype in ATTACK_TYPES:
|
|
|
|
|
unit["Attack"]["Ranged"][atttype] = extract_value(
|
|
|
|
|
template.find("./Attack/Ranged/Damage/" + atttype)
|
2024-08-22 00:18:20 -07:00
|
|
|
)
|
2023-01-18 15:38:44 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
if template.find("./Attack/Ranged/Bonuses") is not None:
|
|
|
|
|
for bonus in template.find("./Attack/Ranged/Bonuses"):
|
|
|
|
|
against = []
|
|
|
|
|
civ_ag = []
|
|
|
|
|
if bonus.find("Classes") is not None and bonus.find("Classes").text is not None:
|
|
|
|
|
against = bonus.find("Classes").text.split(" ")
|
|
|
|
|
if bonus.find("Civ") is not None and bonus.find("Civ").text is not None:
|
|
|
|
|
civ_ag = bonus.find("Civ").text.split(" ")
|
|
|
|
|
val = float(bonus.find("Multiplier").text)
|
|
|
|
|
unit["AttackBonuses"][bonus.tag] = {
|
|
|
|
|
"Classes": against,
|
|
|
|
|
"Civs": civ_ag,
|
|
|
|
|
"Multiplier": val,
|
2022-02-17 14:41:53 -08:00
|
|
|
}
|
2024-09-03 23:28:35 -07:00
|
|
|
if template.find("./Attack/Melee/RestrictedClasses") is not None:
|
|
|
|
|
new_classes = template.find("./Attack/Melee/RestrictedClasses").text.split(" ")
|
|
|
|
|
for elem in new_classes:
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem.find("-") != -1:
|
2024-09-03 23:28:35 -07:00
|
|
|
new_classes.pop(new_classes.index(elem))
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem in unit["Restricted"]:
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["Restricted"].pop(new_classes.index(elem))
|
|
|
|
|
unit["Restricted"] += new_classes
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
if template.find("Resistance") is not None:
|
|
|
|
|
for atttype in ATTACK_TYPES:
|
|
|
|
|
unit["Resistance"][atttype] = extract_value(
|
|
|
|
|
template.find("./Resistance/Entity/Damage/" + atttype)
|
2024-08-22 00:18:20 -07:00
|
|
|
)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-08-28 22:00:43 -07:00
|
|
|
if (
|
2024-09-03 23:28:35 -07:00
|
|
|
template.find("./UnitMotion") is not None
|
|
|
|
|
and template.find("./UnitMotion/WalkSpeed") is not None
|
2024-08-28 22:00:43 -07:00
|
|
|
):
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["WalkSpeed"] = extract_value(template.find("./UnitMotion/WalkSpeed"))
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
if template.find("./Identity/VisibleClasses") is not None:
|
|
|
|
|
new_classes = template.find("./Identity/VisibleClasses").text.split(" ")
|
|
|
|
|
for elem in new_classes:
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem.find("-") != -1:
|
2024-09-03 23:28:35 -07:00
|
|
|
new_classes.pop(new_classes.index(elem))
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem in unit["Classes"]:
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["Classes"].pop(new_classes.index(elem))
|
|
|
|
|
unit["Classes"] += new_classes
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
if template.find("./Identity/Classes") is not None:
|
|
|
|
|
new_classes = template.find("./Identity/Classes").text.split(" ")
|
|
|
|
|
for elem in new_classes:
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem.find("-") != -1:
|
2024-09-03 23:28:35 -07:00
|
|
|
new_classes.pop(new_classes.index(elem))
|
2022-02-17 14:41:53 -08:00
|
|
|
if elem in unit["Classes"]:
|
2024-09-03 23:28:35 -07:00
|
|
|
unit["Classes"].pop(new_classes.index(elem))
|
|
|
|
|
unit["Classes"] += new_classes
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
return unit
|
2014-07-12 09:08:35 -07:00
|
|
|
|
2015-11-29 11:19:20 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def write_unit(name, unit_dict):
|
2022-02-17 14:41:53 -08:00
|
|
|
ret = "<tr>"
|
2024-09-03 23:28:35 -07:00
|
|
|
ret += '<td class="Sub">' + name + "</td>"
|
|
|
|
|
ret += "<td>" + str("{:.0f}".format(float(unit_dict["HP"]))) + "</td>"
|
|
|
|
|
ret += "<td>" + str("{:.0f}".format(float(unit_dict["BuildTime"]))) + "</td>"
|
|
|
|
|
ret += "<td>" + str("{:.1f}".format(float(unit_dict["WalkSpeed"]))) + "</td>"
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for atype in ATTACK_TYPES:
|
|
|
|
|
percent_value = 1.0 - (0.9 ** float(unit_dict["Resistance"][atype]))
|
2022-02-17 14:41:53 -08:00
|
|
|
ret += (
|
|
|
|
|
"<td>"
|
2024-09-03 23:28:35 -07:00
|
|
|
+ str("{:.0f}".format(float(unit_dict["Resistance"][atype])))
|
2022-02-17 14:41:53 -08:00
|
|
|
+ " / "
|
2024-09-03 23:28:35 -07:00
|
|
|
+ str("%.0f" % (percent_value * 100.0))
|
2022-02-17 14:41:53 -08:00
|
|
|
+ "%</td>"
|
|
|
|
|
)
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
att_type = "Ranged" if unit_dict["Ranged"] is True else "Melee"
|
|
|
|
|
if unit_dict["RepeatRate"][att_type] != "0":
|
|
|
|
|
for atype in ATTACK_TYPES:
|
|
|
|
|
repeat_time = float(unit_dict["RepeatRate"][att_type]) / 1000.0
|
2022-02-17 14:41:53 -08:00
|
|
|
ret += (
|
|
|
|
|
"<td>"
|
2024-09-03 23:28:35 -07:00
|
|
|
+ str("%.1f" % (float(unit_dict["Attack"][att_type][atype]) / repeat_time))
|
2024-08-22 00:18:20 -07:00
|
|
|
+ "</td>"
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
ret += "<td>" + str("%.1f" % (float(unit_dict["RepeatRate"][att_type]) / 1000.0)) + "</td>"
|
2022-02-17 14:41:53 -08:00
|
|
|
else:
|
2024-09-03 23:28:35 -07:00
|
|
|
for _ in ATTACK_TYPES:
|
2022-02-17 14:41:53 -08:00
|
|
|
ret += "<td> - </td>"
|
|
|
|
|
ret += "<td> - </td>"
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
if unit_dict["Ranged"] is True and unit_dict["Range"] > 0:
|
|
|
|
|
ret += "<td>" + str("{:.1f}".format(float(unit_dict["Range"]))) + "</td>"
|
|
|
|
|
spread = float(unit_dict["Spread"])
|
2024-08-24 21:29:39 -07:00
|
|
|
ret += "<td>" + str(f"{spread:.1f}") + "</td>"
|
2022-02-17 14:41:53 -08:00
|
|
|
else:
|
|
|
|
|
ret += "<td> - </td><td> - </td>"
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for rtype in RESOURCES:
|
|
|
|
|
ret += "<td>" + str("{:.0f}".format(float(unit_dict["Cost"][rtype]))) + "</td>"
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
ret += "<td>" + str("{:.0f}".format(float(unit_dict["Cost"]["population"]))) + "</td>"
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
ret += '<td style="text-align:left;">'
|
2024-09-03 23:28:35 -07:00
|
|
|
for bonus in unit_dict["AttackBonuses"]:
|
2022-02-17 14:41:53 -08:00
|
|
|
ret += "["
|
2024-09-03 23:28:35 -07:00
|
|
|
for classe in unit_dict["AttackBonuses"][bonus]["Classes"]:
|
2022-02-17 14:41:53 -08:00
|
|
|
ret += classe + " "
|
2024-09-03 23:28:35 -07:00
|
|
|
ret += ": {}] ".format(unit_dict["AttackBonuses"][bonus]["Multiplier"])
|
2022-02-17 14:41:53 -08:00
|
|
|
ret += "</td>"
|
|
|
|
|
|
|
|
|
|
ret += "</tr>\n"
|
|
|
|
|
return ret
|
2014-07-13 03:20:35 -07:00
|
|
|
|
2015-11-29 11:19:20 -08:00
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
# Sort the templates dictionary.
|
2024-09-03 23:28:35 -07:00
|
|
|
def sort_fn(a):
|
|
|
|
|
sort_val = 0
|
|
|
|
|
for classe in CLASSES_USED_FOR_SORT:
|
|
|
|
|
sort_val += 1
|
|
|
|
|
if classe in a[1]["Classes"]:
|
2022-02-17 14:41:53 -08:00
|
|
|
break
|
2024-09-03 23:28:35 -07:00
|
|
|
if COMPARATIVE_SORT_BY_CHAMP is True and a[0].find("champion") == -1:
|
|
|
|
|
sort_val -= 20
|
|
|
|
|
if COMPARATIVE_SORT_BY_CAV is True and a[0].find("cavalry") == -1:
|
|
|
|
|
sort_val -= 10
|
|
|
|
|
if a[1]["Civ"] is not None and a[1]["Civ"] in CIVS:
|
|
|
|
|
sort_val += 100 * CIVS.index(a[1]["Civ"])
|
|
|
|
|
return sort_val
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def write_coloured_diff(file, diff, is_changed):
|
2024-08-29 05:35:53 -07:00
|
|
|
"""Help to write coloured text.
|
|
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
diff value must always be computed as a unit_spec - unit_generic.
|
|
|
|
|
A positive imaginary part represents advantageous trait.
|
|
|
|
|
"""
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def clever_parse(diff):
|
2022-02-17 14:41:53 -08:00
|
|
|
if float(diff) - int(diff) < 0.001:
|
|
|
|
|
return str(int(diff))
|
2024-08-24 21:29:39 -07:00
|
|
|
return str(f"{float(diff):.1f}")
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
is_advantageous = diff.imag > 0
|
2022-02-17 14:41:53 -08:00
|
|
|
diff = diff.real
|
|
|
|
|
if diff != 0:
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = True
|
2022-02-17 14:41:53 -08:00
|
|
|
else:
|
|
|
|
|
# do not change its value if one parameter is not changed (yet)
|
|
|
|
|
# some other parameter might be different
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
if diff == 0:
|
|
|
|
|
rgb_str = "200,200,200"
|
2025-01-09 07:35:31 -08:00
|
|
|
elif (is_advantageous and diff > 0) or (not is_advantageous and diff < 0):
|
2022-02-17 14:41:53 -08:00
|
|
|
rgb_str = "180,0,0"
|
|
|
|
|
else:
|
|
|
|
|
rgb_str = "0,150,0"
|
|
|
|
|
|
|
|
|
|
file.write(
|
2024-09-03 23:28:35 -07:00
|
|
|
f"""<td><span style="color:rgb({rgb_str});">{clever_parse(diff)}</span></td>
|
2024-08-24 21:29:39 -07:00
|
|
|
"""
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
return is_changed
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def compute_unit_efficiency_diff(templates_by_parent, civs):
|
2022-02-17 14:41:53 -08:00
|
|
|
efficiency_table = {}
|
2024-09-03 23:28:35 -07:00
|
|
|
for parent in templates_by_parent:
|
2024-08-22 00:18:20 -07:00
|
|
|
for template in [
|
2024-09-03 23:28:35 -07:00
|
|
|
template for template in templates_by_parent[parent] if template[1]["Civ"] not in civs
|
2024-08-22 00:18:20 -07:00
|
|
|
]:
|
2023-01-18 15:38:44 -08:00
|
|
|
print(template)
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
templates_by_parent[parent] = [
|
|
|
|
|
template for template in templates_by_parent[parent] if template[1]["Civ"] in civs
|
2024-08-22 00:18:20 -07:00
|
|
|
]
|
2024-09-03 23:28:35 -07:00
|
|
|
templates_by_parent[parent].sort(key=lambda x: civs.index(x[1]["Civ"]))
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for tp in templates_by_parent[parent]:
|
2022-02-17 14:41:53 -08:00
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
# Build Time
|
2024-08-22 00:18:20 -07:00
|
|
|
diff = +1j + (int(tp[1]["BuildTime"]) - int(templates[parent]["BuildTime"]))
|
2022-02-17 14:41:53 -08:00
|
|
|
efficiency_table[(parent, tp[0], "BuildTime")] = diff
|
|
|
|
|
|
|
|
|
|
# walk speed
|
2024-08-22 00:18:20 -07:00
|
|
|
diff = -1j + (float(tp[1]["WalkSpeed"]) - float(templates[parent]["WalkSpeed"]))
|
2022-02-17 14:41:53 -08:00
|
|
|
efficiency_table[(parent, tp[0], "WalkSpeed")] = diff
|
|
|
|
|
|
|
|
|
|
# Resistance
|
2024-09-03 23:28:35 -07:00
|
|
|
for atype in ATTACK_TYPES:
|
2022-02-17 14:41:53 -08:00
|
|
|
diff = -1j + (
|
|
|
|
|
float(tp[1]["Resistance"][atype])
|
|
|
|
|
- float(templates[parent]["Resistance"][atype])
|
|
|
|
|
)
|
|
|
|
|
efficiency_table[(parent, tp[0], "Resistance/" + atype)] = diff
|
|
|
|
|
|
|
|
|
|
# Attack types (DPS) and rate.
|
2024-09-03 23:28:35 -07:00
|
|
|
att_type = "Ranged" if tp[1]["Ranged"] is True else "Melee"
|
|
|
|
|
if tp[1]["RepeatRate"][att_type] != "0":
|
|
|
|
|
for atype in ATTACK_TYPES:
|
|
|
|
|
my_dps = float(tp[1]["Attack"][att_type][atype]) / (
|
|
|
|
|
float(tp[1]["RepeatRate"][att_type]) / 1000.0
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
parent_dps = float(templates[parent]["Attack"][att_type][atype]) / (
|
|
|
|
|
float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
diff = -1j + (my_dps - parent_dps)
|
|
|
|
|
efficiency_table[(parent, tp[0], "Attack/" + att_type + "/" + atype)] = diff
|
2022-02-17 14:41:53 -08:00
|
|
|
diff = -1j + (
|
2024-09-03 23:28:35 -07:00
|
|
|
float(tp[1]["RepeatRate"][att_type]) / 1000.0
|
|
|
|
|
- float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
efficiency_table[
|
2024-09-03 23:28:35 -07:00
|
|
|
(parent, tp[0], "Attack/" + att_type + "/" + atype + "/RepeatRate")
|
2022-02-17 14:41:53 -08:00
|
|
|
] = diff
|
|
|
|
|
# range and spread
|
2024-08-22 00:18:20 -07:00
|
|
|
if tp[1]["Ranged"] is True:
|
|
|
|
|
diff = -1j + (float(tp[1]["Range"]) - float(templates[parent]["Range"]))
|
2024-09-03 23:28:35 -07:00
|
|
|
efficiency_table[(parent, tp[0], "Attack/" + att_type + "/Ranged/Range")] = (
|
|
|
|
|
diff
|
|
|
|
|
)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-08-22 00:18:20 -07:00
|
|
|
diff = float(tp[1]["Spread"]) - float(templates[parent]["Spread"])
|
2024-12-28 13:37:58 -08:00
|
|
|
efficiency_table[
|
|
|
|
|
(parent, tp[0], "Attack/" + att_type + "/Ranged/Projectile/Spread")
|
|
|
|
|
] = diff
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for rtype in RESOURCES:
|
2022-02-17 14:41:53 -08:00
|
|
|
diff = +1j + (
|
2024-08-22 00:18:20 -07:00
|
|
|
float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
efficiency_table[(parent, tp[0], "Resources/" + rtype)] = diff
|
|
|
|
|
|
|
|
|
|
diff = +1j + (
|
2024-08-22 00:18:20 -07:00
|
|
|
float(tp[1]["Cost"]["population"]) - float(templates[parent]["Cost"]["population"])
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
efficiency_table[(parent, tp[0], "Population")] = diff
|
|
|
|
|
|
|
|
|
|
return efficiency_table
|
|
|
|
|
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def compute_templates(load_templates_if_parent):
|
2022-02-17 14:41:53 -08:00
|
|
|
"""Loops over template XMLs and selectively insert into templates dict."""
|
|
|
|
|
templates = {}
|
2025-05-23 01:41:31 -07:00
|
|
|
for template in base_path.glob("template_*.xml"):
|
|
|
|
|
if not template.is_file():
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
template_file_name = str(template.relative_to(base_path))
|
|
|
|
|
if has_any_parent_template(template_file_name, load_templates_if_parent):
|
|
|
|
|
templates[template_file_name] = calc_unit(str(template))
|
|
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
return templates
|
|
|
|
|
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def compute_civ_templates(civs: list):
|
2024-08-29 05:35:53 -07:00
|
|
|
"""Load Civ specific templates.
|
|
|
|
|
|
|
|
|
|
NOTE: whether a Civ can train a certain unit is not recorded in the unit
|
|
|
|
|
.xml files, and hence we have to get that info elsewhere, e.g. from the
|
|
|
|
|
Civ tree. This should be delayed until this whole parser is based on the
|
|
|
|
|
Civ tree itself.
|
|
|
|
|
|
|
|
|
|
This function must always ensure that Civ unit parenthood works as
|
|
|
|
|
intended, i.e. a unit in a Civ indeed has a 'Civ' field recording its
|
|
|
|
|
loyalty to that Civ. Check this when upgrading this script to keep
|
|
|
|
|
up with the game engine.
|
|
|
|
|
"""
|
2024-09-03 23:28:35 -07:00
|
|
|
civ_templates = {}
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for civ in civs:
|
|
|
|
|
civ_templates[civ] = {}
|
2022-02-17 14:41:53 -08:00
|
|
|
# Load all templates that start with that civ indicator
|
|
|
|
|
# TODO: consider adding mixin/civs here too
|
2025-05-23 01:41:31 -07:00
|
|
|
for template in base_path.glob("units/" + civ + "/*.xml"):
|
|
|
|
|
if not template.is_file():
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
template_file_name = str(template.relative_to(base_path))
|
|
|
|
|
|
|
|
|
|
# filter based on FilterOut
|
|
|
|
|
break_it = False
|
|
|
|
|
for civ_filter in FILTER_OUT:
|
|
|
|
|
if template_file_name.find(civ_filter) != -1:
|
|
|
|
|
break_it = True
|
|
|
|
|
if break_it:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# filter based on loaded generic templates
|
|
|
|
|
if not has_any_parent_template(template_file_name, LOAD_TEMPLATES_IF_PARENT):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
unit = calc_unit(str(template))
|
|
|
|
|
|
|
|
|
|
# Remove variants for now
|
|
|
|
|
if unit["Parent"].find("template_") == -1:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# load template
|
|
|
|
|
civ_templates[civ][template_file_name] = unit
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
return civ_templates
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
def compute_templates_by_parent(templates: dict, civs: list, civ_templates: dict):
|
2024-08-29 05:35:53 -07:00
|
|
|
"""Get them in the array."""
|
2024-09-03 23:28:35 -07:00
|
|
|
# civs:list -> civ_templates:dict -> templates:dict -> templates_by_parent
|
|
|
|
|
templates_by_parent = {}
|
|
|
|
|
for civ in civs:
|
|
|
|
|
for civ_unit_template in civ_templates[civ]:
|
|
|
|
|
parent = civ_templates[civ][civ_unit_template]["Parent"]
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
# We have the following constant equality
|
|
|
|
|
# templates[*]["Civ"] === gaia
|
|
|
|
|
# if parent in templates and templates[parent]["Civ"] == None:
|
|
|
|
|
if parent in templates:
|
2024-09-03 23:28:35 -07:00
|
|
|
if parent not in templates_by_parent:
|
|
|
|
|
templates_by_parent[parent] = []
|
|
|
|
|
templates_by_parent[parent].append(
|
|
|
|
|
(civ_unit_template, civ_templates[civ][civ_unit_template])
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# debug after CivTemplates are non-empty
|
2024-09-03 23:28:35 -07:00
|
|
|
return templates_by_parent
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
############################################################
|
|
|
|
|
## Pre-compute all tables
|
2024-09-03 23:28:35 -07:00
|
|
|
templates = compute_templates(LOAD_TEMPLATES_IF_PARENT)
|
|
|
|
|
CivTemplates = compute_civ_templates(CIVS)
|
|
|
|
|
TemplatesByParent = compute_templates_by_parent(templates, CIVS, CivTemplates)
|
2015-11-29 11:19:20 -08:00
|
|
|
|
2022-02-17 14:41:53 -08:00
|
|
|
# Not used; use it for your own custom analysis
|
2024-09-03 23:28:35 -07:00
|
|
|
efficiency_table = compute_unit_efficiency_diff(TemplatesByParent, CIVS)
|
2015-11-29 11:19:20 -08:00
|
|
|
|
2014-07-12 09:08:35 -07:00
|
|
|
|
2014-07-14 09:25:39 -07:00
|
|
|
############################################################
|
2024-09-03 23:28:35 -07:00
|
|
|
def write_html():
|
2024-08-29 05:35:53 -07:00
|
|
|
"""Create the HTML file."""
|
2024-09-03 23:28:35 -07:00
|
|
|
f = open(Path(__file__).parent / "unit_summary_table.html", "w", encoding="utf8")
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
f.write(
|
|
|
|
|
"""
|
|
|
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<title>Unit Tables</title>
|
|
|
|
|
<link rel="stylesheet" href="style.css">
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
htbout(f, "h1", "Unit Summary Table")
|
|
|
|
|
f.write("\n")
|
|
|
|
|
|
|
|
|
|
# Write generic templates
|
|
|
|
|
htbout(f, "h2", "Units")
|
|
|
|
|
f.write(
|
|
|
|
|
"""
|
|
|
|
|
<table id="genericTemplates">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th> </th> <th>HP </th> <th>BuildTime </th> <th>Speed(walk) </th>
|
2023-01-18 15:38:44 -08:00
|
|
|
<th colspan="5">Resistance </th>
|
|
|
|
|
<th colspan="8">Attack (DPS) </th>
|
2022-02-17 14:41:53 -08:00
|
|
|
<th colspan="5">Costs </th>
|
|
|
|
|
<th>Efficient Against </th>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr class="Label" style="border-bottom:1px black solid;">
|
|
|
|
|
<th> </th> <th> </th> <th> </th> <th> </th>
|
2023-01-18 15:38:44 -08:00
|
|
|
<th>H </th> <th>P </th> <th>C </th><th>P </th><th>F </th>
|
|
|
|
|
<th>H </th> <th>P </th> <th>C </th><th>P </th><th>F </th>
|
2022-02-17 14:41:53 -08:00
|
|
|
<th>Rate </th> <th>Range </th> <th>Spread (/100m) </th>
|
|
|
|
|
<th>F </th> <th>W </th> <th>S </th> <th>M </th> <th>P </th>
|
|
|
|
|
<th> </th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
for template in templates:
|
2024-09-03 23:28:35 -07:00
|
|
|
f.write(write_unit(template, templates[template]))
|
2022-02-17 14:41:53 -08:00
|
|
|
f.write("</table>")
|
|
|
|
|
|
|
|
|
|
# Write unit specialization
|
|
|
|
|
# Sort them by civ and write them in a table.
|
|
|
|
|
#
|
|
|
|
|
# TODO: pre-compute the diffs then render, filtering out the non-interesting
|
|
|
|
|
# ones
|
|
|
|
|
#
|
|
|
|
|
f.write(
|
|
|
|
|
"""
|
|
|
|
|
<h2>Units Specializations
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
<p class="desc">This table compares each template to its parent, showing the
|
|
|
|
|
differences between the two.
|
|
|
|
|
<br/>Note that like any table, you can copy/paste this in Excel (or Numbers or
|
|
|
|
|
...) and sort it.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<table id="TemplateParentComp">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th> </th> <th> </th> <th>HP </th> <th>BuildTime </th>
|
|
|
|
|
<th>Speed (/100m) </th>
|
2023-01-18 15:38:44 -08:00
|
|
|
<th colspan="5">Resistance </th>
|
|
|
|
|
<th colspan="8">Attack </th>
|
2022-02-17 14:41:53 -08:00
|
|
|
<th colspan="5">Costs </th>
|
|
|
|
|
<th>Civ </th>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr class="Label" style="border-bottom:1px black solid;">
|
|
|
|
|
<th> </th> <th> </th> <th> </th> <th> </th> <th> </th>
|
2023-01-18 15:38:44 -08:00
|
|
|
<th>H </th> <th>P </th> <th>C </th><th>P </th><th>F </th>
|
|
|
|
|
<th>H </th> <th>P </th> <th>C </th><th>P </th><th>F </th>
|
2022-02-17 14:41:53 -08:00
|
|
|
<th>Rate </th> <th>Range </th> <th>Spread </th>
|
|
|
|
|
<th>F </th> <th>W </th> <th>S </th> <th>M </th> <th>P </th>
|
|
|
|
|
<th> </th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
for parent in TemplatesByParent:
|
2024-09-03 23:28:35 -07:00
|
|
|
TemplatesByParent[parent].sort(key=lambda x: CIVS.index(x[1]["Civ"]))
|
2022-02-17 14:41:53 -08:00
|
|
|
for tp in TemplatesByParent[parent]:
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = False
|
|
|
|
|
ff = open(Path(__file__).parent / ".cache", "w", encoding="utf8")
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
ff.write("<tr>")
|
|
|
|
|
ff.write(
|
|
|
|
|
"<th style='font-size:10px'>"
|
|
|
|
|
+ parent.replace(".xml", "").replace("template_", "")
|
|
|
|
|
+ "</th>"
|
|
|
|
|
)
|
|
|
|
|
ff.write(
|
2024-08-22 00:18:20 -07:00
|
|
|
'<td class="Sub">' + tp[0].replace(".xml", "").replace("units/", "") + "</td>"
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# HP
|
|
|
|
|
diff = -1j + (int(tp[1]["HP"]) - int(templates[parent]["HP"]))
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = write_coloured_diff(ff, diff, is_changed)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
# Build Time
|
2024-08-22 00:18:20 -07:00
|
|
|
diff = +1j + (int(tp[1]["BuildTime"]) - int(templates[parent]["BuildTime"]))
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = write_coloured_diff(ff, diff, is_changed)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
# walk speed
|
2024-08-22 00:18:20 -07:00
|
|
|
diff = -1j + (float(tp[1]["WalkSpeed"]) - float(templates[parent]["WalkSpeed"]))
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = write_coloured_diff(ff, diff, is_changed)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
# Resistance
|
2024-09-03 23:28:35 -07:00
|
|
|
for atype in ATTACK_TYPES:
|
2022-02-17 14:41:53 -08:00
|
|
|
diff = -1j + (
|
|
|
|
|
float(tp[1]["Resistance"][atype])
|
|
|
|
|
- float(templates[parent]["Resistance"][atype])
|
|
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = write_coloured_diff(ff, diff, is_changed)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
# Attack types (DPS) and rate.
|
2024-09-03 23:28:35 -07:00
|
|
|
att_type = "Ranged" if tp[1]["Ranged"] is True else "Melee"
|
|
|
|
|
if tp[1]["RepeatRate"][att_type] != "0":
|
|
|
|
|
for atype in ATTACK_TYPES:
|
|
|
|
|
my_dps = float(tp[1]["Attack"][att_type][atype]) / (
|
|
|
|
|
float(tp[1]["RepeatRate"][att_type]) / 1000.0
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
parent_dps = float(templates[parent]["Attack"][att_type][atype]) / (
|
|
|
|
|
float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = write_coloured_diff(ff, -1j + (my_dps - parent_dps), is_changed)
|
|
|
|
|
is_changed = write_coloured_diff(
|
2022-02-17 14:41:53 -08:00
|
|
|
ff,
|
|
|
|
|
-1j
|
|
|
|
|
+ (
|
2024-09-03 23:28:35 -07:00
|
|
|
float(tp[1]["RepeatRate"][att_type]) / 1000.0
|
|
|
|
|
- float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
2022-02-17 14:41:53 -08:00
|
|
|
),
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed,
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
# range and spread
|
2024-08-22 00:18:20 -07:00
|
|
|
if tp[1]["Ranged"] is True:
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = write_coloured_diff(
|
2022-02-17 14:41:53 -08:00
|
|
|
ff,
|
2024-08-22 00:18:20 -07:00
|
|
|
-1j + (float(tp[1]["Range"]) - float(templates[parent]["Range"])),
|
2024-09-03 23:28:35 -07:00
|
|
|
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
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
else:
|
2024-08-22 00:18:20 -07:00
|
|
|
ff.write(
|
2024-08-24 21:29:39 -07:00
|
|
|
"<td><span style='color:rgb(200,200,200);'>-</span></td><td>"
|
|
|
|
|
"<span style='color:rgb(200,200,200);'>-</span></td>"
|
2024-08-22 00:18:20 -07:00
|
|
|
)
|
2022-02-17 14:41:53 -08:00
|
|
|
else:
|
|
|
|
|
ff.write("<td></td><td></td><td></td><td></td><td></td><td></td>")
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for rtype in RESOURCES:
|
|
|
|
|
is_changed = write_coloured_diff(
|
2022-02-17 14:41:53 -08:00
|
|
|
ff,
|
2024-08-22 00:18:20 -07:00
|
|
|
+1j + (float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])),
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed,
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed = write_coloured_diff(
|
2022-02-17 14:41:53 -08:00
|
|
|
ff,
|
|
|
|
|
+1j
|
|
|
|
|
+ (
|
|
|
|
|
float(tp[1]["Cost"]["population"])
|
|
|
|
|
- float(templates[parent]["Cost"]["population"])
|
|
|
|
|
),
|
2024-09-03 23:28:35 -07:00
|
|
|
is_changed,
|
2022-02-17 14:41:53 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
ff.write("<td>" + tp[1]["Civ"] + "</td>")
|
|
|
|
|
ff.write("</tr>\n")
|
|
|
|
|
|
|
|
|
|
ff.close() # to actually write into the file
|
2024-09-03 23:28:35 -07:00
|
|
|
with open(Path(__file__).parent / ".cache", encoding="utf-8") as ff:
|
|
|
|
|
unit_str = ff.read()
|
|
|
|
|
|
|
|
|
|
if SHOW_CHANGED_ONLY:
|
|
|
|
|
if is_changed:
|
|
|
|
|
f.write(unit_str)
|
2022-02-17 14:41:53 -08:00
|
|
|
else:
|
2024-09-03 23:28:35 -07:00
|
|
|
# print the full table if SHOW_CHANGED_ONLY is false
|
|
|
|
|
f.write(unit_str)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
|
|
|
|
f.write("<table/>")
|
|
|
|
|
|
|
|
|
|
# Table of unit having or not having some units.
|
|
|
|
|
f.write(
|
|
|
|
|
"""
|
|
|
|
|
<h2>Roster Variety
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
<p class="desc">This table show which civilizations have units who derive from
|
|
|
|
|
each loaded generic template.
|
|
|
|
|
<br/>Grey means the civilization has no unit derived from a generic template;
|
|
|
|
|
<br/>dark green means 1 derived unit, mid-tone green means 2, bright green
|
|
|
|
|
means 3 or more.
|
|
|
|
|
<br/>The total is the total number of loaded units for this civ, which may be
|
|
|
|
|
more than the total of units inheriting from loaded templates.
|
|
|
|
|
</p>
|
|
|
|
|
<table class="CivRosterVariety">
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Template </th>
|
|
|
|
|
"""
|
|
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
for civ in CIVS:
|
2022-02-17 14:41:53 -08:00
|
|
|
f.write('<td class="vertical-text">' + civ + "</td>\n")
|
|
|
|
|
f.write("</tr>\n")
|
|
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
sorted_dict = sorted(templates.items(), key=sort_fn)
|
2022-02-17 14:41:53 -08:00
|
|
|
|
2024-09-03 23:28:35 -07:00
|
|
|
for tp in sorted_dict:
|
2022-02-17 14:41:53 -08:00
|
|
|
if tp[0] not in TemplatesByParent:
|
|
|
|
|
continue
|
|
|
|
|
f.write("<tr><td>" + tp[0] + "</td>\n")
|
2024-09-03 23:28:35 -07:00
|
|
|
for civ in CIVS:
|
2022-02-17 14:41:53 -08:00
|
|
|
found = 0
|
|
|
|
|
for temp in TemplatesByParent[tp[0]]:
|
|
|
|
|
if temp[1]["Civ"] == civ:
|
|
|
|
|
found += 1
|
|
|
|
|
if found == 1:
|
|
|
|
|
f.write('<td style="background-color:rgb(0,90,0);"></td>')
|
|
|
|
|
elif found == 2:
|
|
|
|
|
f.write('<td style="background-color:rgb(0,150,0);"></td>')
|
|
|
|
|
elif found >= 3:
|
|
|
|
|
f.write('<td style="background-color:rgb(0,255,0);"></td>')
|
|
|
|
|
else:
|
|
|
|
|
f.write('<td style="background-color:rgb(200,200,200);"></td>')
|
|
|
|
|
f.write("</tr>\n")
|
|
|
|
|
f.write(
|
|
|
|
|
'<tr style="margin-top:2px;border-top:2px #aaa solid;">\
|
|
|
|
|
<th style="text-align:right; padding-right:10px;">Total:</th>\n'
|
|
|
|
|
)
|
2024-09-03 23:28:35 -07:00
|
|
|
for civ in CIVS:
|
2022-02-17 14:41:53 -08:00
|
|
|
count = 0
|
2024-08-24 21:29:39 -07:00
|
|
|
for _units in CivTemplates[civ]:
|
2022-02-17 14:41:53 -08:00
|
|
|
count += 1
|
|
|
|
|
f.write('<td style="text-align:center;">' + str(count) + "</td>\n")
|
|
|
|
|
|
|
|
|
|
f.write("</tr>\n")
|
|
|
|
|
|
|
|
|
|
f.write("<table/>")
|
|
|
|
|
|
|
|
|
|
# Add a simple script to allow filtering on sorting directly in the HTML
|
|
|
|
|
# page.
|
2024-09-03 23:28:35 -07:00
|
|
|
if ADD_SORTING_OVERLAY:
|
2022-02-17 14:41:53 -08:00
|
|
|
f.write(
|
|
|
|
|
"""
|
2023-01-18 16:24:04 -08:00
|
|
|
<script src="tablefilter/tablefilter.js"></script>
|
2022-02-17 14:41:53 -08:00
|
|
|
<script data-config>
|
|
|
|
|
var cast = function (val) {
|
|
|
|
|
console.log(val); if (+val != val)
|
|
|
|
|
return -999999999999;
|
|
|
|
|
return +val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var filtersConfig = {
|
|
|
|
|
base_path: "tablefilter/",
|
|
|
|
|
col_0: "checklist",
|
|
|
|
|
alternate_rows: true,
|
|
|
|
|
rows_counter: true,
|
|
|
|
|
btn_reset: true,
|
|
|
|
|
loader: false,
|
|
|
|
|
status_bar: false,
|
|
|
|
|
mark_active_columns: true,
|
|
|
|
|
highlight_keywords: true,
|
|
|
|
|
col_number_format: Array(22).fill("US"),
|
|
|
|
|
filters_row_index: 2,
|
|
|
|
|
headers_row_index: 1,
|
|
|
|
|
extensions: [
|
|
|
|
|
{
|
|
|
|
|
name: "sort",
|
|
|
|
|
types: ["string",
|
|
|
|
|
...Array(6).fill("us"),
|
|
|
|
|
...Array(6).fill("mytype"),
|
|
|
|
|
...Array(5).fill("us"),
|
|
|
|
|
"string",
|
|
|
|
|
],
|
|
|
|
|
on_sort_loaded: function (o, sort) {
|
|
|
|
|
sort.addSortType("mytype", cast);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
col_widths: [...Array(18).fill(null), "120px"],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var tf = new TableFilter('genericTemplates', filtersConfig,2);
|
|
|
|
|
tf.init();
|
|
|
|
|
|
|
|
|
|
var secondFiltersConfig = {
|
|
|
|
|
base_path: "tablefilter/",
|
|
|
|
|
col_0: "checklist",
|
|
|
|
|
col_19: "checklist",
|
|
|
|
|
alternate_rows: true,
|
|
|
|
|
rows_counter: true,
|
|
|
|
|
btn_reset: true,
|
|
|
|
|
loader: false,
|
|
|
|
|
status_bar: false,
|
|
|
|
|
mark_active_columns: true,
|
|
|
|
|
highlight_keywords: true,
|
|
|
|
|
col_number_format: [null, null, ...Array(17).fill("US"), null],
|
|
|
|
|
filters_row_index: 2,
|
|
|
|
|
headers_row_index: 1,
|
|
|
|
|
extensions: [
|
|
|
|
|
{
|
|
|
|
|
name: "sort",
|
|
|
|
|
types: ["string", "string",
|
|
|
|
|
...Array(6).fill("us"),
|
|
|
|
|
...Array(6).fill("typetwo"),
|
|
|
|
|
...Array(5).fill("us"),
|
|
|
|
|
"string",
|
|
|
|
|
],
|
|
|
|
|
on_sort_loaded: function (o, sort) {
|
|
|
|
|
sort.addSortType("typetwo", cast);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
col_widths: Array(20).fill(null),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var tf2 = new TableFilter('TemplateParentComp', secondFiltersConfig,2);
|
|
|
|
|
tf2.init();
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
f.write("</body>\n</html>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2024-09-03 23:28:35 -07:00
|
|
|
write_html()
|