From 77ee6d73bc8c3b8d82b5ea7e0abff67e7fa20e3e Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Fri, 25 Oct 2024 08:51:53 +0200 Subject: [PATCH 01/85] Setup: more typing (#4089) --- setup.py | 98 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/setup.py b/setup.py index 0c9ee2c293..7a8c226261 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,6 @@ import platform import shutil import sys import sysconfig -import typing import warnings import zipfile import urllib.request @@ -14,14 +13,14 @@ import json import threading import subprocess -from collections.abc import Iterable from hashlib import sha3_512 from pathlib import Path +from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union # This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it +requirement = 'cx-Freeze==7.2.0' try: - requirement = 'cx-Freeze==7.2.0' import pkg_resources try: pkg_resources.require(requirement) @@ -30,7 +29,7 @@ try: install_cx_freeze = True except ImportError: install_cx_freeze = True - pkg_resources = None # type: ignore [assignment] + pkg_resources = None # type: ignore[assignment] if install_cx_freeze: # check if pip is available @@ -61,7 +60,7 @@ from Cython.Build import cythonize # On Python < 3.10 LogicMixin is not currently supported. -non_apworlds: set = { +non_apworlds: Set[str] = { "A Link to the Past", "Adventure", "ArchipIDLE", @@ -84,7 +83,7 @@ non_apworlds: set = { if sys.version_info < (3,10): non_apworlds.add("Hollow Knight") -def download_SNI(): +def download_SNI() -> None: print("Updating SNI") machine_to_go = { "x86_64": "amd64", @@ -114,8 +113,8 @@ def download_SNI(): if source_url and source_url.endswith(".zip"): with urllib.request.urlopen(source_url) as download: with zipfile.ZipFile(io.BytesIO(download.read()), "r") as zf: - for member in zf.infolist(): - zf.extract(member, path="SNI") + for zf_member in zf.infolist(): + zf.extract(zf_member, path="SNI") print(f"Downloaded SNI from {source_url}") elif source_url and (source_url.endswith(".tar.xz") or source_url.endswith(".tar.gz")): @@ -129,11 +128,13 @@ def download_SNI(): raise ValueError(f"Unexpected file '{member.name}' in {source_url}") elif member.isdir() and not sni_dir: sni_dir = member.name - elif member.isfile() and not sni_dir or not member.name.startswith(sni_dir): + elif member.isfile() and not sni_dir or sni_dir and not member.name.startswith(sni_dir): raise ValueError(f"Expected folder before '{member.name}' in {source_url}") elif member.isfile() and sni_dir: tf.extract(member) # sadly SNI is in its own folder on non-windows, so we need to rename + if not sni_dir: + raise ValueError("Did not find SNI in archive") shutil.rmtree("SNI", True) os.rename(sni_dir, "SNI") print(f"Downloaded SNI from {source_url}") @@ -145,7 +146,7 @@ def download_SNI(): print(f"No SNI found for system spec {platform_name} {machine_name}") -signtool: typing.Optional[str] +signtool: Optional[str] if os.path.exists("X:/pw.txt"): print("Using signtool") with open("X:/pw.txt", encoding="utf-8-sig") as f: @@ -197,13 +198,13 @@ extra_data = ["LICENSE", "data", "EnemizerCLI", "SNI"] extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else [] -def remove_sprites_from_folder(folder): +def remove_sprites_from_folder(folder: Path) -> None: for file in os.listdir(folder): if file != ".gitignore": os.remove(folder / file) -def _threaded_hash(filepath): +def _threaded_hash(filepath: Union[str, Path]) -> str: hasher = sha3_512() hasher.update(open(filepath, "rb").read()) return base64.b85encode(hasher.digest()).decode() @@ -217,11 +218,11 @@ class BuildCommand(setuptools.command.build.build): yes: bool last_yes: bool = False # used by sub commands of build - def initialize_options(self): + def initialize_options(self) -> None: super().initialize_options() type(self).last_yes = self.yes = False - def finalize_options(self): + def finalize_options(self) -> None: super().finalize_options() type(self).last_yes = self.yes @@ -233,27 +234,27 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): ('extra-data=', None, 'Additional files to add.'), ] yes: bool - extra_data: Iterable # [any] not available in 3.8 - extra_libs: Iterable # work around broken include_files + extra_data: Iterable[str] + extra_libs: Iterable[str] # work around broken include_files buildfolder: Path libfolder: Path library: Path buildtime: datetime.datetime - def initialize_options(self): + def initialize_options(self) -> None: super().initialize_options() self.yes = BuildCommand.last_yes self.extra_data = [] self.extra_libs = [] - def finalize_options(self): + def finalize_options(self) -> None: super().finalize_options() self.buildfolder = self.build_exe self.libfolder = Path(self.buildfolder, "lib") self.library = Path(self.libfolder, "library.zip") - def installfile(self, path, subpath=None, keep_content: bool = False): + def installfile(self, path: Path, subpath: Optional[Union[str, Path]] = None, keep_content: bool = False) -> None: folder = self.buildfolder if subpath: folder /= subpath @@ -268,7 +269,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): else: print('Warning,', path, 'not found') - def create_manifest(self, create_hashes=False): + def create_manifest(self, create_hashes: bool = False) -> None: # Since the setup is now split into components and the manifest is not, # it makes most sense to just remove the hashes for now. Not aware of anyone using them. hashes = {} @@ -290,7 +291,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): json.dump(manifest, open(manifestpath, "wt"), indent=4) print("Created Manifest") - def run(self): + def run(self) -> None: # start downloading sni asap sni_thread = threading.Thread(target=download_SNI, name="SNI Downloader") sni_thread.start() @@ -341,7 +342,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): # post build steps if is_windows: # kivy_deps is win32 only, linux picks them up automatically - from kivy_deps import sdl2, glew + from kivy_deps import sdl2, glew # type: ignore for folder in sdl2.dep_bins + glew.dep_bins: shutil.copytree(folder, self.libfolder, dirs_exist_ok=True) print(f"copying {folder} -> {self.libfolder}") @@ -362,7 +363,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): self.installfile(Path(data)) # kivi data files - import kivy + import kivy # type: ignore[import-untyped] shutil.copytree(os.path.join(os.path.dirname(kivy.__file__), "data"), self.buildfolder / "data", dirs_exist_ok=True) @@ -372,7 +373,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): from worlds.AutoWorld import AutoWorldRegister assert not non_apworlds - set(AutoWorldRegister.world_types), \ f"Unknown world {non_apworlds - set(AutoWorldRegister.world_types)} designated for .apworld" - folders_to_remove: typing.List[str] = [] + folders_to_remove: List[str] = [] disabled_worlds_folder = "worlds_disabled" for entry in os.listdir(disabled_worlds_folder): if os.path.isdir(os.path.join(disabled_worlds_folder, entry)): @@ -393,7 +394,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): shutil.rmtree(world_directory) shutil.copyfile("meta.yaml", self.buildfolder / "Players" / "Templates" / "meta.yaml") try: - from maseya import z3pr + from maseya import z3pr # type: ignore[import-untyped] except ImportError: print("Maseya Palette Shuffle not found, skipping data files.") else: @@ -444,16 +445,16 @@ class AppImageCommand(setuptools.Command): ("app-exec=", None, "The application to run inside the image."), ("yes", "y", 'Answer "yes" to all questions.'), ] - build_folder: typing.Optional[Path] - dist_file: typing.Optional[Path] - app_dir: typing.Optional[Path] + build_folder: Optional[Path] + dist_file: Optional[Path] + app_dir: Optional[Path] app_name: str - app_exec: typing.Optional[Path] - app_icon: typing.Optional[Path] # source file + app_exec: Optional[Path] + app_icon: Optional[Path] # source file app_id: str # lower case name, used for icon and .desktop yes: bool - def write_desktop(self): + def write_desktop(self) -> None: assert self.app_dir, "Invalid app_dir" desktop_filename = self.app_dir / f"{self.app_id}.desktop" with open(desktop_filename, 'w', encoding="utf-8") as f: @@ -468,7 +469,7 @@ class AppImageCommand(setuptools.Command): ))) desktop_filename.chmod(0o755) - def write_launcher(self, default_exe: Path): + def write_launcher(self, default_exe: Path) -> None: assert self.app_dir, "Invalid app_dir" launcher_filename = self.app_dir / "AppRun" with open(launcher_filename, 'w', encoding="utf-8") as f: @@ -491,7 +492,7 @@ $APPDIR/$exe "$@" """) launcher_filename.chmod(0o755) - def install_icon(self, src: Path, name: typing.Optional[str] = None, symlink: typing.Optional[Path] = None): + def install_icon(self, src: Path, name: Optional[str] = None, symlink: Optional[Path] = None) -> None: assert self.app_dir, "Invalid app_dir" try: from PIL import Image @@ -513,7 +514,8 @@ $APPDIR/$exe "$@" if symlink: symlink.symlink_to(dest_file.relative_to(symlink.parent)) - def initialize_options(self): + def initialize_options(self) -> None: + assert self.distribution.metadata.name self.build_folder = None self.app_dir = None self.app_name = self.distribution.metadata.name @@ -527,17 +529,22 @@ $APPDIR/$exe "$@" )) self.yes = False - def finalize_options(self): + def finalize_options(self) -> None: + assert self.build_folder if not self.app_dir: self.app_dir = self.build_folder.parent / "AppDir" self.app_id = self.app_name.lower() - def run(self): + def run(self) -> None: + assert self.build_folder and self.dist_file, "Command not properly set up" + assert ( + self.app_icon and self.app_id and self.app_dir and self.app_exec and self.app_name + ), "AppImageCommand not properly set up" self.dist_file.parent.mkdir(parents=True, exist_ok=True) if self.app_dir.is_dir(): shutil.rmtree(self.app_dir) self.app_dir.mkdir(parents=True) - opt_dir = self.app_dir / "opt" / self.distribution.metadata.name + opt_dir = self.app_dir / "opt" / self.app_name shutil.copytree(self.build_folder, opt_dir) root_icon = self.app_dir / f'{self.app_id}{self.app_icon.suffix}' self.install_icon(self.app_icon, self.app_id, symlink=root_icon) @@ -548,7 +555,7 @@ $APPDIR/$exe "$@" subprocess.call(f'ARCH={build_arch} ./appimagetool -n "{self.app_dir}" "{self.dist_file}"', shell=True) -def find_libs(*args: str) -> typing.Sequence[typing.Tuple[str, str]]: +def find_libs(*args: str) -> Sequence[Tuple[str, str]]: """Try to find system libraries to be included.""" if not args: return [] @@ -556,7 +563,7 @@ def find_libs(*args: str) -> typing.Sequence[typing.Tuple[str, str]]: arch = build_arch.replace('_', '-') libc = 'libc6' # we currently don't support musl - def parse(line): + def parse(line: str) -> Tuple[Tuple[str, str, str], str]: lib, path = line.strip().split(' => ') lib, typ = lib.split(' ', 1) for test_arch in ('x86-64', 'i386', 'aarch64'): @@ -577,26 +584,29 @@ def find_libs(*args: str) -> typing.Sequence[typing.Tuple[str, str]]: ldconfig = shutil.which("ldconfig") assert ldconfig, "Make sure ldconfig is in PATH" data = subprocess.run([ldconfig, "-p"], capture_output=True, text=True).stdout.split("\n")[1:] - find_libs.cache = { # type: ignore [attr-defined] + find_libs.cache = { # type: ignore[attr-defined] k: v for k, v in (parse(line) for line in data if "=>" in line) } - def find_lib(lib, arch, libc): - for k, v in find_libs.cache.items(): + def find_lib(lib: str, arch: str, libc: str) -> Optional[str]: + cache: Dict[Tuple[str, str, str], str] = getattr(find_libs, "cache") + for k, v in cache.items(): if k == (lib, arch, libc): return v - for k, v, in find_libs.cache.items(): + for k, v, in cache.items(): if k[0].startswith(lib) and k[1] == arch and k[2] == libc: return v return None - res = [] + res: List[Tuple[str, str]] = [] for arg in args: # try exact match, empty libc, empty arch, empty arch and libc file = find_lib(arg, arch, libc) file = file or find_lib(arg, arch, '') file = file or find_lib(arg, '', libc) file = file or find_lib(arg, '', '') + if not file: + raise ValueError(f"Could not find lib {arg}") # resolve symlinks for n in range(0, 5): res.append((file, os.path.join('lib', os.path.basename(file)))) From af77b762652137f141c4c503b9f440b442a60f17 Mon Sep 17 00:00:00 2001 From: qwint Date: Fri, 25 Oct 2024 23:59:28 -0400 Subject: [PATCH 02/85] Core: fix item links for alternate menu regions #4097 --- BaseClasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index 0d4f34e514..53b69d30e2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -341,7 +341,7 @@ class MultiWorld(): new_item.classification |= classifications[item_name] new_itempool.append(new_item) - region = Region("Menu", group_id, self, "ItemLink") + region = Region(group["world"].origin_region_name, group_id, self, "ItemLink") self.regions.append(region) locations = region.locations # ensure that progression items are linked first, then non-progression From cd7b1df6501cb117a3c6b1cbc1f3b5f96217da0d Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:25:03 -0500 Subject: [PATCH 03/85] OoT: fix plando/item links (again) #4098 --- worlds/oot/__init__.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index c3925bf2a8..975902ae6e 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -1348,23 +1348,9 @@ class OOTWorld(World): def get_locations(self): return self.multiworld.get_locations(self.player) - def get_location(self, location): - return self.multiworld.get_location(location, self.player) - - def get_region(self, region_name): - try: - return self._regions_cache[region_name] - except KeyError: - ret = self.multiworld.get_region(region_name, self.player) - self._regions_cache[region_name] = ret - return ret - def get_entrances(self): return self.multiworld.get_entrances(self.player) - def get_entrance(self, entrance): - return self.multiworld.get_entrance(entrance, self.player) - def is_major_item(self, item: OOTItem): if item.type == 'Token': return self.bridge == 'tokens' or self.lacs_condition == 'tokens' From 5d4684f31550d692824991f32b6b3878b18dc5c4 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sat, 26 Oct 2024 23:10:38 +0200 Subject: [PATCH 04/85] WebHost: restore fragment links for glossary and faq and make titles clickable (#4103) * WebHost: restore fragment links for glossary and faq such as /faq/en/#what-does-multi-game-mean * WebHost: faq, glossary: make markdown titles clickable --- WebHostLib/misc.py | 16 ++++++++++++++-- WebHostLib/static/styles/markdown.css | 21 +++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py index 1f86e21066..c49b1ae178 100644 --- a/WebHostLib/misc.py +++ b/WebHostLib/misc.py @@ -77,7 +77,13 @@ def faq(lang: str): return render_template( "markdown_document.html", title="Frequently Asked Questions", - html_from_markdown=markdown.markdown(document, extensions=["mdx_breakless_lists"]), + html_from_markdown=markdown.markdown( + document, + extensions=["toc", "mdx_breakless_lists"], + extension_configs={ + "toc": {"anchorlink": True} + } + ), ) @@ -90,7 +96,13 @@ def glossary(lang: str): return render_template( "markdown_document.html", title="Glossary", - html_from_markdown=markdown.markdown(document, extensions=["mdx_breakless_lists"]), + html_from_markdown=markdown.markdown( + document, + extensions=["toc", "mdx_breakless_lists"], + extension_configs={ + "toc": {"anchorlink": True} + } + ), ) diff --git a/WebHostLib/static/styles/markdown.css b/WebHostLib/static/styles/markdown.css index e0165b7489..5ead2c60f7 100644 --- a/WebHostLib/static/styles/markdown.css +++ b/WebHostLib/static/styles/markdown.css @@ -28,7 +28,7 @@ font-weight: normal; font-family: LondrinaSolid-Regular, sans-serif; text-transform: uppercase; - cursor: pointer; + cursor: pointer; /* TODO: remove once we drop showdown.js */ width: 100%; text-shadow: 1px 1px 4px #000000; } @@ -37,7 +37,7 @@ font-size: 38px; font-weight: normal; font-family: LondrinaSolid-Light, sans-serif; - cursor: pointer; + cursor: pointer; /* TODO: remove once we drop showdown.js */ width: 100%; margin-top: 20px; margin-bottom: 0.5rem; @@ -50,7 +50,7 @@ font-family: LexendDeca-Regular, sans-serif; text-transform: none; text-align: left; - cursor: pointer; + cursor: pointer; /* TODO: remove once we drop showdown.js */ width: 100%; margin-bottom: 0.5rem; } @@ -59,7 +59,7 @@ font-family: LexendDeca-Regular, sans-serif; text-transform: none; font-size: 24px; - cursor: pointer; + cursor: pointer; /* TODO: remove once we drop showdown.js */ margin-bottom: 24px; } @@ -67,20 +67,29 @@ font-family: LexendDeca-Regular, sans-serif; text-transform: none; font-size: 22px; - cursor: pointer; + cursor: pointer; /* TODO: remove once we drop showdown.js */ } .markdown h6, .markdown details summary.h6{ font-family: LexendDeca-Regular, sans-serif; text-transform: none; font-size: 20px; - cursor: pointer;; + cursor: pointer; /* TODO: remove once we drop showdown.js */ } .markdown h4, .markdown h5, .markdown h6{ margin-bottom: 0.5rem; } +.markdown h1 > a, +.markdown h2 > a, +.markdown h3 > a, +.markdown h4 > a, +.markdown h5 > a, +.markdown h6 > a { + color: inherit; +} + .markdown ul{ margin-top: 0.5rem; margin-bottom: 0.5rem; From d61a76fb02317e736540e42672507d66bd547128 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 27 Oct 2024 00:28:47 +0200 Subject: [PATCH 05/85] Setup: SNI: separate win7/non-win7 build, update macos naming (#4088) * Setup: fix macos SNI download name * Setup: prefer SNI Windows7 build on py3.8, non-7 on py3.9+ --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 7a8c226261..4d7fc39420 100644 --- a/setup.py +++ b/setup.py @@ -93,7 +93,7 @@ def download_SNI() -> None: platform_name = platform.system().lower() machine_name = platform.machine().lower() # force amd64 on macos until we have universal2 sni, otherwise resolve to GOARCH - machine_name = "amd64" if platform_name == "darwin" else machine_to_go.get(machine_name, machine_name) + machine_name = "universal" if platform_name == "darwin" else machine_to_go.get(machine_name, machine_name) with urllib.request.urlopen("https://api.github.com/repos/alttpo/sni/releases/latest") as request: data = json.load(request) files = data["assets"] @@ -104,11 +104,13 @@ def download_SNI() -> None: download_url: str = file["browser_download_url"] machine_match = download_url.rsplit("-", 1)[1].split(".", 1)[0] == machine_name if platform_name in download_url and machine_match: + source_url = download_url # prefer "many" builds if "many" in download_url: - source_url = download_url break - source_url = download_url + # prefer the correct windows or windows7 build + if platform_name == "windows" and ("windows7" in download_url) == (sys.version_info < (3, 9)): + break if source_url and source_url.endswith(".zip"): with urllib.request.urlopen(source_url) as download: From daad3d0350cffabe356a80dd2f815645dd485ca4 Mon Sep 17 00:00:00 2001 From: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Date: Sat, 26 Oct 2024 19:31:56 -0400 Subject: [PATCH 06/85] SM64ex: Add links to documentation for makeflags and patches #4092 --- worlds/sm64ex/docs/setup_en.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/worlds/sm64ex/docs/setup_en.md b/worlds/sm64ex/docs/setup_en.md index 7456bcb70b..afb5bad50f 100644 --- a/worlds/sm64ex/docs/setup_en.md +++ b/worlds/sm64ex/docs/setup_en.md @@ -36,6 +36,8 @@ Then continue to `Using the Launcher` - Windows: If you did not use the default install directory for MSYS, close this window, check `Show advanced options` and reopen using `Re-check Requirements`. You can then set the path manually. 5. When finished, use `Compile default SM64AP build` to continue - Advanced user can use `Show advanced options` to build with custom makeflags (`BETTERCAMERA`, `NODRAWINGDISTANCE`, ...), different repos and branches, and game patches such as 60FPS, Enhanced Moveset and others. + - [Available Makeflags](https://github.com/sm64pc/sm64ex/wiki/Build-options) + - [Included Game Patches](https://github.com/N00byKing/sm64ex/blob/archipelago/enhancements/README.md) 6. Press `Download Files` to prepare the build, afterwards `Create Build`. 7. SM64EX will now be compiled. This can take a while. From 579abb33c0272823e1c19a3971190b53bf68ed4a Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Sun, 27 Oct 2024 23:01:41 +0000 Subject: [PATCH 07/85] Properly fall back to a world's `rich_text_options_doc` (#4109) Previously, because `default(..., true)` only checks if the left-hand-side is falsey, not just `None`, this always forced options to render in non-rich mode if they were explicitly set to `rich_text_doc = None`. Now it matches the intended and documented behavior, where `None` falls back to the world's `rich_text_options_doc` setting. --- WebHostLib/templates/playerOptions/macros.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html index 30a4fc78df..64f0f140de 100644 --- a/WebHostLib/templates/playerOptions/macros.html +++ b/WebHostLib/templates/playerOptions/macros.html @@ -196,13 +196,14 @@ {% macro OptionTitle(option_name, option) %}