Compare commits

...

16 Commits

Author SHA1 Message Date
57308bb847
Avoid unnecessary computations
This refactors the script for cleaning the translations to get the
same result by doing less. This is achieved by the following changes:

- Use glob-patterns for finding the files to clean more efficiently,
  without the need to exclude collected files again.
- Only write files which are supposed to be modified (previously all
  portable object files did get rewritten by this script, no matter if
  it did change something or not).
- Stop searching for sections in files to clean up, once they are
  already passed.
2024-09-22 07:59:07 +02:00
c59030857d
Rebuild SPIR-V shaders when compile script changes 2024-09-22 06:28:58 +02:00
8d70ced693
Add myself as code owner for ruff.toml 2024-09-21 20:54:32 +02:00
0ea6d32fa5
Enable various ruff rules
This commit enables a bunch of unrelated ruff rules, which only require
minimal changes to the code base to enable them.

The rules enabled by this commit are:

- check the use of datetime objects without timezone (DTZ005)
- check the performance of try-except in loops (PERF203)
- check the number of function arguments (PLR0913)
- check for mutable class defaults (RUF012)
- check for the use of secure hashing algorithms (S324)
- check for raising base exceptions (TRY002)
- check for raising other exceptions where TypeErrors should be raised
  (TRY004)
2024-09-21 20:54:30 +02:00
c0232c6b5f
Specify the Python target version in ruff.toml
This ensures the same Python target version used for `ruff format`
is used for `ruff check` as well. It also allows ruff, even if it's not
run through pre-commit, to use the correct target Python version.
2024-09-21 20:54:24 +02:00
265ed76131
Simplify check for identical shaders
Previously when checking if two SPIR-V shaders are identical the
hashs of their file content would be compared and afterwards their
(unhashed) file contents as well. Comparing the file contents isn't
necessary, as the hash function used is a cryptographic one, which
guarantees the hash can be used as a representative of the hashed data.
2024-09-21 20:39:59 +02:00
668ae8a20e Increase height of middle panel to prevent icon overflow
The icon/portrait of the middle panel when a single entity was selected
was very subtly (4 px) overflowing its lane and invading that of the
entity's name. This change fixes that by:

 - Raising the top of the middle panel by 4 px to leave room for the
   portrait/icon. This avoids having to shrink it and lose quality.

 - Distributing the 4 px of difference in height in the statistics area
   by lowering 1 px the top bar, 1 px the middle bar (if any), and 3 px
   the bottom bar (if any). The rest of the elements are lowered 4 px,
   and therefore remain in the same place.

 - Increasing the height of the minimap panel by 4 px so that it remains
   aligned with the middle panel, vertically centering the minimap, and
   making the necessary adjustments to the position of its buttons.

Additionally, a couple of minor changes are applied:

 - The separators between the statistics area and the attack/resistance
   and resources area, and between the attack/resistance and resources
   area and the entity name area, which had different heights, are set
   to the same height/thickness.

 - The attack/resistance icon, which was very close to the entity
   icon/portrait, is moved 1 px to the right, and the
   resourceCarryingText (in the same area), which was very far from the
   resourceCarryingIcon, is also moved 3 px to the right.

Fixes #7029
2024-09-18 13:38:05 +02:00
b15eb6909e Remove unnecessary comments in selection_details.js
Related: #7012
2024-09-18 06:46:51 +02:00
798cff1f6f Left-click the portrait to follow the entity
- Left-clicking the portrait of a unit will make the camera follow that
   unit (before: no action). A tooltip informs the player of this
   possibility.

 - Left-clicking the portrait of a structure will make the camera focus on
   that structure (before: no action). A tooltip informs the player of
   this possibility.

 - Double-clicking a hero/treasure icon will make the camera follow that
   hero/treasure (before: just focus on that hero/treasure).

 - Some minor related changes.

Fixes #6879
2024-09-18 06:46:51 +02:00
230c7ca27d
Add EditorConfig options for Python
While the desired options for indent size and style are Python's
defaults, let's make it explicit by specifying it in the EditorConfig.

As part of this, this also removes unnecessary inline formatting options
for Python files.
2024-09-17 11:03:15 +02:00
e56ebb3f46
Enable ruff naming rules 2024-09-13 11:04:07 +02:00
cd8b4266a4
Fix class name in xmlvalidator 2024-09-13 11:04:06 +02:00
8c7cc7373d
Fix variable names in SPIRV compile.py 2024-09-13 11:04:06 +02:00
0d3e3fbc29
Rename simple-example.py 2024-09-13 11:04:05 +02:00
661328ab15
Fix variable naming for map compatibility file 2024-09-13 11:04:05 +02:00
616f2e134b
Fix variable names in checkrefs.py 2024-09-13 11:04:04 +02:00
32 changed files with 215 additions and 187 deletions

