From 9edd55961f8918face11d0a0e22ce9b24467147c Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 2 Aug 2025 01:26:50 +0200 Subject: [PATCH] LttP: remove sprite download from setup flow & make sprite repo dynamic (#4830) --- LttPAdjuster.py | 51 +++++++++++++--------- WebHostLib/lttpsprites.py | 2 +- data/sprites/{alttpr => remote}/.gitignore | 0 inno_setup.iss | 5 --- setup.py | 8 ++-- worlds/alttp/Rom.py | 3 +- 6 files changed, 39 insertions(+), 30 deletions(-) rename data/sprites/{alttpr => remote}/.gitignore (100%) diff --git a/LttPAdjuster.py b/LttPAdjuster.py index d44f4134..4816210f 100644 --- a/LttPAdjuster.py +++ b/LttPAdjuster.py @@ -32,6 +32,7 @@ GAME_ALTTP = "A Link to the Past" WINDOW_MIN_HEIGHT = 525 WINDOW_MIN_WIDTH = 425 + class AdjusterWorld(object): class AdjusterSubWorld(object): def __init__(self, random): @@ -48,6 +49,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): def _get_help_string(self, action): return textwrap.dedent(action.help) + # See argparse.BooleanOptionalAction class BooleanOptionalActionWithDisable(argparse.Action): def __init__(self, @@ -363,10 +365,10 @@ def run_sprite_update(): logging.info("Done updating sprites") -def update_sprites(task, on_finish=None): +def update_sprites(task, on_finish=None, repository_url: str = "https://alttpr.com/sprites"): resultmessage = "" successful = True - sprite_dir = user_path("data", "sprites", "alttpr") + sprite_dir = user_path("data", "sprites", "alttp", "remote") os.makedirs(sprite_dir, exist_ok=True) ctx = get_cert_none_ssl_context() @@ -376,11 +378,11 @@ def update_sprites(task, on_finish=None): on_finish(successful, resultmessage) try: - task.update_status("Downloading alttpr sprites list") - with urlopen('https://alttpr.com/sprites', context=ctx) as response: + task.update_status("Downloading remote sprites list") + with urlopen(repository_url, context=ctx) as response: sprites_arr = json.loads(response.read().decode("utf-8")) except Exception as e: - resultmessage = "Error getting list of alttpr sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) + resultmessage = "Error getting list of remote sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) successful = False task.queue_event(finished) return @@ -388,13 +390,13 @@ def update_sprites(task, on_finish=None): try: task.update_status("Determining needed sprites") current_sprites = [os.path.basename(file) for file in glob(sprite_dir + '/*')] - alttpr_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) + remote_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr if sprite["author"] != "Nintendo"] - needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in alttpr_sprites if + needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in remote_sprites if filename not in current_sprites] - alttpr_filenames = [filename for (_, filename) in alttpr_sprites] - obsolete_sprites = [sprite for sprite in current_sprites if sprite not in alttpr_filenames] + remote_filenames = [filename for (_, filename) in remote_sprites] + obsolete_sprites = [sprite for sprite in current_sprites if sprite not in remote_filenames] except Exception as e: resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % ( type(e).__name__, e) @@ -446,7 +448,7 @@ def update_sprites(task, on_finish=None): successful = False if successful: - resultmessage = "alttpr sprites updated successfully" + resultmessage = "Remote sprites updated successfully" task.queue_event(finished) @@ -867,7 +869,7 @@ class SpriteSelector(): def open_custom_sprite_dir(_evt): open_file(self.custom_sprite_dir) - alttpr_frametitle = Label(self.window, text='ALTTPR Sprites') + remote_frametitle = Label(self.window, text='Remote Sprites') custom_frametitle = Frame(self.window) title_text = Label(custom_frametitle, text="Custom Sprites") @@ -876,8 +878,8 @@ class SpriteSelector(): title_link.pack(side=LEFT) title_link.bind("", open_custom_sprite_dir) - self.icon_section(alttpr_frametitle, self.alttpr_sprite_dir, - 'ALTTPR sprites not found. Click "Update alttpr sprites" to download them.') + self.icon_section(remote_frametitle, self.remote_sprite_dir, + 'Remote sprites not found. Click "Update remote sprites" to download them.') self.icon_section(custom_frametitle, self.custom_sprite_dir, 'Put sprites in the custom sprites folder (see open link above) to have them appear here.') if not randomOnEvent: @@ -890,11 +892,18 @@ class SpriteSelector(): button = Button(frame, text="Browse for file...", command=self.browse_for_sprite) button.pack(side=RIGHT, padx=(5, 0)) - button = Button(frame, text="Update alttpr sprites", command=self.update_alttpr_sprites) + button = Button(frame, text="Update remote sprites", command=self.update_remote_sprites) button.pack(side=RIGHT, padx=(5, 0)) + + repository_label = Label(frame, text='Sprite Repository:') + self.repository_url = StringVar(frame, "https://alttpr.com/sprites") + repository_entry = Entry(frame, textvariable=self.repository_url) + repository_entry.pack(side=RIGHT, expand=True, fill=BOTH, pady=1) + repository_label.pack(side=RIGHT, expand=False, padx=(0, 5)) + button = Button(frame, text="Do not adjust sprite",command=self.use_default_sprite) - button.pack(side=LEFT,padx=(0,5)) + button.pack(side=LEFT, padx=(0, 5)) button = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite) button.pack(side=LEFT, padx=(0, 5)) @@ -1054,7 +1063,7 @@ class SpriteSelector(): for i, button in enumerate(frame.buttons): button.grid(row=i // self.spritesPerRow, column=i % self.spritesPerRow) - def update_alttpr_sprites(self): + def update_remote_sprites(self): # need to wrap in try catch. We don't want errors getting the json or downloading the files to break us. self.window.destroy() self.parent.update() @@ -1067,7 +1076,8 @@ class SpriteSelector(): messagebox.showerror("Sprite Updater", resultmessage) SpriteSelector(self.parent, self.callback, self.adjuster) - BackgroundTaskProgress(self.parent, update_sprites, "Updating Sprites", on_finish) + BackgroundTaskProgress(self.parent, update_sprites, "Updating Sprites", + on_finish, self.repository_url.get()) def browse_for_sprite(self): sprite = filedialog.askopenfilename( @@ -1157,12 +1167,13 @@ class SpriteSelector(): os.makedirs(self.custom_sprite_dir) @property - def alttpr_sprite_dir(self): - return user_path("data", "sprites", "alttpr") + def remote_sprite_dir(self): + return user_path("data", "sprites", "alttp", "remote") @property def custom_sprite_dir(self): - return user_path("data", "sprites", "custom") + return user_path("data", "sprites", "alttp", "custom") + def get_image_for_sprite(sprite, gif_only: bool = False): if not sprite.valid: diff --git a/WebHostLib/lttpsprites.py b/WebHostLib/lttpsprites.py index 1b8ee4cf..9d780b13 100644 --- a/WebHostLib/lttpsprites.py +++ b/WebHostLib/lttpsprites.py @@ -14,7 +14,7 @@ def update_sprites_lttp(): from LttPAdjuster import update_sprites # Target directories - input_dir = user_path("data", "sprites", "alttpr") + input_dir = user_path("data", "sprites", "alttp", "remote") output_dir = local_path("WebHostLib", "static", "generated") # TODO: move to user_path os.makedirs(os.path.join(output_dir, "sprites"), exist_ok=True) diff --git a/data/sprites/alttpr/.gitignore b/data/sprites/remote/.gitignore similarity index 100% rename from data/sprites/alttpr/.gitignore rename to data/sprites/remote/.gitignore diff --git a/inno_setup.iss b/inno_setup.iss index 6f41b204..8611c849 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -53,10 +53,6 @@ Name: "full"; Description: "Full installation" Name: "minimal"; Description: "Minimal installation" Name: "custom"; Description: "Custom installation"; Flags: iscustom -[Components] -Name: "core"; Description: "Archipelago"; Types: full minimal custom; Flags: fixed -Name: "lttp_sprites"; Description: "Download ""A Link to the Past"" player sprites"; Types: full; - [Dirs] NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; @@ -76,7 +72,6 @@ Name: "{commondesktop}\{#MyAppName} Launcher"; Filename: "{app}\ArchipelagoLaunc [Run] Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: lttp_sprites Filename: "{app}\ArchipelagoLauncher"; Parameters: "--update_settings"; StatusMsg: "Updating host.yaml..."; Flags: runasoriginaluser runhidden Filename: "{app}\ArchipelagoLauncher"; Description: "{cm:LaunchProgram,{#StringChange('Launcher', '&', '&&')}}"; Flags: nowait postinstall skipifsilent diff --git a/setup.py b/setup.py index 593a45f8..1808b22c 100644 --- a/setup.py +++ b/setup.py @@ -199,9 +199,10 @@ extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else [] def remove_sprites_from_folder(folder: Path) -> None: - for file in os.listdir(folder): - if file != ".gitignore": - os.remove(folder / file) + if os.path.isdir(folder): + for file in os.listdir(folder): + if file != ".gitignore": + os.remove(folder / file) def _threaded_hash(filepath: str | Path) -> str: @@ -410,6 +411,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): os.system(signtool + os.path.join(self.buildfolder, "lib", "worlds", "oot", "data", *exe_path)) remove_sprites_from_folder(self.buildfolder / "data" / "sprites" / "alttpr") + remove_sprites_from_folder(self.buildfolder / "data" / "sprites" / "alttp" / "remote") self.create_manifest() diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 88b9485a..6a5792d2 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -515,7 +515,8 @@ def _populate_sprite_table(): logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.") with concurrent.futures.ThreadPoolExecutor() as pool: - sprite_paths = [user_path('data', 'sprites', 'alttpr'), user_path('data', 'sprites', 'custom')] + sprite_paths = [user_path("data", "sprites", "alttp", "remote"), + user_path("data", "sprites", "alttp", "custom")] for dir in [dir for dir in sprite_paths if os.path.isdir(dir)]: for file in os.listdir(dir): pool.submit(load_sprite_from_file, os.path.join(dir, file))