Compare commits
23 Commits
31442358c2
...
668ae8a20e
Author | SHA1 | Date | |
---|---|---|---|
668ae8a20e | |||
b15eb6909e | |||
798cff1f6f | |||
230c7ca27d | |||
e56ebb3f46 | |||
cd8b4266a4 | |||
8c7cc7373d | |||
0d3e3fbc29 | |||
661328ab15 | |||
616f2e134b | |||
ea4b580527 | |||
0e84957979 | |||
eeb502c115 | |||
f4c40b740c | |||
20ab96a0f4 | |||
ac48b72550 | |||
4d3be23bac | |||
f856a7663f | |||
7575dfe3c8 | |||
e86fd58524 | |||
04aa01a39b | |||
ccb1d747f0 | |||
5bea0a0f97 |
@ -5,6 +5,10 @@ charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
|
||||
[*.sh]
|
||||
indent_style = tab
|
||||
function_next_line = true
|
||||
|
@ -1219,16 +1219,28 @@ function getResourceDropsiteTooltip(template)
|
||||
});
|
||||
}
|
||||
|
||||
function showTemplateViewerOnRightClickTooltip()
|
||||
function getFocusOnLeftClickTooltip()
|
||||
{
|
||||
// Translation: Appears in a tooltip to indicate that right-clicking the corresponding GUI element will open the Template Details GUI page.
|
||||
return translate("Right-click to view more information.");
|
||||
// Translation: Appears in a tooltip to indicate that left-clicking the corresponding GUI element will center the view on the selected entity.
|
||||
return translate("Left-click to focus.");
|
||||
}
|
||||
|
||||
function showTemplateViewerOnClickTooltip()
|
||||
function getFollowOnLeftClickTooltip()
|
||||
{
|
||||
// Translation: Appears in a tooltip to indicate that left-clicking the corresponding GUI element will make the camera follow the selected unit.
|
||||
return translate("Left-click to follow.");
|
||||
}
|
||||
|
||||
function getTemplateViewerOnRightClickTooltip()
|
||||
{
|
||||
// Translation: Appears in a tooltip to indicate that right-clicking the corresponding GUI element will open the Template Details GUI page.
|
||||
return translate("Right-click for more information.");
|
||||
}
|
||||
|
||||
function getTemplateViewerOnClickTooltip()
|
||||
{
|
||||
// Translation: Appears in a tooltip to indicate that clicking the corresponding GUI element will open the Template Details GUI page.
|
||||
return translate("Click to view more information.");
|
||||
return translate("Click for more information.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ class EntityBox
|
||||
|
||||
static compileTooltip(template)
|
||||
{
|
||||
return ReferencePage.buildText(template, this.prototype.TooltipFunctions) + "\n" + showTemplateViewerOnClickTooltip();
|
||||
return ReferencePage.buildText(template, this.prototype.TooltipFunctions) + "\n" + getTemplateViewerOnClickTooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,7 @@
|
||||
<!-- Background circle -->
|
||||
<object
|
||||
type="image"
|
||||
size="4 4 100%-4 100%-4"
|
||||
size="4 6 100%-4 100%-6"
|
||||
sprite="stretched:session/minimap_circle_modern.png"
|
||||
ghost="true"
|
||||
/>
|
||||
@ -24,7 +24,7 @@
|
||||
<!-- Idle Worker Button -->
|
||||
<object name="idleWorkerButton"
|
||||
type="button"
|
||||
size="100%-119 100%-120 100%-4 100%-5"
|
||||
size="100%-119 100%-121 100%-4 100%-6"
|
||||
tooltip_style="sessionToolTip"
|
||||
hotkey="selection.idleworker"
|
||||
sprite="stretched:session/minimap-idle.png"
|
||||
@ -45,7 +45,7 @@
|
||||
<!-- Diplomacy Colors Button -->
|
||||
<object name="diplomacyColorsButton"
|
||||
type="button"
|
||||
size="4 100%-120 119 100%-5"
|
||||
size="3 100%-121 118 100%-6"
|
||||
tooltip_style="sessionToolTip"
|
||||
hotkey="session.diplomacycolors"
|
||||
/>
|
||||
@ -54,7 +54,7 @@
|
||||
<object
|
||||
name="flareButton"
|
||||
type="button"
|
||||
size="3 3 118 118"
|
||||
size="2 4 117 119"
|
||||
tooltip_style="sessionToolTip"
|
||||
hotkey="session.flareactivate"
|
||||
sprite="stretched:session/minimap-flare.png"
|
||||
@ -66,7 +66,7 @@
|
||||
<!-- MiniMap -->
|
||||
<object
|
||||
name="minimap"
|
||||
size="8 8 100%-8 100%-8"
|
||||
size="8 10 100%-8 100%-10"
|
||||
type="minimap"
|
||||
mask="true"
|
||||
flare_texture_count="16"
|
||||
|
@ -479,8 +479,8 @@ EntitySelection.prototype.selectAndMoveTo = function(entityID)
|
||||
this.reset();
|
||||
this.addList([entityID]);
|
||||
|
||||
Engine.CameraMoveTo(entState.position.x, entState.position.z);
|
||||
}
|
||||
setCameraFollow(entityID);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the formation members of a selected entities to the selection.
|
||||
|
@ -324,9 +324,17 @@ function displaySingle(entState)
|
||||
// TODO: we should require all entities to have icons
|
||||
Engine.GetGUIObjectByName("icon").sprite = template.icon ? ("stretched:session/portraits/" + template.icon) : "BackgroundBlack";
|
||||
if (template.icon)
|
||||
Engine.GetGUIObjectByName("iconBorder").onPressRight = () => {
|
||||
{
|
||||
const iconBorder = Engine.GetGUIObjectByName("iconBorder");
|
||||
|
||||
iconBorder.onPress = () => {
|
||||
setCameraFollow(entState.id);
|
||||
};
|
||||
|
||||
iconBorder.onPressRight = () => {
|
||||
showTemplateDetails(entState.template, playerState.civ);
|
||||
};
|
||||
}
|
||||
|
||||
let detailedTooltip = [
|
||||
getAttackTooltip,
|
||||
@ -357,10 +365,12 @@ function displaySingle(entState)
|
||||
getVisibleEntityClassesFormatted,
|
||||
getAurasTooltip,
|
||||
getEntityTooltip,
|
||||
getTreasureTooltip,
|
||||
showTemplateViewerOnRightClickTooltip
|
||||
getTreasureTooltip
|
||||
].map(func => func(template)));
|
||||
|
||||
const leftClickTooltip = hasClass(entState, "Unit") ? getFollowOnLeftClickTooltip() : getFocusOnLeftClickTooltip();
|
||||
iconTooltips.push(leftClickTooltip + " " + getTemplateViewerOnRightClickTooltip());
|
||||
|
||||
Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltips.filter(tip => tip).join("\n");
|
||||
|
||||
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
|
||||
|
@ -207,7 +207,7 @@ g_SelectionPanels.Construction = {
|
||||
getGarrisonTooltip(template),
|
||||
getTurretsTooltip(template),
|
||||
getPopulationBonusTooltip(template),
|
||||
showTemplateViewerOnRightClickTooltip(template)
|
||||
getTemplateViewerOnRightClickTooltip(template)
|
||||
);
|
||||
|
||||
|
||||
@ -578,7 +578,7 @@ g_SelectionPanels.Queue = {
|
||||
"neededSlots": queuedItem.neededSlots
|
||||
}));
|
||||
}
|
||||
tooltips.push(showTemplateViewerOnRightClickTooltip(template));
|
||||
tooltips.push(getTemplateViewerOnRightClickTooltip(template));
|
||||
data.button.tooltip = tooltips.join("\n");
|
||||
|
||||
data.countDisplay.caption = queuedItem.count > 1 ? queuedItem.count : "";
|
||||
@ -763,7 +763,7 @@ g_SelectionPanels.Research = {
|
||||
getEntityNamesFormatted,
|
||||
getEntityTooltip,
|
||||
getEntityCostTooltip,
|
||||
showTemplateViewerOnRightClickTooltip
|
||||
getTemplateViewerOnRightClickTooltip
|
||||
].map(func => func(template));
|
||||
|
||||
if (!requirementsPassed)
|
||||
@ -1058,7 +1058,7 @@ g_SelectionPanels.Training = {
|
||||
getResourceDropsiteTooltip
|
||||
].map(func => func(template)));
|
||||
|
||||
tooltips.push(showTemplateViewerOnRightClickTooltip());
|
||||
tooltips.push(getTemplateViewerOnRightClickTooltip());
|
||||
tooltips.push(
|
||||
formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch),
|
||||
getRequirementsTooltip(requirementsMet, template.requirements, GetSimState().players[data.player].civ),
|
||||
@ -1180,7 +1180,8 @@ g_SelectionPanels.Upgrade = {
|
||||
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type),
|
||||
getRequirementsTooltip(requirementsMet, data.item.requirements, GetSimState().players[data.player].civ),
|
||||
getNeededResourcesTooltip(neededResources),
|
||||
showTemplateViewerOnRightClickTooltip());
|
||||
getTemplateViewerOnRightClickTooltip()
|
||||
);
|
||||
|
||||
tooltip = tooltips.filter(tip => tip).join("\n");
|
||||
|
||||
|
@ -386,14 +386,16 @@ function cancelUpgradeEntity()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the camera to follow the given entity if it's a unit.
|
||||
* Otherwise stop following.
|
||||
* Focus the camera on the entity and follow if it's a unit.
|
||||
* If that's not possible, stop any current follow.
|
||||
*/
|
||||
function setCameraFollow(entity)
|
||||
{
|
||||
let entState = entity && GetEntityState(entity);
|
||||
const entState = entity && GetEntityState(entity);
|
||||
if (entState && hasClass(entState, "Unit"))
|
||||
Engine.CameraFollow(entity);
|
||||
else if (entState?.position)
|
||||
Engine.CameraMoveTo(entState.position.x, entState.position.z);
|
||||
else
|
||||
Engine.CameraFollow(0);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
>
|
||||
|
||||
<!-- Names and civilization emblem etc. (This must come before the attack and resistance icon to avoid clipping issues.) -->
|
||||
<object size="0 92 100% 100%" name="statsArea" type="image" sprite="edgedPanelShader">
|
||||
<object size="0 96 100% 100%" name="statsArea" type="image" sprite="edgedPanelShader">
|
||||
<!-- Civilization tooltip. -->
|
||||
<object size="0 38 100% 62" tooltip_style="sessionToolTip">
|
||||
<!-- Civilization emblem. -->
|
||||
@ -26,14 +26,14 @@
|
||||
</object>
|
||||
|
||||
<!-- Stats Bars -->
|
||||
<object size= "0 0 100% 96" type="image" tooltip_style="sessionToolTip">
|
||||
<object size= "0 0 100% 100" type="image" tooltip_style="sessionToolTip">
|
||||
|
||||
<object size="0 0 100% 60" type="image" sprite="topEdgedPanelShader">
|
||||
<object size="0 0 100% 65" type="image" sprite="topEdgedPanelShader">
|
||||
|
||||
<!-- Placeholders storing the position for the bars. -->
|
||||
<object size="96 1 100% 24" name="sectionPosTop" hidden="true"/>
|
||||
<object size="96 11 100% 34" name="sectionPosMiddle" hidden="true"/>
|
||||
<object size="96 32 100% 55" name="sectionPosBottom" hidden="true"/>
|
||||
<object size="96 2 100% 24" name="sectionPosTop" hidden="true"/>
|
||||
<object size="96 12 100% 34" name="sectionPosMiddle" hidden="true"/>
|
||||
<object size="96 35 100% 55" name="sectionPosBottom" hidden="true"/>
|
||||
|
||||
<!-- Capture bar -->
|
||||
<object name="captureSection">
|
||||
@ -78,15 +78,15 @@
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object size="0 59 100% 95" type="image" sprite="edgedPanelShader">
|
||||
<object size="0 63 100% 98" type="image" sprite="edgedPanelShader">
|
||||
<!-- Attack and Resistance -->
|
||||
<object size="96 0 128 32" name="attackAndResistanceStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip_style="sessionToolTipInstantly">
|
||||
<object size="97 0 129 32" name="attackAndResistanceStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip_style="sessionToolTipInstantly">
|
||||
<translatableAttribute id="tooltip">Attack and Resistance</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<!-- Resource carrying icon/counter -->
|
||||
<!-- Used also for number of gatherers/builders -->
|
||||
<object size="100%-96 0 100%-36 32" type="text" name="resourceCarryingText" style="CarryingTextRight"/>
|
||||
<object size="100%-96 0 100%-33 32" type="text" name="resourceCarryingText" style="CarryingTextRight"/>
|
||||
<object size="100%-32 0 100% 32" type="image" name="resourceCarryingIcon" tooltip_style="sessionToolTip"/>
|
||||
</object>
|
||||
|
||||
|
@ -79,7 +79,7 @@
|
||||
<!-- Limit to the minimal supported width of 1024 pixels. -->
|
||||
<object size="50%-512 0 50%+512 100%">
|
||||
|
||||
<object size="50%-512 100%-200 50%-312 100%">
|
||||
<object size="50%-512 100%-204 50%-312 100%">
|
||||
<include file="gui/session/minimap/MiniMap.xml"/>
|
||||
</object>
|
||||
|
||||
@ -95,7 +95,7 @@
|
||||
|
||||
<!-- Selection Details Panel (middle). -->
|
||||
<object name="selectionDetails"
|
||||
size="50%-114 100%-200 50%+114 100%"
|
||||
size="50%-114 100%-204 50%+114 100%"
|
||||
sprite="selectionDetailsPanel"
|
||||
type="image"
|
||||
>
|
||||
|
@ -65,7 +65,7 @@ pipeline {
|
||||
bat "cd libraries && get-windows-libs.bat"
|
||||
bat "(robocopy C:\\wxwidgets3.2.5\\lib libraries\\win32\\wxwidgets\\lib /MIR /NDL /NJH /NJS /NP /NS /NC) ^& IF %ERRORLEVEL% LEQ 1 exit 0"
|
||||
bat "(robocopy C:\\wxwidgets3.2.5\\include libraries\\win32\\wxwidgets\\include /MIR /NDL /NJH /NJS /NP /NS /NC) ^& IF %ERRORLEVEL% LEQ 1 exit 0"
|
||||
bat "cd build\\workspaces && update-workspaces.bat --atlas --without-pch --without-tests"
|
||||
bat "cd build\\workspaces && update-workspaces.bat --atlas --without-pch --large-address-aware --without-tests"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ pipeline {
|
||||
stage("Template Analyzer") {
|
||||
steps {
|
||||
ws("/zpool0/entity-docs"){
|
||||
sh "cd source/tools/templatesanalyzer/ && python3 unitTables.py"
|
||||
sh "cd source/tools/templatesanalyzer/ && python3 unit_tables.py"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ ignore = [
|
||||
"FIX",
|
||||
"FBT",
|
||||
"ISC001",
|
||||
"N",
|
||||
"N817",
|
||||
"PERF203",
|
||||
"PERF401",
|
||||
"PLR0912",
|
||||
|
1
source/tools/dist/dmgbuild-settings.py
vendored
1
source/tools/dist/dmgbuild-settings.py
vendored
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os.path
|
||||
|
||||
#
|
||||
|
@ -387,8 +387,8 @@ class CheckRefs:
|
||||
|
||||
cmp_auras = entity.find("Auras")
|
||||
if cmp_auras is not None:
|
||||
auraString = cmp_auras.text
|
||||
for aura in auraString.split():
|
||||
aura_string = cmp_auras.text
|
||||
for aura in aura_string.split():
|
||||
if not aura:
|
||||
continue
|
||||
if aura.startswith("-"):
|
||||
@ -397,33 +397,33 @@ class CheckRefs:
|
||||
|
||||
cmp_identity = entity.find("Identity")
|
||||
if cmp_identity is not None:
|
||||
reqTag = cmp_identity.find("Requirements")
|
||||
if reqTag is not None:
|
||||
req_tag = cmp_identity.find("Requirements")
|
||||
if req_tag is not None:
|
||||
|
||||
def parse_requirements(fp, req, recursionDepth=1):
|
||||
techsTag = req.find("Techs")
|
||||
if techsTag is not None:
|
||||
for techTag in techsTag.text.split():
|
||||
def parse_requirements(fp, req, recursion_depth=1):
|
||||
techs_tag = req.find("Techs")
|
||||
if techs_tag is not None:
|
||||
for tech_tag in techs_tag.text.split():
|
||||
self.deps.append(
|
||||
(fp, Path(f"simulation/data/technologies/{techTag}.json"))
|
||||
(fp, Path(f"simulation/data/technologies/{tech_tag}.json"))
|
||||
)
|
||||
|
||||
if recursionDepth > 0:
|
||||
recursionDepth -= 1
|
||||
allReqTag = req.find("All")
|
||||
if allReqTag is not None:
|
||||
parse_requirements(fp, allReqTag, recursionDepth)
|
||||
anyReqTag = req.find("Any")
|
||||
if anyReqTag is not None:
|
||||
parse_requirements(fp, anyReqTag, recursionDepth)
|
||||
if recursion_depth > 0:
|
||||
recursion_depth -= 1
|
||||
all_req_tag = req.find("All")
|
||||
if all_req_tag is not None:
|
||||
parse_requirements(fp, all_req_tag, recursion_depth)
|
||||
any_req_tag = req.find("Any")
|
||||
if any_req_tag is not None:
|
||||
parse_requirements(fp, any_req_tag, recursion_depth)
|
||||
|
||||
parse_requirements(fp, reqTag)
|
||||
parse_requirements(fp, req_tag)
|
||||
|
||||
cmp_researcher = entity.find("Researcher")
|
||||
if cmp_researcher is not None:
|
||||
techString = cmp_researcher.find("Technologies")
|
||||
if techString is not None:
|
||||
for tech in techString.text.split():
|
||||
tech_string = cmp_researcher.find("Technologies")
|
||||
if tech_string is not None:
|
||||
for tech in tech_string.text.split():
|
||||
if not tech:
|
||||
continue
|
||||
if tech.startswith("-"):
|
||||
|
@ -23,13 +23,32 @@
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import codecs
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from textwrap import dedent
|
||||
|
||||
from babel.messages.jslexer import tokenize, unquote_string
|
||||
from lxml import etree
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_mask_pattern(mask: str) -> re.Pattern:
|
||||
"""Build a regex pattern for matching file paths."""
|
||||
parts = re.split(r"([*][*]?)", mask)
|
||||
pattern = ""
|
||||
for i, part in enumerate(parts):
|
||||
if i % 2 != 0:
|
||||
pattern += "[^/]+"
|
||||
if len(part) == 2:
|
||||
pattern += "(/[^/]+)*"
|
||||
else:
|
||||
pattern += re.escape(part)
|
||||
pattern += "$"
|
||||
return re.compile(pattern)
|
||||
|
||||
|
||||
def pathmatch(mask, path):
|
||||
"""Match paths to a mask, where the mask supports * and **.
|
||||
@ -38,32 +57,22 @@ def pathmatch(mask, path):
|
||||
* matches a sequence of characters without /.
|
||||
** matches a sequence of characters without / followed by a / and
|
||||
sequence of characters without /
|
||||
:return: true iff path matches the mask, false otherwise
|
||||
:return: true if path matches the mask, false otherwise
|
||||
"""
|
||||
s = re.split(r"([*][*]?)", mask)
|
||||
p = ""
|
||||
for i in range(len(s)):
|
||||
if i % 2 != 0:
|
||||
p = p + "[^/]+"
|
||||
if len(s[i]) == 2:
|
||||
p = p + "(/[^/]+)*"
|
||||
else:
|
||||
p = p + re.escape(s[i])
|
||||
p = p + "$"
|
||||
return re.match(p, path) is not None
|
||||
return get_mask_pattern(mask).match(path) is not None
|
||||
|
||||
|
||||
class Extractor:
|
||||
def __init__(self, directory_path, filemasks, options):
|
||||
self.directoryPath = directory_path
|
||||
self.directory_path = directory_path
|
||||
self.options = options
|
||||
|
||||
if isinstance(filemasks, dict):
|
||||
self.includeMasks = filemasks["includeMasks"]
|
||||
self.excludeMasks = filemasks["excludeMasks"]
|
||||
self.include_masks = filemasks["includeMasks"]
|
||||
self.exclude_masks = filemasks["excludeMasks"]
|
||||
else:
|
||||
self.includeMasks = filemasks
|
||||
self.excludeMasks = []
|
||||
self.include_masks = filemasks
|
||||
self.exclude_masks = []
|
||||
|
||||
def run(self):
|
||||
"""Extract messages.
|
||||
@ -73,7 +82,7 @@ class Extractor:
|
||||
:rtype: ``iterator``
|
||||
"""
|
||||
empty_string_pattern = re.compile(r"^\s*$")
|
||||
directory_absolute_path = os.path.abspath(self.directoryPath)
|
||||
directory_absolute_path = os.path.abspath(self.directory_path)
|
||||
for root, folders, filenames in os.walk(directory_absolute_path):
|
||||
for subdir in folders:
|
||||
if subdir.startswith((".", "_")):
|
||||
@ -82,28 +91,26 @@ class Extractor:
|
||||
filenames.sort()
|
||||
for filename in filenames:
|
||||
filename = os.path.relpath(
|
||||
os.path.join(root, filename), self.directoryPath
|
||||
os.path.join(root, filename), self.directory_path
|
||||
).replace(os.sep, "/")
|
||||
for filemask in self.excludeMasks:
|
||||
for filemask in self.exclude_masks:
|
||||
if pathmatch(filemask, filename):
|
||||
break
|
||||
else:
|
||||
for filemask in self.includeMasks:
|
||||
if pathmatch(filemask, filename):
|
||||
filepath = os.path.join(directory_absolute_path, filename)
|
||||
for (
|
||||
message,
|
||||
plural,
|
||||
context,
|
||||
position,
|
||||
comments,
|
||||
) in self.extract_from_file(filepath):
|
||||
if empty_string_pattern.match(message):
|
||||
continue
|
||||
for filemask in self.include_masks:
|
||||
if not pathmatch(filemask, filename):
|
||||
continue
|
||||
|
||||
if " " in filename or "\t" in filename:
|
||||
filename = "\u2068" + filename + "\u2069"
|
||||
yield message, plural, context, (filename, position), comments
|
||||
filepath = os.path.join(directory_absolute_path, filename)
|
||||
for message, plural, context, position, comments in self.extract_from_file(
|
||||
filepath
|
||||
):
|
||||
if empty_string_pattern.match(message):
|
||||
continue
|
||||
|
||||
if " " in filename or "\t" in filename:
|
||||
filename = "\u2068" + filename + "\u2069"
|
||||
yield message, plural, context, (filename, position), comments
|
||||
|
||||
def extract_from_file(self, filepath):
|
||||
"""Extract messages from a specific file.
|
||||
@ -122,8 +129,6 @@ class JavascriptExtractor(Extractor):
|
||||
)
|
||||
|
||||
def extract_javascript_from_file(self, file_object):
|
||||
from babel.messages.jslexer import tokenize, unquote_string
|
||||
|
||||
funcname = message_lineno = None
|
||||
messages = []
|
||||
last_argument = None
|
||||
@ -237,7 +242,7 @@ class JavascriptExtractor(Extractor):
|
||||
last_token = token
|
||||
|
||||
def extract_from_file(self, filepath):
|
||||
with codecs.open(filepath, "r", encoding="utf-8-sig") as file_object:
|
||||
with open(filepath, encoding="utf-8-sig") as file_object:
|
||||
for lineno, funcname, messages, comments in self.extract_javascript_from_file(
|
||||
file_object
|
||||
):
|
||||
@ -301,10 +306,8 @@ class TxtExtractor(Extractor):
|
||||
"""Extract messages from plain text files."""
|
||||
|
||||
def extract_from_file(self, filepath):
|
||||
with codecs.open(filepath, "r", encoding="utf-8-sig") as file_object:
|
||||
for lineno, line in enumerate(
|
||||
[line.strip("\n\r") for line in file_object.readlines()], start=1
|
||||
):
|
||||
with open(filepath, encoding="utf-8-sig") as file_object:
|
||||
for lineno, line in enumerate([line.strip("\n\r") for line in file_object], start=1):
|
||||
if line:
|
||||
yield line, None, None, lineno, []
|
||||
|
||||
@ -329,66 +332,38 @@ class JsonExtractor(Extractor):
|
||||
self.comments = self.options.get("comments", [])
|
||||
|
||||
def extract_from_file(self, filepath):
|
||||
with codecs.open(filepath, "r", "utf-8") as file_object:
|
||||
with open(filepath, encoding="utf-8") as file_object:
|
||||
for message, context in self.extract_from_string(file_object.read()):
|
||||
yield message, None, context, None, self.comments
|
||||
|
||||
def extract_from_string(self, string):
|
||||
json_document = json.loads(string)
|
||||
if isinstance(json_document, list):
|
||||
for message, context in self.parse_list(json_document):
|
||||
if message: # Skip empty strings.
|
||||
yield message, context
|
||||
elif isinstance(json_document, dict):
|
||||
for message, context in self.parse_dictionary(json_document):
|
||||
if message: # Skip empty strings.
|
||||
yield message, context
|
||||
else:
|
||||
raise Exception(
|
||||
"Unexpected JSON document parent structure (not a list or a dictionary). "
|
||||
"You must extend the JSON extractor to support it."
|
||||
)
|
||||
yield from self.parse(json_document)
|
||||
|
||||
def parse_list(self, items_list):
|
||||
for list_item in items_list:
|
||||
if isinstance(list_item, list):
|
||||
for message, context in self.parse_list(list_item):
|
||||
yield message, context
|
||||
elif isinstance(list_item, dict):
|
||||
for message, context in self.parse_dictionary(list_item):
|
||||
yield message, context
|
||||
|
||||
def parse_dictionary(self, dictionary):
|
||||
for keyword in dictionary:
|
||||
if keyword in self.keywords:
|
||||
if isinstance(dictionary[keyword], str):
|
||||
yield self.extract_string(dictionary[keyword], keyword)
|
||||
elif isinstance(dictionary[keyword], list):
|
||||
for message, context in self.extract_list(dictionary[keyword], keyword):
|
||||
yield message, context
|
||||
elif isinstance(dictionary[keyword], dict):
|
||||
extract = None
|
||||
if (
|
||||
"extractFromInnerKeys" in self.keywords[keyword]
|
||||
and self.keywords[keyword]["extractFromInnerKeys"]
|
||||
):
|
||||
for message, context in self.extract_dictionary_inner_keys(
|
||||
dictionary[keyword], keyword
|
||||
):
|
||||
yield message, context
|
||||
else:
|
||||
extract = self.extract_dictionary(dictionary[keyword], keyword)
|
||||
if extract:
|
||||
yield extract
|
||||
elif isinstance(dictionary[keyword], list):
|
||||
for message, context in self.parse_list(dictionary[keyword]):
|
||||
yield message, context
|
||||
elif isinstance(dictionary[keyword], dict):
|
||||
for message, context in self.parse_dictionary(dictionary[keyword]):
|
||||
yield message, context
|
||||
def parse(self, data, key=None):
|
||||
"""Recursively parse JSON data and extract strings."""
|
||||
if isinstance(data, list):
|
||||
for item in data:
|
||||
yield from self.parse(item)
|
||||
elif isinstance(data, dict):
|
||||
for key2, value in data.items():
|
||||
if key2 in self.keywords:
|
||||
if isinstance(value, str):
|
||||
yield self.extract_string(value, key2)
|
||||
elif isinstance(value, list):
|
||||
yield from self.extract_list(value, key2)
|
||||
elif isinstance(value, dict):
|
||||
if self.keywords[key2].get("extractFromInnerKeys"):
|
||||
for value2 in value.values():
|
||||
yield from self.parse(value2, key2)
|
||||
else:
|
||||
yield from self.extract_dictionary(value, key2)
|
||||
else:
|
||||
yield from self.parse(value, key2)
|
||||
elif isinstance(data, str) and key in self.keywords:
|
||||
yield self.extract_string(data, key)
|
||||
|
||||
def extract_string(self, string, keyword):
|
||||
context = None
|
||||
if "tagAsContext" in self.keywords[keyword]:
|
||||
context = keyword
|
||||
elif "customContext" in self.keywords[keyword]:
|
||||
@ -409,7 +384,6 @@ class JsonExtractor(Extractor):
|
||||
def extract_dictionary(self, dictionary, keyword):
|
||||
message = dictionary.get("_string", None)
|
||||
if message and isinstance(message, str):
|
||||
context = None
|
||||
if "context" in dictionary:
|
||||
context = str(dictionary["context"])
|
||||
elif "tagAsContext" in self.keywords[keyword]:
|
||||
@ -418,19 +392,7 @@ class JsonExtractor(Extractor):
|
||||
context = self.keywords[keyword]["customContext"]
|
||||
else:
|
||||
context = self.context
|
||||
return message, context
|
||||
return None
|
||||
|
||||
def extract_dictionary_inner_keys(self, dictionary, keyword):
|
||||
for inner_keyword in dictionary:
|
||||
if isinstance(dictionary[inner_keyword], str):
|
||||
yield self.extract_string(dictionary[inner_keyword], keyword)
|
||||
elif isinstance(dictionary[inner_keyword], list):
|
||||
yield from self.extract_list(dictionary[inner_keyword], keyword)
|
||||
elif isinstance(dictionary[inner_keyword], dict):
|
||||
extract = self.extract_dictionary(dictionary[inner_keyword], keyword)
|
||||
if extract:
|
||||
yield extract
|
||||
yield message, context
|
||||
|
||||
|
||||
class XmlExtractor(Extractor):
|
||||
@ -439,86 +401,50 @@ class XmlExtractor(Extractor):
|
||||
def __init__(self, directory_path, filemasks, options):
|
||||
super().__init__(directory_path, filemasks, options)
|
||||
self.keywords = self.options.get("keywords", {})
|
||||
self.jsonExtractor = None
|
||||
self.json_extractor = None
|
||||
|
||||
def get_json_extractor(self):
|
||||
if not self.jsonExtractor:
|
||||
self.jsonExtractor = JsonExtractor()
|
||||
return self.jsonExtractor
|
||||
if not self.json_extractor:
|
||||
self.json_extractor = JsonExtractor(self.directory_path)
|
||||
return self.json_extractor
|
||||
|
||||
def extract_from_file(self, filepath):
|
||||
from lxml import etree
|
||||
|
||||
with codecs.open(filepath, "r", encoding="utf-8-sig") as file_object:
|
||||
with open(filepath, encoding="utf-8-sig") as file_object:
|
||||
xml_document = etree.parse(file_object)
|
||||
for keyword in self.keywords:
|
||||
for element in xml_document.iter(keyword):
|
||||
lineno = element.sourceline
|
||||
if element.text is not None:
|
||||
comments = []
|
||||
if "extractJson" in self.keywords[keyword]:
|
||||
json_extractor = self.get_json_extractor()
|
||||
json_extractor.set_options(self.keywords[keyword]["extractJson"])
|
||||
for message, context in json_extractor.extract_from_string(
|
||||
element.text
|
||||
):
|
||||
yield message, None, context, lineno, comments
|
||||
else:
|
||||
context = None
|
||||
if "context" in element.attrib:
|
||||
context = str(element.get("context"))
|
||||
elif "tagAsContext" in self.keywords[keyword]:
|
||||
context = keyword
|
||||
elif "customContext" in self.keywords[keyword]:
|
||||
context = self.keywords[keyword]["customContext"]
|
||||
if "comment" in element.attrib:
|
||||
comment = element.get("comment")
|
||||
comment = " ".join(
|
||||
comment.split()
|
||||
) # Remove tabs, line breaks and unecessary spaces.
|
||||
comments.append(comment)
|
||||
if "splitOnWhitespace" in self.keywords[keyword]:
|
||||
for split_text in element.text.split():
|
||||
# split on whitespace is used for token lists, there, a
|
||||
# leading '-' means the token has to be removed, so it's not
|
||||
# to be processed here either
|
||||
if split_text[0] != "-":
|
||||
yield str(split_text), None, context, lineno, comments
|
||||
else:
|
||||
yield str(element.text), None, context, lineno, comments
|
||||
|
||||
for element in xml_document.iter(*self.keywords.keys()):
|
||||
keyword = element.tag
|
||||
|
||||
# Hack from http://stackoverflow.com/a/2819788
|
||||
class FakeSectionHeader:
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
self.sechead = "[root]\n"
|
||||
lineno = element.sourceline
|
||||
if element.text is None:
|
||||
continue
|
||||
|
||||
def readline(self):
|
||||
if self.sechead:
|
||||
try:
|
||||
return self.sechead
|
||||
finally:
|
||||
self.sechead = None
|
||||
else:
|
||||
return self.fp.readline()
|
||||
|
||||
|
||||
class IniExtractor(Extractor):
|
||||
"""Extract messages from INI files."""
|
||||
|
||||
def __init__(self, directory_path, filemasks, options):
|
||||
super().__init__(directory_path, filemasks, options)
|
||||
self.keywords = self.options.get("keywords", [])
|
||||
|
||||
def extract_from_file(self, filepath):
|
||||
import ConfigParser
|
||||
|
||||
config = ConfigParser.RawConfigParser()
|
||||
with open(filepath, encoding="utf-8") as fd:
|
||||
config.read_file(FakeSectionHeader(fd))
|
||||
for keyword in self.keywords:
|
||||
message = config.get("root", keyword).strip('"').strip("'")
|
||||
context = None
|
||||
comments = []
|
||||
yield message, None, context, None, comments
|
||||
if "extractJson" in self.keywords[keyword]:
|
||||
json_extractor = self.get_json_extractor()
|
||||
json_extractor.set_options(self.keywords[keyword]["extractJson"])
|
||||
for message, context in json_extractor.extract_from_string(element.text):
|
||||
yield message, None, context, lineno, comments
|
||||
else:
|
||||
context = None
|
||||
if "context" in element.attrib:
|
||||
context = str(element.get("context"))
|
||||
elif "tagAsContext" in self.keywords[keyword]:
|
||||
context = keyword
|
||||
elif "customContext" in self.keywords[keyword]:
|
||||
context = self.keywords[keyword]["customContext"]
|
||||
if "comment" in element.attrib:
|
||||
comment = element.get("comment")
|
||||
comment = " ".join(
|
||||
comment.split()
|
||||
) # Remove tabs, line breaks and unnecessary spaces.
|
||||
comments.append(comment)
|
||||
if "splitOnWhitespace" in self.keywords[keyword]:
|
||||
for split_text in element.text.split():
|
||||
# split on whitespace is used for token lists, there, a
|
||||
# leading '-' means the token has to be removed, so it's not
|
||||
# to be processed here either
|
||||
if split_text[0] != "-":
|
||||
yield str(split_text), None, context, lineno, comments
|
||||
else:
|
||||
yield str(element.text), None, context, lineno, comments
|
@ -32,7 +32,7 @@ def main():
|
||||
path = os.path.join(root, folder)
|
||||
os.chdir(path)
|
||||
print(f"INFO: Starting to pull translations in {path}...")
|
||||
subprocess.run(["tx", "pull", "-a", "-f"], check=False)
|
||||
subprocess.run(["tx", "pull", "--all", "--force", "--workers=12"], check=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -16,6 +16,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
@ -33,18 +35,21 @@ def warn_about_untouched_mods():
|
||||
mods_root_folder = os.path.join(PROJECT_ROOT_DIRECTORY, "binaries", "data", "mods")
|
||||
untouched_mods = {}
|
||||
for mod_folder in os.listdir(mods_root_folder):
|
||||
if mod_folder[0] != "_" and mod_folder[0] != ".":
|
||||
if not os.path.exists(os.path.join(mods_root_folder, mod_folder, L10N_FOLDER_NAME)):
|
||||
untouched_mods[mod_folder] = (
|
||||
f"There is no '{L10N_FOLDER_NAME}' folder in the root folder of this mod."
|
||||
)
|
||||
elif not os.path.exists(
|
||||
os.path.join(mods_root_folder, mod_folder, L10N_FOLDER_NAME, messages_filename)
|
||||
):
|
||||
untouched_mods[mod_folder] = (
|
||||
f"There is no '{messages_filename}' file within the '{L10N_FOLDER_NAME}' "
|
||||
f"folder in the root folder of this mod."
|
||||
)
|
||||
if mod_folder.startswith(("_", ".")):
|
||||
continue
|
||||
|
||||
if not os.path.exists(os.path.join(mods_root_folder, mod_folder, L10N_FOLDER_NAME)):
|
||||
untouched_mods[mod_folder] = (
|
||||
f"There is no '{L10N_FOLDER_NAME}' folder in the root folder of this mod."
|
||||
)
|
||||
elif not os.path.exists(
|
||||
os.path.join(mods_root_folder, mod_folder, L10N_FOLDER_NAME, messages_filename)
|
||||
):
|
||||
untouched_mods[mod_folder] = (
|
||||
f"There is no '{messages_filename}' file within the '{L10N_FOLDER_NAME}' "
|
||||
f"folder in the root folder of this mod."
|
||||
)
|
||||
|
||||
if untouched_mods:
|
||||
print("Warning: No messages were extracted from the following mods:")
|
||||
for mod in untouched_mods:
|
||||
@ -78,7 +83,7 @@ def generate_pot(template_settings, root_path):
|
||||
|
||||
options = rule.get("options", {})
|
||||
extractor_class = getattr(
|
||||
import_module("extractors.extractors"), f'{rule["extractor"].title()}Extractor'
|
||||
import_module("i18n_helper.extractors"), f'{rule["extractor"].title()}Extractor'
|
||||
)
|
||||
extractor = extractor_class(input_root_path, rule["filemasks"], options)
|
||||
format_flag = None
|
||||
@ -111,8 +116,6 @@ def generate_templates_for_messages_file(messages_file_path):
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--scandir",
|
||||
@ -120,12 +123,13 @@ def main():
|
||||
"Type '.' for current working directory",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
for root, folders, _filenames in os.walk(args.scandir or PROJECT_ROOT_DIRECTORY):
|
||||
for folder in folders:
|
||||
if folder == L10N_FOLDER_NAME:
|
||||
messages_file_path = os.path.join(root, folder, messages_filename)
|
||||
if os.path.exists(messages_file_path):
|
||||
generate_templates_for_messages_file(messages_file_path)
|
||||
dir_to_scan = args.scandir or PROJECT_ROOT_DIRECTORY
|
||||
for messages_file_path in glob.glob(
|
||||
f"**/{L10N_FOLDER_NAME}/{messages_filename}", root_dir=dir_to_scan, recursive=True
|
||||
):
|
||||
generate_templates_for_messages_file(
|
||||
os.path.abspath(f"{dir_to_scan}/{messages_file_path}")
|
||||
)
|
||||
|
||||
warn_about_untouched_mods()
|
||||
|
||||
|
@ -56,13 +56,13 @@ args = parser.parse_args()
|
||||
|
||||
HEIGHTMAP_BIT_SHIFT = 3
|
||||
|
||||
for xmlFile in args.files:
|
||||
pmpFile = xmlFile[:-3] + "pmp"
|
||||
for xml_file in args.files:
|
||||
pmp_file = xml_file[:-3] + "pmp"
|
||||
|
||||
print("Processing " + xmlFile + " ...")
|
||||
print("Processing " + xml_file + " ...")
|
||||
|
||||
if os.path.isfile(pmpFile):
|
||||
with open(pmpFile, "rb") as f1, open(pmpFile + "~", "wb") as f2:
|
||||
if os.path.isfile(pmp_file):
|
||||
with open(pmp_file, "rb") as f1, open(pmp_file + "~", "wb") as f2:
|
||||
# 4 bytes PSMP to start the file
|
||||
f2.write(f1.read(4))
|
||||
|
||||
@ -73,7 +73,7 @@ for xmlFile in args.files:
|
||||
elif args.reverse:
|
||||
if version != 6:
|
||||
print(
|
||||
f"Warning: File {pmpFile} was not at version 6, while a negative version "
|
||||
f"Warning: File {pmp_file} was not at version 6, while a negative version "
|
||||
f"bump was requested.\nABORTING ..."
|
||||
)
|
||||
continue
|
||||
@ -81,7 +81,7 @@ for xmlFile in args.files:
|
||||
else:
|
||||
if version != 5:
|
||||
print(
|
||||
f"Warning: File {pmpFile} was not at version 5, while a version bump was "
|
||||
f"Warning: File {pmp_file} was not at version 5, while a version bump was "
|
||||
f"requested.\nABORTING ..."
|
||||
)
|
||||
continue
|
||||
@ -122,13 +122,13 @@ for xmlFile in args.files:
|
||||
f1.close()
|
||||
|
||||
# replace the old file, comment to see both files
|
||||
os.remove(pmpFile)
|
||||
os.rename(pmpFile + "~", pmpFile)
|
||||
os.remove(pmp_file)
|
||||
os.rename(pmp_file + "~", pmp_file)
|
||||
|
||||
if os.path.isfile(xmlFile):
|
||||
if os.path.isfile(xml_file):
|
||||
with (
|
||||
open(xmlFile, encoding="utf-8") as f1,
|
||||
open(xmlFile + "~", "w", encoding="utf-8") as f2,
|
||||
open(xml_file, encoding="utf-8") as f1,
|
||||
open(xml_file + "~", "w", encoding="utf-8") as f2,
|
||||
):
|
||||
data = f1.read()
|
||||
|
||||
@ -137,7 +137,7 @@ for xmlFile in args.files:
|
||||
if args.reverse:
|
||||
if data.find('<Scenario version="6">') == -1:
|
||||
print(
|
||||
f"Warning: File {xmlFile} was not at version 6, while a negative "
|
||||
f"Warning: File {xml_file} was not at version 6, while a negative "
|
||||
f"version bump was requested.\nABORTING ..."
|
||||
)
|
||||
sys.exit()
|
||||
@ -145,7 +145,7 @@ for xmlFile in args.files:
|
||||
data = data.replace('<Scenario version="6">', '<Scenario version="5">')
|
||||
elif data.find('<Scenario version="5">') == -1:
|
||||
print(
|
||||
f"Warning: File {xmlFile} was not at version 5, while a version bump "
|
||||
f"Warning: File {xml_file} was not at version 5, while a version bump "
|
||||
f"was requested.\nABORTING ..."
|
||||
)
|
||||
sys.exit()
|
||||
@ -164,5 +164,5 @@ for xmlFile in args.files:
|
||||
f2.close()
|
||||
|
||||
# replace the old file, comment to see both files
|
||||
os.remove(xmlFile)
|
||||
os.rename(xmlFile + "~", xmlFile)
|
||||
os.remove(xml_file)
|
||||
os.rename(xml_file + "~", xml_file)
|
||||
|
@ -58,4 +58,4 @@ actions = [zero_ad.actions.attack(my_units, enemy_units[0])]
|
||||
state = game.step(actions)
|
||||
```
|
||||
|
||||
For a more thorough example, check out samples/simple-example.py!
|
||||
For a more thorough example, check out samples/simple_example.py!
|
||||
|
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- mode: python-mode; python-indent-offset: 4; -*-
|
||||
#
|
||||
# Copyright (C) 2023 Wildfire Games.
|
||||
# Copyright (C) 2024 Wildfire Games.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -29,6 +28,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
from enum import Enum
|
||||
|
||||
import yaml
|
||||
|
||||
@ -169,18 +169,21 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
|
||||
add_push_constants(module["push_constants"][0], push_constants)
|
||||
descriptor_sets = []
|
||||
if module.get("descriptor_sets"):
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7
|
||||
|
||||
class VkDescriptorType(Enum):
|
||||
COMBINED_IMAGE_SAMPLER = 1
|
||||
STORAGE_IMAGE = 3
|
||||
UNIFORM_BUFFER = 6
|
||||
STORAGE_BUFFER = 7
|
||||
|
||||
for descriptor_set in module["descriptor_sets"]:
|
||||
UNIFORM_SET = 1 if use_descriptor_indexing else 0
|
||||
STORAGE_SET = 2
|
||||
uniform_set = 1 if use_descriptor_indexing else 0
|
||||
storage_set = 2
|
||||
bindings = []
|
||||
if descriptor_set["set"] == UNIFORM_SET:
|
||||
if descriptor_set["set"] == uniform_set:
|
||||
assert descriptor_set["binding_count"] > 0
|
||||
for binding in descriptor_set["bindings"]:
|
||||
assert binding["set"] == UNIFORM_SET
|
||||
assert binding["set"] == uniform_set
|
||||
block = binding["block"]
|
||||
members = []
|
||||
for member in block["members"]:
|
||||
@ -200,15 +203,15 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
|
||||
}
|
||||
)
|
||||
binding = descriptor_set["bindings"][0]
|
||||
assert binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
|
||||
elif descriptor_set["set"] == STORAGE_SET:
|
||||
assert binding["descriptor_type"] == VkDescriptorType.UNIFORM_BUFFER.value
|
||||
elif descriptor_set["set"] == storage_set:
|
||||
assert descriptor_set["binding_count"] > 0
|
||||
for binding in descriptor_set["bindings"]:
|
||||
is_storage_image = (
|
||||
binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
|
||||
binding["descriptor_type"] == VkDescriptorType.STORAGE_IMAGE.value
|
||||
)
|
||||
is_storage_buffer = (
|
||||
binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
|
||||
binding["descriptor_type"] == VkDescriptorType.STORAGE_BUFFER.value
|
||||
)
|
||||
assert is_storage_image or is_storage_buffer
|
||||
assert (
|
||||
@ -217,13 +220,13 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
|
||||
)
|
||||
assert binding["image"]["arrayed"] == 0
|
||||
assert binding["image"]["ms"] == 0
|
||||
bindingType = "storageImage"
|
||||
binding_type = "storageImage"
|
||||
if is_storage_buffer:
|
||||
bindingType = "storageBuffer"
|
||||
binding_type = "storageBuffer"
|
||||
bindings.append(
|
||||
{
|
||||
"binding": binding["binding"],
|
||||
"type": bindingType,
|
||||
"type": binding_type,
|
||||
"name": binding["name"],
|
||||
}
|
||||
)
|
||||
@ -232,7 +235,8 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
|
||||
assert descriptor_set["binding_count"] >= 1
|
||||
for binding in descriptor_set["bindings"]:
|
||||
assert (
|
||||
binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
|
||||
binding["descriptor_type"]
|
||||
== VkDescriptorType.COMBINED_IMAGE_SAMPLER.value
|
||||
)
|
||||
assert binding["array"]["dims"][0] == 16384
|
||||
if binding["binding"] == 0:
|
||||
@ -246,7 +250,9 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
|
||||
else:
|
||||
assert descriptor_set["binding_count"] > 0
|
||||
for binding in descriptor_set["bindings"]:
|
||||
assert binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
|
||||
assert (
|
||||
binding["descriptor_type"] == VkDescriptorType.COMBINED_IMAGE_SAMPLER.value
|
||||
)
|
||||
assert binding["image"]["sampled"] == 1
|
||||
assert binding["image"]["arrayed"] == 0
|
||||
assert binding["image"]["ms"] == 0
|
||||
|
@ -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
|
||||
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
|
||||
`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
|
||||
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
|
||||
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).
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Alternatively, only create the `.dot` file using the following line, and render
|
||||
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
|
||||
|
||||
Enjoy!
|
||||
|
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- mode: python-mode; python-indent-offset: 4; -*-
|
||||
#
|
||||
# Copyright (C) 2023 Wildfire Games.
|
||||
# Copyright (C) 2024 Wildfire Games.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -34,14 +33,14 @@ sys.path.append("../entity")
|
||||
from scriptlib import SimulTemplateEntity
|
||||
|
||||
|
||||
AttackTypes = ["Hack", "Pierce", "Crush", "Poison", "Fire"]
|
||||
Resources = ["food", "wood", "stone", "metal"]
|
||||
ATTACK_TYPES = ["Hack", "Pierce", "Crush", "Poison", "Fire"]
|
||||
RESOURCES = ["food", "wood", "stone", "metal"]
|
||||
|
||||
# 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.
|
||||
LoadTemplatesIfParent = [
|
||||
LOAD_TEMPLATES_IF_PARENT = [
|
||||
"template_unit_infantry.xml",
|
||||
"template_unit_cavalry.xml",
|
||||
"template_unit_champion.xml",
|
||||
@ -51,7 +50,7 @@ LoadTemplatesIfParent = [
|
||||
# Those describe Civs to analyze.
|
||||
# The script will load all entities that derive (to the nth degree) from one of
|
||||
# the above templates.
|
||||
Civs = [
|
||||
CIVS = [
|
||||
"athen",
|
||||
"brit",
|
||||
"cart",
|
||||
@ -70,16 +69,16 @@ Civs = [
|
||||
]
|
||||
|
||||
# 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
|
||||
# that are different from the generic templates?
|
||||
showChangedOnly = True
|
||||
SHOW_CHANGED_ONLY = True
|
||||
|
||||
# Sorting parameters for the "roster variety" table
|
||||
ComparativeSortByCav = True
|
||||
ComparativeSortByChamp = True
|
||||
ClassesUsedForSort = [
|
||||
COMPARATIVE_SORT_BY_CAV = True
|
||||
COMPARATIVE_SORT_BY_CHAMP = True
|
||||
CLASSES_USED_FOR_SORT = [
|
||||
"Support",
|
||||
"Pike",
|
||||
"Spear",
|
||||
@ -92,16 +91,16 @@ ClassesUsedForSort = [
|
||||
|
||||
# Disable if you want the more compact basic data. Enable to allow filtering and
|
||||
# sorting in-place.
|
||||
AddSortingOverlay = True
|
||||
ADD_SORTING_OVERLAY = True
|
||||
|
||||
# This is the path to the /templates/ folder to consider. Change this for mod
|
||||
# support.
|
||||
modsFolder = Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods"
|
||||
basePath = modsFolder / "public" / "simulation" / "templates"
|
||||
mods_folder = Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods"
|
||||
base_path = mods_folder / "public" / "simulation" / "templates"
|
||||
|
||||
# For performance purposes, cache opened templates files.
|
||||
globalTemplatesList = {}
|
||||
sim_entity = SimulTemplateEntity(modsFolder, None)
|
||||
global_templates_list = {}
|
||||
sim_entity = SimulTemplateEntity(mods_folder, None)
|
||||
|
||||
|
||||
def htbout(file, balise, value):
|
||||
@ -112,27 +111,27 @@ def htout(file, value):
|
||||
file.write("<p>" + value + "</p>\n")
|
||||
|
||||
|
||||
def fastParse(template_name):
|
||||
def fast_parse(template_name):
|
||||
"""Run ET.parse() with memoising in a global table."""
|
||||
if template_name in globalTemplatesList:
|
||||
return globalTemplatesList[template_name]
|
||||
if template_name in global_templates_list:
|
||||
return global_templates_list[template_name]
|
||||
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"]
|
||||
)
|
||||
globalTemplatesList[template_name].set("parent", parent_string)
|
||||
return globalTemplatesList[template_name]
|
||||
global_templates_list[template_name].set("parent", parent_string)
|
||||
return global_templates_list[template_name]
|
||||
|
||||
|
||||
def getParents(template_name):
|
||||
template_data = fastParse(template_name)
|
||||
def get_parents(template_name):
|
||||
template_data = fast_parse(template_name)
|
||||
parents_string = template_data.get("parent")
|
||||
if parents_string is None:
|
||||
return set()
|
||||
parents = set()
|
||||
for parent in parents_string.split("|"):
|
||||
parents.add(parent)
|
||||
for element in getParents(
|
||||
for element in get_parents(
|
||||
sim_entity.get_file("simulation/templates/", parent + ".xml", "public")
|
||||
):
|
||||
parents.add(element)
|
||||
@ -140,18 +139,18 @@ def getParents(template_name):
|
||||
return parents
|
||||
|
||||
|
||||
def ExtractValue(value):
|
||||
def extract_value(value):
|
||||
return float(value.text) if value is not None else 0.0
|
||||
|
||||
|
||||
# This function checks that a template has the given parent.
|
||||
def hasParentTemplate(template_name, parentName):
|
||||
return any(parentName == parent + ".xml" for parent in getParents(template_name))
|
||||
def has_parent_template(template_name, parent_name):
|
||||
return any(parent_name == parent + ".xml" for parent in get_parents(template_name))
|
||||
|
||||
|
||||
def CalcUnit(UnitName, existingUnit=None):
|
||||
if existingUnit is not None:
|
||||
unit = existingUnit
|
||||
def calc_unit(unit_name, existing_unit=None):
|
||||
if existing_unit is not None:
|
||||
unit = existing_unit
|
||||
else:
|
||||
unit = {
|
||||
"HP": 0,
|
||||
@ -180,177 +179,177 @@ def CalcUnit(UnitName, existingUnit=None):
|
||||
"Civ": None,
|
||||
}
|
||||
|
||||
Template = fastParse(UnitName)
|
||||
template = fast_parse(unit_name)
|
||||
|
||||
# 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
|
||||
unit["Parent"] = Template.get("parent").split("|")[-1] + ".xml"
|
||||
unit["Civ"] = Template.find("./Identity/Civ").text
|
||||
unit["HP"] = ExtractValue(Template.find("./Health/Max"))
|
||||
unit["BuildTime"] = ExtractValue(Template.find("./Cost/BuildTime"))
|
||||
unit["Cost"]["population"] = ExtractValue(Template.find("./Cost/Population"))
|
||||
unit["Parent"] = template.get("parent").split("|")[-1] + ".xml"
|
||||
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"))
|
||||
|
||||
resource_cost = Template.find("./Cost/Resources")
|
||||
resource_cost = template.find("./Cost/Resources")
|
||||
if resource_cost is not None:
|
||||
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:
|
||||
unit["RepeatRate"]["Melee"] = ExtractValue(Template.find("./Attack/Melee/RepeatTime"))
|
||||
unit["PrepRate"]["Melee"] = ExtractValue(Template.find("./Attack/Melee/PrepareTime"))
|
||||
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"))
|
||||
|
||||
for atttype in AttackTypes:
|
||||
unit["Attack"]["Melee"][atttype] = ExtractValue(
|
||||
Template.find("./Attack/Melee/Damage/" + atttype)
|
||||
for atttype in ATTACK_TYPES:
|
||||
unit["Attack"]["Melee"][atttype] = extract_value(
|
||||
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:
|
||||
for Bonus in attack_melee_bonus:
|
||||
Against = []
|
||||
CivAg = []
|
||||
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:
|
||||
CivAg = Bonus.find("Civ").text.split(" ")
|
||||
Val = float(Bonus.find("Multiplier").text)
|
||||
unit["AttackBonuses"][Bonus.tag] = {
|
||||
"Classes": Against,
|
||||
"Civs": CivAg,
|
||||
"Multiplier": Val,
|
||||
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,
|
||||
}
|
||||
|
||||
attack_restricted_classes = Template.find("./Attack/Melee/RestrictedClasses")
|
||||
attack_restricted_classes = template.find("./Attack/Melee/RestrictedClasses")
|
||||
if attack_restricted_classes is not None:
|
||||
newClasses = attack_restricted_classes.text.split(" ")
|
||||
for elem in newClasses:
|
||||
new_classes = attack_restricted_classes.text.split(" ")
|
||||
for elem in new_classes:
|
||||
if elem.find("-") != -1:
|
||||
newClasses.pop(newClasses.index(elem))
|
||||
new_classes.pop(new_classes.index(elem))
|
||||
if elem in unit["Restricted"]:
|
||||
unit["Restricted"].pop(newClasses.index(elem))
|
||||
unit["Restricted"] += newClasses
|
||||
unit["Restricted"].pop(new_classes.index(elem))
|
||||
unit["Restricted"] += new_classes
|
||||
|
||||
elif Template.find("./Attack/Ranged") is not None:
|
||||
elif template.find("./Attack/Ranged") is not None:
|
||||
unit["Ranged"] = True
|
||||
unit["Range"] = ExtractValue(Template.find("./Attack/Ranged/MaxRange"))
|
||||
unit["Spread"] = ExtractValue(Template.find("./Attack/Ranged/Projectile/Spread"))
|
||||
unit["RepeatRate"]["Ranged"] = ExtractValue(Template.find("./Attack/Ranged/RepeatTime"))
|
||||
unit["PrepRate"]["Ranged"] = ExtractValue(Template.find("./Attack/Ranged/PrepareTime"))
|
||||
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 AttackTypes:
|
||||
unit["Attack"]["Ranged"][atttype] = ExtractValue(
|
||||
Template.find("./Attack/Ranged/Damage/" + atttype)
|
||||
for atttype in ATTACK_TYPES:
|
||||
unit["Attack"]["Ranged"][atttype] = extract_value(
|
||||
template.find("./Attack/Ranged/Damage/" + atttype)
|
||||
)
|
||||
|
||||
if Template.find("./Attack/Ranged/Bonuses") is not None:
|
||||
for Bonus in Template.find("./Attack/Ranged/Bonuses"):
|
||||
Against = []
|
||||
CivAg = []
|
||||
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:
|
||||
CivAg = Bonus.find("Civ").text.split(" ")
|
||||
Val = float(Bonus.find("Multiplier").text)
|
||||
unit["AttackBonuses"][Bonus.tag] = {
|
||||
"Classes": Against,
|
||||
"Civs": CivAg,
|
||||
"Multiplier": Val,
|
||||
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,
|
||||
}
|
||||
if Template.find("./Attack/Melee/RestrictedClasses") is not None:
|
||||
newClasses = Template.find("./Attack/Melee/RestrictedClasses").text.split(" ")
|
||||
for elem in newClasses:
|
||||
if template.find("./Attack/Melee/RestrictedClasses") is not None:
|
||||
new_classes = template.find("./Attack/Melee/RestrictedClasses").text.split(" ")
|
||||
for elem in new_classes:
|
||||
if elem.find("-") != -1:
|
||||
newClasses.pop(newClasses.index(elem))
|
||||
new_classes.pop(new_classes.index(elem))
|
||||
if elem in unit["Restricted"]:
|
||||
unit["Restricted"].pop(newClasses.index(elem))
|
||||
unit["Restricted"] += newClasses
|
||||
unit["Restricted"].pop(new_classes.index(elem))
|
||||
unit["Restricted"] += new_classes
|
||||
|
||||
if Template.find("Resistance") is not None:
|
||||
for atttype in AttackTypes:
|
||||
unit["Resistance"][atttype] = ExtractValue(
|
||||
Template.find("./Resistance/Entity/Damage/" + atttype)
|
||||
if template.find("Resistance") is not None:
|
||||
for atttype in ATTACK_TYPES:
|
||||
unit["Resistance"][atttype] = extract_value(
|
||||
template.find("./Resistance/Entity/Damage/" + atttype)
|
||||
)
|
||||
|
||||
if (
|
||||
Template.find("./UnitMotion") is not None
|
||||
and Template.find("./UnitMotion/WalkSpeed") is not None
|
||||
template.find("./UnitMotion") 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:
|
||||
newClasses = Template.find("./Identity/VisibleClasses").text.split(" ")
|
||||
for elem in newClasses:
|
||||
if template.find("./Identity/VisibleClasses") is not None:
|
||||
new_classes = template.find("./Identity/VisibleClasses").text.split(" ")
|
||||
for elem in new_classes:
|
||||
if elem.find("-") != -1:
|
||||
newClasses.pop(newClasses.index(elem))
|
||||
new_classes.pop(new_classes.index(elem))
|
||||
if elem in unit["Classes"]:
|
||||
unit["Classes"].pop(newClasses.index(elem))
|
||||
unit["Classes"] += newClasses
|
||||
unit["Classes"].pop(new_classes.index(elem))
|
||||
unit["Classes"] += new_classes
|
||||
|
||||
if Template.find("./Identity/Classes") is not None:
|
||||
newClasses = Template.find("./Identity/Classes").text.split(" ")
|
||||
for elem in newClasses:
|
||||
if template.find("./Identity/Classes") is not None:
|
||||
new_classes = template.find("./Identity/Classes").text.split(" ")
|
||||
for elem in new_classes:
|
||||
if elem.find("-") != -1:
|
||||
newClasses.pop(newClasses.index(elem))
|
||||
new_classes.pop(new_classes.index(elem))
|
||||
if elem in unit["Classes"]:
|
||||
unit["Classes"].pop(newClasses.index(elem))
|
||||
unit["Classes"] += newClasses
|
||||
unit["Classes"].pop(new_classes.index(elem))
|
||||
unit["Classes"] += new_classes
|
||||
|
||||
return unit
|
||||
|
||||
|
||||
def WriteUnit(Name, UnitDict):
|
||||
def write_unit(name, unit_dict):
|
||||
ret = "<tr>"
|
||||
ret += '<td class="Sub">' + Name + "</td>"
|
||||
ret += "<td>" + str("{:.0f}".format(float(UnitDict["HP"]))) + "</td>"
|
||||
ret += "<td>" + str("{:.0f}".format(float(UnitDict["BuildTime"]))) + "</td>"
|
||||
ret += "<td>" + str("{:.1f}".format(float(UnitDict["WalkSpeed"]))) + "</td>"
|
||||
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>"
|
||||
|
||||
for atype in AttackTypes:
|
||||
PercentValue = 1.0 - (0.9 ** float(UnitDict["Resistance"][atype]))
|
||||
for atype in ATTACK_TYPES:
|
||||
percent_value = 1.0 - (0.9 ** float(unit_dict["Resistance"][atype]))
|
||||
ret += (
|
||||
"<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>"
|
||||
)
|
||||
|
||||
attType = "Ranged" if UnitDict["Ranged"] is True else "Melee"
|
||||
if UnitDict["RepeatRate"][attType] != "0":
|
||||
for atype in AttackTypes:
|
||||
repeatTime = float(UnitDict["RepeatRate"][attType]) / 1000.0
|
||||
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
|
||||
ret += (
|
||||
"<td>"
|
||||
+ str("%.1f" % (float(UnitDict["Attack"][attType][atype]) / repeatTime))
|
||||
+ str("%.1f" % (float(unit_dict["Attack"][att_type][atype]) / repeat_time))
|
||||
+ "</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:
|
||||
for _ in AttackTypes:
|
||||
for _ in ATTACK_TYPES:
|
||||
ret += "<td> - </td>"
|
||||
ret += "<td> - </td>"
|
||||
|
||||
if UnitDict["Ranged"] is True and UnitDict["Range"] > 0:
|
||||
ret += "<td>" + str("{:.1f}".format(float(UnitDict["Range"]))) + "</td>"
|
||||
spread = float(UnitDict["Spread"])
|
||||
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"])
|
||||
ret += "<td>" + str(f"{spread:.1f}") + "</td>"
|
||||
else:
|
||||
ret += "<td> - </td><td> - </td>"
|
||||
|
||||
for rtype in Resources:
|
||||
ret += "<td>" + str("{:.0f}".format(float(UnitDict["Cost"][rtype]))) + "</td>"
|
||||
for rtype in RESOURCES:
|
||||
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;">'
|
||||
for Bonus in UnitDict["AttackBonuses"]:
|
||||
for bonus in unit_dict["AttackBonuses"]:
|
||||
ret += "["
|
||||
for classe in UnitDict["AttackBonuses"][Bonus]["Classes"]:
|
||||
for classe in unit_dict["AttackBonuses"][bonus]["Classes"]:
|
||||
ret += classe + " "
|
||||
ret += ": {}] ".format(UnitDict["AttackBonuses"][Bonus]["Multiplier"])
|
||||
ret += ": {}] ".format(unit_dict["AttackBonuses"][bonus]["Multiplier"])
|
||||
ret += "</td>"
|
||||
|
||||
ret += "</tr>\n"
|
||||
@ -358,37 +357,37 @@ def WriteUnit(Name, UnitDict):
|
||||
|
||||
|
||||
# Sort the templates dictionary.
|
||||
def SortFn(A):
|
||||
sortVal = 0
|
||||
for classe in ClassesUsedForSort:
|
||||
sortVal += 1
|
||||
if classe in A[1]["Classes"]:
|
||||
def sort_fn(a):
|
||||
sort_val = 0
|
||||
for classe in CLASSES_USED_FOR_SORT:
|
||||
sort_val += 1
|
||||
if classe in a[1]["Classes"]:
|
||||
break
|
||||
if ComparativeSortByChamp is True and A[0].find("champion") == -1:
|
||||
sortVal -= 20
|
||||
if ComparativeSortByCav is True and A[0].find("cavalry") == -1:
|
||||
sortVal -= 10
|
||||
if A[1]["Civ"] is not None and A[1]["Civ"] in Civs:
|
||||
sortVal += 100 * Civs.index(A[1]["Civ"])
|
||||
return sortVal
|
||||
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
|
||||
|
||||
|
||||
def WriteColouredDiff(file, diff, isChanged):
|
||||
def write_coloured_diff(file, diff, is_changed):
|
||||
"""Help to write coloured text.
|
||||
|
||||
diff value must always be computed as a unit_spec - unit_generic.
|
||||
A positive imaginary part represents advantageous trait.
|
||||
"""
|
||||
|
||||
def cleverParse(diff):
|
||||
def clever_parse(diff):
|
||||
if float(diff) - int(diff) < 0.001:
|
||||
return str(int(diff))
|
||||
return str(f"{float(diff):.1f}")
|
||||
|
||||
isAdvantageous = diff.imag > 0
|
||||
is_advantageous = diff.imag > 0
|
||||
diff = diff.real
|
||||
if diff != 0:
|
||||
isChanged = True
|
||||
is_changed = True
|
||||
else:
|
||||
# do not change its value if one parameter is not changed (yet)
|
||||
# some other parameter might be different
|
||||
@ -396,32 +395,32 @@ def WriteColouredDiff(file, diff, isChanged):
|
||||
|
||||
if diff == 0:
|
||||
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"
|
||||
else:
|
||||
rgb_str = "0,150,0"
|
||||
|
||||
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 = {}
|
||||
for parent in TemplatesByParent:
|
||||
for parent in templates_by_parent:
|
||||
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)
|
||||
|
||||
TemplatesByParent[parent] = [
|
||||
template for template in TemplatesByParent[parent] if template[1]["Civ"] in Civs
|
||||
templates_by_parent[parent] = [
|
||||
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
|
||||
diff = -1j + (int(tp[1]["HP"]) - int(templates[parent]["HP"]))
|
||||
efficiency_table[(parent, tp[0], "HP")] = diff
|
||||
@ -436,7 +435,7 @@ def computeUnitEfficiencyDiff(TemplatesByParent, Civs):
|
||||
efficiency_table[(parent, tp[0], "WalkSpeed")] = diff
|
||||
|
||||
# Resistance
|
||||
for atype in AttackTypes:
|
||||
for atype in ATTACK_TYPES:
|
||||
diff = -1j + (
|
||||
float(tp[1]["Resistance"][atype])
|
||||
- float(templates[parent]["Resistance"][atype])
|
||||
@ -444,35 +443,37 @@ def computeUnitEfficiencyDiff(TemplatesByParent, Civs):
|
||||
efficiency_table[(parent, tp[0], "Resistance/" + atype)] = diff
|
||||
|
||||
# Attack types (DPS) and rate.
|
||||
attType = "Ranged" if tp[1]["Ranged"] is True else "Melee"
|
||||
if tp[1]["RepeatRate"][attType] != "0":
|
||||
for atype in AttackTypes:
|
||||
myDPS = float(tp[1]["Attack"][attType][atype]) / (
|
||||
float(tp[1]["RepeatRate"][attType]) / 1000.0
|
||||
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
|
||||
)
|
||||
parentDPS = float(templates[parent]["Attack"][attType][atype]) / (
|
||||
float(templates[parent]["RepeatRate"][attType]) / 1000.0
|
||||
parent_dps = float(templates[parent]["Attack"][att_type][atype]) / (
|
||||
float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
||||
)
|
||||
diff = -1j + (myDPS - parentDPS)
|
||||
efficiency_table[(parent, tp[0], "Attack/" + attType + "/" + atype)] = diff
|
||||
diff = -1j + (my_dps - parent_dps)
|
||||
efficiency_table[(parent, tp[0], "Attack/" + att_type + "/" + atype)] = diff
|
||||
diff = -1j + (
|
||||
float(tp[1]["RepeatRate"][attType]) / 1000.0
|
||||
- float(templates[parent]["RepeatRate"][attType]) / 1000.0
|
||||
float(tp[1]["RepeatRate"][att_type]) / 1000.0
|
||||
- float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
||||
)
|
||||
efficiency_table[
|
||||
(parent, tp[0], "Attack/" + attType + "/" + atype + "/RepeatRate")
|
||||
(parent, tp[0], "Attack/" + att_type + "/" + atype + "/RepeatRate")
|
||||
] = diff
|
||||
# range and spread
|
||||
if tp[1]["Ranged"] is True:
|
||||
diff = -1j + (float(tp[1]["Range"]) - float(templates[parent]["Range"]))
|
||||
efficiency_table[(parent, tp[0], "Attack/" + attType + "/Ranged/Range")] = diff
|
||||
|
||||
diff = float(tp[1]["Spread"]) - float(templates[parent]["Spread"])
|
||||
efficiency_table[(parent, tp[0], "Attack/" + attType + "/Ranged/Spread")] = (
|
||||
efficiency_table[(parent, tp[0], "Attack/" + att_type + "/Ranged/Range")] = (
|
||||
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 + (
|
||||
float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])
|
||||
)
|
||||
@ -486,25 +487,25 @@ def computeUnitEfficiencyDiff(TemplatesByParent, Civs):
|
||||
return efficiency_table
|
||||
|
||||
|
||||
def computeTemplates(LoadTemplatesIfParent):
|
||||
def compute_templates(load_templates_if_parent):
|
||||
"""Loops over template XMLs and selectively insert into templates dict."""
|
||||
pwd = os.getcwd()
|
||||
os.chdir(basePath)
|
||||
os.chdir(base_path)
|
||||
templates = {}
|
||||
for template in list(glob.glob("template_*.xml")):
|
||||
if os.path.isfile(template):
|
||||
found = False
|
||||
for possParent in LoadTemplatesIfParent:
|
||||
if hasParentTemplate(template, possParent):
|
||||
for poss_parent in load_templates_if_parent:
|
||||
if has_parent_template(template, poss_parent):
|
||||
found = True
|
||||
break
|
||||
if found is True:
|
||||
templates[template] = CalcUnit(template)
|
||||
templates[template] = calc_unit(template)
|
||||
os.chdir(pwd)
|
||||
return templates
|
||||
|
||||
|
||||
def computeCivTemplates(Civs: list):
|
||||
def compute_civ_templates(civs: list):
|
||||
"""Load Civ specific templates.
|
||||
|
||||
NOTE: whether a Civ can train a certain unit is not recorded in the unit
|
||||
@ -518,87 +519,83 @@ def computeCivTemplates(Civs: list):
|
||||
up with the game engine.
|
||||
"""
|
||||
pwd = os.getcwd()
|
||||
os.chdir(basePath)
|
||||
os.chdir(base_path)
|
||||
|
||||
CivTemplates = {}
|
||||
civ_templates = {}
|
||||
|
||||
for Civ in Civs:
|
||||
CivTemplates[Civ] = {}
|
||||
for civ in civs:
|
||||
civ_templates[civ] = {}
|
||||
# Load all templates that start with that civ indicator
|
||||
# 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:
|
||||
if os.path.isfile(template):
|
||||
# filter based on FilterOut
|
||||
breakIt = False
|
||||
for civ_filter in FilterOut:
|
||||
break_it = False
|
||||
for civ_filter in FILTER_OUT:
|
||||
if template.find(civ_filter) != -1:
|
||||
breakIt = True
|
||||
if breakIt:
|
||||
break_it = True
|
||||
if break_it:
|
||||
continue
|
||||
|
||||
# filter based on loaded generic templates
|
||||
breakIt = True
|
||||
for possParent in LoadTemplatesIfParent:
|
||||
if hasParentTemplate(template, possParent):
|
||||
breakIt = False
|
||||
break_it = True
|
||||
for poss_parent in LOAD_TEMPLATES_IF_PARENT:
|
||||
if has_parent_template(template, poss_parent):
|
||||
break_it = False
|
||||
break
|
||||
if breakIt:
|
||||
if break_it:
|
||||
continue
|
||||
|
||||
unit = CalcUnit(template)
|
||||
unit = calc_unit(template)
|
||||
|
||||
# Remove variants for now
|
||||
if unit["Parent"].find("template_") == -1:
|
||||
continue
|
||||
|
||||
# load template
|
||||
CivTemplates[Civ][template] = unit
|
||||
civ_templates[civ][template] = unit
|
||||
|
||||
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."""
|
||||
# Civs:list -> CivTemplates:dict -> templates:dict -> TemplatesByParent
|
||||
TemplatesByParent = {}
|
||||
for Civ in Civs:
|
||||
for CivUnitTemplate in CivTemplates[Civ]:
|
||||
parent = CivTemplates[Civ][CivUnitTemplate]["Parent"]
|
||||
# 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"]
|
||||
|
||||
# We have the following constant equality
|
||||
# templates[*]["Civ"] === gaia
|
||||
# if parent in templates and templates[parent]["Civ"] == None:
|
||||
if parent in templates:
|
||||
if parent not in TemplatesByParent:
|
||||
TemplatesByParent[parent] = []
|
||||
TemplatesByParent[parent].append(
|
||||
(CivUnitTemplate, CivTemplates[Civ][CivUnitTemplate])
|
||||
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])
|
||||
)
|
||||
|
||||
# debug after CivTemplates are non-empty
|
||||
return TemplatesByParent
|
||||
return templates_by_parent
|
||||
|
||||
|
||||
############################################################
|
||||
## Pre-compute all tables
|
||||
templates = computeTemplates(LoadTemplatesIfParent)
|
||||
CivTemplates = computeCivTemplates(Civs)
|
||||
TemplatesByParent = computeTemplatesByParent(templates, Civs, CivTemplates)
|
||||
templates = compute_templates(LOAD_TEMPLATES_IF_PARENT)
|
||||
CivTemplates = compute_civ_templates(CIVS)
|
||||
TemplatesByParent = compute_templates_by_parent(templates, CIVS, CivTemplates)
|
||||
|
||||
# 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."""
|
||||
f = open(
|
||||
os.path.realpath(__file__).replace("unitTables.py", "") + "unit_summary_table.html",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
)
|
||||
f = open(Path(__file__).parent / "unit_summary_table.html", "w", encoding="utf8")
|
||||
|
||||
f.write(
|
||||
"""
|
||||
@ -639,7 +636,7 @@ def writeHTML():
|
||||
"""
|
||||
)
|
||||
for template in templates:
|
||||
f.write(WriteUnit(template, templates[template]))
|
||||
f.write(write_unit(template, templates[template]))
|
||||
f.write("</table>")
|
||||
|
||||
# Write unit specialization
|
||||
@ -681,14 +678,10 @@ differences between the two.
|
||||
"""
|
||||
)
|
||||
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]:
|
||||
isChanged = False
|
||||
ff = open(
|
||||
os.path.realpath(__file__).replace("unitTables.py", "") + ".cache",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
)
|
||||
is_changed = False
|
||||
ff = open(Path(__file__).parent / ".cache", "w", encoding="utf8")
|
||||
|
||||
ff.write("<tr>")
|
||||
ff.write(
|
||||
@ -702,54 +695,56 @@ differences between the two.
|
||||
|
||||
# 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
|
||||
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
|
||||
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
|
||||
for atype in AttackTypes:
|
||||
for atype in ATTACK_TYPES:
|
||||
diff = -1j + (
|
||||
float(tp[1]["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.
|
||||
attType = "Ranged" if tp[1]["Ranged"] is True else "Melee"
|
||||
if tp[1]["RepeatRate"][attType] != "0":
|
||||
for atype in AttackTypes:
|
||||
myDPS = float(tp[1]["Attack"][attType][atype]) / (
|
||||
float(tp[1]["RepeatRate"][attType]) / 1000.0
|
||||
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
|
||||
)
|
||||
parentDPS = float(templates[parent]["Attack"][attType][atype]) / (
|
||||
float(templates[parent]["RepeatRate"][attType]) / 1000.0
|
||||
parent_dps = float(templates[parent]["Attack"][att_type][atype]) / (
|
||||
float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
||||
)
|
||||
isChanged = WriteColouredDiff(ff, -1j + (myDPS - parentDPS), isChanged)
|
||||
isChanged = WriteColouredDiff(
|
||||
is_changed = write_coloured_diff(ff, -1j + (my_dps - parent_dps), is_changed)
|
||||
is_changed = write_coloured_diff(
|
||||
ff,
|
||||
-1j
|
||||
+ (
|
||||
float(tp[1]["RepeatRate"][attType]) / 1000.0
|
||||
- float(templates[parent]["RepeatRate"][attType]) / 1000.0
|
||||
float(tp[1]["RepeatRate"][att_type]) / 1000.0
|
||||
- float(templates[parent]["RepeatRate"][att_type]) / 1000.0
|
||||
),
|
||||
isChanged,
|
||||
is_changed,
|
||||
)
|
||||
# range and spread
|
||||
if tp[1]["Ranged"] is True:
|
||||
isChanged = WriteColouredDiff(
|
||||
is_changed = write_coloured_diff(
|
||||
ff,
|
||||
-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:
|
||||
ff.write(
|
||||
"<td><span style='color:rgb(200,200,200);'>-</span></td><td>"
|
||||
@ -758,39 +753,36 @@ differences between the two.
|
||||
else:
|
||||
ff.write("<td></td><td></td><td></td><td></td><td></td><td></td>")
|
||||
|
||||
for rtype in Resources:
|
||||
isChanged = WriteColouredDiff(
|
||||
for rtype in RESOURCES:
|
||||
is_changed = write_coloured_diff(
|
||||
ff,
|
||||
+1j + (float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])),
|
||||
isChanged,
|
||||
is_changed,
|
||||
)
|
||||
|
||||
isChanged = WriteColouredDiff(
|
||||
is_changed = write_coloured_diff(
|
||||
ff,
|
||||
+1j
|
||||
+ (
|
||||
float(tp[1]["Cost"]["population"])
|
||||
- float(templates[parent]["Cost"]["population"])
|
||||
),
|
||||
isChanged,
|
||||
is_changed,
|
||||
)
|
||||
|
||||
ff.write("<td>" + tp[1]["Civ"] + "</td>")
|
||||
ff.write("</tr>\n")
|
||||
|
||||
ff.close() # to actually write into the file
|
||||
with open(
|
||||
os.path.realpath(__file__).replace("unitTables.py", "") + ".cache",
|
||||
encoding="utf-8",
|
||||
) as ff:
|
||||
unitStr = ff.read()
|
||||
with open(Path(__file__).parent / ".cache", encoding="utf-8") as ff:
|
||||
unit_str = ff.read()
|
||||
|
||||
if showChangedOnly:
|
||||
if isChanged:
|
||||
f.write(unitStr)
|
||||
if SHOW_CHANGED_ONLY:
|
||||
if is_changed:
|
||||
f.write(unit_str)
|
||||
else:
|
||||
# print the full table if showChangedOnly is false
|
||||
f.write(unitStr)
|
||||
# print the full table if SHOW_CHANGED_ONLY is false
|
||||
f.write(unit_str)
|
||||
|
||||
f.write("<table/>")
|
||||
|
||||
@ -813,17 +805,17 @@ each loaded generic template.
|
||||
<th>Template </th>
|
||||
"""
|
||||
)
|
||||
for civ in Civs:
|
||||
for civ in CIVS:
|
||||
f.write('<td class="vertical-text">' + civ + "</td>\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:
|
||||
continue
|
||||
f.write("<tr><td>" + tp[0] + "</td>\n")
|
||||
for civ in Civs:
|
||||
for civ in CIVS:
|
||||
found = 0
|
||||
for temp in TemplatesByParent[tp[0]]:
|
||||
if temp[1]["Civ"] == civ:
|
||||
@ -841,7 +833,7 @@ each loaded generic template.
|
||||
'<tr style="margin-top:2px;border-top:2px #aaa solid;">\
|
||||
<th style="text-align:right; padding-right:10px;">Total:</th>\n'
|
||||
)
|
||||
for civ in Civs:
|
||||
for civ in CIVS:
|
||||
count = 0
|
||||
for _units in CivTemplates[civ]:
|
||||
count += 1
|
||||
@ -853,7 +845,7 @@ each loaded generic template.
|
||||
|
||||
# Add a simple script to allow filtering on sorting directly in the HTML
|
||||
# page.
|
||||
if AddSortingOverlay:
|
||||
if ADD_SORTING_OVERLAY:
|
||||
f.write(
|
||||
"""
|
||||
<script src="tablefilter/tablefilter.js"></script>
|
||||
@ -941,4 +933,4 @@ tf2.init();
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
writeHTML()
|
||||
write_html()
|
@ -21,7 +21,7 @@ class SingleLevelFilter(Filter):
|
||||
return record.levelno == self.passlevel
|
||||
|
||||
|
||||
class VFS_File:
|
||||
class VFSFile:
|
||||
def __init__(self, mod_name, vfs_path):
|
||||
self.mod_name = mod_name
|
||||
self.vfs_path = vfs_path
|
||||
|
Loading…
Reference in New Issue
Block a user