View File

@ -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

View File

@ -3,6 +3,7 @@
\\.gitea/.* @Stan @Itms
## Linting
\\.pre-commit-config\\.yaml @Dunedan
ruff\\.toml @Dunedan
## == Build & Libraries
(build|libraries)/.* @Itms @Stan

View File

@ -30,8 +30,6 @@ repos:
- id: ruff-format
args:
- --check
- --target-version
- py311
exclude: ^source/tools/webservices/
- repo: local
hooks:

View File

@ -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.");
}
/**

View File

@ -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();
}
/**

View File

@ -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"

View File

@ -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.

View File

@ -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;

View File

@ -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");

View File

@ -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);
}

View File

@ -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>

View File

@ -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"
>

View File

@ -53,7 +53,10 @@ pipeline {
stage("Check for shader changes") {
when {
changeset 'binaries/data/mods/**/shaders/**/*.xml'
anyOf {
changeset 'binaries/data/mods/**/shaders/**/*.xml'
changeset 'source/tools/spirv/compile.py'
}
}
steps {
script { buildSPIRV = true }

View File

@ -1,5 +1,7 @@
line-length = 99
target-version = "py311"
[format]
line-ending = "lf"
@ -10,36 +12,29 @@ ignore = [
"C90",
"COM812",
"D10",
"DTZ005",
"EM",
"FA",
"FIX",
"FBT",
"ISC001",
"N",
"PERF203",
"N817",
"PERF401",
"PLR0912",
"PLR0913",
"PLR0915",
"PLR2004",
"PLW2901",
"PT",
"PTH",
"RUF012",
"S101",
"S310",
"S314",
"S324",
"S320",
"S603",
"S607",
"T20",
"TD002",
"TD003",
"TRY002",
"TRY003",
"TRY004",
"UP038",
"W505"
]
@ -52,3 +47,6 @@ max-doc-length = 72
[lint.pydocstyle]
convention = "pep257"
[lint.pylint]
max-args = 8

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import os.path
#

View File

@ -8,7 +8,6 @@ from json import load, loads
from logging import INFO, WARNING, Filter, Formatter, StreamHandler, getLogger
from pathlib import Path
from struct import calcsize, unpack
from typing import Dict, List, Set, Tuple
from xml.etree import ElementTree as ET
from scriptlib import SimulTemplateEntity, find_files
@ -27,9 +26,9 @@ class SingleLevelFilter(Filter):
class CheckRefs:
def __init__(self):
self.files: List[Path] = []
self.roots: List[Path] = []
self.deps: List[Tuple[Path, Path]] = []
self.files: list[Path] = []
self.roots: list[Path] = []
self.deps: list[tuple[Path, Path]] = []
self.vfs_root = Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods"
self.supportedTextureFormats = ("dds", "png")
self.supportedMeshesFormats = ("pmd", "dae")
@ -387,8 +386,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 +396,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("-"):
@ -794,7 +793,7 @@ class CheckRefs:
uniq_files = {r.as_posix() for r in self.files}
lower_case_files = {f.lower(): f for f in uniq_files}
missing_files: Dict[str, Set[str]] = defaultdict(set)
missing_files: dict[str, set[str]] = defaultdict(set)
for parent, dep in self.deps:
dep_str = dep.as_posix()

View File

@ -7,7 +7,6 @@ import shutil
import sys
from pathlib import Path
from subprocess import CalledProcessError, run
from typing import Sequence
from xml.etree import ElementTree as ET
from scriptlib import SimulTemplateEntity, find_files
@ -50,7 +49,7 @@ errorch.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))
logger.addHandler(errorch)
def main(argv: Sequence[str] | None = None) -> int:
def main() -> int:
parser = argparse.ArgumentParser(description="Validate templates")
parser.add_argument("-m", "--mod-name", required=True, help="The name of the mod to validate.")
parser.add_argument(
@ -73,7 +72,7 @@ def main(argv: Sequence[str] | None = None) -> int:
)
parser.add_argument("-v", "--verbose", help="Be verbose about the output.", default=False)
args = parser.parse_args(argv)
args = parser.parse_args()
if not args.relaxng_schema.exists():
logging.error(RELAXNG_SCHEMA_ERROR_MSG.format(args.relaxng_schema))

View File

