| 
									
										
										
										
											2018-01-01 13:11:11 -05:00
										 |  |  | #!/usr/bin/env python3 | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  | import argparse | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  | import os | 
					
						
							|  |  |  | import logging | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | import queue | 
					
						
							|  |  |  | import random | 
					
						
							|  |  |  | import shutil | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  | import textwrap | 
					
						
							|  |  |  | import sys | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | import threading | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | 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, \ | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     IntVar, Checkbutton, E, W, OptionMenu, Toplevel, BOTTOM, RIGHT, font as font, PhotoImage | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | from urllib.parse import urlparse | 
					
						
							|  |  |  | from urllib.request import urlopen | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 08:00:53 +02:00
										 |  |  | from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings, get_base_rom_bytes | 
					
						
							| 
									
										
										
										
											2021-12-17 19:17:41 +01:00
										 |  |  | from Utils import output_path, local_path, open_file, get_cert_none_ssl_context, persistent_store | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  | class AdjusterWorld(object): | 
					
						
							|  |  |  |     def __init__(self, sprite_pool): | 
					
						
							|  |  |  |         import random | 
					
						
							|  |  |  |         self.sprite_pool = {1: sprite_pool} | 
					
						
							| 
									
										
										
										
											2021-06-11 14:47:13 +02:00
										 |  |  |         self.slot_seeds = {1: random} | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  | class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _get_help_string(self, action): | 
					
						
							|  |  |  |         return textwrap.dedent(action.help) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | def main(): | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |     parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-24 13:45:28 +02:00
										 |  |  |     parser.add_argument('--rom', default='ER_base.sfc', help='Path to an ALttP rom to adjust.') | 
					
						
							|  |  |  |     parser.add_argument('--baserom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', | 
					
						
							|  |  |  |                         help='Path to an ALttP JAP(1.0) rom to use as a base.') | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     parser.add_argument('--loglevel', default='info', const='info', nargs='?', | 
					
						
							|  |  |  |                         choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |     parser.add_argument('--menuspeed', default='normal', const='normal', nargs='?', | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |                         choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'], | 
					
						
							| 
									
										
										
										
											2018-01-05 16:53:29 -06:00
										 |  |  |                         help='''\
 | 
					
						
							|  |  |  |                              Select the rate at which the menu opens and closes. | 
					
						
							|  |  |  |                              (default: %(default)s) | 
					
						
							|  |  |  |                              ''')
 | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |     parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     parser.add_argument('--deathlink', help='Enable DeathLink system.', action='store_true') | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |     parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     parser.add_argument('--triforcehud', default='hide_goal', const='hide_goal', nargs='?', | 
					
						
							|  |  |  |                         choices=['normal', 'hide_goal', 'hide_required', 'hide_both'], | 
					
						
							|  |  |  |                         help='''\
 | 
					
						
							| 
									
										
										
										
											2021-01-29 15:42:00 -06:00
										 |  |  |                             Hide the triforce hud in certain circumstances. | 
					
						
							| 
									
										
										
										
											2021-02-05 20:37:27 -06:00
										 |  |  |                             hide_goal will hide the hud until finding a triforce piece, hide_required will hide the total amount needed to win | 
					
						
							|  |  |  |                             (Both can be revealed when speaking to Murahalda) | 
					
						
							| 
									
										
										
										
											2021-01-29 15:42:00 -06:00
										 |  |  |                             (default: %(default)s) | 
					
						
							|  |  |  |                             ''')
 | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     parser.add_argument('--enableflashing', | 
					
						
							|  |  |  |                         help='Reenable flashing animations (unfriendly to epilepsy, always disabled in race roms)', | 
					
						
							|  |  |  |                         action='store_false', dest="reduceflashing") | 
					
						
							|  |  |  |     parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', | 
					
						
							|  |  |  |                         choices=['double', 'normal', 'half', 'quarter', 'off'], | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |                         help='''\
 | 
					
						
							|  |  |  |                              Select the rate at which the heart beep sound is played at | 
					
						
							|  |  |  |                              low health. (default: %(default)s) | 
					
						
							|  |  |  |                              ''')
 | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     parser.add_argument('--heartcolor', default='red', const='red', nargs='?', | 
					
						
							|  |  |  |                         choices=['red', 'blue', 'green', 'yellow', 'random'], | 
					
						
							| 
									
										
										
										
											2018-02-27 20:26:33 -06:00
										 |  |  |                         help='Select the color of Link\'s heart meter. (default: %(default)s)') | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     parser.add_argument('--ow_palettes', default='default', | 
					
						
							|  |  |  |                         choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy', | 
					
						
							|  |  |  |                                  'sick']) | 
					
						
							|  |  |  |     parser.add_argument('--link_palettes', default='default', | 
					
						
							|  |  |  |                         choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy', | 
					
						
							|  |  |  |                                  'sick']) | 
					
						
							|  |  |  |     parser.add_argument('--shield_palettes', default='default', | 
					
						
							|  |  |  |                         choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy', | 
					
						
							|  |  |  |                                  'sick']) | 
					
						
							|  |  |  |     parser.add_argument('--sword_palettes', default='default', | 
					
						
							|  |  |  |                         choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy', | 
					
						
							|  |  |  |                                  'sick']) | 
					
						
							|  |  |  |     parser.add_argument('--hud_palettes', default='default', | 
					
						
							|  |  |  |                         choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy', | 
					
						
							|  |  |  |                                  'sick']) | 
					
						
							|  |  |  |     parser.add_argument('--uw_palettes', default='default', | 
					
						
							|  |  |  |                         choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy', | 
					
						
							|  |  |  |                                  'sick']) | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |     parser.add_argument('--sprite', help='''\
 | 
					
						
							|  |  |  |                              Path to a sprite sheet to use for Link. Needs to be in | 
					
						
							|  |  |  |                              binary format and have a length of 0x7000 (28672) bytes, | 
					
						
							|  |  |  |                              or 0x7078 (28792) bytes including palette data. | 
					
						
							|  |  |  |                              Alternatively, can be a ALttP Rom patched with a Link | 
					
						
							|  |  |  |                              sprite that will be extracted. | 
					
						
							|  |  |  |                              ''')
 | 
					
						
							| 
									
										
										
										
											2019-12-09 19:27:56 +01:00
										 |  |  |     parser.add_argument('--names', default='', type=str) | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     parser.add_argument('--update_sprites', action='store_true', help='Update Sprite Database, then exit.') | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |     args = parser.parse_args() | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |     args.music = not args.disablemusic | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |     # set up logger | 
					
						
							| 
									
										
										
										
											2020-04-26 15:14:30 +02:00
										 |  |  |     loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[ | 
					
						
							|  |  |  |         args.loglevel] | 
					
						
							| 
									
										
										
										
											2017-12-12 08:17:52 -06:00
										 |  |  |     logging.basicConfig(format='%(message)s', level=loglevel) | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-13 20:24:54 +01:00
										 |  |  |     if args.update_sprites: | 
					
						
							|  |  |  |         run_sprite_update() | 
					
						
							|  |  |  |         sys.exit() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     if not os.path.isfile(args.rom): | 
					
						
							|  |  |  |         adjustGUI() | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         if args.sprite is not None and not os.path.isfile(args.sprite) and not Sprite.get_sprite_from_name(args.sprite): | 
					
						
							|  |  |  |             input('Could not find link sprite sheet at given location. \nPress Enter to exit.') | 
					
						
							|  |  |  |             sys.exit(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         args, path = adjust(args=args) | 
					
						
							|  |  |  |         if isinstance(args.sprite, Sprite): | 
					
						
							|  |  |  |             args.sprite = args.sprite.name | 
					
						
							|  |  |  |         persistent_store("adjuster", "last_settings_3", args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def adjust(args): | 
					
						
							|  |  |  |     start = time.perf_counter() | 
					
						
							|  |  |  |     logger = logging.getLogger('Adjuster') | 
					
						
							|  |  |  |     logger.info('Patching ROM.') | 
					
						
							|  |  |  |     vanillaRom = args.baserom | 
					
						
							| 
									
										
										
										
											2021-03-03 02:02:41 +01:00
										 |  |  |     if os.path.splitext(args.rom)[-1].lower() == '.apbp': | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |         import Patch | 
					
						
							|  |  |  |         meta, args.rom = Patch.create_rom_file(args.rom) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc': | 
					
						
							|  |  |  |         rom = LocalRom(args.rom, patch=False, vanillaRom=vanillaRom) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         raise RuntimeError( | 
					
						
							|  |  |  |             'Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.') | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     palettes_options = {} | 
					
						
							|  |  |  |     palettes_options['dungeon'] = args.uw_palettes | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     palettes_options['overworld'] = args.ow_palettes | 
					
						
							|  |  |  |     palettes_options['hud'] = args.hud_palettes | 
					
						
							|  |  |  |     palettes_options['sword'] = args.sword_palettes | 
					
						
							|  |  |  |     palettes_options['shield'] = args.shield_palettes | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     # palettes_options['link']=args.link_palettesvera | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     racerom = rom.read_byte(0x180213) > 0 | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  |     world = None | 
					
						
							|  |  |  |     if hasattr(args, "world"): | 
					
						
							|  |  |  |         world = getattr(args, "world") | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |     apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.menuspeed, args.music, | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |                        args.sprite, palettes_options, reduceflashing=args.reduceflashing or racerom, world=world, | 
					
						
							|  |  |  |                        deathlink=args.deathlink) | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc') | 
					
						
							|  |  |  |     rom.write_to_file(path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logger.info('Done. Enjoy.') | 
					
						
							|  |  |  |     logger.debug('Total Time: %s', time.perf_counter() - start) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return args, path | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  | def adjustGUI(): | 
					
						
							| 
									
										
										
										
											2021-05-24 12:48:18 +02:00
										 |  |  |     from tkinter import Tk, LEFT, BOTTOM, TOP, \ | 
					
						
							|  |  |  |         StringVar, Frame, Label, X, Entry, Button, filedialog, messagebox, ttk | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     from argparse import Namespace | 
					
						
							|  |  |  |     from Main import __version__ as MWVersion | 
					
						
							|  |  |  |     adjustWindow = Tk() | 
					
						
							| 
									
										
										
										
											2021-02-25 18:23:19 +01:00
										 |  |  |     adjustWindow.wm_title("Archipelago %s LttP Adjuster" % MWVersion) | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     set_icon(adjustWindow) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rom_options_frame, rom_vars, set_sprite = get_rom_options_frame(adjustWindow) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bottomFrame2 = Frame(adjustWindow) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     romFrame, romVar = get_rom_frame(adjustWindow) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     romDialogFrame = Frame(adjustWindow) | 
					
						
							|  |  |  |     baseRomLabel2 = Label(romDialogFrame, text='Rom to adjust') | 
					
						
							|  |  |  |     romVar2 = StringVar() | 
					
						
							|  |  |  |     romEntry2 = Entry(romDialogFrame, textvariable=romVar2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def RomSelect2(): | 
					
						
							| 
									
										
										
										
											2021-03-03 02:02:41 +01:00
										 |  |  |         rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".apbp")), ("All Files", "*")]) | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |         romVar2.set(rom) | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |     romSelectButton2 = Button(romDialogFrame, text='Select Rom', command=RomSelect2) | 
					
						
							|  |  |  |     romDialogFrame.pack(side=TOP, expand=True, fill=X) | 
					
						
							|  |  |  |     baseRomLabel2.pack(side=LEFT) | 
					
						
							|  |  |  |     romEntry2.pack(side=LEFT, expand=True, fill=X) | 
					
						
							|  |  |  |     romSelectButton2.pack(side=LEFT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def adjustRom(): | 
					
						
							|  |  |  |         guiargs = Namespace() | 
					
						
							|  |  |  |         guiargs.heartbeep = rom_vars.heartbeepVar.get() | 
					
						
							|  |  |  |         guiargs.heartcolor = rom_vars.heartcolorVar.get() | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |         guiargs.menuspeed = rom_vars.menuspeedVar.get() | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |         guiargs.ow_palettes = rom_vars.owPalettesVar.get() | 
					
						
							|  |  |  |         guiargs.uw_palettes = rom_vars.uwPalettesVar.get() | 
					
						
							|  |  |  |         guiargs.hud_palettes = rom_vars.hudPalettesVar.get() | 
					
						
							|  |  |  |         guiargs.sword_palettes = rom_vars.swordPalettesVar.get() | 
					
						
							|  |  |  |         guiargs.shield_palettes = rom_vars.shieldPalettesVar.get() | 
					
						
							|  |  |  |         guiargs.quickswap = bool(rom_vars.quickSwapVar.get()) | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |         guiargs.music = bool(rom_vars.MusicVar.get()) | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |         guiargs.reduceflashing = bool(rom_vars.disableFlashingVar.get()) | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |         guiargs.deathlink = bool(rom_vars.DeathLinkVar.get()) | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |         guiargs.rom = romVar2.get() | 
					
						
							|  |  |  |         guiargs.baserom = romVar.get() | 
					
						
							|  |  |  |         guiargs.sprite = rom_vars.sprite | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  |         if rom_vars.sprite_pool: | 
					
						
							|  |  |  |             guiargs.world = AdjusterWorld(rom_vars.sprite_pool) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |         try: | 
					
						
							|  |  |  |             guiargs, path = adjust(args=guiargs) | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  |             if rom_vars.sprite_pool: | 
					
						
							|  |  |  |                 guiargs.sprite_pool = rom_vars.sprite_pool | 
					
						
							|  |  |  |                 delattr(guiargs, "world") | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |         except Exception as e: | 
					
						
							|  |  |  |             logging.exception(e) | 
					
						
							|  |  |  |             messagebox.showerror(title="Error while adjusting Rom", message=str(e)) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2021-02-19 19:10:01 +01:00
										 |  |  |             messagebox.showinfo(title="Success", message=f"Rom patched successfully to {path}") | 
					
						
							| 
									
										
										
										
											2021-02-19 19:08:11 +01:00
										 |  |  |             if isinstance(guiargs.sprite, Sprite): | 
					
						
							|  |  |  |                 guiargs.sprite = guiargs.sprite.name | 
					
						
							|  |  |  |             persistent_store("adjuster", "last_settings_3", guiargs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom) | 
					
						
							|  |  |  |     rom_options_frame.pack(side=TOP) | 
					
						
							|  |  |  |     adjustButton.pack(side=BOTTOM, padx=(5, 5)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bottomFrame2.pack(side=BOTTOM, pady=(5, 5)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     adjustWindow.mainloop() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  | def run_sprite_update(): | 
					
						
							|  |  |  |     import threading | 
					
						
							|  |  |  |     done = threading.Event() | 
					
						
							| 
									
										
										
										
											2021-12-13 20:24:54 +01:00
										 |  |  |     try: | 
					
						
							|  |  |  |         top = Tk() | 
					
						
							|  |  |  |     except: | 
					
						
							|  |  |  |         task = BackgroundTaskProgressNullWindow(update_sprites, lambda successful, resultmessage: done.set()) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         top.withdraw() | 
					
						
							|  |  |  |         task = BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set()) | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  |     while not done.isSet(): | 
					
						
							| 
									
										
										
										
											2021-12-13 20:24:54 +01:00
										 |  |  |         task.do_events() | 
					
						
							|  |  |  |     logging.info("Done updating sprites") | 
					
						
							| 
									
										
										
										
											2021-04-21 23:53:59 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | def update_sprites(task, on_finish=None): | 
					
						
							|  |  |  |     resultmessage = "" | 
					
						
							|  |  |  |     successful = True | 
					
						
							|  |  |  |     sprite_dir = local_path("data", "sprites", "alttpr") | 
					
						
							|  |  |  |     os.makedirs(sprite_dir, exist_ok=True) | 
					
						
							| 
									
										
										
										
											2021-12-17 19:17:41 +01:00
										 |  |  |     ctx = get_cert_none_ssl_context() | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     def finished(): | 
					
						
							|  |  |  |         task.close_window() | 
					
						
							|  |  |  |         if on_finish: | 
					
						
							|  |  |  |             on_finish(successful, resultmessage) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         task.update_status("Downloading alttpr sprites list") | 
					
						
							| 
									
										
										
										
											2021-12-17 19:17:41 +01:00
										 |  |  |         with urlopen('https://alttpr.com/sprites', context=ctx) as response: | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |             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"] | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |         needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in alttpr_sprites if | 
					
						
							|  |  |  |                           filename not in current_sprites] | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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: | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |         resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % ( | 
					
						
							|  |  |  |         type(e).__name__, e) | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |         successful = False | 
					
						
							|  |  |  |         task.queue_event(finished) | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def dl(sprite_url, filename): | 
					
						
							|  |  |  |         target = os.path.join(sprite_dir, filename) | 
					
						
							| 
									
										
										
										
											2021-12-17 19:17:41 +01:00
										 |  |  |         with urlopen(sprite_url, context=ctx) as response, open(target, 'wb') as out: | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |             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" % ( | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |                     type(e).__name__, e) | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |                 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" % ( | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |                     type(e).__name__, e) | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |                 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: | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |                     # if self is no longer running self.window may no longer be valid | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |                     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)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-13 20:24:54 +01:00
										 |  |  |     def do_events(self): | 
					
						
							|  |  |  |         self.parent.update() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     # only call this in an event callback | 
					
						
							|  |  |  |     def close_window(self): | 
					
						
							|  |  |  |         self.stop() | 
					
						
							|  |  |  |         self.window.destroy() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-13 20:24:54 +01:00
										 |  |  | class BackgroundTaskProgressNullWindow(BackgroundTask): | 
					
						
							|  |  |  |     def __init__(self, code_to_run, *args): | 
					
						
							|  |  |  |         super().__init__(None, code_to_run, *args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def process_queue(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             while True: | 
					
						
							|  |  |  |                 if not self.running: | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 event = self.queue.get_nowait() | 
					
						
							|  |  |  |                 event() | 
					
						
							|  |  |  |         except queue.Empty: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def do_events(self): | 
					
						
							|  |  |  |         self.process_queue() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def update_status(self, text): | 
					
						
							|  |  |  |         self.queue_event(lambda: logging.info(text)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def close_window(self): | 
					
						
							|  |  |  |         self.stop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | 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", "*")]) | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2021-08-10 08:00:53 +02:00
										 |  |  |             get_base_rom_bytes(rom)  # throws error on checksum fail | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |         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" | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |     vars.MusicVar = IntVar() | 
					
						
							|  |  |  |     vars.MusicVar.set(1) | 
					
						
							|  |  |  |     MusicCheckbutton = Checkbutton(romOptionsFrame, text="Music", variable=vars.MusicVar) | 
					
						
							|  |  |  |     MusicCheckbutton.grid(row=0, column=0, sticky=E) | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     vars.disableFlashingVar = IntVar(value=1) | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     disableFlashingCheckbutton = Checkbutton(romOptionsFrame, text="Disable flashing (anti-epilepsy)", | 
					
						
							|  |  |  |                                              variable=vars.disableFlashingVar) | 
					
						
							|  |  |  |     disableFlashingCheckbutton.grid(row=6, column=0, sticky=W) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     vars.DeathLinkVar = IntVar(value=0) | 
					
						
							|  |  |  |     DeathLinkCheckbutton = Checkbutton(romOptionsFrame, text="DeathLink (Team Deaths)", variable=vars.DeathLinkVar) | 
					
						
							|  |  |  |     DeathLinkCheckbutton.grid(row=7, column=0, sticky=W) | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     spriteDialogFrame = Frame(romOptionsFrame) | 
					
						
							|  |  |  |     spriteDialogFrame.grid(row=0, column=1) | 
					
						
							|  |  |  |     baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     vars.spriteNameVar = StringVar() | 
					
						
							|  |  |  |     vars.sprite = None | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |     menuspeedFrame = Frame(romOptionsFrame) | 
					
						
							|  |  |  |     menuspeedFrame.grid(row=1, column=1, sticky=E) | 
					
						
							|  |  |  |     menuspeedLabel = Label(menuspeedFrame, text='Menu speed') | 
					
						
							|  |  |  |     menuspeedLabel.pack(side=LEFT) | 
					
						
							|  |  |  |     vars.menuspeedVar = StringVar() | 
					
						
							|  |  |  |     vars.menuspeedVar.set('normal') | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     menuspeedOptionMenu = OptionMenu(menuspeedFrame, vars.menuspeedVar, 'normal', 'instant', 'double', 'triple', | 
					
						
							|  |  |  |                                      'quadruple', 'half') | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |     menuspeedOptionMenu.pack(side=LEFT) | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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') | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     owPalettesOptionMenu = OptionMenu(owPalettesFrame, vars.owPalettesVar, 'default', 'good', 'blackout', 'grayscale', | 
					
						
							|  |  |  |                                       'negative', 'classic', 'dizzy', 'sick', 'puke') | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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') | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, vars.uwPalettesVar, 'default', 'good', 'blackout', 'grayscale', | 
					
						
							|  |  |  |                                       'negative', 'classic', 'dizzy', 'sick', 'puke') | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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') | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, vars.hudPalettesVar, 'default', 'good', 'blackout', | 
					
						
							|  |  |  |                                        'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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') | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, vars.swordPalettesVar, 'default', 'good', 'blackout', | 
					
						
							|  |  |  |                                          'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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') | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, vars.shieldPalettesVar, 'default', 'good', 'blackout', | 
					
						
							|  |  |  |                                           'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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 = [] | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |     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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |         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.') | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |         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)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |             button = Checkbutton(frame, text="Random", command=self.update_random_button, | 
					
						
							|  |  |  |                                  variable=self.randomOnRandomVar) | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |             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 | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  |         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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |         gif_img_minimum_code_size = bytes( | 
					
						
							|  |  |  |             [7])  # we choose 7 bits, so that each pixel is represented by a byte, for conviennce. | 
					
						
							| 
									
										
										
										
											2021-07-21 09:18:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2021-11-08 16:34:54 +01:00
										 |  |  |     main() |