mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
remove Gui.py, GuiUtils.py, ER_hint_reference.txt and icon.ico from root directory
This commit is contained in:
877
LttPAdjuster.py
877
LttPAdjuster.py
@@ -1,17 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import queue
|
||||
import random
|
||||
import shutil
|
||||
import textwrap
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from tkinter import Tk
|
||||
|
||||
from Gui import update_sprites
|
||||
from GuiUtils import BackgroundTaskProgress
|
||||
import tkinter as tk
|
||||
from argparse import Namespace
|
||||
from concurrent.futures import as_completed, ThreadPoolExecutor
|
||||
from glob import glob
|
||||
from tkinter import Tk, Frame, Label, StringVar, Entry, filedialog, messagebox, Button, LEFT, X, TOP, LabelFrame, \
|
||||
IntVar, Checkbutton, E, OptionMenu, Toplevel, BOTTOM, RIGHT, font as font, PhotoImage
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings
|
||||
from Utils import output_path
|
||||
from Utils import output_path, local_path, open_file
|
||||
|
||||
|
||||
class AdjusterWorld(object):
|
||||
@@ -155,8 +164,6 @@ def adjust(args):
|
||||
def adjustGUI():
|
||||
from tkinter import Tk, LEFT, BOTTOM, TOP, \
|
||||
StringVar, Frame, Label, X, Entry, Button, filedialog, messagebox, ttk
|
||||
from Gui import get_rom_options_frame, get_rom_frame
|
||||
from GuiUtils import set_icon
|
||||
from argparse import Namespace
|
||||
from Main import __version__ as MWVersion
|
||||
adjustWindow = Tk()
|
||||
@@ -239,5 +246,859 @@ def run_sprite_update():
|
||||
print("Done updating sprites")
|
||||
|
||||
|
||||
def update_sprites(task, on_finish=None):
|
||||
resultmessage = ""
|
||||
successful = True
|
||||
sprite_dir = local_path("data", "sprites", "alttpr")
|
||||
os.makedirs(sprite_dir, exist_ok=True)
|
||||
|
||||
def finished():
|
||||
task.close_window()
|
||||
if on_finish:
|
||||
on_finish(successful, resultmessage)
|
||||
|
||||
try:
|
||||
task.update_status("Downloading alttpr sprites list")
|
||||
with urlopen('https://alttpr.com/sprites') 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)
|
||||
successful = False
|
||||
task.queue_event(finished)
|
||||
return
|
||||
|
||||
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))
|
||||
for sprite in sprites_arr if sprite["author"] != "Nintendo"]
|
||||
needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in alttpr_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]
|
||||
except Exception as e:
|
||||
resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
task.queue_event(finished)
|
||||
return
|
||||
|
||||
|
||||
def dl(sprite_url, filename):
|
||||
target = os.path.join(sprite_dir, filename)
|
||||
with urlopen(sprite_url) as response, open(target, 'wb') as out:
|
||||
shutil.copyfileobj(response, out)
|
||||
|
||||
def rem(sprite):
|
||||
os.remove(os.path.join(sprite_dir, sprite))
|
||||
|
||||
|
||||
with ThreadPoolExecutor() as pool:
|
||||
dl_tasks = []
|
||||
rem_tasks = []
|
||||
|
||||
for (sprite_url, filename) in needed_sprites:
|
||||
dl_tasks.append(pool.submit(dl, sprite_url, filename))
|
||||
|
||||
for sprite in obsolete_sprites:
|
||||
rem_tasks.append(pool.submit(rem, sprite))
|
||||
|
||||
deleted = 0
|
||||
updated = 0
|
||||
|
||||
for dl_task in as_completed(dl_tasks):
|
||||
updated += 1
|
||||
task.update_status("Downloading needed sprite %g/%g" % (updated, len(needed_sprites)))
|
||||
try:
|
||||
dl_task.result()
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % (
|
||||
type(e).__name__, e)
|
||||
successful = False
|
||||
|
||||
for rem_task in as_completed(rem_tasks):
|
||||
deleted += 1
|
||||
task.update_status("Removing obsolete sprite %g/%g" % (deleted, len(obsolete_sprites)))
|
||||
try:
|
||||
rem_task.result()
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % (
|
||||
type(e).__name__, e)
|
||||
successful = False
|
||||
|
||||
if successful:
|
||||
resultmessage = "alttpr sprites updated successfully"
|
||||
|
||||
task.queue_event(finished)
|
||||
|
||||
|
||||
def set_icon(window):
|
||||
logo = tk.PhotoImage(file=local_path('data', 'icon.png'))
|
||||
window.tk.call('wm', 'iconphoto', window._w, logo)
|
||||
|
||||
|
||||
class BackgroundTask(object):
|
||||
def __init__(self, window, code_to_run, *args):
|
||||
self.window = window
|
||||
self.queue = queue.Queue()
|
||||
self.running = True
|
||||
self.process_queue()
|
||||
self.task = threading.Thread(target=code_to_run, args=(self, *args))
|
||||
self.task.start()
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
# safe to call from worker
|
||||
def queue_event(self, event):
|
||||
self.queue.put(event)
|
||||
|
||||
def process_queue(self):
|
||||
try:
|
||||
while True:
|
||||
if not self.running:
|
||||
return
|
||||
event = self.queue.get_nowait()
|
||||
event()
|
||||
if self.running:
|
||||
#if self is no longer running self.window may no longer be valid
|
||||
self.window.update_idletasks()
|
||||
except queue.Empty:
|
||||
pass
|
||||
if self.running:
|
||||
self.window.after(100, self.process_queue)
|
||||
|
||||
|
||||
class BackgroundTaskProgress(BackgroundTask):
|
||||
def __init__(self, parent, code_to_run, title, *args):
|
||||
self.parent = parent
|
||||
self.window = tk.Toplevel(parent)
|
||||
self.window['padx'] = 5
|
||||
self.window['pady'] = 5
|
||||
|
||||
try:
|
||||
self.window.attributes("-toolwindow", 1)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
self.window.wm_title(title)
|
||||
self.label_var = tk.StringVar()
|
||||
self.label_var.set("")
|
||||
self.label = tk.Label(self.window, textvariable=self.label_var, width=50)
|
||||
self.label.pack()
|
||||
self.window.resizable(width=False, height=False)
|
||||
|
||||
set_icon(self.window)
|
||||
self.window.focus()
|
||||
super().__init__(self.window, code_to_run, *args)
|
||||
|
||||
# safe to call from worker thread
|
||||
def update_status(self, text):
|
||||
self.queue_event(lambda: self.label_var.set(text))
|
||||
|
||||
# only call this in an event callback
|
||||
def close_window(self):
|
||||
self.stop()
|
||||
self.window.destroy()
|
||||
|
||||
|
||||
def get_rom_frame(parent=None):
|
||||
romFrame = Frame(parent)
|
||||
baseRomLabel = Label(romFrame, text='LttP Base Rom: ')
|
||||
romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc")
|
||||
romEntry = Entry(romFrame, textvariable=romVar)
|
||||
|
||||
def RomSelect():
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
|
||||
import Patch
|
||||
try:
|
||||
Patch.get_base_rom_bytes(rom) # throws error on checksum fail
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
messagebox.showerror(title="Error while reading ROM", message=str(e))
|
||||
else:
|
||||
romVar.set(rom)
|
||||
romSelectButton['state'] = "disabled"
|
||||
romSelectButton["text"] = "ROM verified"
|
||||
romSelectButton = Button(romFrame, text='Select Rom', command=RomSelect)
|
||||
|
||||
baseRomLabel.pack(side=LEFT)
|
||||
romEntry.pack(side=LEFT, expand=True, fill=X)
|
||||
romSelectButton.pack(side=LEFT)
|
||||
romFrame.pack(side=TOP, expand=True, fill=X)
|
||||
|
||||
return romFrame, romVar
|
||||
|
||||
|
||||
def get_rom_options_frame(parent=None):
|
||||
romOptionsFrame = LabelFrame(parent, text="Rom options")
|
||||
romOptionsFrame.columnconfigure(0, weight=1)
|
||||
romOptionsFrame.columnconfigure(1, weight=1)
|
||||
for i in range(5):
|
||||
romOptionsFrame.rowconfigure(i, weight=1)
|
||||
vars = Namespace()
|
||||
|
||||
vars.disableMusicVar = IntVar()
|
||||
disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=vars.disableMusicVar)
|
||||
disableMusicCheckbutton.grid(row=0, column=0, sticky=E)
|
||||
|
||||
vars.disableFlashingVar = IntVar(value=1)
|
||||
disableFlashingCheckbutton = Checkbutton(romOptionsFrame, text="Disable flashing (anti-epilepsy)", variable=vars.disableFlashingVar)
|
||||
disableFlashingCheckbutton.grid(row=6, column=0, sticky=E)
|
||||
|
||||
spriteDialogFrame = Frame(romOptionsFrame)
|
||||
spriteDialogFrame.grid(row=0, column=1)
|
||||
baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:')
|
||||
|
||||
|
||||
|
||||
vars.spriteNameVar = StringVar()
|
||||
vars.sprite = None
|
||||
def set_sprite(sprite_param):
|
||||
nonlocal vars
|
||||
if isinstance(sprite_param, str):
|
||||
vars.sprite = sprite_param
|
||||
vars.spriteNameVar.set(sprite_param)
|
||||
elif sprite_param is None or not sprite_param.valid:
|
||||
vars.sprite = None
|
||||
vars.spriteNameVar.set('(unchanged)')
|
||||
else:
|
||||
vars.sprite = sprite_param
|
||||
vars.spriteNameVar.set(vars.sprite.name)
|
||||
|
||||
set_sprite(None)
|
||||
vars.spriteNameVar.set('(unchanged)')
|
||||
spriteEntry = Label(spriteDialogFrame, textvariable=vars.spriteNameVar)
|
||||
|
||||
def SpriteSelect():
|
||||
nonlocal vars
|
||||
SpriteSelector(parent, set_sprite, spritePool=vars.sprite_pool)
|
||||
|
||||
spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect)
|
||||
|
||||
baseSpriteLabel.pack(side=LEFT)
|
||||
spriteEntry.pack(side=LEFT)
|
||||
spriteSelectButton.pack(side=LEFT)
|
||||
|
||||
vars.quickSwapVar = IntVar(value=1)
|
||||
quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=vars.quickSwapVar)
|
||||
quickSwapCheckbutton.grid(row=1, column=0, sticky=E)
|
||||
|
||||
fastMenuFrame = Frame(romOptionsFrame)
|
||||
fastMenuFrame.grid(row=1, column=1, sticky=E)
|
||||
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
|
||||
fastMenuLabel.pack(side=LEFT)
|
||||
vars.fastMenuVar = StringVar()
|
||||
vars.fastMenuVar.set('normal')
|
||||
fastMenuOptionMenu = OptionMenu(fastMenuFrame, vars.fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
||||
fastMenuOptionMenu.pack(side=LEFT)
|
||||
|
||||
heartcolorFrame = Frame(romOptionsFrame)
|
||||
heartcolorFrame.grid(row=2, column=0, sticky=E)
|
||||
heartcolorLabel = Label(heartcolorFrame, text='Heart color')
|
||||
heartcolorLabel.pack(side=LEFT)
|
||||
vars.heartcolorVar = StringVar()
|
||||
vars.heartcolorVar.set('red')
|
||||
heartcolorOptionMenu = OptionMenu(heartcolorFrame, vars.heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
|
||||
heartcolorOptionMenu.pack(side=LEFT)
|
||||
|
||||
heartbeepFrame = Frame(romOptionsFrame)
|
||||
heartbeepFrame.grid(row=2, column=1, sticky=E)
|
||||
heartbeepLabel = Label(heartbeepFrame, text='Heartbeep')
|
||||
heartbeepLabel.pack(side=LEFT)
|
||||
vars.heartbeepVar = StringVar()
|
||||
vars.heartbeepVar.set('normal')
|
||||
heartbeepOptionMenu = OptionMenu(heartbeepFrame, vars.heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
|
||||
heartbeepOptionMenu.pack(side=LEFT)
|
||||
|
||||
owPalettesFrame = Frame(romOptionsFrame)
|
||||
owPalettesFrame.grid(row=3, column=0, sticky=E)
|
||||
owPalettesLabel = Label(owPalettesFrame, text='Overworld palettes')
|
||||
owPalettesLabel.pack(side=LEFT)
|
||||
vars.owPalettesVar = StringVar()
|
||||
vars.owPalettesVar.set('default')
|
||||
owPalettesOptionMenu = OptionMenu(owPalettesFrame, vars.owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||
owPalettesOptionMenu.pack(side=LEFT)
|
||||
|
||||
uwPalettesFrame = Frame(romOptionsFrame)
|
||||
uwPalettesFrame.grid(row=3, column=1, sticky=E)
|
||||
uwPalettesLabel = Label(uwPalettesFrame, text='Dungeon palettes')
|
||||
uwPalettesLabel.pack(side=LEFT)
|
||||
vars.uwPalettesVar = StringVar()
|
||||
vars.uwPalettesVar.set('default')
|
||||
uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, vars.uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||
uwPalettesOptionMenu.pack(side=LEFT)
|
||||
|
||||
hudPalettesFrame = Frame(romOptionsFrame)
|
||||
hudPalettesFrame.grid(row=4, column=0, sticky=E)
|
||||
hudPalettesLabel = Label(hudPalettesFrame, text='HUD palettes')
|
||||
hudPalettesLabel.pack(side=LEFT)
|
||||
vars.hudPalettesVar = StringVar()
|
||||
vars.hudPalettesVar.set('default')
|
||||
hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, vars.hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||
hudPalettesOptionMenu.pack(side=LEFT)
|
||||
|
||||
swordPalettesFrame = Frame(romOptionsFrame)
|
||||
swordPalettesFrame.grid(row=4, column=1, sticky=E)
|
||||
swordPalettesLabel = Label(swordPalettesFrame, text='Sword palettes')
|
||||
swordPalettesLabel.pack(side=LEFT)
|
||||
vars.swordPalettesVar = StringVar()
|
||||
vars.swordPalettesVar.set('default')
|
||||
swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, vars.swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||
swordPalettesOptionMenu.pack(side=LEFT)
|
||||
|
||||
shieldPalettesFrame = Frame(romOptionsFrame)
|
||||
shieldPalettesFrame.grid(row=5, column=0, sticky=E)
|
||||
shieldPalettesLabel = Label(shieldPalettesFrame, text='Shield palettes')
|
||||
shieldPalettesLabel.pack(side=LEFT)
|
||||
vars.shieldPalettesVar = StringVar()
|
||||
vars.shieldPalettesVar.set('default')
|
||||
shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, vars.shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||
shieldPalettesOptionMenu.pack(side=LEFT)
|
||||
|
||||
spritePoolFrame = Frame(romOptionsFrame)
|
||||
spritePoolFrame.grid(row=5, column=1)
|
||||
baseSpritePoolLabel = Label(spritePoolFrame, text='Sprite Pool:')
|
||||
|
||||
vars.spritePoolCountVar = StringVar()
|
||||
vars.sprite_pool = []
|
||||
def set_sprite_pool(sprite_param):
|
||||
nonlocal vars
|
||||
operation = "add"
|
||||
if isinstance(sprite_param, tuple):
|
||||
operation, sprite_param = sprite_param
|
||||
if isinstance(sprite_param, Sprite) and sprite_param.valid:
|
||||
sprite_param = sprite_param.name
|
||||
if isinstance(sprite_param, str):
|
||||
if operation == "add":
|
||||
vars.sprite_pool.append(sprite_param)
|
||||
elif operation == "remove":
|
||||
vars.sprite_pool.remove(sprite_param)
|
||||
elif operation == "clear":
|
||||
vars.sprite_pool.clear()
|
||||
vars.spritePoolCountVar.set(str(len(vars.sprite_pool)))
|
||||
|
||||
set_sprite_pool(None)
|
||||
vars.spritePoolCountVar.set('0')
|
||||
spritePoolEntry = Label(spritePoolFrame, textvariable=vars.spritePoolCountVar)
|
||||
|
||||
def SpritePoolSelect():
|
||||
nonlocal vars
|
||||
SpriteSelector(parent, set_sprite_pool, randomOnEvent=False, spritePool=vars.sprite_pool)
|
||||
|
||||
def SpritePoolClear():
|
||||
nonlocal vars
|
||||
vars.sprite_pool.clear()
|
||||
vars.spritePoolCountVar.set('0')
|
||||
|
||||
spritePoolSelectButton = Button(spritePoolFrame, text='...', command=SpritePoolSelect)
|
||||
spritePoolClearButton = Button(spritePoolFrame, text='Clear', command=SpritePoolClear)
|
||||
|
||||
baseSpritePoolLabel.pack(side=LEFT)
|
||||
spritePoolEntry.pack(side=LEFT)
|
||||
spritePoolSelectButton.pack(side=LEFT)
|
||||
spritePoolClearButton.pack(side=LEFT)
|
||||
|
||||
return romOptionsFrame, vars, set_sprite
|
||||
|
||||
|
||||
class SpriteSelector():
|
||||
def __init__(self, parent, callback, adjuster=False, randomOnEvent=True, spritePool=None):
|
||||
self.deploy_icons()
|
||||
self.parent = parent
|
||||
self.window = Toplevel(parent)
|
||||
self.callback = callback
|
||||
self.adjuster = adjuster
|
||||
self.randomOnEvent = randomOnEvent
|
||||
self.spritePoolButtons = None
|
||||
|
||||
self.window.wm_title("TAKE ANY ONE YOU WANT")
|
||||
self.window['padx'] = 5
|
||||
self.window['pady'] = 5
|
||||
self.spritesPerRow = 32
|
||||
self.all_sprites = []
|
||||
self.sprite_pool = spritePool
|
||||
|
||||
def open_custom_sprite_dir(_evt):
|
||||
open_file(self.custom_sprite_dir)
|
||||
|
||||
alttpr_frametitle = Label(self.window, text='ALTTPR Sprites')
|
||||
|
||||
custom_frametitle = Frame(self.window)
|
||||
title_text = Label(custom_frametitle, text="Custom Sprites")
|
||||
title_link = Label(custom_frametitle, text="(open)", fg="blue", cursor="hand2")
|
||||
title_text.pack(side=LEFT)
|
||||
title_link.pack(side=LEFT)
|
||||
title_link.bind("<Button-1>", 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(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:
|
||||
self.sprite_pool_section(spritePool)
|
||||
|
||||
frame = Frame(self.window)
|
||||
frame.pack(side=BOTTOM, fill=X, pady=5)
|
||||
|
||||
if self.randomOnEvent:
|
||||
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.pack(side=RIGHT, padx=(5, 0))
|
||||
|
||||
button = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
self.randomButtonText = StringVar()
|
||||
button = Button(frame, textvariable=self.randomButtonText, command=self.use_random_sprite)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
self.randomButtonText.set("Random")
|
||||
|
||||
self.randomOnEventText = StringVar()
|
||||
self.randomOnHitVar = IntVar()
|
||||
self.randomOnEnterVar = IntVar()
|
||||
self.randomOnExitVar = IntVar()
|
||||
self.randomOnSlashVar = IntVar()
|
||||
self.randomOnItemVar = IntVar()
|
||||
self.randomOnBonkVar = IntVar()
|
||||
self.randomOnRandomVar = IntVar()
|
||||
|
||||
if self.randomOnEvent:
|
||||
button = Checkbutton(frame, text="Hit", command=self.update_random_button, variable=self.randomOnHitVar)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
button = Checkbutton(frame, text="Enter", command=self.update_random_button, variable=self.randomOnEnterVar)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
button = Checkbutton(frame, text="Exit", command=self.update_random_button, variable=self.randomOnExitVar)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
button = Checkbutton(frame, text="Slash", command=self.update_random_button, variable=self.randomOnSlashVar)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
button = Checkbutton(frame, text="Item", command=self.update_random_button, variable=self.randomOnItemVar)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
button = Checkbutton(frame, text="Bonk", command=self.update_random_button, variable=self.randomOnBonkVar)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
button = Checkbutton(frame, text="Random", command=self.update_random_button, variable=self.randomOnRandomVar)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
if adjuster:
|
||||
button = Button(frame, text="Current sprite from rom", command=self.use_default_sprite)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
set_icon(self.window)
|
||||
self.window.focus()
|
||||
|
||||
def remove_from_sprite_pool(self, button, spritename):
|
||||
self.callback(("remove", spritename))
|
||||
self.spritePoolButtons.buttons.remove(button)
|
||||
button.destroy()
|
||||
|
||||
def add_to_sprite_pool(self, spritename):
|
||||
if isinstance(spritename, str):
|
||||
if spritename == "random":
|
||||
button = Button(self.spritePoolButtons, text="?")
|
||||
button['font'] = font.Font(size=19)
|
||||
button.configure(command=lambda spr="random": self.remove_from_sprite_pool(button, spr))
|
||||
ToolTips.register(button, "Random")
|
||||
self.spritePoolButtons.buttons.append(button)
|
||||
else:
|
||||
spritename = Sprite.get_sprite_from_name(spritename)
|
||||
if isinstance(spritename, Sprite) and spritename.valid:
|
||||
image = get_image_for_sprite(spritename)
|
||||
if image is None:
|
||||
return
|
||||
button = Button(self.spritePoolButtons, image=image)
|
||||
button.configure(command=lambda spr=spritename: self.remove_from_sprite_pool(button, spr.name))
|
||||
ToolTips.register(button, spritename.name +
|
||||
f"\nBy: {spritename.author_name if spritename.author_name else ''}")
|
||||
button.image = image
|
||||
|
||||
self.spritePoolButtons.buttons.append(button)
|
||||
self.grid_fill_sprites(self.spritePoolButtons)
|
||||
|
||||
def sprite_pool_section(self, spritePool):
|
||||
def clear_sprite_pool(_evt):
|
||||
self.callback(("clear", "Clear"))
|
||||
for button in self.spritePoolButtons.buttons:
|
||||
button.destroy()
|
||||
self.spritePoolButtons.buttons.clear()
|
||||
|
||||
frametitle = Frame(self.window)
|
||||
title_text = Label(frametitle, text="Sprite Pool")
|
||||
title_link = Label(frametitle, text="(clear)", fg="blue", cursor="hand2")
|
||||
title_text.pack(side=LEFT)
|
||||
title_link.pack(side=LEFT)
|
||||
title_link.bind("<Button-1>", clear_sprite_pool)
|
||||
|
||||
self.spritePoolButtons = LabelFrame(self.window, labelwidget=frametitle, padx=5, pady=5)
|
||||
self.spritePoolButtons.pack(side=TOP, fill=X)
|
||||
self.spritePoolButtons.buttons = []
|
||||
|
||||
def update_sprites(event):
|
||||
self.spritesPerRow = (event.width - 10) // 38
|
||||
self.grid_fill_sprites(self.spritePoolButtons)
|
||||
|
||||
self.grid_fill_sprites(self.spritePoolButtons)
|
||||
self.spritePoolButtons.bind("<Configure>", update_sprites)
|
||||
|
||||
if spritePool:
|
||||
for sprite in spritePool:
|
||||
self.add_to_sprite_pool(sprite)
|
||||
|
||||
def icon_section(self, frame_label, path, no_results_label):
|
||||
frame = LabelFrame(self.window, labelwidget=frame_label, padx=5, pady=5)
|
||||
frame.pack(side=TOP, fill=X)
|
||||
|
||||
sprites = []
|
||||
|
||||
for file in os.listdir(path):
|
||||
sprites.append((file, Sprite(os.path.join(path, file))))
|
||||
|
||||
sprites.sort(key=lambda s: str.lower(s[1].name or "").strip())
|
||||
|
||||
frame.buttons = []
|
||||
for file, sprite in sprites:
|
||||
image = get_image_for_sprite(sprite)
|
||||
if image is None:
|
||||
continue
|
||||
self.all_sprites.append(sprite)
|
||||
button = Button(frame, image=image, command=lambda spr=sprite: self.select_sprite(spr))
|
||||
ToolTips.register(button, sprite.name +
|
||||
("\nBy: %s" % sprite.author_name if sprite.author_name else "") +
|
||||
f"\nFrom: {file}")
|
||||
button.image = image
|
||||
frame.buttons.append(button)
|
||||
|
||||
if not frame.buttons:
|
||||
label = Label(frame, text=no_results_label)
|
||||
label.pack()
|
||||
|
||||
def update_sprites(event):
|
||||
self.spritesPerRow = (event.width - 10) // 38
|
||||
self.grid_fill_sprites(frame)
|
||||
|
||||
self.grid_fill_sprites(frame)
|
||||
|
||||
frame.bind("<Configure>", update_sprites)
|
||||
|
||||
def grid_fill_sprites(self, frame):
|
||||
for i, button in enumerate(frame.buttons):
|
||||
button.grid(row=i // self.spritesPerRow, column=i % self.spritesPerRow)
|
||||
|
||||
def update_alttpr_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()
|
||||
|
||||
def on_finish(successful, resultmessage):
|
||||
if successful:
|
||||
messagebox.showinfo("Sprite Updater", resultmessage)
|
||||
else:
|
||||
logging.error(resultmessage)
|
||||
messagebox.showerror("Sprite Updater", resultmessage)
|
||||
SpriteSelector(self.parent, self.callback, self.adjuster)
|
||||
|
||||
BackgroundTaskProgress(self.parent, update_sprites, "Updating Sprites", on_finish)
|
||||
|
||||
|
||||
def browse_for_sprite(self):
|
||||
sprite = filedialog.askopenfilename(
|
||||
filetypes=[("All Sprite Sources", (".zspr", ".spr", ".sfc", ".smc")),
|
||||
("ZSprite files", ".zspr"),
|
||||
("Sprite files", ".spr"),
|
||||
("Rom Files", (".sfc", ".smc")),
|
||||
("All Files", "*")])
|
||||
try:
|
||||
self.callback(Sprite(sprite))
|
||||
except Exception:
|
||||
self.callback(None)
|
||||
self.window.destroy()
|
||||
|
||||
|
||||
def use_default_sprite(self):
|
||||
self.callback(None)
|
||||
self.window.destroy()
|
||||
|
||||
def use_default_link_sprite(self):
|
||||
if self.randomOnEvent:
|
||||
self.callback(Sprite.default_link_sprite())
|
||||
self.window.destroy()
|
||||
else:
|
||||
self.callback("link")
|
||||
self.add_to_sprite_pool("link")
|
||||
|
||||
def update_random_button(self):
|
||||
if self.randomOnRandomVar.get():
|
||||
randomon = "random"
|
||||
else:
|
||||
randomon = "-hit" if self.randomOnHitVar.get() else ""
|
||||
randomon += "-enter" if self.randomOnEnterVar.get() else ""
|
||||
randomon += "-exit" if self.randomOnExitVar.get() else ""
|
||||
randomon += "-slash" if self.randomOnSlashVar.get() else ""
|
||||
randomon += "-item" if self.randomOnItemVar.get() else ""
|
||||
randomon += "-bonk" if self.randomOnBonkVar.get() else ""
|
||||
|
||||
self.randomOnEventText.set(f"randomon{randomon}" if randomon else None)
|
||||
self.randomButtonText.set("Random On Event" if randomon else "Random")
|
||||
|
||||
def use_random_sprite(self):
|
||||
if not self.randomOnEvent:
|
||||
self.callback("random")
|
||||
self.add_to_sprite_pool("random")
|
||||
return
|
||||
elif self.randomOnEventText.get():
|
||||
self.callback(self.randomOnEventText.get())
|
||||
elif self.sprite_pool:
|
||||
self.callback(random.choice(self.sprite_pool))
|
||||
elif self.all_sprites:
|
||||
self.callback(random.choice(self.all_sprites))
|
||||
else:
|
||||
self.callback(None)
|
||||
self.window.destroy()
|
||||
|
||||
def select_sprite(self, spritename):
|
||||
self.callback(spritename)
|
||||
if self.randomOnEvent:
|
||||
self.window.destroy()
|
||||
else:
|
||||
self.add_to_sprite_pool(spritename)
|
||||
|
||||
def deploy_icons(self):
|
||||
if not os.path.exists(self.custom_sprite_dir):
|
||||
os.makedirs(self.custom_sprite_dir)
|
||||
|
||||
@property
|
||||
def alttpr_sprite_dir(self):
|
||||
return local_path("data", "sprites", "alttpr")
|
||||
|
||||
@property
|
||||
def custom_sprite_dir(self):
|
||||
return local_path("data", "sprites", "custom")
|
||||
|
||||
|
||||
def get_image_for_sprite(sprite, gif_only: bool = False):
|
||||
if not sprite.valid:
|
||||
return None
|
||||
height = 24
|
||||
width = 16
|
||||
|
||||
def draw_sprite_into_gif(add_palette_color, set_pixel_color_index):
|
||||
|
||||
def drawsprite(spr, pal_as_colors, offset):
|
||||
for y, row in enumerate(spr):
|
||||
for x, pal_index in enumerate(row):
|
||||
if pal_index:
|
||||
color = pal_as_colors[pal_index - 1]
|
||||
set_pixel_color_index(x + offset[0], y + offset[1], color)
|
||||
|
||||
add_palette_color(16, (40, 40, 40))
|
||||
shadow = [
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
]
|
||||
|
||||
drawsprite(shadow, [16], (2, 17))
|
||||
|
||||
palettes = sprite.decode_palette()
|
||||
for i in range(15):
|
||||
add_palette_color(i + 1, palettes[0][i])
|
||||
|
||||
body = sprite.decode16(0x4C0)
|
||||
drawsprite(body, list(range(1, 16)), (0, 8))
|
||||
head = sprite.decode16(0x40)
|
||||
drawsprite(head, list(range(1, 16)), (0, 0))
|
||||
|
||||
def make_gif(callback):
|
||||
gif_header = b'GIF89a'
|
||||
|
||||
gif_lsd = bytearray(7)
|
||||
gif_lsd[0] = width
|
||||
gif_lsd[2] = height
|
||||
gif_lsd[4] = 0xF4 # 32 color palette follows. transparant + 15 for sprite + 1 for shadow=17 which rounds up to 32 as nearest power of 2
|
||||
gif_lsd[5] = 0 # background color is zero
|
||||
gif_lsd[6] = 0 # aspect raio not specified
|
||||
gif_gct = bytearray(3 * 32)
|
||||
|
||||
gif_gce = bytearray(8)
|
||||
gif_gce[0] = 0x21 # start of extention blocked
|
||||
gif_gce[1] = 0xF9 # identifies this as the Graphics Control extension
|
||||
gif_gce[2] = 4 # we are suppling only the 4 four bytes
|
||||
gif_gce[3] = 0x01 # this gif includes transparency
|
||||
gif_gce[4] = gif_gce[5] = 0 # animation frrame delay (unused)
|
||||
gif_gce[6] = 0 # transparent color is index 0
|
||||
gif_gce[7] = 0 # end of gif_gce
|
||||
gif_id = bytearray(10)
|
||||
gif_id[0] = 0x2c
|
||||
# byte 1,2 are image left. 3,4 are image top both are left as zerosuitsamus
|
||||
gif_id[5] = width
|
||||
gif_id[7] = height
|
||||
gif_id[9] = 0 # no local color table
|
||||
|
||||
gif_img_minimum_code_size = bytes([7]) # we choose 7 bits, so that each pixel is represented by a byte, for conviennce.
|
||||
|
||||
clear = 0x80
|
||||
stop = 0x81
|
||||
|
||||
unchunked_image_data = bytearray(height * (width + 1) + 1)
|
||||
# we technically need a Clear code once every 125 bytes, but we do it at the start of every row for simplicity
|
||||
for row in range(height):
|
||||
unchunked_image_data[row * (width + 1)] = clear
|
||||
unchunked_image_data[-1] = stop
|
||||
|
||||
def add_palette_color(index, color):
|
||||
gif_gct[3 * index] = color[0]
|
||||
gif_gct[3 * index + 1] = color[1]
|
||||
gif_gct[3 * index + 2] = color[2]
|
||||
|
||||
def set_pixel_color_index(x, y, color):
|
||||
unchunked_image_data[y * (width + 1) + x + 1] = color
|
||||
|
||||
callback(add_palette_color, set_pixel_color_index)
|
||||
|
||||
def chunk_image(img):
|
||||
for i in range(0, len(img), 255):
|
||||
chunk = img[i:i + 255]
|
||||
yield bytes([len(chunk)])
|
||||
yield chunk
|
||||
|
||||
gif_img = b''.join([gif_img_minimum_code_size] + list(chunk_image(unchunked_image_data)) + [b'\x00'])
|
||||
|
||||
gif = b''.join([gif_header, gif_lsd, gif_gct, gif_gce, gif_id, gif_img, b'\x3b'])
|
||||
|
||||
return gif
|
||||
|
||||
gif_data = make_gif(draw_sprite_into_gif)
|
||||
if gif_only:
|
||||
return gif_data
|
||||
|
||||
image = PhotoImage(data=gif_data)
|
||||
|
||||
return image.zoom(2)
|
||||
|
||||
|
||||
class ToolTips(object):
|
||||
# This class derived from wckToolTips which is available under the following license:
|
||||
|
||||
# Copyright (c) 1998-2007 by Secret Labs AB
|
||||
# Copyright (c) 1998-2007 by Fredrik Lundh
|
||||
#
|
||||
# By obtaining, using, and/or copying this software and/or its
|
||||
# associated documentation, you agree that you have read, understood,
|
||||
# and will comply with the following terms and conditions:
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# associated documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appears in all
|
||||
# copies, and that both that copyright notice and this permission notice
|
||||
# appear in supporting documentation, and that the name of Secret Labs
|
||||
# AB or the author not be used in advertising or publicity pertaining to
|
||||
# distribution of the software without specific, written prior
|
||||
# permission.
|
||||
#
|
||||
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
# FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
label = None
|
||||
window = None
|
||||
active = 0
|
||||
tag = None
|
||||
after_id = None
|
||||
|
||||
@classmethod
|
||||
def getcontroller(cls, widget):
|
||||
if cls.tag is None:
|
||||
|
||||
cls.tag = "ui_tooltip_%d" % id(cls)
|
||||
widget.bind_class(cls.tag, "<Enter>", cls.enter)
|
||||
widget.bind_class(cls.tag, "<Leave>", cls.leave)
|
||||
widget.bind_class(cls.tag, "<Motion>", cls.motion)
|
||||
widget.bind_class(cls.tag, "<Destroy>", cls.leave)
|
||||
|
||||
# pick suitable colors for tooltips
|
||||
try:
|
||||
cls.bg = "systeminfobackground"
|
||||
cls.fg = "systeminfotext"
|
||||
widget.winfo_rgb(cls.fg) # make sure system colors exist
|
||||
widget.winfo_rgb(cls.bg)
|
||||
except Exception:
|
||||
cls.bg = "#ffffe0"
|
||||
cls.fg = "black"
|
||||
|
||||
return cls.tag
|
||||
|
||||
@classmethod
|
||||
def register(cls, widget, text):
|
||||
widget.ui_tooltip_text = text
|
||||
tags = list(widget.bindtags())
|
||||
tags.append(cls.getcontroller(widget))
|
||||
widget.bindtags(tuple(tags))
|
||||
|
||||
@classmethod
|
||||
def unregister(cls, widget):
|
||||
tags = list(widget.bindtags())
|
||||
tags.remove(cls.getcontroller(widget))
|
||||
widget.bindtags(tuple(tags))
|
||||
|
||||
# event handlers
|
||||
@classmethod
|
||||
def enter(cls, event):
|
||||
widget = event.widget
|
||||
if not cls.label:
|
||||
# create and hide balloon help window
|
||||
cls.popup = tk.Toplevel(bg=cls.fg, bd=1)
|
||||
cls.popup.overrideredirect(1)
|
||||
cls.popup.withdraw()
|
||||
cls.label = tk.Label(
|
||||
cls.popup, fg=cls.fg, bg=cls.bg, bd=0, padx=2, justify=tk.LEFT
|
||||
)
|
||||
cls.label.pack()
|
||||
cls.active = 0
|
||||
cls.xy = event.x_root + 16, event.y_root + 10
|
||||
cls.event_xy = event.x, event.y
|
||||
cls.after_id = widget.after(200, cls.display, widget)
|
||||
|
||||
@classmethod
|
||||
def motion(cls, event):
|
||||
cls.xy = event.x_root + 16, event.y_root + 10
|
||||
cls.event_xy = event.x, event.y
|
||||
|
||||
@classmethod
|
||||
def display(cls, widget):
|
||||
if not cls.active:
|
||||
# display balloon help window
|
||||
text = widget.ui_tooltip_text
|
||||
if callable(text):
|
||||
text = text(widget, cls.event_xy)
|
||||
cls.label.config(text=text)
|
||||
cls.popup.deiconify()
|
||||
cls.popup.lift()
|
||||
cls.popup.geometry("+%d+%d" % cls.xy)
|
||||
cls.active = 1
|
||||
cls.after_id = None
|
||||
|
||||
@classmethod
|
||||
def leave(cls, event):
|
||||
widget = event.widget
|
||||
if cls.active:
|
||||
cls.popup.withdraw()
|
||||
cls.active = 0
|
||||
if cls.after_id:
|
||||
widget.after_cancel(cls.after_id)
|
||||
cls.after_id = None
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
Reference in New Issue
Block a user