@ -1,7 +1,10 @@
# Adapted from http://cairographics.org/freetypepython/
# ruff: noqa: TRY002
import ctypes
import sys
from typing import ClassVar
import cairo
@ -40,7 +43,7 @@ _surface = cairo.ImageSurface(cairo.FORMAT_A8, 0, 0)
class PycairoContext(ctypes.Structure):
_fields_ = [
_fields_: ClassVar = [
("PyObject_HEAD", ctypes.c_byte * object.__basicsize__),
("ctx", ctypes.c_void_p),
("base", ctypes.c_void_p),

View File

@ -22,7 +22,6 @@ import io
import os
import subprocess
from itertools import islice
from typing import List
from i18n_helper import PROJECT_ROOT_DIRECTORY
@ -38,7 +37,7 @@ def get_diff():
return io.StringIO(diff_process.stdout.decode("utf-8"))
def check_diff(diff: io.StringIO) -> List[str]:
def check_diff(diff: io.StringIO) -> list[str]:
"""Check a diff of .po files for meaningful changes.
Run through a diff of .po files and check that some of the changes
@ -87,7 +86,7 @@ def check_diff(diff: io.StringIO) -> List[str]:
return list(files.difference(keep))
def revert_files(files: List[str], verbose=False):
def revert_files(files: list[str], verbose=False):
def batched(iterable, n):
"""Split an iterable in equally sized chunks.

View File

@ -27,51 +27,64 @@ However that needs to be fixed on the transifex side, see rP25896. For now
strip the e-mails using this script.
"""
import fileinput
import glob
import os
import re
import sys
from i18n_helper import L10N_FOLDER_NAME, PROJECT_ROOT_DIRECTORY, TRANSIFEX_CLIENT_FOLDER
TRANSLATOR_REGEX = re.compile(r"^(#\s+[^,<]*)\s+<.*>(.*)$")
LAST_TRANSLATION_REGEX = re.compile(r"^(\"Last-Translator:[^,<]*)\s+<.*>(.*)$")
def main():
translator_match = re.compile(r"^(#\s+[^,<]*)\s+<.*>(.*)")
last_translator_match = re.compile(r"^(\"Last-Translator:[^,<]*)\s+<.*>(.*)")
for folder in glob.iglob(
f"**/{L10N_FOLDER_NAME}/{TRANSIFEX_CLIENT_FOLDER}/",
root_dir=PROJECT_ROOT_DIRECTORY,
recursive=True,
):
for file in glob.iglob(
f"{os.path.join(folder, os.pardir)}/*.po", root_dir=PROJECT_ROOT_DIRECTORY
):
absolute_file_path = os.path.abspath(f"{PROJECT_ROOT_DIRECTORY}/{file}")
for root, folders, _ in os.walk(PROJECT_ROOT_DIRECTORY):
for folder in folders:
if folder != L10N_FOLDER_NAME:
continue
if not os.path.exists(os.path.join(root, folder, TRANSIFEX_CLIENT_FOLDER)):
continue
path = os.path.join(root, folder, "*.po")
files = glob.glob(path)
for file in files:
usernames = []
reached = False
for line in fileinput.input(
file.replace("\\", "/"), inplace=True, encoding="utf-8"
):
if reached:
file_content = []
usernames = []
changes = False
in_translators = False
found_last_translator = False
with open(absolute_file_path, "r+", encoding="utf-8") as fd:
for line in fd:
if line.strip() == "# Translators:":
in_translators = True
elif not line.strip().startswith("#"):
in_translators = False
elif in_translators:
if line == "# \n":
line = ""
m = translator_match.match(line)
if m:
if m.group(1) in usernames:
line = ""
else:
line = m.group(1) + m.group(2) + "\n"
usernames.append(m.group(1))
m2 = last_translator_match.match(line)
if m2:
line = re.sub(last_translator_match, r"\1\2", line)
elif line.strip() == "# Translators:":
reached = True
sys.stdout.write(line)
changes = True
continue
translator_match = TRANSLATOR_REGEX.match(line)
if translator_match:
changes = True
if translator_match.group(1) in usernames:
continue
line = TRANSLATOR_REGEX.sub(r"\1\2", line)
usernames.append(translator_match.group(1))
if not in_translators and not found_last_translator:
last_translator_match = LAST_TRANSLATION_REGEX.match(line)
if last_translator_match:
found_last_translator = True
changes = True
line = LAST_TRANSLATION_REGEX.sub(r"\1\2", line)
file_content.append(line)
if changes:
fd.seek(0)
fd.truncate()
fd.writelines(file_content)
if __name__ == "__main__":

View File

@ -1,6 +1,6 @@
"""Wrapper around babel Catalog / .po handling."""
from datetime import datetime
from datetime import UTC, datetime
from babel.messages.catalog import Catalog as BabelCatalog
from babel.messages.pofile import read_po, write_po
@ -10,7 +10,7 @@ class Catalog(BabelCatalog):
"""Wraps a BabelCatalog for convenience."""
def __init__(self, *args, project=None, copyright_holder=None, **other_kwargs):
date = datetime.now()
date = datetime.now(tz=UTC)
super().__init__(
*args,
header_comment=(

View File

@ -1,12 +1,11 @@
"""Utils to list .po."""
import os
from typing import List, Optional
from i18n_helper.catalog import Catalog
def get_catalogs(input_file_path, filters: Optional[List[str]] = None) -> List[Catalog]:
def get_catalogs(input_file_path, filters: list[str] | None = None) -> list[Catalog]:
"""Return a list of "real" catalogs (.po) in the given folder."""
existing_translation_catalogs = []
l10n_folder_path = os.path.dirname(input_file_path)

View File

@ -97,7 +97,7 @@ PATCHES_EXPECT_REVERT = [
]
@pytest.fixture(params=zip(PATCHES, PATCHES_EXPECT_REVERT))
@pytest.fixture(params=zip(PATCHES, PATCHES_EXPECT_REVERT, strict=False))
def patch(request):
return [io.StringIO(request.param[0]), request.param[1]]

View File

@ -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)

View 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!

View File

@ -7,11 +7,11 @@ import zero_ad
def dist(p1, p2):
return math.sqrt(sum(math.pow(x2 - x1, 2) for (x1, x2) in zip(p1, p2)))
return math.sqrt(sum(math.pow(x2 - x1, 2) for (x1, x2) in zip(p1, p2, strict=False)))
def center(units):
sum_position = map(sum, zip(*(u.position() for u in units)))
sum_position = map(sum, zip(*(u.position() for u in units), strict=False))
return [x / len(units) for x in sum_position]

View File

@ -11,11 +11,11 @@ with open(path.join(scriptdir, "..", "samples", "arcadia.json"), encoding="utf-8
def dist(p1, p2):
return math.sqrt(sum(math.pow(x2 - x1, 2) for (x1, x2) in zip(p1, p2)))
return math.sqrt(sum(math.pow(x2 - x1, 2) for (x1, x2) in zip(p1, p2, strict=False)))
def center(units):
sum_position = map(sum, zip(*(u.position() for u in units)))
sum_position = map(sum, zip(*(u.position() for u in units), strict=False))
return [x / len(units) for x in sum_position]

View File

@ -26,7 +26,7 @@ class RLAPI:
def get_templates(self, names):
post_data = "\n".join(names)
response = self.post("templates", post_data)
return zip(names, response.decode().split("\n"))
return zip(names, response.decode().split("\n"), strict=False)
def evaluate(self, code):
response = self.post("evaluate", code)

View File

@ -17,7 +17,7 @@ class ZeroAD:
actions = []
player_ids = cycle([self.player_id]) if player is None else cycle(player)
cmds = zip(player_ids, actions)
cmds = zip(player_ids, actions, strict=False)
cmds = ((player, action) for (player, action) in cmds if action is not None)
state_json = self.api.step(cmds)
self.current_state = GameState(json.loads(state_json), self)

View File

@ -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
@ -46,15 +46,7 @@ def execute(command):
def calculate_hash(path):
assert os.path.isfile(path)
with open(path, "rb") as handle:
return hashlib.sha1(handle.read()).hexdigest()
def compare_spirv(path1, path2):
with open(path1, "rb") as handle:
spirv1 = handle.read()
with open(path2, "rb") as handle:
spirv2 = handle.read()
return spirv1 == spirv2
return hashlib.sha256(handle.read()).hexdigest()
def resolve_if(defines, expression):
@ -169,18 +161,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 +195,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 +212,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 +227,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 +242,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
@ -450,19 +448,10 @@ def build(rules, input_mod_path, output_mod_path, dependencies, program_name):
spirv_hash = calculate_hash(output_spirv_path)
if spirv_hash not in hashed_cache:
hashed_cache[spirv_hash] = [file_name]
hashed_cache[spirv_hash] = file_name
else:
found_candidate = False
for candidate_name in hashed_cache[spirv_hash]:
candidate_path = os.path.join(output_spirv_mod_path, candidate_name)
if compare_spirv(output_spirv_path, candidate_path):
found_candidate = True
file_name = candidate_name
break
if found_candidate:
os.remove(output_spirv_path)
else:
hashed_cache[spirv_hash].append(file_name)
file_name = hashed_cache[spirv_hash]
os.remove(output_spirv_path)
shader_element = ET.SubElement(program_root, shader["type"])
shader_element.set("file", "spirv/" + file_name)

View File

@ -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

View File

@ -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
@ -201,7 +201,7 @@ class RelaxNGValidator:
try:
doc = lxml.etree.parse(str(file[1]))
relaxng.assertValid(doc)
except Exception:
except (lxml.etree.DocumentInvalid, lxml.etree.XMLSyntaxError):
error_count = error_count + 1
self.logger.exception(file[1])