diff --git a/Adjuster.py b/Adjuster.py index aebd2eaa..5d3b1057 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -4,9 +4,11 @@ import os import logging import textwrap import sys +import time from AdjusterMain import adjust -from worlds.alttp.Rom import Sprite +from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings +from Utils import output_path class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -27,6 +29,7 @@ def main(): ''') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') + 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'], help='''\ Select the rate at which the heart beep sound is played at @@ -50,25 +53,127 @@ def main(): parser.add_argument('--names', default='', type=str) args = parser.parse_args() - # ToDo: Validate files further than mere existance - if not os.path.isfile(args.rom): - input( - 'Could not find valid rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) - sys.exit(1) - 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) - # set up logger loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[ args.loglevel] logging.basicConfig(format='%(message)s', level=loglevel) - args, path = adjust(args=args) - from Utils import persistent_store - from Rom import Sprite - if isinstance(args.sprite, Sprite): - args.sprite = args.sprite.name - persistent_store("adjuster", "last_settings_3", args) + + 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) + from Utils import persistent_store + 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 + if os.path.splitext(args.rom)[-1].lower() == '.bmbp': + 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.') + palettes_options={} + palettes_options['dungeon']=args.uw_palettes + + palettes_options['overworld']=args.ow_palettes + palettes_options['hud']=args.hud_palettes + palettes_options['sword']=args.sword_palettes + palettes_options['shield']=args.shield_palettes + # palettes_options['link']=args.link_palettesvera + racerom = rom.read_byte(0x180213) > 0 + + apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, + args.sprite, palettes_options, reduceflashing=args.reduceflashing or racerom) + 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 + +def adjustGUI(): + from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, \ + StringVar, IntVar, Frame, Label, W, E, X, BOTH, Entry, Spinbox, Button, filedialog, messagebox, ttk + from Gui import get_rom_options_frame, get_rom_frame + from GuiUtils import set_icon + from argparse import Namespace + from Main import __version__ as MWVersion + adjustWindow = Tk() + adjustWindow.wm_title("Berserker's Multiworld %s LttP Adjuster" % MWVersion) + 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(): + rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".bmbp")), ("All Files", "*")]) + romVar2.set(rom) + 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() + guiargs.fastmenu = rom_vars.fastMenuVar.get() + 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()) + guiargs.disablemusic = bool(rom_vars.disableMusicVar.get()) + guiargs.reduceflashing = bool(rom_vars.disableFlashingVar.get()) + guiargs.rom = romVar2.get() + guiargs.baserom = romVar.get() + guiargs.sprite = rom_vars.sprite + try: + guiargs, path = adjust(args=guiargs) + except Exception as e: + logging.exception(e) + messagebox.showerror(title="Error while adjusting Rom", message=str(e)) + else: + messagebox.showinfo(title="Success", message=f"Rom patched successfully to {path}") + from Utils import persistent_store + from Rom import Sprite + 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() + if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/BaseClasses.py b/BaseClasses.py index dc005488..4f8caf31 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -4,6 +4,7 @@ import copy from enum import Enum, unique import logging import json +import functools from collections import OrderedDict, Counter, deque from typing import * import secrets @@ -25,6 +26,7 @@ class MultiWorld(): plando_texts: List[Dict[str, str]] plando_items: List[PlandoItem] plando_connections: List[PlandoConnection] + er_seeds: Dict[int, str] def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, item_functionality, timer, progressive, @@ -157,6 +159,10 @@ class MultiWorld(): region.world = self self._region_cache[region.player][region.name] = region + @functools.cached_property + def world_name_lookup(self): + return {self.player_names[player_id][0]: player_id for player_id in self.player_ids} + def _recache(self): """Rebuild world cache""" for region in self.regions: @@ -1285,7 +1291,8 @@ class Spoiler(object): 'shop_shuffle_slots': self.world.shop_shuffle_slots, 'shuffle_prizes': self.world.shuffle_prizes, 'sprite_pool': self.world.sprite_pool, - 'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss + 'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss, + 'er_seeds': self.world.er_seeds } def to_json(self): @@ -1349,6 +1356,8 @@ class Spoiler(object): outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) outfile.write('Item Progression: %s\n' % self.metadata['progressive'][player]) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) + if self.metadata['shuffle'][player] != "vanilla": + outfile.write('Entrance Shuffle Seed %s\n' % self.metadata['er_seeds'][player]) outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player]) outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player]) outfile.write('Pyramid hole pre-opened: %s\n' % ( diff --git a/Fill.py b/Fill.py index aadb64ff..b090f123 100644 --- a/Fill.py +++ b/Fill.py @@ -359,7 +359,7 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked= def distribute_planned(world): - world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids} + world_name_lookup = world.world_name_lookup for player in world.player_ids: placement: PlandoItem diff --git a/Gui.py b/Gui.py index 36def0a4..e2494d58 100755 --- a/Gui.py +++ b/Gui.py @@ -19,7 +19,7 @@ from worlds.alttp.EntranceRandomizer import parse_arguments from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress from worlds.alttp.Main import main, get_seed, __version__ as MWVersion from worlds.alttp.Rom import Sprite -from Utils import is_bundled, local_path, output_path, open_file +from Utils import local_path, output_path, open_file def guiMain(args=None): @@ -30,10 +30,8 @@ def guiMain(args=None): notebook = ttk.Notebook(mainWindow) randomizerWindow = ttk.Frame(notebook) - adjustWindow = ttk.Frame(notebook) customWindow = ttk.Frame(notebook) notebook.add(randomizerWindow, text='Randomize') - notebook.add(adjustWindow, text='Adjust') notebook.add(customWindow, text='Custom Items') notebook.pack() @@ -57,6 +55,8 @@ def guiMain(args=None): # randomizer controls topFrame = Frame(randomizerWindow) + romFrame, romVar = get_rom_frame(topFrame) + rightHalfFrame = Frame(topFrame) checkBoxFrame = Frame(rightHalfFrame) @@ -119,154 +119,9 @@ def guiMain(args=None): hintsCheckbutton.pack(expand=True, anchor=W) tileShuffleButton.pack(expand=True, anchor=W) - - romOptionsFrame = LabelFrame(rightHalfFrame, text="Rom options") - romOptionsFrame.columnconfigure(0, weight=1) - romOptionsFrame.columnconfigure(1, weight=1) - for i in range(5): - romOptionsFrame.rowconfigure(i, weight=1) - - disableMusicVar = IntVar() - disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=disableMusicVar) - disableMusicCheckbutton.grid(row=0, column=0, sticky=E) - - spriteDialogFrame = Frame(romOptionsFrame) - spriteDialogFrame.grid(row=0, column=1) - baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:') - - spriteNameVar = StringVar() - sprite = None - def set_sprite(sprite_param): - nonlocal sprite - if isinstance(sprite_param, str): - sprite = sprite_param - spriteNameVar.set(sprite_param) - elif sprite_param is None or not sprite_param.valid: - sprite = None - spriteNameVar.set('(unchanged)') - else: - sprite = sprite_param - spriteNameVar.set(sprite.name) - - set_sprite(None) - spriteNameVar.set('(unchanged)') - spriteEntry = Label(spriteDialogFrame, textvariable=spriteNameVar) - - def SpriteSelect(): - SpriteSelector(mainWindow, set_sprite) - - spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect) - - baseSpriteLabel.pack(side=LEFT) - spriteEntry.pack(side=LEFT) - spriteSelectButton.pack(side=LEFT) - - quickSwapVar = IntVar(value=1) - quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=quickSwapVar) - quickSwapCheckbutton.grid(row=1, column=0, sticky=E) - - fastMenuFrame = Frame(romOptionsFrame) - fastMenuFrame.grid(row=1, column=1, sticky=E) - fastMenuLabel = Label(fastMenuFrame, text='Menu speed') - fastMenuLabel.pack(side=LEFT) - fastMenuVar = StringVar() - fastMenuVar.set('normal') - fastMenuOptionMenu = OptionMenu(fastMenuFrame, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half') - fastMenuOptionMenu.pack(side=LEFT) - - heartcolorFrame = Frame(romOptionsFrame) - heartcolorFrame.grid(row=2, column=0, sticky=E) - heartcolorLabel = Label(heartcolorFrame, text='Heart color') - heartcolorLabel.pack(side=LEFT) - heartcolorVar = StringVar() - heartcolorVar.set('red') - heartcolorOptionMenu = OptionMenu(heartcolorFrame, 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) - heartbeepVar = StringVar() - heartbeepVar.set('normal') - heartbeepOptionMenu = OptionMenu(heartbeepFrame, 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) - owPalettesVar = StringVar() - owPalettesVar.set('default') - owPalettesOptionMenu = OptionMenu(owPalettesFrame, owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - owPalettesOptionMenu.pack(side=LEFT) - - uwPalettesFrame = Frame(romOptionsFrame) - uwPalettesFrame.grid(row=3, column=1, sticky=E) - uwPalettesLabel = Label(uwPalettesFrame, text='Dungeon palettes') - uwPalettesLabel.pack(side=LEFT) - uwPalettesVar = StringVar() - uwPalettesVar.set('default') - uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - uwPalettesOptionMenu.pack(side=LEFT) - - hudPalettesFrame = Frame(romOptionsFrame) - hudPalettesFrame.grid(row=4, column=0, sticky=E) - hudPalettesLabel = Label(hudPalettesFrame, text='HUD palettes') - hudPalettesLabel.pack(side=LEFT) - hudPalettesVar = StringVar() - hudPalettesVar.set('default') - hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - hudPalettesOptionMenu.pack(side=LEFT) - - swordPalettesFrame = Frame(romOptionsFrame) - swordPalettesFrame.grid(row=4, column=1, sticky=E) - swordPalettesLabel = Label(swordPalettesFrame, text='Sword palettes') - swordPalettesLabel.pack(side=LEFT) - swordPalettesVar = StringVar() - swordPalettesVar.set('default') - swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - swordPalettesOptionMenu.pack(side=LEFT) - - shieldPalettesFrame = Frame(romOptionsFrame) - shieldPalettesFrame.grid(row=5, column=0, sticky=E) - shieldPalettesLabel = Label(shieldPalettesFrame, text='Shield palettes') - shieldPalettesLabel.pack(side=LEFT) - shieldPalettesVar = StringVar() - shieldPalettesVar.set('default') - shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - shieldPalettesOptionMenu.pack(side=LEFT) - - - - - romDialogFrame = Frame(romOptionsFrame) - romDialogFrame.grid(row=6, column=0, columnspan=2, sticky=W+E) - - baseRomLabel = Label(romDialogFrame, text='Base Rom: ') - romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc") - romEntry = Entry(romDialogFrame, textvariable=romVar) - - def RomSelect(): - rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")]) - import Patch - try: - Patch.get_base_rom_bytes(rom) # throws error on checksum fail - except Exception as e: - logging.exception(e) - messagebox.showerror(title="Error while reading ROM", message=str(e)) - else: - romVar.set(rom) - romSelectButton['state'] = "disabled" - romSelectButton["text"] = "ROM verified" - romSelectButton = Button(romDialogFrame, text='Select Rom', command=RomSelect) - - baseRomLabel.pack(side=LEFT) - romEntry.pack(side=LEFT, expand=True, fill=X) - romSelectButton.pack(side=LEFT) - checkBoxFrame.pack(side=TOP, anchor=W, padx=5, pady=10) + romOptionsFrame, rom_vars, set_sprite = get_rom_options_frame(rightHalfFrame) romOptionsFrame.pack(expand=True, fill=BOTH, padx=3) drowDownFrame = Frame(topFrame) @@ -382,7 +237,8 @@ def guiMain(args=None): shuffleVar.set('vanilla') shuffleOptionMenu = OptionMenu(shuffleFrame, shuffleVar, 'vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', - 'dungeonsfull', 'dungeonssimple') + 'dungeonsfull', 'dungeonssimple', "same simple", "same restricted", "same full", + "same crossed", "same insanity", "same dungeonsfull", "same dungeonssimple") shuffleOptionMenu.pack(side=RIGHT) shuffleLabel = Label(shuffleFrame, text='Entrance shuffle') shuffleLabel.pack(side=LEFT) @@ -563,9 +419,12 @@ def guiMain(args=None): guiargs.accessibility = accessibilityVar.get() guiargs.algorithm = algorithmVar.get() guiargs.shuffle = shuffleVar.get() - guiargs.heartbeep = heartbeepVar.get() - guiargs.heartcolor = heartcolorVar.get() - guiargs.fastmenu = fastMenuVar.get() + if "same " in guiargs.shuffle: + guiargs.shuffle = guiargs.shuffle[5:] + "-" + str(seedVar.get() if seedVar.get() else + random.randint(0, 2**64)) + guiargs.heartbeep = rom_vars.heartbeepVar.get() + guiargs.heartcolor = rom_vars.heartcolorVar.get() + guiargs.fastmenu = rom_vars.fastMenuVar.get() guiargs.create_spoiler = bool(createSpoilerVar.get()) guiargs.skip_playthrough = not bool(createSpoilerVar.get()) guiargs.suppress_rom = bool(suppressRomVar.get()) @@ -575,13 +434,14 @@ def guiMain(args=None): guiargs.keyshuffle = {"on": True, "universal": "universal", "off": False}[keyshuffleVar.get()] guiargs.bigkeyshuffle = bool(bigkeyshuffleVar.get()) guiargs.retro = bool(retroVar.get()) - guiargs.quickswap = bool(quickSwapVar.get()) - guiargs.disablemusic = bool(disableMusicVar.get()) - guiargs.ow_palettes = owPalettesVar.get() - guiargs.uw_palettes = uwPalettesVar.get() - guiargs.hud_palettes = hudPalettesVar.get() - guiargs.sword_palettes = swordPalettesVar.get() - guiargs.shield_palettes = shieldPalettesVar.get() + guiargs.quickswap = bool(rom_vars.quickSwapVar.get()) + guiargs.disablemusic = bool(rom_vars.disableMusicVar.get()) + guiargs.reduceflashing = bool(rom_vars.disableFlashingVar.get()) + 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.shuffleganon = bool(shuffleGanonVar.get()) guiargs.hints = bool(hintsVar.get()) guiargs.enemizercli = enemizerCLIpathVar.get() @@ -641,7 +501,7 @@ def guiMain(args=None): int(rupoorcostVar.get()), int(triforceVar.get())] guiargs.rom = romVar.get() guiargs.create_diff = patchesVar.get() - guiargs.sprite = sprite + guiargs.sprite = rom_vars.sprite # get default values for missing parameters for k,v in vars(parse_arguments(['--multi', str(guiargs.multi)])).items(): if k not in vars(guiargs): @@ -689,150 +549,6 @@ def guiMain(args=None): enemizerFrame.pack(side=BOTTOM, fill=BOTH) shopframe.pack(side=BOTTOM, expand=True, fill=X) - # Adjuster Controls - - topFrame2 = Frame(adjustWindow) - rightHalfFrame2 = Frame(topFrame2) - checkBoxFrame2 = Frame(rightHalfFrame2) - - quickSwapCheckbutton2 = Checkbutton(checkBoxFrame2, text="L/R Item quickswapping", variable=quickSwapVar) - disableMusicCheckbutton2 = Checkbutton(checkBoxFrame2, text="Disable game music", variable=disableMusicVar) - - quickSwapCheckbutton2.pack(expand=True, anchor=W) - disableMusicCheckbutton2.pack(expand=True, anchor=W) - - fileDialogFrame2 = Frame(rightHalfFrame2) - - romDialogFrame2 = Frame(fileDialogFrame2) - baseRomLabel2 = Label(romDialogFrame2, text='Rom to adjust') - romVar2 = StringVar() - romEntry2 = Entry(romDialogFrame2, textvariable=romVar2) - - def RomSelect2(): - rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".apbp")), ("All Files", "*")]) - romVar2.set(rom) - romSelectButton2 = Button(romDialogFrame2, text='Select Rom', command=RomSelect2) - - baseRomLabel2.pack(side=LEFT) - romEntry2.pack(side=LEFT) - romSelectButton2.pack(side=LEFT) - - spriteDialogFrame2 = Frame(fileDialogFrame2) - baseSpriteLabel2 = Label(spriteDialogFrame2, text='Link Sprite') - spriteEntry2 = Label(spriteDialogFrame2, textvariable=spriteNameVar) - - def SpriteSelectAdjuster(): - SpriteSelector(mainWindow, set_sprite, adjuster=True) - - spriteSelectButton2 = Button(spriteDialogFrame2, text='Select Sprite', command=SpriteSelectAdjuster) - - baseSpriteLabel2.pack(side=LEFT) - spriteEntry2.pack(side=LEFT) - spriteSelectButton2.pack(side=LEFT) - - romDialogFrame2.pack() - spriteDialogFrame2.pack() - - checkBoxFrame2.pack() - fileDialogFrame2.pack() - - drowDownFrame2 = Frame(topFrame2) - heartbeepFrame2 = Frame(drowDownFrame2) - heartbeepOptionMenu2 = OptionMenu(heartbeepFrame2, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off') - heartbeepOptionMenu2.pack(side=RIGHT) - heartbeepLabel2 = Label(heartbeepFrame2, text='Heartbeep sound rate') - heartbeepLabel2.pack(side=LEFT) - - heartcolorFrame2 = Frame(drowDownFrame2) - heartcolorOptionMenu2 = OptionMenu(heartcolorFrame2, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random') - heartcolorOptionMenu2.pack(side=RIGHT) - heartcolorLabel2 = Label(heartcolorFrame2, text='Heart color') - heartcolorLabel2.pack(side=LEFT) - - fastMenuFrame2 = Frame(drowDownFrame2) - fastMenuOptionMenu2 = OptionMenu(fastMenuFrame2, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half') - fastMenuOptionMenu2.pack(side=RIGHT) - fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed') - fastMenuLabel2.pack(side=LEFT) - - owPalettesFrame2 = Frame(drowDownFrame2) - owPalettesOptionMenu2 = OptionMenu(owPalettesFrame2, owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - owPalettesOptionMenu2.pack(side=RIGHT) - owPalettesLabel2 = Label(owPalettesFrame2, text='Overworld palettes') - owPalettesLabel2.pack(side=LEFT) - - uwPalettesFrame2 = Frame(drowDownFrame2) - uwPalettesOptionMenu2 = OptionMenu(uwPalettesFrame2, uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - uwPalettesOptionMenu2.pack(side=RIGHT) - uwPalettesLabel2 = Label(uwPalettesFrame2, text='Dungeon palettes') - uwPalettesLabel2.pack(side=LEFT) - - hudPalettesFrame2 = Frame(drowDownFrame2) - hudPalettesOptionMenu2 = OptionMenu(hudPalettesFrame2, hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - hudPalettesOptionMenu2.pack(side=RIGHT) - hudPalettesLabel2 = Label(hudPalettesFrame2, text='HUD palettes') - hudPalettesLabel2.pack(side=LEFT) - - swordPalettesFrame2 = Frame(drowDownFrame2) - swordPalettesOptionMenu2 = OptionMenu(swordPalettesFrame2, swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - swordPalettesOptionMenu2.pack(side=RIGHT) - swordPalettesLabel2 = Label(swordPalettesFrame2, text='Sword palettes') - swordPalettesLabel2.pack(side=LEFT) - - shieldPalettesFrame2 = Frame(drowDownFrame2) - shieldPalettesOptionMenu2 = OptionMenu(shieldPalettesFrame2, shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') - shieldPalettesOptionMenu2.pack(side=RIGHT) - shieldPalettesLabel2 = Label(shieldPalettesFrame2, text='Shield palettes') - shieldPalettesLabel2.pack(side=LEFT) - - heartbeepFrame2.pack(expand=True, anchor=E) - heartcolorFrame2.pack(expand=True, anchor=E) - fastMenuFrame2.pack(expand=True, anchor=E) - owPalettesFrame2.pack(expand=True, anchor=E) - uwPalettesFrame2.pack(expand=True, anchor=E) - hudPalettesFrame2.pack(expand=True, anchor=E) - swordPalettesFrame2.pack(expand=True, anchor=E) - shieldPalettesFrame2.pack(expand=True, anchor=E) - - bottomFrame2 = Frame(topFrame2) - - def adjustRom(): - guiargs = Namespace() - guiargs.heartbeep = heartbeepVar.get() - guiargs.heartcolor = heartcolorVar.get() - guiargs.fastmenu = fastMenuVar.get() - guiargs.ow_palettes = owPalettesVar.get() - guiargs.uw_palettes = uwPalettesVar.get() - guiargs.hud_palettes = hudPalettesVar.get() - guiargs.sword_palettes = swordPalettesVar.get() - guiargs.shield_palettes = shieldPalettesVar.get() - guiargs.quickswap = bool(quickSwapVar.get()) - guiargs.disablemusic = bool(disableMusicVar.get()) - guiargs.rom = romVar2.get() - guiargs.baserom = romVar.get() - guiargs.sprite = sprite - try: - guiargs, path = adjust(args=guiargs) - except Exception as e: - logging.exception(e) - messagebox.showerror(title="Error while adjusting Rom", message=str(e)) - else: - messagebox.showinfo(title="Success", message="Rom patched successfully") - from Utils import persistent_store - from Rom import Sprite - 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) - - adjustButton.pack(side=LEFT, padx=(5, 0)) - - drowDownFrame2.pack(side=LEFT, pady=(0, 40)) - rightHalfFrame2.pack(side=RIGHT) - topFrame2.pack(side=TOP, pady=70) - bottomFrame2.pack(side=BOTTOM, pady=(180, 0)) - # Custom Controls topFrame3 = Frame(customWindow) @@ -1490,8 +1206,9 @@ def guiMain(args=None): keyshuffleVar.set(args.keyshuffle) bigkeyshuffleVar.set(args.bigkeyshuffle) retroVar.set(args.retro) - quickSwapVar.set(int(args.quickswap)) - disableMusicVar.set(int(args.disablemusic)) + rom_vars.quickSwapVar.set(int(args.quickswap)) + rom_vars.disableMusicVar.set(int(args.disablemusic)) + rom_vars.disableFlashingVar.set(int(args.reduceflashing)) if args.count: countVar.set(str(args.count)) if args.seed: @@ -1512,10 +1229,10 @@ def guiMain(args=None): crystalsGanonVar.set(args.crystals_ganon) algorithmVar.set(args.algorithm) shuffleVar.set(args.shuffle) - heartbeepVar.set(args.heartbeep) - fastMenuVar.set(args.fastmenu) + rom_vars.heartbeepVar.set(args.heartbeep) + rom_vars.fastMenuVar.set(args.fastmenu) logicVar.set(args.logic) - romVar.set(args.rom) + rom_vars.romVar.set(args.rom) shuffleGanonVar.set(args.shuffleganon) hintsVar.set(args.hints) if args.sprite is not None: @@ -1523,11 +1240,165 @@ def guiMain(args=None): mainWindow.mainloop() +def get_rom_frame(parent=None): + romFrame = Frame(parent) + baseRomLabel = Label(romFrame, text='LttP Base Rom: ') + romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc") + romEntry = Entry(romFrame, textvariable=romVar) + + def RomSelect(): + rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")]) + import Patch + try: + Patch.get_base_rom_bytes(rom) # throws error on checksum fail + except Exception as e: + logging.exception(e) + messagebox.showerror(title="Error while reading ROM", message=str(e)) + else: + romVar.set(rom) + romSelectButton['state'] = "disabled" + romSelectButton["text"] = "ROM verified" + romSelectButton = Button(romFrame, text='Select Rom', command=RomSelect) + + baseRomLabel.pack(side=LEFT) + romEntry.pack(side=LEFT, expand=True, fill=X) + romSelectButton.pack(side=LEFT) + romFrame.pack(side=TOP, expand=True, fill=X) + + return romFrame, romVar + + +def get_rom_options_frame(parent=None): + romOptionsFrame = LabelFrame(parent, text="Rom options") + romOptionsFrame.columnconfigure(0, weight=1) + romOptionsFrame.columnconfigure(1, weight=1) + for i in range(5): + romOptionsFrame.rowconfigure(i, weight=1) + vars = Namespace() + + vars.disableMusicVar = IntVar() + disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=vars.disableMusicVar) + disableMusicCheckbutton.grid(row=0, column=0, sticky=E) + + vars.disableFlashingVar = IntVar(value=1) + disableFlashingCheckbutton = Checkbutton(romOptionsFrame, text="Disable flashing (anti-epilepsy)", variable=vars.disableFlashingVar) + disableFlashingCheckbutton.grid(row=6, column=0, sticky=E) + + spriteDialogFrame = Frame(romOptionsFrame) + spriteDialogFrame.grid(row=0, column=1) + baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:') + + + + vars.spriteNameVar = StringVar() + vars.sprite = None + def set_sprite(sprite_param): + nonlocal vars + if isinstance(sprite_param, str): + vars.sprite = sprite_param + vars.spriteNameVar.set(sprite_param) + elif sprite_param is None or not sprite_param.valid: + vars.sprite = None + vars.spriteNameVar.set('(unchanged)') + else: + vars.sprite = sprite_param + vars.spriteNameVar.set(vars.sprite.name) + + set_sprite(None) + vars.spriteNameVar.set('(unchanged)') + spriteEntry = Label(spriteDialogFrame, textvariable=vars.spriteNameVar) + + def SpriteSelect(): + SpriteSelector(parent, set_sprite) + + spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect) + + baseSpriteLabel.pack(side=LEFT) + spriteEntry.pack(side=LEFT) + spriteSelectButton.pack(side=LEFT) + + vars.quickSwapVar = IntVar(value=1) + quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=vars.quickSwapVar) + quickSwapCheckbutton.grid(row=1, column=0, sticky=E) + + fastMenuFrame = Frame(romOptionsFrame) + fastMenuFrame.grid(row=1, column=1, sticky=E) + fastMenuLabel = Label(fastMenuFrame, text='Menu speed') + fastMenuLabel.pack(side=LEFT) + vars.fastMenuVar = StringVar() + vars.fastMenuVar.set('normal') + fastMenuOptionMenu = OptionMenu(fastMenuFrame, vars.fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half') + fastMenuOptionMenu.pack(side=LEFT) + + heartcolorFrame = Frame(romOptionsFrame) + heartcolorFrame.grid(row=2, column=0, sticky=E) + heartcolorLabel = Label(heartcolorFrame, text='Heart color') + heartcolorLabel.pack(side=LEFT) + vars.heartcolorVar = StringVar() + vars.heartcolorVar.set('red') + heartcolorOptionMenu = OptionMenu(heartcolorFrame, vars.heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random') + heartcolorOptionMenu.pack(side=LEFT) + + heartbeepFrame = Frame(romOptionsFrame) + heartbeepFrame.grid(row=2, column=1, sticky=E) + heartbeepLabel = Label(heartbeepFrame, text='Heartbeep') + heartbeepLabel.pack(side=LEFT) + vars.heartbeepVar = StringVar() + vars.heartbeepVar.set('normal') + heartbeepOptionMenu = OptionMenu(heartbeepFrame, vars.heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off') + heartbeepOptionMenu.pack(side=LEFT) + + owPalettesFrame = Frame(romOptionsFrame) + owPalettesFrame.grid(row=3, column=0, sticky=E) + owPalettesLabel = Label(owPalettesFrame, text='Overworld palettes') + owPalettesLabel.pack(side=LEFT) + vars.owPalettesVar = StringVar() + vars.owPalettesVar.set('default') + owPalettesOptionMenu = OptionMenu(owPalettesFrame, vars.owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') + owPalettesOptionMenu.pack(side=LEFT) + + uwPalettesFrame = Frame(romOptionsFrame) + uwPalettesFrame.grid(row=3, column=1, sticky=E) + uwPalettesLabel = Label(uwPalettesFrame, text='Dungeon palettes') + uwPalettesLabel.pack(side=LEFT) + vars.uwPalettesVar = StringVar() + vars.uwPalettesVar.set('default') + uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, vars.uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') + uwPalettesOptionMenu.pack(side=LEFT) + + hudPalettesFrame = Frame(romOptionsFrame) + hudPalettesFrame.grid(row=4, column=0, sticky=E) + hudPalettesLabel = Label(hudPalettesFrame, text='HUD palettes') + hudPalettesLabel.pack(side=LEFT) + vars.hudPalettesVar = StringVar() + vars.hudPalettesVar.set('default') + hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, vars.hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') + hudPalettesOptionMenu.pack(side=LEFT) + + swordPalettesFrame = Frame(romOptionsFrame) + swordPalettesFrame.grid(row=4, column=1, sticky=E) + swordPalettesLabel = Label(swordPalettesFrame, text='Sword palettes') + swordPalettesLabel.pack(side=LEFT) + vars.swordPalettesVar = StringVar() + vars.swordPalettesVar.set('default') + swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, vars.swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') + swordPalettesOptionMenu.pack(side=LEFT) + + shieldPalettesFrame = Frame(romOptionsFrame) + shieldPalettesFrame.grid(row=5, column=0, sticky=E) + shieldPalettesLabel = Label(shieldPalettesFrame, text='Shield palettes') + shieldPalettesLabel.pack(side=LEFT) + vars.shieldPalettesVar = StringVar() + vars.shieldPalettesVar.set('default') + shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, vars.shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') + shieldPalettesOptionMenu.pack(side=LEFT) + + return romOptionsFrame, vars, set_sprite + class SpriteSelector(): def __init__(self, parent, callback, adjuster=False): - if is_bundled(): - self.deploy_icons() + self.deploy_icons() self.parent = parent self.window = Toplevel(parent) self.callback = callback diff --git a/GuiUtils.py b/GuiUtils.py index c0542076..9e9e33e9 100644 --- a/GuiUtils.py +++ b/GuiUtils.py @@ -25,7 +25,7 @@ class BackgroundTask(object): def stop(self): self.running = False - #safe to call from worker + # safe to call from worker def queue_event(self, event): self.queue.put(event) diff --git a/MultiClient.py b/MultiClient.py index b774c359..b88e23fd 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -935,7 +935,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict] found = ReceivedItem(*args["item"]) ctx.ui_node.notify_item_found(ctx.player_names[found.player], get_item_name_from_id(found.item), get_location_name_from_address(found.location), found.player == ctx.slot, - True if get_item_name_from_id(found.item) in Items.progression_items else False) + get_item_name_from_id(found.item) in Items.progression_items) item = color_item(found.item, found.player == ctx.slot) player_sent = color(ctx.player_names[found.player], 'yellow' if found.player != ctx.slot else 'magenta') logging.info('%s found %s (%s)' % (player_sent, item, color(get_location_name_from_address(found.location), @@ -1066,7 +1066,7 @@ class ClientCommandProcessor(CommandProcessor): self.ctx.ui_node.notify_item_received(self.ctx.player_names[item.player], get_item_name_from_id(item.item), get_location_name_from_address(item.location), index, len(self.ctx.items_received), - True if get_item_name_from_id(item.item) in Items.progression_items else False) + get_item_name_from_id(item.item) in Items.progression_items) logging.info('%s from %s (%s) (%d/%d in list)' % ( color(get_item_name_from_id(item.item), 'red', 'bold'), color(self.ctx.player_names[item.player], 'yellow'), @@ -1327,7 +1327,7 @@ async def game_watcher(ctx: Context): ctx.ui_node.notify_item_received(ctx.player_names[item.player], get_item_name_from_id(item.item), get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received), - True if get_item_name_from_id(item.item) in Items.progression_items else False) + get_item_name_from_id(item.item) in Items.progression_items) logging.info('Received %s from %s (%s) (%d/%d in list)' % ( color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received))) diff --git a/Mystery.py b/Mystery.py index 8f37fe75..98bc6935 100644 --- a/Mystery.py +++ b/Mystery.py @@ -325,6 +325,7 @@ def roll_linked_options(weights: dict) -> dict: def roll_triggers(weights: dict) -> dict: weights = weights.copy() # make sure we don't write back to other weights sets in same_settings + weights["_Generator_Version"] = "Main" # Some means for triggers to know if the seed is on main or doors. for option_set in weights["triggers"]: try: key = get_choice("option_name", option_set) @@ -691,6 +692,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b ret.disablemusic = get_choice('disablemusic', romweights, False) ret.quickswap = get_choice('quickswap', romweights, True) ret.fastmenu = get_choice('menuspeed', romweights, "normal") + ret.reduceflashing = get_choice('reduceflashing', romweights, False) ret.heartcolor = get_choice('heartcolor', romweights, "red") ret.heartbeep = convert_to_on_off(get_choice('heartbeep', romweights, "normal")) ret.ow_palettes = get_choice('ow_palettes', romweights, "default") diff --git a/Utils.py b/Utils.py index 85d179f3..82c75136 100644 --- a/Utils.py +++ b/Utils.py @@ -341,8 +341,8 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]: f"Enter yes, no or never: ") if adjust_wanted and adjust_wanted.startswith("y"): adjusted = True - import AdjusterMain - _, romfile = AdjusterMain.adjust(adjuster_settings) + import Adjuster + _, romfile = Adjuster.adjust(adjuster_settings) elif adjust_wanted and "never" in adjust_wanted: persistent_store("adjuster", "never_adjust", True) return romfile, False diff --git a/WebHostLib/autolauncher.py b/WebHostLib/autolauncher.py index e58effb1..c8c7910e 100644 --- a/WebHostLib/autolauncher.py +++ b/WebHostLib/autolauncher.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging import multiprocessing from datetime import timedelta, datetime +import concurrent.futures import sys import typing import time @@ -135,6 +136,7 @@ def autohost(config: dict): multiworlds = {} +guardians = concurrent.futures.ThreadPoolExecutor(2, thread_name_prefix="Guardian") class MultiworldInstance(): def __init__(self, room: Room, config: dict): @@ -152,12 +154,18 @@ class MultiworldInstance(): args=(self.room_id, self.ponyconfig), name="MultiHost") self.process.start() + self.guardian = guardians.submit(self._collect) def stop(self): if self.process: self.process.terminate() self.process = None + def _collect(self): + self.process.join() # wait for process to finish + self.process = None + self.guardian = None + from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed from .customserver import run_server_process diff --git a/WebHostLib/static/assets/tracker.js b/WebHostLib/static/assets/tracker.js index 50cb518c..e88b2ee6 100644 --- a/WebHostLib/static/assets/tracker.js +++ b/WebHostLib/static/assets/tracker.js @@ -55,7 +55,7 @@ window.addEventListener('load', () => { window.addEventListener('resize', () => { adjustTableHeight(); - tables.draw() + tables.draw(); }); $(".table-wrapper").scrollsync({ diff --git a/WebHostLib/static/static/weightedSettings.json b/WebHostLib/static/static/weightedSettings.json index b6e1b7cb..cd0bbfbf 100644 --- a/WebHostLib/static/static/weightedSettings.json +++ b/WebHostLib/static/static/weightedSettings.json @@ -1602,6 +1602,26 @@ } } }, + "reduceflashing": { + "keyString": "rom.reduceflashing", + "friendlyName": "Full-Screen Flashing Effects", + "description": "Enable or disable full-screen flashing effects in game.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "rom.reduceflashing.on", + "friendlyName": "Disabled", + "description": "Disables flashing.", + "defaultValue": 50 + }, + "off": { + "keyString": "rom.reduceflashing.off", + "friendlyName": "Enabled", + "description": "Enables flashing.", + "defaultValue": 0 + } + } + }, "quickswap": { "keyString": "rom.quickswap", "friendlyName": "Item Quick-Swap", @@ -1797,7 +1817,7 @@ "defaultValue": 0 }, "puke": { - "keyString": "rom.ow_palettes.Puke", + "keyString": "rom.ow_palettes.puke", "friendlyName": "Puke", "description": "No logic at all.", "defaultValue": 0 @@ -1859,7 +1879,7 @@ "defaultValue": 0 }, "puke": { - "keyString": "rom.uw_palettes.Puke", + "keyString": "rom.uw_palettes.puke", "friendlyName": "Puke", "description": "No logic at all.", "defaultValue": 0 @@ -1921,7 +1941,7 @@ "defaultValue": 0 }, "puke": { - "keyString": "rom.hud_palettes.Puke", + "keyString": "rom.hud_palettes.puke", "friendlyName": "Puke", "description": "No logic at all.", "defaultValue": 0 @@ -1983,7 +2003,7 @@ "defaultValue": 0 }, "puke": { - "keyString": "rom.shield_palettes.Puke", + "keyString": "rom.shield_palettes.puke", "friendlyName": "Puke", "description": "No logic at all.", "defaultValue": 0 @@ -2045,7 +2065,7 @@ "defaultValue": 0 }, "puke": { - "keyString": "rom.sword_palettes.Puke", + "keyString": "rom.sword_palettes.puke", "friendlyName": "Puke", "description": "No logic at all.", "defaultValue": 0 diff --git a/WebHostLib/static/static/weightedSettings.yaml b/WebHostLib/static/static/weightedSettings.yaml index 6fe041c3..657c32f6 100644 --- a/WebHostLib/static/static/weightedSettings.yaml +++ b/WebHostLib/static/static/weightedSettings.yaml @@ -20,7 +20,7 @@ # For use with the weighted-settings page on the website. Changing this value will cause all users to be prompted # to update their settings. The version number should match the current released version number, and the revision # should be updated manually by whoever edits this file. -ws_version: 4.0.1 rev0 +ws_version: 4.0.1 rev1 description: Template Name # Used to describe your yaml. Useful if you have multiple files name: YourName # Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit @@ -363,6 +363,9 @@ rom: quickswap: # Enable switching items by pressing the L+R shoulder buttons on: 50 off: 0 + reduceflashing: # Reduces instances of flashing such as lightning attacks, weather, ether and more. + on: 50 + off: 0 menuspeed: # Controls how fast the item menu opens and closes normal: 50 instant: 0 diff --git a/WebHostLib/templates/tracker.html b/WebHostLib/templates/tracker.html index 3a528c16..1ba56337 100644 --- a/WebHostLib/templates/tracker.html +++ b/WebHostLib/templates/tracker.html @@ -1,7 +1,7 @@ {% extends 'tablepage.html' %} {% block head %} {{ super() }} - Multiworld Tracker for Room {{ room.id }} + Multiworld Tracker @@ -151,7 +151,7 @@ {% endfor %} {% for team, hints in hints.items() %}
- +
diff --git a/WebUI.py b/WebUI.py index c2e80a52..36770812 100644 --- a/WebUI.py +++ b/WebUI.py @@ -61,9 +61,9 @@ class WebUiClient(Node, logging.Handler): 'recipient': recipient, 'item': item, 'location': location, - 'iAmFinder': 1 if i_am_finder else 0, - 'iAmRecipient': 1 if i_am_recipient else 0, - 'itemIsUnique': 1 if item_is_unique else 0, + 'iAmFinder': int(i_am_finder), + 'iAmRecipient': int(i_am_recipient), + 'itemIsUnique': int(item_is_unique), })) def notify_item_found(self, finder: str, item: str, location: str, i_am_finder: bool, item_is_unique: bool = False): @@ -71,8 +71,8 @@ class WebUiClient(Node, logging.Handler): 'finder': finder, 'item': item, 'location': location, - 'iAmFinder': 1 if i_am_finder else 0, - 'itemIsUnique': 1 if item_is_unique else 0, + 'iAmFinder': int(i_am_finder), + 'itemIsUnique': int(item_is_unique), })) def notify_item_received(self, finder: str, item: str, location: str, item_index: int, queue_length: int, @@ -83,7 +83,7 @@ class WebUiClient(Node, logging.Handler): 'location': location, 'itemIndex': item_index, 'queueLength': queue_length, - 'itemIsUnique': 1 if item_is_unique else 0, + 'itemIsUnique': int(item_is_unique), })) def send_hint(self, finder, recipient, item, location, found, i_am_finder: bool, i_am_recipient: bool, diff --git a/playerSettings.yaml b/playerSettings.yaml index f911f766..0a524049 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -87,6 +87,9 @@ entrance_shuffle: # Documentation: https://alttpr.com/en/options#entrance_shuffl full: 0 # Less strict than restricted crossed: 0 # Less strict than full insanity: 0 # Very few grouping rules. Good luck + # you can also define entrance shuffle seed, like so: + crossed-1000: 0 # using this method, you can have the same layout as another player and share entrance information + # however, many other settings like logic, world state, retro etc. may affect the shuffle result as well. goals: ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon fast_ganon: 0 # Only killing Ganon is required. However, items may still be placed in GT @@ -401,6 +404,9 @@ rom: quickswap: # Enable switching items by pressing the L+R shoulder buttons on: 50 off: 0 + reduceflashing: # Reduces instances of flashing such as lightning attacks, weather, ether and more. + on: 50 + off: 0 menuspeed: # Controls how fast the item menu opens and closes normal: 50 instant: 0 diff --git a/requirements.txt b/requirements.txt index 1a479447..4b8f6ca5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ websockets>=8.1 PyYAML>=5.4.1 fuzzywuzzy>=0.18.0 bsdiff4>=1.2.0 -prompt_toolkit>=3.0.14 +prompt_toolkit>=3.0.16 appdirs>=1.4.4 maseya-z3pr>=1.0.0rc1 xxtea>=2.0.0.post0 diff --git a/setup.py b/setup.py index 99ec0d64..a8e40078 100644 --- a/setup.py +++ b/setup.py @@ -48,8 +48,8 @@ def manifest_creation(): path = os.path.join(dirpath, filename) hashes[os.path.relpath(path, start=buildfolder)] = pool.submit(_threaded_hash, path) import json - manifest = {"buildtime": buildtime.isoformat(sep=" ", timespec="seconds")} - manifest["hashes"] = {path: hash.result() for path, hash in hashes.items()} + manifest = {"buildtime": buildtime.isoformat(sep=" ", timespec="seconds"), + "hashes": {path: hash.result() for path, hash in hashes.items()}} json.dump(manifest, open(manifestpath, "wt"), indent=4) print("Created Manifest") @@ -58,7 +58,8 @@ scripts = {"MultiClient.py": "ArchipelagoClient", "MultiMystery.py": "ArchipelagoMultiMystery", "MultiServer.py": "ArchipelagoServer", "gui.py": "ArchipelagoCreator", - "Mystery.py": "ArchipelagoMystery"} + "Mystery.py": "ArchipelagoMystery", + "Adjuster.py": "ArchipelagoLttPAdjuster"} exes = [] diff --git a/worlds/alttp/AdjusterMain.py b/worlds/alttp/AdjusterMain.py index fd70fe22..e69de29b 100644 --- a/worlds/alttp/AdjusterMain.py +++ b/worlds/alttp/AdjusterMain.py @@ -1,40 +0,0 @@ -import os -import time -import logging - -from Utils import output_path -from worlds.alttp.Rom import LocalRom, apply_rom_settings - - -def adjust(args): - start = time.perf_counter() - logger = logging.getLogger('Adjuster') - logger.info('Patching ROM.') - vanillaRom = args.baserom - if os.path.splitext(args.rom)[-1].lower() == '.apbp': - 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.') - palettes_options={} - palettes_options['dungeon']=args.uw_palettes - - palettes_options['overworld']=args.ow_palettes - palettes_options['hud']=args.hud_palettes - palettes_options['sword']=args.sword_palettes - palettes_options['shield']=args.shield_palettes - # palettes_options['link']=args.link_palettesvera - - apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, - args.sprite, palettes_options) - 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 diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index af005d2b..b1eb7ba4 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -251,6 +251,7 @@ def parse_arguments(argv, no_defaults=False): ''') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') + parser.add_argument('--enableflashing', help='Reenable flashing animations (unfriendly to epilepsy, always disabled in race roms)', action='store_false', dest="reduceflashing") parser.add_argument('--mapshuffle', default=defval(False), help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true') @@ -377,6 +378,7 @@ def parse_arguments(argv, no_defaults=False): ret.plando_items = [] ret.plando_texts = {} ret.plando_connections = [] + ret.er_seeds = {} ret.glitch_boots = not ret.disable_glitch_boots if ret.timer == "none": @@ -407,10 +409,10 @@ def parse_arguments(argv, no_defaults=False): 'heartbeep', "skip_progression_balancing", "triforce_pieces_available", "triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots", "required_medallions", - "plando_items", "plando_texts", "plando_connections", + "plando_items", "plando_texts", "plando_connections", "er_seeds", 'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves', 'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', - 'restrict_dungeon_item_on_boss', + 'restrict_dungeon_item_on_boss', 'reduceflashing', 'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index c78a90ab..fa441bbf 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -2054,10 +2054,11 @@ def connect_doors(world, doors, targets, player): """This works inplace""" world.random.shuffle(doors) world.random.shuffle(targets) - while doors: - door = doors.pop() - target = targets.pop() + placing = min(len(doors), len(targets)) + for door, target in zip(doors, targets): connect_entrance(world, door, target, player) + doors[:] = doors[placing:] + targets[:] = targets[placing:] def skull_woods_shuffle(world, player): diff --git a/worlds/alttp/Main.py b/worlds/alttp/Main.py index 8daaab8f..3586364a 100644 --- a/worlds/alttp/Main.py +++ b/worlds/alttp/Main.py @@ -93,11 +93,20 @@ def main(args, seed=None): world.plando_items = args.plando_items.copy() world.plando_texts = args.plando_texts.copy() world.plando_connections = args.plando_connections.copy() + world.er_seeds = args.er_seeds.copy() world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy() world.required_medallions = args.required_medallions.copy() world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)} + for player in range(1, world.players+1): + world.er_seeds[player] = str(world.random.randint(0, 2 ** 64)) + + if "-" in world.shuffle[player]: + shuffle, seed = world.shuffle[player].split("-") + world.shuffle[player] = shuffle + world.er_seeds[player] = seed + logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed) parsed_names = parse_player_names(args.names, world.players, args.teams) @@ -171,12 +180,18 @@ def main(args, seed=None): {"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}: world.fix_fake_world[player] = False + # seeded entrance shuffle + old_random = world.random + world.random = random.Random(world.er_seeds[player]) + if world.mode[player] != 'inverted': link_entrances(world, player) mark_light_world_regions(world, player) else: link_inverted_entrances(world, player) mark_dark_world_regions(world, player) + + world.random = old_random plando_connect(world, player) logger.info('Generating Item Pool.') @@ -260,7 +275,7 @@ def main(args, seed=None): apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], - palettes_options, world, player, True) + palettes_options, world, player, True, reduceflashing=args.reduceflashing[player] if not args.race else True) mcsb_name = '' if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 4786dfc5..17d87751 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -1672,7 +1672,7 @@ def hud_format_text(text): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options, - world=None, player=1, allow_random_on_event=False): + world=None, player=1, allow_random_on_event=False, reduceflashing=False): local_random = random if not world else world.rom_seeds[player] # enable instant item menu @@ -1697,6 +1697,23 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr else: rom.write_byte(0x180048, 0x08) + + # Reduce flashing by nopping out instructions + if reduceflashing: + rom.write_bytes(0x17E07, [0x06]) # reduce amount of colors changed, add this branch if we need to reduce more ""+ [0x80] + [(0x81-0x08)]"" + rom.write_bytes(0x17EAB, [0xD0, 0x03, 0xA9, 0x40, 0x29, 0x60]) # nullifies aga lightning, cutscene, vitreous, bat, ether + # ONLY write to black values with this low pale blue to indicate flashing, that's IT. ""BNE + : LDA #$2940 : + : RTS"" + rom.write_bytes(0x123FE, [0x72]) # set lightning flash in misery mire (and standard) to brightness 0x72 + rom.write_bytes(0x3FA7B, [0x80, 0xac-0x7b]) # branch from palette writing lightning on death mountain + rom.write_byte(0x10817F, 0x01) # internal rom option + else: + rom.write_bytes(0x17E07, [0x00]) + rom.write_bytes(0x17EAB, [0x85, 0x00, 0x29, 0x1F, 0x00, 0x18]) + rom.write_bytes(0x123FE, [0x32]) # original weather flash value + rom.write_bytes(0x3FA7B, [0xc2, 0x20]) # rep #$20 + rom.write_byte(0x10817F, 0x00) # internal rom option + + rom.write_byte(0x18004B, 0x01 if quickswap else 0x00) rom.write_byte(0x0CFE18, 0x00 if disable_music else rom.orig_buffer[0x0CFE18] if rom.orig_buffer else 0x70) diff --git a/worlds/alttp/Text.py b/worlds/alttp/Text.py index 460d78e5..0f314f4b 100644 --- a/worlds/alttp/Text.py +++ b/worlds/alttp/Text.py @@ -1884,7 +1884,7 @@ class TextTable(object): text['item_get_whole_heart'] = CompressedTextMapper.convert("You got a whole ♥!!\nGo you!") text['item_get_sanc_heart'] = CompressedTextMapper.convert("You got a whole ♥!\nGo you!") text['fairy_fountain_refill'] = CompressedTextMapper.convert("Well done, lettuce have a cup of tea…") - text['death_mountain_bullied_no_pearl'] = CompressedTextMapper.convert("The following license applies to the base patch for the randomizer.\n\nCopyright (c) 2017 LLCoolDave\n\nCopyright (c) 2020 Berserker66\n\nCopyright (c) 2020 CaitSith2\n\nCopyright 2016, 2017 Equilateral IT\n\n Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.") + text['death_mountain_bullied_no_pearl'] = CompressedTextMapper.convert("The following license applies to the base patch for the randomizer.\n\nCopyright (c) 2017 LLCoolDave\n\nCopyright (c) 2021 Berserker66\n\nCopyright (c) 2021 CaitSith2\n\nCopyright 2016, 2017 Equilateral IT\n\n Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.") text['death_mountain_bullied_with_pearl'] = CompressedTextMapper.convert("The software is provided \"as is\", without warranty of any kind, express or implied, including but not limited to the warranties of\nmerchantability,\nfitness for a particular purpose and\nnoninfringement.\nIn no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the software.") text['death_mountain_bully_no_pearl'] = CompressedTextMapper.convert("Add garlic, ginger and apple and cook for 2 minutes. Add carrots, potatoes, garam masala and curry powder and stir well. Add tomato paste, stir well and slowly add red wine and bring to a boil. Add sugar, soy sauce and water, stir and bring to a boil again.") text['death_mountain_bully_with_pearl'] = CompressedTextMapper.convert("I think I forgot how to smile…")
Finder