LttP: remove sprite download from setup flow & make sprite repo dynamic (#4830)

This commit is contained in:
Fabian Dill
2025-08-02 01:26:50 +02:00
committed by GitHub
parent 9ad6959559
commit 9edd55961f
6 changed files with 39 additions and 30 deletions

View File

@@ -32,6 +32,7 @@ GAME_ALTTP = "A Link to the Past"
WINDOW_MIN_HEIGHT = 525 WINDOW_MIN_HEIGHT = 525
WINDOW_MIN_WIDTH = 425 WINDOW_MIN_WIDTH = 425
class AdjusterWorld(object): class AdjusterWorld(object):
class AdjusterSubWorld(object): class AdjusterSubWorld(object):
def __init__(self, random): def __init__(self, random):
@@ -48,6 +49,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def _get_help_string(self, action): def _get_help_string(self, action):
return textwrap.dedent(action.help) return textwrap.dedent(action.help)
# See argparse.BooleanOptionalAction # See argparse.BooleanOptionalAction
class BooleanOptionalActionWithDisable(argparse.Action): class BooleanOptionalActionWithDisable(argparse.Action):
def __init__(self, def __init__(self,
@@ -363,10 +365,10 @@ def run_sprite_update():
logging.info("Done updating sprites") 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 = "" resultmessage = ""
successful = True successful = True
sprite_dir = user_path("data", "sprites", "alttpr") sprite_dir = user_path("data", "sprites", "alttp", "remote")
os.makedirs(sprite_dir, exist_ok=True) os.makedirs(sprite_dir, exist_ok=True)
ctx = get_cert_none_ssl_context() ctx = get_cert_none_ssl_context()
@@ -376,11 +378,11 @@ def update_sprites(task, on_finish=None):
on_finish(successful, resultmessage) on_finish(successful, resultmessage)
try: try:
task.update_status("Downloading alttpr sprites list") task.update_status("Downloading remote sprites list")
with urlopen('https://alttpr.com/sprites', context=ctx) as response: with urlopen(repository_url, context=ctx) as response:
sprites_arr = json.loads(response.read().decode("utf-8")) sprites_arr = json.loads(response.read().decode("utf-8"))
except Exception as e: 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 successful = False
task.queue_event(finished) task.queue_event(finished)
return return
@@ -388,13 +390,13 @@ def update_sprites(task, on_finish=None):
try: try:
task.update_status("Determining needed sprites") task.update_status("Determining needed sprites")
current_sprites = [os.path.basename(file) for file in glob(sprite_dir + '/*')] 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"] 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] filename not in current_sprites]
alttpr_filenames = [filename for (_, filename) in alttpr_sprites] remote_filenames = [filename for (_, filename) in remote_sprites]
obsolete_sprites = [sprite for sprite in current_sprites if sprite not in alttpr_filenames] obsolete_sprites = [sprite for sprite in current_sprites if sprite not in remote_filenames]
except Exception as e: except Exception as e:
resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % ( resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (
type(e).__name__, e) type(e).__name__, e)
@@ -446,7 +448,7 @@ def update_sprites(task, on_finish=None):
successful = False successful = False
if successful: if successful:
resultmessage = "alttpr sprites updated successfully" resultmessage = "Remote sprites updated successfully"
task.queue_event(finished) task.queue_event(finished)
@@ -867,7 +869,7 @@ class SpriteSelector():
def open_custom_sprite_dir(_evt): def open_custom_sprite_dir(_evt):
open_file(self.custom_sprite_dir) 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) custom_frametitle = Frame(self.window)
title_text = Label(custom_frametitle, text="Custom Sprites") title_text = Label(custom_frametitle, text="Custom Sprites")
@@ -876,8 +878,8 @@ class SpriteSelector():
title_link.pack(side=LEFT) title_link.pack(side=LEFT)
title_link.bind("<Button-1>", open_custom_sprite_dir) title_link.bind("<Button-1>", open_custom_sprite_dir)
self.icon_section(alttpr_frametitle, self.alttpr_sprite_dir, self.icon_section(remote_frametitle, self.remote_sprite_dir,
'ALTTPR sprites not found. Click "Update alttpr sprites" to download them.') 'Remote sprites not found. Click "Update remote sprites" to download them.')
self.icon_section(custom_frametitle, self.custom_sprite_dir, 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.') 'Put sprites in the custom sprites folder (see open link above) to have them appear here.')
if not randomOnEvent: if not randomOnEvent:
@@ -890,11 +892,18 @@ class SpriteSelector():
button = Button(frame, text="Browse for file...", command=self.browse_for_sprite) button = Button(frame, text="Browse for file...", command=self.browse_for_sprite)
button.pack(side=RIGHT, padx=(5, 0)) 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)) 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 = 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 = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite)
button.pack(side=LEFT, padx=(0, 5)) button.pack(side=LEFT, padx=(0, 5))
@@ -1054,7 +1063,7 @@ class SpriteSelector():
for i, button in enumerate(frame.buttons): for i, button in enumerate(frame.buttons):
button.grid(row=i // self.spritesPerRow, column=i % self.spritesPerRow) 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. # 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.window.destroy()
self.parent.update() self.parent.update()
@@ -1067,7 +1076,8 @@ class SpriteSelector():
messagebox.showerror("Sprite Updater", resultmessage) messagebox.showerror("Sprite Updater", resultmessage)
SpriteSelector(self.parent, self.callback, self.adjuster) 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): def browse_for_sprite(self):
sprite = filedialog.askopenfilename( sprite = filedialog.askopenfilename(
@@ -1157,12 +1167,13 @@ class SpriteSelector():
os.makedirs(self.custom_sprite_dir) os.makedirs(self.custom_sprite_dir)
@property @property
def alttpr_sprite_dir(self): def remote_sprite_dir(self):
return user_path("data", "sprites", "alttpr") return user_path("data", "sprites", "alttp", "remote")
@property @property
def custom_sprite_dir(self): 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): def get_image_for_sprite(sprite, gif_only: bool = False):
if not sprite.valid: if not sprite.valid:

View File

@@ -14,7 +14,7 @@ def update_sprites_lttp():
from LttPAdjuster import update_sprites from LttPAdjuster import update_sprites
# Target directories # 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 output_dir = local_path("WebHostLib", "static", "generated") # TODO: move to user_path
os.makedirs(os.path.join(output_dir, "sprites"), exist_ok=True) os.makedirs(os.path.join(output_dir, "sprites"), exist_ok=True)

View File

@@ -53,10 +53,6 @@ Name: "full"; Description: "Full installation"
Name: "minimal"; Description: "Minimal installation" Name: "minimal"; Description: "Minimal installation"
Name: "custom"; Description: "Custom installation"; Flags: iscustom 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] [Dirs]
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify;
@@ -76,7 +72,6 @@ Name: "{commondesktop}\{#MyAppName} Launcher"; Filename: "{app}\ArchipelagoLaunc
[Run] [Run]
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." 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"; Parameters: "--update_settings"; StatusMsg: "Updating host.yaml..."; Flags: runasoriginaluser runhidden
Filename: "{app}\ArchipelagoLauncher"; Description: "{cm:LaunchProgram,{#StringChange('Launcher', '&', '&&')}}"; Flags: nowait postinstall skipifsilent Filename: "{app}\ArchipelagoLauncher"; Description: "{cm:LaunchProgram,{#StringChange('Launcher', '&', '&&')}}"; Flags: nowait postinstall skipifsilent

View File

@@ -199,9 +199,10 @@ extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else []
def remove_sprites_from_folder(folder: Path) -> None: def remove_sprites_from_folder(folder: Path) -> None:
for file in os.listdir(folder): if os.path.isdir(folder):
if file != ".gitignore": for file in os.listdir(folder):
os.remove(folder / file) if file != ".gitignore":
os.remove(folder / file)
def _threaded_hash(filepath: str | Path) -> str: 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)) 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" / "alttpr")
remove_sprites_from_folder(self.buildfolder / "data" / "sprites" / "alttp" / "remote")
self.create_manifest() self.create_manifest()

View File

@@ -515,7 +515,8 @@ def _populate_sprite_table():
logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.") logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.")
with concurrent.futures.ThreadPoolExecutor() as pool: 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 dir in [dir for dir in sprite_paths if os.path.isdir(dir)]:
for file in os.listdir(dir): for file in os.listdir(dir):
pool.submit(load_sprite_from_file, os.path.join(dir, file)) pool.submit(load_sprite_from_file, os.path.join(dir, file))