diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index b66e31b7..c64b9d2a 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -3,11 +3,7 @@ name: unittests -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] +on: [push, pull_request] jobs: build: diff --git a/.gitignore b/.gitignore index ecd316cb..9ba862d6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ weights/ _persistent_storage.yaml mystery_result_*.yaml /db.db3 +*-errors.txt +success.txt +output/ +Output Logs/ diff --git a/Adjuster.py b/Adjuster.py index df923148..aebd2eaa 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -6,7 +6,7 @@ import textwrap import sys from AdjusterMain import adjust -from worlds.alttp.Rom import get_sprite_from_name +from worlds.alttp.Rom import Sprite class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -34,8 +34,12 @@ def main(): ''') parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'], help='Select the color of Link\'s heart meter. (default: %(default)s)') - parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout']) - parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout']) + 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']) 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, @@ -51,7 +55,7 @@ def main(): 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 get_sprite_from_name(args.sprite): + 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) @@ -61,7 +65,10 @@ def main(): logging.basicConfig(format='%(message)s', level=loglevel) args, path = adjust(args=args) from Utils import persistent_store - persistent_store("adjuster", "last_settings", args) + from Rom import Sprite + if isinstance(args.sprite, Sprite): + args.sprite = args.sprite.name + persistent_store("adjuster", "last_settings_3", args) if __name__ == '__main__': main() diff --git a/AdjusterMain.py b/AdjusterMain.py index b9497695..fd70fe22 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -10,19 +10,27 @@ 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) + 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, args.ow_palettes, args.uw_palettes) + args.sprite, palettes_options) path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc') rom.write_to_file(path) diff --git a/BaseClasses.py b/BaseClasses.py index 1c79392f..da6ccaba 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -5,7 +5,7 @@ from enum import Enum, unique import logging import json from collections import OrderedDict, Counter, deque -from typing import Union, Optional, List, Dict +from typing import Union, Optional, List, Dict, NamedTuple import secrets import random @@ -20,12 +20,15 @@ class World(): class MultiWorld(): debug_types = False - player_names: list + player_names: Dict[int, List[str]] _region_cache: dict difficulty_requirements: dict required_medallions: dict dark_room_logic: Dict[int, str] restrict_dungeon_item_on_boss: Dict[int, bool] + plando_texts: List[Dict[str, str]] + plando_items: List[PlandoItem] + plando_connections: List[PlandoConnection] def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, @@ -115,10 +118,15 @@ class MultiWorld(): set_player_attr('treasure_hunt_icon', 'Triforce Piece') set_player_attr('treasure_hunt_count', 0) set_player_attr('clock_mode', False) + set_player_attr('countdown_start_time', 10) + set_player_attr('red_clock_time', -2) + set_player_attr('blue_clock_time', 2) + set_player_attr('green_clock_time', 4) set_player_attr('can_take_damage', True) set_player_attr('glitch_boots', True) set_player_attr('progression_balancing', True) set_player_attr('local_items', set()) + set_player_attr('non_local_items', set()) set_player_attr('triforce_pieces_available', 30) set_player_attr('triforce_pieces_required', 20) set_player_attr('shop_shuffle', 'off') @@ -126,6 +134,9 @@ class MultiWorld(): set_player_attr('sprite_pool', []) set_player_attr('dark_room_logic', "lamp") set_player_attr('restrict_dungeon_item_on_boss', False) + set_player_attr('plando_items', []) + set_player_attr('plando_texts', {}) + set_player_attr('plando_connections', []) self.worlds = [] #for i in range(players): @@ -578,7 +589,7 @@ class CollectionState(object): def can_retrieve_tablet(self, player:int) -> bool: return self.has('Book of Mudora', player) and (self.has_beam_sword(player) or - ((self.world.swords[player] == "swordless" or self.world.difficulty_adjustments[player] == "easy") and + (self.world.swords[player] == "swordless" and self.has("Hammer", player))) def has_sword(self, player: int) -> bool: @@ -612,7 +623,7 @@ class CollectionState(object): def can_melt_things(self, player: int) -> bool: return self.has('Fire Rod', player) or \ (self.has('Bombos', player) and - (self.world.difficulty_adjustments[player] == "easy" or self.world.swords[player] == "swordless" or + (self.world.swords[player] == "swordless" or self.has_sword(player))) def can_avoid_lasers(self, player: int) -> bool: @@ -987,6 +998,12 @@ class Item(object): self.world = None self.player = player + def __eq__(self, other): + return self.name == other.name and self.player == other.player + + def __hash__(self): + return hash((self.name, self.player)) + @property def crystal(self) -> bool: return self.type == 'Crystal' @@ -1402,3 +1419,16 @@ class Spoiler(object): path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines))) outfile.write('\n'.join(path_listings)) + + +class PlandoItem(NamedTuple): + item: str + location: str + world: Union[bool, str] = False # False -> own world, True -> not own world + from_pool: bool = True # if item should be removed from item pool + + +class PlandoConnection(NamedTuple): + entrance: str + exit: str + direction: str # entrance, exit or both diff --git a/Fill.py b/Fill.py index 9051f93a..a4ef61d9 100644 --- a/Fill.py +++ b/Fill.py @@ -54,8 +54,10 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si for location in region.locations: if location.item and not location.event: placements.append(location) + # fill in name of world for item + item_to_place.world = world raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. ' - f'Already placed {len(placements)}: {", ".join(placements)}') + f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}') world.push_item(spot_to_fill, item_to_place, False) locations.remove(spot_to_fill) @@ -127,9 +129,12 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None world.random.shuffle(fill_locations) # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots - progitempool.sort( - key=lambda item: 1 if item.name == 'Small Key (Hyrule Castle)' and world.mode[item.player] == 'standard' and - world.keyshuffle[item.player] else 0) + standard_keyshuffle_players = {player for player, mode in world.mode.items() if mode == 'standard' and + world.keyshuffle[player] is True} + if standard_keyshuffle_players: + progitempool.sort( + key=lambda item: 1 if item.name == 'Small Key (Hyrule Castle)' and + item.player in standard_keyshuffle_players else 0) fill_restrictive(world, world.state, fill_locations, progitempool) diff --git a/Gui.py b/Gui.py index d65fef16..57483e2c 100755 --- a/Gui.py +++ b/Gui.py @@ -34,7 +34,7 @@ def guiMain(args=None): customWindow = ttk.Frame(notebook) notebook.add(randomizerWindow, text='Randomize') notebook.add(adjustWindow, text='Adjust') - notebook.add(customWindow, text='Custom') + notebook.add(customWindow, text='Custom Items') notebook.pack() # Shared Controls @@ -96,14 +96,10 @@ def guiMain(args=None): hintsVar = IntVar() hintsVar.set(1) # set default hintsCheckbutton = Checkbutton(checkBoxFrame, text="Include Helpful Hints", variable=hintsVar) - customVar = IntVar() - customCheckbutton = Checkbutton(checkBoxFrame, text="Use custom item pool", variable=customVar) - balancingVar = IntVar() - balancingVar.set(1) # set default - balancingCheckbutton = Checkbutton(checkBoxFrame, text="Multiworld Progression Balancing", variable=balancingVar) - patchesVar = IntVar() - patchesVar.set(1) # set default - patchesCheckbutton = Checkbutton(checkBoxFrame, text="Create Delta Patches", variable=patchesVar) + + tileShuffleVar = IntVar() + tileShuffleButton = Checkbutton(checkBoxFrame, text="Tile shuffle", variable=tileShuffleVar) + createSpoilerCheckbutton.pack(expand=True, anchor=W) suppressRomCheckbutton.pack(expand=True, anchor=W) openpyramidCheckbutton.pack(expand=True, anchor=W) @@ -116,9 +112,8 @@ def guiMain(args=None): retroCheckbutton.pack(expand=True, anchor=W) shuffleGanonCheckbutton.pack(expand=True, anchor=W) hintsCheckbutton.pack(expand=True, anchor=W) - customCheckbutton.pack(expand=True, anchor=W) - balancingCheckbutton.pack(expand=True, anchor=W) - patchesCheckbutton.pack(expand=True, anchor=W) + tileShuffleButton.pack(expand=True, anchor=W) + romOptionsFrame = LabelFrame(rightHalfFrame, text="Rom options") romOptionsFrame.columnconfigure(0, weight=1) @@ -198,7 +193,7 @@ def guiMain(args=None): owPalettesLabel.pack(side=LEFT) owPalettesVar = StringVar() owPalettesVar.set('default') - owPalettesOptionMenu = OptionMenu(owPalettesFrame, owPalettesVar, 'default', 'random', 'blackout') + owPalettesOptionMenu = OptionMenu(owPalettesFrame, owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke') owPalettesOptionMenu.pack(side=LEFT) uwPalettesFrame = Frame(romOptionsFrame) @@ -207,11 +202,41 @@ def guiMain(args=None): uwPalettesLabel.pack(side=LEFT) uwPalettesVar = StringVar() uwPalettesVar.set('default') - uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, uwPalettesVar, 'default', 'random', 'blackout') + 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=4, column=0, columnspan=2, sticky=W+E) + 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") @@ -236,6 +261,7 @@ def guiMain(args=None): romSelectButton.pack(side=LEFT) checkBoxFrame.pack(side=TOP, anchor=W, padx=5, pady=10) + romOptionsFrame.pack(expand=True, fill=BOTH, padx=3) drowDownFrame = Frame(topFrame) @@ -314,14 +340,6 @@ def guiMain(args=None): itemfunctionLabel = Label(itemfunctionFrame, text='Difficulty: item functionality') itemfunctionLabel.pack(side=LEFT) - timerFrame = Frame(drowDownFrame) - timerVar = StringVar() - timerVar.set('none') - timerOptionMenu = OptionMenu(timerFrame, timerVar, 'none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown') - timerOptionMenu.pack(side=RIGHT) - timerLabel = Label(timerFrame, text='Timer setting') - timerLabel.pack(side=LEFT) - dungeonCounterFrame = Frame(drowDownFrame) dungeonCounterVar = StringVar() dungeonCounterVar.set('auto') @@ -382,7 +400,6 @@ def guiMain(args=None): swordFrame.pack(expand=True, anchor=E) difficultyFrame.pack(expand=True, anchor=E) itemfunctionFrame.pack(expand=True, anchor=E) - timerFrame.pack(expand=True, anchor=E) dungeonCounterFrame.pack(expand=True, anchor=E) progressiveFrame.pack(expand=True, anchor=E) accessibilityFrame.pack(expand=True, anchor=E) @@ -449,17 +466,13 @@ def guiMain(args=None): potShuffleButton = Checkbutton(enemizerFrame, text="Pot shuffle", variable=potShuffleVar) potShuffleButton.grid(row=2, column=0, sticky=W) - tileShuffleVar = IntVar() - tileShuffleButton = Checkbutton(enemizerFrame, text="Tile shuffle", variable=tileShuffleVar) - tileShuffleButton.grid(row=2, column=1, sticky=W) - bushShuffleVar = IntVar() bushShuffleButton = Checkbutton(enemizerFrame, text="Bush shuffle", variable=bushShuffleVar) - bushShuffleButton.grid(row=2, column=2, sticky=W) + bushShuffleButton.grid(row=2, column=1, sticky=W) killableThievesVar = IntVar() killable_thievesShuffleButton = Checkbutton(enemizerFrame, text="Killable Thieves", variable=killableThievesVar) - killable_thievesShuffleButton.grid(row=2, column=3, sticky=W) + killable_thievesShuffleButton.grid(row=2, column=2, sticky=W) shopframe = LabelFrame(randomizerWindow, text="Shops", padx=5, pady=2) @@ -477,7 +490,7 @@ def guiMain(args=None): multiworldframe = LabelFrame(randomizerWindow, text="Multiworld", padx=5, pady=2) - worldLabel = Label(multiworldframe, text='Worlds') + worldLabel = Label(multiworldframe, text='Players per Team') worldVar = StringVar() worldSpinbox = Spinbox(multiworldframe, from_=1, to=255, width=5, textvariable=worldVar) namesLabel = Label(multiworldframe, text='Player names') @@ -486,10 +499,17 @@ def guiMain(args=None): seedLabel = Label(multiworldframe, text='Seed #') seedVar = StringVar() seedEntry = Entry(multiworldframe, width=20, textvariable=seedVar) - countLabel = Label(multiworldframe, text='Count') + countLabel = Label(multiworldframe, text='Amount of Multiworlds') countVar = StringVar() countSpinbox = Spinbox(multiworldframe, from_=1, to=100, width=5, textvariable=countVar) + balancingVar = IntVar() + balancingVar.set(1) # set default + balancingCheckbutton = Checkbutton(multiworldframe, text="Progression Balancing", variable=balancingVar) + patchesVar = IntVar() + patchesVar.set(1) # set default + patchesCheckbutton = Checkbutton(multiworldframe, text="Create Delta Patches", variable=patchesVar) + def generateRom(): guiargs = Namespace() guiargs.multi = int(worldVar.get()) @@ -508,6 +528,10 @@ def guiMain(args=None): guiargs.difficulty = difficultyVar.get() guiargs.item_functionality = itemfunctionVar.get() guiargs.timer = timerVar.get() + guiargs.countdown_start_time = timerCountdownVar.get() + guiargs.red_clock_time = timerRedVar.get() + guiargs.blue_clock_time = timerBlueVar.get() + guiargs.green_clock_time = timerGreenVar.get() guiargs.skip_progression_balancing = not balancingVar.get() if guiargs.timer == "none": guiargs.timer = False @@ -538,6 +562,9 @@ def guiMain(args=None): 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.shuffleganon = bool(shuffleGanonVar.get()) guiargs.hints = bool(hintsVar.get()) guiargs.enemizercli = enemizerCLIpathVar.get() @@ -614,16 +641,20 @@ def guiMain(args=None): else: messagebox.showinfo(title="Success", message="Multiworld created successfully") - generateButton = Button(farBottomFrame, text='Generate Patched Rom', command=generateRom) + generateButton = Button(farBottomFrame, text='Generate Multiworld', command=generateRom) + + worldLabel.grid(row=0, column=0, sticky=W) + worldSpinbox.grid(row=0, column=1, sticky=W) + namesLabel.grid(row=0, column=2, sticky=W) + namesEntry.grid(row=0, column=3, sticky=W + E) + multiworldframe.grid_columnconfigure(3, weight=1) # stretch name field + seedLabel.grid(row=0, column=4, sticky=W) + seedEntry.grid(row=0, column=5, sticky=W) + countLabel.grid(row=1, column=0, sticky=W) + countSpinbox.grid(row=1, column=1, sticky=W) + balancingCheckbutton.grid(row=1, column=2, sticky=W, columnspan=2) + patchesCheckbutton.grid(row=1, column=4, sticky=W, columnspan=2) - worldLabel.pack(side=LEFT) - worldSpinbox.pack(side=LEFT) - namesLabel.pack(side=LEFT) - namesEntry.pack(side=LEFT, expand=True, fill=X) - seedLabel.pack(side=LEFT, padx=(5, 0)) - seedEntry.pack(side=LEFT) - countLabel.pack(side=LEFT, padx=(5, 0)) - countSpinbox.pack(side=LEFT) generateButton.pack(side=RIGHT, padx=(5, 0)) openOutputButton.pack(side=LEFT) @@ -702,22 +733,43 @@ def guiMain(args=None): fastMenuLabel2.pack(side=LEFT) owPalettesFrame2 = Frame(drowDownFrame2) - owPalettesOptionMenu2 = OptionMenu(owPalettesFrame2, owPalettesVar, 'default', 'random', 'blackout') + 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') + 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) @@ -728,6 +780,9 @@ def guiMain(args=None): 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() @@ -741,7 +796,10 @@ def guiMain(args=None): else: messagebox.showinfo(title="Success", message="Rom patched successfully") from Utils import persistent_store - persistent_store("adjuster", "last_settings", guiargs) + 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) @@ -763,12 +821,65 @@ def guiMain(args=None): return False vcmd=(topFrame3.register(validation), '%P') + timerOptionsFrame = LabelFrame(topFrame3, text="Timer options") + for i in range(3): + timerOptionsFrame.columnconfigure(i, weight=1) + timerOptionsFrame.rowconfigure(i, weight=1) + + timerModeFrame = Frame(timerOptionsFrame) + timerModeFrame.grid(row=0, column=0, columnspan=3, sticky=E, padx=3) + timerVar = StringVar() + timerVar.set('none') + timerModeMenu = OptionMenu(timerModeFrame, timerVar, 'none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown') + timerLabel = Label(timerModeFrame, text='Timer setting') + timerLabel.pack(side=LEFT) + timerModeMenu.pack(side=LEFT) + + timerCountdownFrame = Frame(timerOptionsFrame) + timerCountdownFrame.grid(row=1, column=0, columnspan=3, sticky=E, padx=3) + timerCountdownLabel = Label(timerCountdownFrame, text='Countdown starting time') + timerCountdownLabel.pack(side=LEFT) + timerCountdownVar = IntVar(value=10) + timerCountdownSpinbox = Spinbox(timerCountdownFrame, from_=0, to=480, width=3, textvariable=timerCountdownVar) + timerCountdownSpinbox.pack(side=LEFT) + + timerRedFrame = Frame(timerOptionsFrame) + timerRedFrame.grid(row=2, column=0, sticky=E, padx=3) + timerRedLabel = Label(timerRedFrame, text='Clock adjustments: Red') + timerRedLabel.pack(side=LEFT) + timerRedVar = IntVar(value=-2) + timerRedSpinbox = Spinbox(timerRedFrame, from_=-60, to=60, width=3, textvariable=timerRedVar) + timerRedSpinbox.pack(side=LEFT) + + timerBlueFrame = Frame(timerOptionsFrame) + timerBlueFrame.grid(row=2, column=1, sticky=E, padx=3) + timerBlueLabel = Label(timerBlueFrame, text='Blue') + timerBlueLabel.pack(side=LEFT) + timerBlueVar = IntVar(value=2) + timerBlueSpinbox = Spinbox(timerBlueFrame, from_=-60, to=60, width=3, textvariable=timerBlueVar) + timerBlueSpinbox.pack(side=LEFT) + + timerGreenFrame = Frame(timerOptionsFrame) + timerGreenFrame.grid(row=2, column=2, sticky=E, padx=3) + timerGreenLabel = Label(timerGreenFrame, text='Green') + timerGreenLabel.pack(side=LEFT) + timerGreenVar = IntVar(value=4) + timerGreenSpinbox = Spinbox(timerGreenFrame, from_=-60, to=60, width=3, textvariable=timerGreenVar) + timerGreenSpinbox.pack(side=LEFT) + + timerOptionsFrame.pack(expand=True, fill=BOTH, padx=3) + + itemList1 = Frame(topFrame3) itemList2 = Frame(topFrame3) itemList3 = Frame(topFrame3) itemList4 = Frame(topFrame3) itemList5 = Frame(topFrame3) + customVar = IntVar() + customCheckbutton = Checkbutton(topFrame3, text="Use custom item pool", variable=customVar) + customCheckbutton.pack(expand=True, anchor=W) + bowFrame = Frame(itemList1) bowLabel = Label(bowFrame, text='Bow') bowVar = StringVar(value='0') @@ -1367,6 +1478,10 @@ def guiMain(args=None): difficultyVar.set(args.difficulty) itemfunctionVar.set(args.item_functionality) timerVar.set(args.timer) + timerCountdownVar.set(args.countdown_start_time) + timerRedVar.set(args.red_clock_time) + timerBlueVar.set(args.blue_clock_time) + timerGreenVar.set(args.green_clock_time) progressiveVar.set(args.progressive) accessibilityVar.set(args.accessibility) goalVar.set(args.goal) @@ -1385,7 +1500,8 @@ def guiMain(args=None): mainWindow.mainloop() -class SpriteSelector(object): + +class SpriteSelector(): def __init__(self, parent, callback, adjuster=False): if is_bundled(): self.deploy_icons() @@ -1411,8 +1527,8 @@ class SpriteSelector(object): title_link.pack(side=LEFT) title_link.bind("", open_custom_sprite_dir) - self.icon_section(alttpr_frametitle, self.alttpr_sprite_dir + '/*', 'ALTTPR sprites not found. Click "Update alttpr sprites" to download them.') - self.icon_section(custom_frametitle, self.custom_sprite_dir + '/*', 'Put sprites in the custom sprites folder (see open link above) to have them appear here.') + 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.') frame = Frame(self.window) frame.pack(side=BOTTOM, fill=X, pady=5) @@ -1471,19 +1587,21 @@ class SpriteSelector(object): sprites = [] - for file in glob(output_path(path)): - sprites.append(Sprite(file)) + for file in os.listdir(path): + sprites.append((file, Sprite(os.path.join(path, file)))) - sprites.sort(key=lambda s: str.lower(s.name or "").strip()) + sprites.sort(key=lambda s: str.lower(s[1].name or "").strip()) frame.buttons = [] - for sprite in sprites: + 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 "")) + 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) @@ -1508,93 +1626,15 @@ class SpriteSelector(object): self.window.destroy() self.parent.update() - def work(task): - resultmessage = "" - successful = True - - def finished(): - task.close_window() - if successful: - messagebox.showinfo("Sprite Updater", resultmessage) - else: - messagebox.showerror("Sprite Updater", resultmessage) - SpriteSelector(self.parent, self.callback, self.adjuster) - - try: - task.update_status("Downloading alttpr sprites list") - with urlopen('https://alttpr.com/sprites') as response: - sprites_arr = json.loads(response.read().decode("utf-8")) - except Exception as e: - resultmessage = "Error getting list of alttpr sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) - successful = False - task.queue_event(finished) - return - - try: - task.update_status("Determining needed sprites") - current_sprites = [os.path.basename(file) for file in glob(self.alttpr_sprite_dir + '/*')] - alttpr_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr] - needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in alttpr_sprites if filename not in current_sprites] - - alttpr_filenames = [filename for (_, filename) in alttpr_sprites] - obsolete_sprites = [sprite for sprite in current_sprites if sprite not in alttpr_filenames] - except Exception as e: - resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) - successful = False - task.queue_event(finished) - return - - - def dl(sprite_url, filename): - target = os.path.join(self.alttpr_sprite_dir, filename) - with urlopen(sprite_url) as response, open(target, 'wb') as out: - shutil.copyfileobj(response, out) - - def rem(sprite): - os.remove(os.path.join(self.alttpr_sprite_dir, sprite)) - - - with ThreadPoolExecutor() as pool: - dl_tasks = [] - rem_tasks = [] - - for (sprite_url, filename) in needed_sprites: - dl_tasks.append(pool.submit(dl, sprite_url, filename)) - - for sprite in obsolete_sprites: - rem_tasks.append(pool.submit(rem, sprite)) - - deleted = 0 - updated = 0 - - for dl_task in as_completed(dl_tasks): - updated += 1 - task.update_status("Downloading needed sprite %g/%g" % (updated, len(needed_sprites))) - try: - dl_task.result() - except Exception as e: - logging.exception(e) - resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % ( - type(e).__name__, e) - successful = False - - for rem_task in as_completed(rem_tasks): - deleted += 1 - task.update_status("Removing obsolete sprite %g/%g" % (deleted, len(obsolete_sprites))) - try: - rem_task.result() - except Exception as e: - logging.exception(e) - resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % ( - type(e).__name__, e) - successful = False - + def on_finish(successful, resultmessage): if successful: - resultmessage = "alttpr sprites updated successfully" + messagebox.showinfo("Sprite Updater", resultmessage) + else: + logging.error(resultmessage) + messagebox.showerror("Sprite Updater", resultmessage) + SpriteSelector(self.parent, self.callback, self.adjuster) - task.queue_event(finished) - - BackgroundTaskProgress(self.parent, work, "Updating Sprites") + BackgroundTaskProgress(self.parent, update_sprites, "Updating Sprites", on_finish) def browse_for_sprite(self): @@ -1638,34 +1678,104 @@ class SpriteSelector(object): self.callback(spritename) self.window.destroy() - def deploy_icons(self): if not os.path.exists(self.custom_sprite_dir): os.makedirs(self.custom_sprite_dir) - if not os.path.exists(self.alttpr_sprite_dir): - shutil.copytree(self.local_alttpr_sprite_dir, self.alttpr_sprite_dir) @property def alttpr_sprite_dir(self): - if is_bundled(): - return output_path("sprites", "alttpr") - return self.local_alttpr_sprite_dir - - @property - def local_alttpr_sprite_dir(self): return local_path("data", "sprites", "alttpr") @property def custom_sprite_dir(self): - if is_bundled(): - return output_path("sprites", "custom") - return self.local_custom_sprite_dir - - @property - def local_custom_sprite_dir(self): return local_path("data", "sprites", "custom") +def update_sprites(task, on_finish=None): + resultmessage = "" + successful = True + sprite_dir = local_path("data", "sprites", "alttpr") + os.makedirs(sprite_dir, exist_ok=True) + + def finished(): + task.close_window() + if on_finish: + on_finish(successful, resultmessage) + + try: + task.update_status("Downloading alttpr sprites list") + with urlopen('https://alttpr.com/sprites') as response: + sprites_arr = json.loads(response.read().decode("utf-8")) + except Exception as e: + resultmessage = "Error getting list of alttpr sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) + successful = False + task.queue_event(finished) + return + + try: + task.update_status("Determining needed sprites") + current_sprites = [os.path.basename(file) for file in glob(sprite_dir + '/*')] + alttpr_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr] + needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in alttpr_sprites if filename not in current_sprites] + + alttpr_filenames = [filename for (_, filename) in alttpr_sprites] + obsolete_sprites = [sprite for sprite in current_sprites if sprite not in alttpr_filenames] + except Exception as e: + resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) + successful = False + task.queue_event(finished) + return + + + def dl(sprite_url, filename): + target = os.path.join(sprite_dir, filename) + with urlopen(sprite_url) as response, open(target, 'wb') as out: + shutil.copyfileobj(response, out) + + def rem(sprite): + os.remove(os.path.join(sprite_dir, sprite)) + + + with ThreadPoolExecutor() as pool: + dl_tasks = [] + rem_tasks = [] + + for (sprite_url, filename) in needed_sprites: + dl_tasks.append(pool.submit(dl, sprite_url, filename)) + + for sprite in obsolete_sprites: + rem_tasks.append(pool.submit(rem, sprite)) + + deleted = 0 + updated = 0 + + for dl_task in as_completed(dl_tasks): + updated += 1 + task.update_status("Downloading needed sprite %g/%g" % (updated, len(needed_sprites))) + try: + dl_task.result() + except Exception as e: + logging.exception(e) + resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % ( + type(e).__name__, e) + successful = False + + for rem_task in as_completed(rem_tasks): + deleted += 1 + task.update_status("Removing obsolete sprite %g/%g" % (deleted, len(obsolete_sprites))) + try: + rem_task.result() + except Exception as e: + logging.exception(e) + resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % ( + type(e).__name__, e) + successful = False + + if successful: + resultmessage = "alttpr sprites updated successfully" + + task.queue_event(finished) + def get_image_for_sprite(sprite, gif_only: bool = False): if not sprite.valid: return None @@ -1770,5 +1880,16 @@ def get_image_for_sprite(sprite, gif_only: bool = False): return image.zoom(2) if __name__ == '__main__': - logging.basicConfig(format='%(message)s', level=logging.INFO) - guiMain() + import sys + if "update_sprites" in sys.argv: + import threading + done = threading.Event() + top = Tk() + top.withdraw() + BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set()) + while not done.isSet(): + top.update() + print("Done updating sprites") + else: + logging.basicConfig(format='%(message)s', level=logging.INFO) + guiMain() diff --git a/GuiUtils.py b/GuiUtils.py index 0bebfda6..c0542076 100644 --- a/GuiUtils.py +++ b/GuiUtils.py @@ -14,12 +14,12 @@ def set_icon(window): # some which may be platform specific, or depend on if the TCL library was compiled without # multithreading support. Therefore I will assume it is not thread safe to avoid any possible problems class BackgroundTask(object): - def __init__(self, window, code_to_run): + 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,)) + self.task = threading.Thread(target=code_to_run, args=(self, *args)) self.task.start() def stop(self): @@ -45,7 +45,7 @@ class BackgroundTask(object): self.window.after(100, self.process_queue) class BackgroundTaskProgress(BackgroundTask): - def __init__(self, parent, code_to_run, title): + def __init__(self, parent, code_to_run, title, *args): self.parent = parent self.window = tk.Toplevel(parent) self.window['padx'] = 5 @@ -65,7 +65,7 @@ class BackgroundTaskProgress(BackgroundTask): set_icon(self.window) self.window.focus() - super().__init__(self.window, code_to_run) + super().__init__(self.window, code_to_run, *args) #safe to call from worker thread def update_status(self, text): diff --git a/LICENSE b/LICENSE index e3c2482f..9e260f41 100644 --- a/LICENSE +++ b/LICENSE @@ -22,3 +22,8 @@ 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. + +Subdirectories of this repository may contain their own LICENSE file. Files within +subdirectories containing their own LICENSE file are exempt from the above license +and are subject to the license contained within the LICENSE file in their containing +directory. diff --git a/MultiClient.py b/MultiClient.py index 5b58ad60..c2968123 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -86,14 +86,17 @@ class Context(): self.slot = None self.player_names: typing.Dict[int: str] = {} self.locations_checked = set() + self.unsafe_locations_checked = set() self.locations_scouted = set() self.items_received = [] + self.items_missing = [] self.locations_info = {} self.awaiting_rom = False self.rom = None self.prev_rom = None self.auth = None self.found_items = found_items + self.send_unsafe = False self.finished_game = False self.slow_mode = False @@ -196,23 +199,34 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Desert Palace - Map Chest': (0x74, 0x10), 'Desert Palace - Compass Chest': (0x85, 0x10), 'Desert Palace - Big Key Chest': (0x75, 0x10), + 'Desert Palace - Desert Tiles 1 Pot Key': (0x63, 0x400), + 'Desert Palace - Beamos Hall Pot Key': (0x53, 0x400), + 'Desert Palace - Desert Tiles 2 Pot Key': (0x43, 0x400), 'Desert Palace - Boss': (0x33, 0x800), 'Eastern Palace - Compass Chest': (0xa8, 0x10), 'Eastern Palace - Big Chest': (0xa9, 0x10), + 'Eastern Palace - Dark Square Pot Key': (0xba, 0x400), + 'Eastern Palace - Dark Eyegore Key Drop': (0x99, 0x400), 'Eastern Palace - Cannonball Chest': (0xb9, 0x10), 'Eastern Palace - Big Key Chest': (0xb8, 0x10), 'Eastern Palace - Map Chest': (0xaa, 0x10), 'Eastern Palace - Boss': (0xc8, 0x800), 'Hyrule Castle - Boomerang Chest': (0x71, 0x10), + 'Hyrule Castle - Boomerang Guard Key Drop': (0x71, 0x400), 'Hyrule Castle - Map Chest': (0x72, 0x10), + 'Hyrule Castle - Map Guard Key Drop': (0x72, 0x400), "Hyrule Castle - Zelda's Chest": (0x80, 0x10), + 'Hyrule Castle - Big Key Drop': (0x80, 0x400), 'Sewers - Dark Cross': (0x32, 0x10), + 'Hyrule Castle - Key Rat Key Drop': (0x21, 0x400), 'Sewers - Secret Room - Left': (0x11, 0x10), 'Sewers - Secret Room - Middle': (0x11, 0x20), 'Sewers - Secret Room - Right': (0x11, 0x40), 'Sanctuary': (0x12, 0x10), 'Castle Tower - Room 03': (0xe0, 0x10), 'Castle Tower - Dark Maze': (0xd0, 0x10), + 'Castle Tower - Dark Archer Key Drop': (0xc0, 0x400), + 'Castle Tower - Circle of Pots Key Drop': (0xb0, 0x400), 'Spectacle Rock Cave': (0xea, 0x400), 'Paradox Cave Lower - Far Left': (0xef, 0x10), 'Paradox Cave Lower - Left': (0xef, 0x20), @@ -251,18 +265,25 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Mimic Cave': (0x10c, 0x10), 'Swamp Palace - Entrance': (0x28, 0x10), 'Swamp Palace - Map Chest': (0x37, 0x10), + 'Swamp Palace - Pot Row Pot Key': (0x38, 0x400), + 'Swamp Palace - Trench 1 Pot Key': (0x37, 0x400), + 'Swamp Palace - Hookshot Pot Key': (0x36, 0x400), 'Swamp Palace - Big Chest': (0x36, 0x10), 'Swamp Palace - Compass Chest': (0x46, 0x10), + 'Swamp Palace - Trench 2 Pot Key': (0x35, 0x400), 'Swamp Palace - Big Key Chest': (0x35, 0x10), 'Swamp Palace - West Chest': (0x34, 0x10), 'Swamp Palace - Flooded Room - Left': (0x76, 0x10), 'Swamp Palace - Flooded Room - Right': (0x76, 0x20), 'Swamp Palace - Waterfall Room': (0x66, 0x10), + 'Swamp Palace - Waterway Pot Key': (0x16, 0x400), 'Swamp Palace - Boss': (0x6, 0x800), "Thieves' Town - Big Key Chest": (0xdb, 0x20), "Thieves' Town - Map Chest": (0xdb, 0x10), "Thieves' Town - Compass Chest": (0xdc, 0x10), "Thieves' Town - Ambush Chest": (0xcb, 0x10), + "Thieves' Town - Hallway Pot Key": (0xbc, 0x400), + "Thieves' Town - Spike Switch Pot Key": (0xab, 0x400), "Thieves' Town - Attic": (0x65, 0x10), "Thieves' Town - Big Chest": (0x44, 0x10), "Thieves' Town - Blind's Cell": (0x45, 0x10), @@ -273,28 +294,39 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Skull Woods - Pot Prison': (0x57, 0x20), 'Skull Woods - Pinball Room': (0x68, 0x10), 'Skull Woods - Big Key Chest': (0x57, 0x10), + 'Skull Woods - West Lobby Pot Key': (0x56, 0x400), 'Skull Woods - Bridge Room': (0x59, 0x10), + 'Skull Woods - Spike Corner Key Drop': (0x39, 0x400), 'Skull Woods - Boss': (0x29, 0x800), + 'Ice Palace - Jelly Key Drop': (0x0e, 0x400), 'Ice Palace - Compass Chest': (0x2e, 0x10), + 'Ice Palace - Conveyor Key Drop': (0x3e, 0x400), 'Ice Palace - Freezor Chest': (0x7e, 0x10), 'Ice Palace - Big Chest': (0x9e, 0x10), 'Ice Palace - Iced T Room': (0xae, 0x10), + 'Ice Palace - Many Pots Pot Key': (0x9f, 0x400), 'Ice Palace - Spike Room': (0x5f, 0x10), 'Ice Palace - Big Key Chest': (0x1f, 0x10), + 'Ice Palace - Hammer Block Key Drop': (0x3f, 0x400), 'Ice Palace - Map Chest': (0x3f, 0x10), 'Ice Palace - Boss': (0xde, 0x800), 'Misery Mire - Big Chest': (0xc3, 0x10), 'Misery Mire - Map Chest': (0xc3, 0x20), 'Misery Mire - Main Lobby': (0xc2, 0x10), 'Misery Mire - Bridge Chest': (0xa2, 0x10), + 'Misery Mire - Spikes Pot Key': (0xb3, 0x400), 'Misery Mire - Spike Chest': (0xb3, 0x10), + 'Misery Mire - Fishbone Pot Key': (0xa1, 0x400), + 'Misery Mire - Conveyor Crystal Key Drop': (0xc1, 0x400), 'Misery Mire - Compass Chest': (0xc1, 0x10), 'Misery Mire - Big Key Chest': (0xd1, 0x10), 'Misery Mire - Boss': (0x90, 0x800), 'Turtle Rock - Compass Chest': (0xd6, 0x10), 'Turtle Rock - Roller Room - Left': (0xb7, 0x10), 'Turtle Rock - Roller Room - Right': (0xb7, 0x20), + 'Turtle Rock - Pokey 1 Key Drop': (0xb6, 0x400), 'Turtle Rock - Chain Chomps': (0xb6, 0x10), + 'Turtle Rock - Pokey 2 Key Drop': (0x13, 0x400), 'Turtle Rock - Big Key Chest': (0x14, 0x10), 'Turtle Rock - Big Chest': (0x24, 0x10), 'Turtle Rock - Crystaroller Room': (0x4, 0x10), @@ -317,6 +349,7 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Palace of Darkness - Big Chest': (0x1a, 0x10), 'Palace of Darkness - Harmless Hellway': (0x1a, 0x40), 'Palace of Darkness - Boss': (0x5a, 0x800), + 'Ganons Tower - Conveyor Cross Pot Key': (0x8b, 0x400), "Ganons Tower - Bob's Torch": (0x8c, 0x400), 'Ganons Tower - Hope Room - Left': (0x8c, 0x20), 'Ganons Tower - Hope Room - Right': (0x8c, 0x40), @@ -325,11 +358,13 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Ganons Tower - Compass Room - Top Right': (0x9d, 0x20), 'Ganons Tower - Compass Room - Bottom Left': (0x9d, 0x40), 'Ganons Tower - Compass Room - Bottom Right': (0x9d, 0x80), + 'Ganons Tower - Conveyor Star Pits Pot Key': (0x7b, 0x400), 'Ganons Tower - DMs Room - Top Left': (0x7b, 0x10), 'Ganons Tower - DMs Room - Top Right': (0x7b, 0x20), 'Ganons Tower - DMs Room - Bottom Left': (0x7b, 0x40), 'Ganons Tower - DMs Room - Bottom Right': (0x7b, 0x80), 'Ganons Tower - Map Chest': (0x8b, 0x10), + 'Ganons Tower - Double Switch Pot Key': (0x9b, 0x400), 'Ganons Tower - Firesnake Room': (0x7d, 0x10), 'Ganons Tower - Randomizer Room - Top Left': (0x7c, 0x10), 'Ganons Tower - Randomizer Room - Top Right': (0x7c, 0x20), @@ -342,6 +377,7 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Ganons Tower - Big Key Chest': (0x1c, 0x10), 'Ganons Tower - Mini Helmasaur Room - Left': (0x3d, 0x10), 'Ganons Tower - Mini Helmasaur Room - Right': (0x3d, 0x20), + 'Ganons Tower - Mini Helmasaur Key Drop': (0x3d, 0x400), 'Ganons Tower - Pre-Moldorm Chest': (0x3d, 0x40), 'Ganons Tower - Validation Chest': (0x4d, 0x10)} location_table_npc = {'Mushroom': 0x1000, @@ -487,8 +523,8 @@ async def snes_connect(ctx: Context, address): ctx.snes_attached_device = (devices.index(device), device) ctx.ui_node.send_connection_status(ctx) - if 'sd2snes' in device.lower() or (len(device) == 4 and device[:3] == 'COM'): - ctx.ui_node.log_info("SD2SNES Detected") + if 'sd2snes' in device.lower() or 'COM' in device: + ctx.ui_node.log_info("SD2SNES/FXPAK Detected") ctx.is_sd2snes = True await ctx.snes_socket.send(dumps({"Opcode" : "Info", "Space" : "SNES"})) reply = loads(await ctx.snes_socket.recv()) @@ -609,10 +645,7 @@ async def snes_write(ctx : Context, write_list): if ctx.snes_state != SNES_ATTACHED or ctx.snes_socket is None or not ctx.snes_socket.open or ctx.snes_socket.closed: return False - PutAddress_Request = { - "Opcode" : "PutAddress", - "Operands" : [] - } + PutAddress_Request = {"Opcode": "PutAddress", "Operands": [], 'Space': 'SNES'} if ctx.is_sd2snes: cmd = b'\x00\xE2\x20\x48\xEB\x48' @@ -634,8 +667,9 @@ async def snes_write(ctx : Context, write_list): try: if ctx.snes_socket is not None: await ctx.snes_socket.send(dumps(PutAddress_Request)) - if ctx.snes_socket is not None: await ctx.snes_socket.send(cmd) + else: + logging.warning(f"Could not send data to SNES: {cmd}") except websockets.ConnectionClosed: return False else: @@ -646,8 +680,9 @@ async def snes_write(ctx : Context, write_list): PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]] if ctx.snes_socket is not None: await ctx.snes_socket.send(dumps(PutAddress_Request)) - if ctx.snes_socket is not None: await ctx.snes_socket.send(data) + else: + logging.warning(f"Could not send data to SNES: {data}") except websockets.ConnectionClosed: return False @@ -657,7 +692,8 @@ async def snes_write(ctx : Context, write_list): def snes_buffered_write(ctx : Context, address, data): - if len(ctx.snes_write_buffer) > 0 and (ctx.snes_write_buffer[-1][0] + len(ctx.snes_write_buffer[-1][1])) == address: + if ctx.snes_write_buffer and (ctx.snes_write_buffer[-1][0] + len(ctx.snes_write_buffer[-1][1])) == address: + # append to existing write command, bundling them ctx.snes_write_buffer[-1] = (ctx.snes_write_buffer[-1][0], ctx.snes_write_buffer[-1][1] + data) else: ctx.snes_write_buffer.append((address, data)) @@ -667,8 +703,9 @@ async def snes_flush_writes(ctx : Context): if not ctx.snes_write_buffer: return - await snes_write(ctx, ctx.snes_write_buffer) - ctx.snes_write_buffer = [] + # swap buffers + ctx.snes_write_buffer, writes = [], ctx.snes_write_buffer + await snes_write(ctx, writes) async def send_msgs(websocket, msgs): @@ -773,8 +810,8 @@ async def process_server_cmd(ctx: Context, cmd, args): if args['password']: ctx.ui_node.log_info('Password required') if "forfeit_mode" in args: # could also be version > 2.2.1, but going with implicit content here - logging.info("Forfeit setting: "+args["forfeit_mode"]) - logging.info("Remaining setting: "+args["remaining_mode"]) + logging.info(f"Forfeit setting: {args['forfeit_mode']}") + logging.info(f"Remaining setting: {args['remaining_mode']}") logging.info(f"A !hint costs {args['hint_cost']} points and you get {args['location_check_points']}" f" for each location checked.") ctx.hint_cost = int(args['hint_cost']) @@ -796,35 +833,47 @@ async def process_server_cmd(ctx: Context, cmd, args): await server_auth(ctx, args['password']) elif cmd == 'ConnectionRefused': - if 'InvalidPassword' in args: - ctx.ui_node.log_error('Invalid password') - ctx.password = None - await server_auth(ctx, True) if 'InvalidRom' in args: if ctx.snes_socket is not None and not ctx.snes_socket.closed: asyncio.create_task(ctx.snes_socket.close()) - raise Exception( - 'Invalid ROM detected, please verify that you have loaded the correct rom and reconnect your snes (/snes)') - if 'SlotAlreadyTaken' in args: + raise Exception('Invalid ROM detected, ' + 'please verify that you have loaded the correct rom and reconnect your snes (/snes)') + elif 'SlotAlreadyTaken' in args: Utils.persistent_store("servers", ctx.rom, ctx.server_address) raise Exception('Player slot already in use for that team') - if 'IncompatibleVersion' in args: + elif 'IncompatibleVersion' in args: raise Exception('Server reported your client version as incompatible') - raise Exception('Connection refused by the multiworld host') + #last to check, recoverable problem + elif 'InvalidPassword' in args: + ctx.ui_node.log_error('Invalid password') + ctx.password = None + await server_auth(ctx, True) + else: + raise Exception("Unknown connection errors: "+str(args)) + raise Exception('Connection refused by the multiworld host, no reason provided') elif cmd == 'Connected': + if ctx.send_unsafe: + ctx.send_unsafe = False + ctx.ui_node.log_info(f'Turning off sending of ALL location checks not declared as missing. If you want it on, please use /send_unsafe true') Utils.persistent_store("servers", ctx.rom, ctx.server_address) ctx.team, ctx.slot = args[0] ctx.player_names = {p: n for p, n in args[1]} msgs = [] if ctx.locations_checked: - msgs.append(['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]]) + msgs.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]]) if ctx.locations_scouted: msgs.append(['LocationScouts', list(ctx.locations_scouted)]) if msgs: await ctx.send_msgs(msgs) if ctx.finished_game: await send_finished_game(ctx) + ctx.items_missing = args[2] if len(args) >= 3 else [] # Get the server side view of missing as of time of connecting. + # This list is used to only send to the server what is reported as ACTUALLY Missing. + # This also serves to allow an easy visual of what locations were already checked previously + # when /missing is used for the client side view of what is missing. + if not ctx.items_missing: + asyncio.create_task(ctx.send_msgs([['Say', '!missing']])) elif cmd == 'ReceivedItems': start_index, items = args @@ -833,7 +882,7 @@ async def process_server_cmd(ctx: Context, cmd, args): elif start_index != len(ctx.items_received): sync_msg = [['Sync']] if ctx.locations_checked: - sync_msg.append(['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]]) + sync_msg.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]]) await ctx.send_msgs(sync_msg) if start_index == len(ctx.items_received): for item in items: @@ -999,13 +1048,34 @@ class ClientCommandProcessor(CommandProcessor): def _cmd_missing(self) -> bool: """List all missing location checks, from your local game state""" count = 0 + checked_count = 0 for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]: if location not in self.ctx.locations_checked: - self.output('Missing: ' + location) + if location not in self.ctx.items_missing: + self.output('Checked: ' + location) + checked_count += 1 + else: + self.output('Missing: ' + location) count += 1 + key_drop_count = 0 + for location in [k for k, v in Regions.key_drop_data.items()]: + if location not in self.ctx.items_missing: + key_drop_count += 1 + + # No point on reporting on missing key drop locations if the server doesn't declare ANY of them missing. + if key_drop_count != len(Regions.key_drop_data.items()): + for location in [k for k, v in Regions.key_drop_data.items()]: + if location not in self.ctx.locations_checked: + if location not in self.ctx.items_missing: + self.output('Checked: ' + location) + key_drop_count += 1 + else: + self.output('Missing: ' + location) + count += 1 + if count: - self.output(f"Found {count} missing location checks") + self.output(f"Found {count} missing location checks{f'. {checked_count} locations checks previously visited.' if checked_count else ''}") else: self.output("No missing location checks found.") return True @@ -1035,6 +1105,15 @@ class ClientCommandProcessor(CommandProcessor): else: self.output("Web UI was never started.") + def _cmd_send_unsafe(self, toggle: str = ""): + """Force sending of locations the server did not specify was actually missing. WARNING: This may brick online trackers. Turned off on reconnect.""" + if toggle: + self.ctx.send_unsafe = toggle.lower() in {"1", "true", "on"} + self.ctx.ui_node.log_info(f'Turning {("on" if self.ctx.send_unsafe else "off")} the option to send ALL location checks to the multiserver.') + else: + self.ctx.ui_node.log_info("You must specify /send_unsafe true explicitly.") + self.ctx.send_unsafe = False + def default(self, raw: str): asyncio.create_task(self.ctx.send_msgs([['Say', raw]])) @@ -1064,20 +1143,22 @@ async def track_locations(ctx : Context, roomid, roomdata): new_locations = [] def new_check(location): - ctx.locations_checked.add(location) - ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.locations_checked))) + ctx.unsafe_locations_checked.add(location) + ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked))) ctx.ui_node.send_location_check(ctx, location) - new_locations.append(Regions.location_table[location][0]) for location, (loc_roomid, loc_mask) in location_table_uw.items(): - if location not in ctx.locations_checked and loc_roomid == roomid and (roomdata << 4) & loc_mask != 0: - new_check(location) + try: + if location not in ctx.unsafe_locations_checked and loc_roomid == roomid and (roomdata << 4) & loc_mask != 0: + new_check(location) + except Exception as e: + ctx.ui_node.log_info(f"Exception: {e}") uw_begin = 0x129 uw_end = 0 uw_unchecked = {} for location, (roomid, mask) in location_table_uw.items(): - if location not in ctx.locations_checked: + if location not in ctx.unsafe_locations_checked: uw_unchecked[location] = (roomid, mask) uw_begin = min(uw_begin, roomid) uw_end = max(uw_end, roomid + 1) @@ -1094,7 +1175,7 @@ async def track_locations(ctx : Context, roomid, roomdata): ow_end = 0 ow_unchecked = {} for location, screenid in location_table_ow.items(): - if location not in ctx.locations_checked: + if location not in ctx.unsafe_locations_checked: ow_unchecked[location] = screenid ow_begin = min(ow_begin, screenid) ow_end = max(ow_end, screenid + 1) @@ -1105,22 +1186,27 @@ async def track_locations(ctx : Context, roomid, roomdata): if ow_data[screenid - ow_begin] & 0x40 != 0: new_check(location) - if not all([location in ctx.locations_checked for location in location_table_npc.keys()]): + if not all([location in ctx.unsafe_locations_checked for location in location_table_npc.keys()]): npc_data = await snes_read(ctx, SAVEDATA_START + 0x410, 2) if npc_data is not None: npc_value = npc_data[0] | (npc_data[1] << 8) for location, mask in location_table_npc.items(): - if npc_value & mask != 0 and location not in ctx.locations_checked: + if npc_value & mask != 0 and location not in ctx.unsafe_locations_checked: new_check(location) - if not all([location in ctx.locations_checked for location in location_table_misc.keys()]): + if not all([location in ctx.unsafe_locations_checked for location in location_table_misc.keys()]): misc_data = await snes_read(ctx, SAVEDATA_START + 0x3c6, 4) if misc_data is not None: for location, (offset, mask) in location_table_misc.items(): assert(0x3c6 <= offset <= 0x3c9) - if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked: + if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.unsafe_locations_checked: new_check(location) + for location in ctx.unsafe_locations_checked: + if (location in ctx.items_missing and location not in ctx.locations_checked) or ctx.send_unsafe: + ctx.locations_checked.add(location) + new_locations.append(Regions.lookup_name_to_id[location]) + await ctx.send_msgs([['LocationChecks', new_locations]]) @@ -1151,6 +1237,7 @@ async def game_watcher(ctx : Context): ctx.rom = rom if not ctx.prev_rom or ctx.prev_rom != ctx.rom: ctx.locations_checked = set() + ctx.unsafe_locations_checked = set() ctx.locations_scouted = set() ctx.prev_rom = ctx.rom diff --git a/MultiMystery.py b/MultiMystery.py index b460d267..0812d2cd 100644 --- a/MultiMystery.py +++ b/MultiMystery.py @@ -1,4 +1,4 @@ -__author__ = "Berserker55" # you can find me on the ALTTP Randomizer Discord +__author__ = "Berserker55" # you can find me on discord.gg/8Z65BR2 """ This script launches a Multiplayer "Multiworld" Mystery Game @@ -18,16 +18,18 @@ import sys import threading import concurrent.futures import argparse +import logging def feedback(text: str): - print(text) + logging.info(text) input("Press Enter to ignore and probably crash.") if __name__ == "__main__": + logging.basicConfig(format='%(message)s', level=logging.INFO) try: - print(f"{__author__}'s MultiMystery Launcher") + logging.info(f"{__author__}'s MultiMystery Launcher") import ModuleUpdate ModuleUpdate.update() @@ -46,56 +48,64 @@ if __name__ == "__main__": output_path = options["general_options"]["output_path"] enemizer_path = multi_mystery_options["enemizer_path"] player_files_path = multi_mystery_options["player_files_path"] + target_player_count = multi_mystery_options["players"] race = multi_mystery_options["race"] + plando_options = multi_mystery_options["plando_options"] create_spoiler = multi_mystery_options["create_spoiler"] zip_roms = multi_mystery_options["zip_roms"] zip_diffs = multi_mystery_options["zip_diffs"] zip_spoiler = multi_mystery_options["zip_spoiler"] zip_multidata = multi_mystery_options["zip_multidata"] zip_format = multi_mystery_options["zip_format"] - #zip_password = multi_mystery_options["zip_password"] not at this time + # zip_password = multi_mystery_options["zip_password"] not at this time player_name = multi_mystery_options["player_name"] meta_file_path = multi_mystery_options["meta_file_path"] + weights_file_path = multi_mystery_options["weights_file_path"] teams = multi_mystery_options["teams"] rom_file = options["general_options"]["rom_file"] host = options["server_options"]["host"] port = options["server_options"]["port"] - py_version = f"{sys.version_info.major}.{sys.version_info.minor}" if not os.path.exists(enemizer_path): - feedback(f"Enemizer not found at {enemizer_path}, please adjust the path in MultiMystery.py's config or put Enemizer in the default location.") + feedback( + f"Enemizer not found at {enemizer_path}, please adjust the path in MultiMystery.py's config or put Enemizer in the default location.") if not os.path.exists(rom_file): feedback(f"Base rom is expected as {rom_file} in the Multiworld root folder please place/rename it there.") player_files = [] os.makedirs(player_files_path, exist_ok=True) for file in os.listdir(player_files_path): lfile = file.lower() - if lfile.endswith(".yaml") and lfile != meta_file_path.lower(): + if lfile.endswith(".yaml") and lfile != meta_file_path.lower() and lfile != weights_file_path.lower(): player_files.append(file) - print(f"Found player's file {file}.") - player_count = len(player_files) - if player_count == 0: - feedback(f"No player files found. Please put them in a {player_files_path} folder.") - else: - print(player_count, "Players found.") + logging.info(f"Found player's file {file}.") player_string = "" for i, file in enumerate(player_files, 1): player_string += f"--p{i} \"{os.path.join(player_files_path, file)}\" " - if os.path.exists("BerserkerMultiServer.exe"): - basemysterycommand = "BerserkerMystery.exe" #compiled windows + basemysterycommand = "BerserkerMystery.exe" # compiled windows elif os.path.exists("BerserkerMultiServer"): - basemysterycommand = "BerserkerMystery" # compiled linux + basemysterycommand = "BerserkerMystery" # compiled linux else: basemysterycommand = f"py -{py_version} Mystery.py" # source - command = f"{basemysterycommand} --multi {len(player_files)} {player_string} " \ + weights_file_path = os.path.join(player_files_path, weights_file_path) + if os.path.exists(weights_file_path): + target_player_count = max(len(player_files), target_player_count) + else: + target_player_count = len(player_files) + + if target_player_count == 0: + feedback(f"No player files found. Please put them in a {player_files_path} folder.") + else: + logging.info(f"{target_player_count} Players found.") + + command = f"{basemysterycommand} --multi {target_player_count} {player_string} " \ f"--rom \"{rom_file}\" --enemizercli \"{enemizer_path}\" " \ - f"--outputpath \"{output_path}\" --teams {teams}" + f"--outputpath \"{output_path}\" --teams {teams} --plando \"{plando_options}\"" if create_spoiler: command += " --create_spoiler" @@ -107,13 +117,15 @@ if __name__ == "__main__": command += " --race" if os.path.exists(os.path.join(player_files_path, meta_file_path)): command += f" --meta {os.path.join(player_files_path, meta_file_path)}" + if os.path.exists(weights_file_path): + command += f" --weights {weights_file_path}" - print(command) + logging.info(command) import time start = time.perf_counter() text = subprocess.check_output(command, shell=True).decode() - print(f"Took {time.perf_counter() - start:.3f} seconds to generate multiworld.") + logging.info(f"Took {time.perf_counter() - start:.3f} seconds to generate multiworld.") seedname = "" for segment in text.split(): @@ -136,9 +148,10 @@ if __name__ == "__main__": if any((zip_roms, zip_multidata, zip_spoiler, zip_diffs)): import zipfile - compression = {1 : zipfile.ZIP_DEFLATED, - 2 : zipfile.ZIP_LZMA, - 3 : zipfile.ZIP_BZIP2}[zip_format] + + compression = {1: zipfile.ZIP_DEFLATED, + 2: zipfile.ZIP_LZMA, + 3: zipfile.ZIP_BZIP2}[zip_format] typical_zip_ending = {1: "zip", 2: "7z", @@ -150,17 +163,17 @@ if __name__ == "__main__": def pack_file(file: str): with ziplock: zf.write(os.path.join(output_path, file), file) - print(f"Packed {file} into zipfile {zipname}") + logging.info(f"Packed {file} into zipfile {zipname}") def remove_zipped_file(file: str): os.remove(os.path.join(output_path, file)) - print(f"Removed {file} which is now present in the zipfile") + logging.info(f"Removed {file} which is now present in the zipfile") zipname = os.path.join(output_path, f"AP_{seedname}.{typical_zip_ending}") - print(f"Creating zipfile {zipname}") + logging.info(f"Creating zipfile {zipname}") ipv4 = (host if host else get_public_ipv4()) + ":" + str(port) @@ -209,10 +222,11 @@ if __name__ == "__main__": baseservercommand = "BerserkerMultiServer" # compiled linux else: baseservercommand = f"py -{py_version} MultiServer.py" # source - #don't have a mac to test that. If you try to run compiled on mac, good luck. + # don't have a mac to test that. If you try to run compiled on mac, good luck. subprocess.call(f"{baseservercommand} --multidata {os.path.join(output_path, multidataname)}") except: import traceback + traceback.print_exc() input("Press enter to close") diff --git a/MultiServer.py b/MultiServer.py index 540bae20..b29a3d36 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -30,7 +30,7 @@ from Utils import get_item_name_from_id, get_location_name_from_address, \ ReceivedItem, _version_tuple, restricted_loads from NetUtils import Node, Endpoint -console_names = frozenset(set(Items.item_table) | set(Regions.location_table) | set(Items.item_name_groups)) +console_names = frozenset(set(Items.item_table) | set(Regions.location_table) | set(Items.item_name_groups) | set(Regions.key_drop_data)) CLIENT_PLAYING = 0 CLIENT_GOAL = 1 @@ -59,6 +59,15 @@ class Client(Endpoint): class Context(Node): + simple_options = {"hint_cost": int, + "location_check_points": int, + "server_password": str, + "password": str, + "forfeit_mode": str, + "remaining_mode": str, + "item_cheat": bool, + "compatibility": int} + def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int, hint_cost: int, item_cheat: bool, forfeit_mode: str = "disabled", remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2): @@ -104,6 +113,7 @@ class Context(Node): self.auto_saver_thread = None self.save_dirty = False self.tags = ['AP'] + self.minimum_client_versions: typing.Dict[typing.Tuple[int, int], Utils.Version] = {} def load(self, multidatapath: str, use_embedded_server_options: bool = False): with open(multidatapath, 'rb') as f: @@ -113,6 +123,16 @@ class Context(Node): self.data_filename = multidatapath def _load(self, decoded_obj: dict, use_embedded_server_options: bool): + if "minimum_versions" in jsonobj: + mdata_ver = tuple(jsonobj["minimum_versions"]["server"]) + if mdata_ver > Utils._version_tuple: + raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver}," + f"however this server is of version {Utils._version_tuple}") + clients_ver = jsonobj["minimum_versions"].get("clients", []) + self.minimum_client_versions = {} + for team, player, version in clients_ver: + self.minimum_client_versions[team, player] = Utils.Version(*version) + for team, names in enumerate(decoded_obj['names']): for player, name in enumerate(names, 1): self.player_names[(team, player)] = name @@ -127,15 +147,23 @@ class Context(Node): self._set_options(server_options) def _set_options(self, server_options: dict): - - sentinel = object() for key, value in server_options.items(): - if key not in self.embedded_blacklist: - current = getattr(self, key, sentinel) - if current is not sentinel: - logging.debug(f"Setting server option {key} to {value} from supplied multidata") - setattr(self, key, value) - self.item_cheat = not server_options.get("disable_item_cheat", True) + data_type = self.simple_options.get(key, None) + if data_type is not None: + if value not in {False, True, None}: # some can be boolean OR text, such as password + try: + value = data_type(value) + except Exception as e: + try: + raise Exception(f"Could not set server option {key}, skipping.") from e + except Exception as e: + logging.exception(e) + logging.debug(f"Setting server option {key} to {value} from supplied multidata") + setattr(self, key, value) + elif key == "disable_item_cheat": + self.item_cheat = not bool(value) + else: + logging.debug(f"Unrecognized server option {key}") def save(self, now=False) -> bool: if self.saving: @@ -435,6 +463,7 @@ def send_new_items(ctx: Context): def forfeit_player(ctx: Context, team: int, slot: int): all_locations = {values[0] for values in Regions.location_table.values() if type(values[0]) is int} + all_locations.update({values[1] for values in Regions.key_drop_data.values()}) ctx.notify_all("%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1)) register_location_checks(ctx, team, slot, all_locations) @@ -450,10 +479,12 @@ def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[int]: def register_location_checks(ctx: Context, team: int, slot: int, locations): found_items = False new_locations = set(locations) - ctx.location_checks[team, slot] + known_locations = set() if new_locations: ctx.client_activity_timers[team, slot] = datetime.datetime.now(datetime.timezone.utc) for location in new_locations: if (location, slot) in ctx.locations: + known_locations.add(location) target_item, target_player = ctx.locations[(location, slot)] if target_player != slot or slot in ctx.remote_items: found = False @@ -478,7 +509,7 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations): if client.team == team and client.wants_item_notification: asyncio.create_task( ctx.send_msgs(client, [['ItemFound', (target_item, location, slot)]])) - ctx.location_checks[team, slot] |= new_locations + ctx.location_checks[team, slot] |= known_locations send_new_items(ctx) if found_items: @@ -510,7 +541,7 @@ def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[ def collect_hints_location(ctx: Context, team: int, slot: int, location: str) -> typing.List[Utils.Hint]: hints = [] - seeked_location = Regions.location_table[location][0] + seeked_location = Regions.lookup_name_to_id[location] for check, result in ctx.locations.items(): location_id, finding_player = check if finding_player == slot and location_id == seeked_location: @@ -644,15 +675,6 @@ class CommandProcessor(metaclass=CommandMeta): class CommonCommandProcessor(CommandProcessor): ctx: Context - simple_options = {"hint_cost": int, - "location_check_points": int, - "server_password": str, - "password": str, - "forfeit_mode": str, - "item_cheat": bool, - "auto_save_interval": int, - "compatibility": int} - def _cmd_countdown(self, seconds: str = "10") -> bool: """Start a countdown in seconds""" try: @@ -665,7 +687,7 @@ class CommonCommandProcessor(CommandProcessor): def _cmd_options(self): """List all current options. Warning: lists password.""" self.output("Current options:") - for option in self.simple_options: + for option in self.ctx.simple_options: if option == "server_password" and self.marker == "!": #Do not display the server password to the client. self.output(f"Option server_password is set to {('*' * random.randint(4,16))}") else: @@ -802,10 +824,7 @@ class ClientMessageProcessor(CommonCommandProcessor): def _cmd_missing(self) -> bool: """List all missing location checks from the server's perspective""" - locations = [] - for location_id, location_name in Regions.lookup_id_to_name.items(): # cheat console is -1, keep in mind - if location_id != -1 and location_id not in self.ctx.location_checks[self.client.team, self.client.slot]: - locations.append(location_name) + locations = get_missing_checks(self.ctx, self.client) if len(locations) > 0: texts = [f'Missing: {location}\n' for location in locations] @@ -930,6 +949,15 @@ class ClientMessageProcessor(CommonCommandProcessor): self.output(response) return False +def get_missing_checks(ctx: Context, client: Client) -> list: + locations = [] + #for location_id in [k[0] for k, v in ctx.locations if k[1] == client.slot]: + # if location_id not in ctx.location_checks[client.team, client.slot]: + # locations.append(Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}')) + for location_id, location_name in Regions.lookup_id_to_name.items(): # cheat console is -1, keep in mind + if location_id != -1 and location_id not in ctx.location_checks[client.team, client.slot] and (location_id, client.slot) in ctx.locations: + locations.append(location_name) + return locations def get_client_points(ctx: Context, client: Client) -> int: return (ctx.location_check_points * len(ctx.location_checks[client.team, client.slot]) - @@ -972,9 +1000,14 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args): client.name = ctx.player_names[(team, slot)] client.team = team client.slot = slot + minver = Utils.Version(*(ctx.minimum_client_versions.get((team, slot), (0,0,0)))) + if minver > tuple(args.get('version', Client.version)): + errors.add('IncompatibleVersion') + if ctx.compatibility == 1 and "AP" not in args.get('tags', Client.tags): errors.add('IncompatibleVersion') - elif ctx.compatibility == 0 and args.get('version', Client.version) != list(_version_tuple): + #only exact version match allowed + elif ctx.compatibility == 0 and tuple(args.get('version', Client.version)) != _version_tuple: errors.add('IncompatibleVersion') if errors: logging.info(f"A client connection was refused due to: {errors}") @@ -986,7 +1019,7 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args): client.tags = args.get('tags', Client.tags) reply = [['Connected', [(client.team, client.slot), [(p, ctx.get_aliased_name(t, p)) for (t, p), n in ctx.player_names.items() if - t == client.team]]]] + t == client.team], get_missing_checks(ctx, client)]]] items = get_received_items(ctx, client.team, client.slot) if items: reply.append(['ReceivedItems', (0, tuplize_received_items(items))]) @@ -1215,7 +1248,7 @@ class ServerCommandProcessor(CommonCommandProcessor): def _cmd_option(self, option_name: str, option: str): """Set options for the server. Warning: expires on restart""" - attrtype = self.simple_options.get(option_name, None) + attrtype = self.ctx.simple_options.get(option_name, None) if attrtype: if attrtype == bool: def attrtype(input_text: str): @@ -1229,7 +1262,7 @@ class ServerCommandProcessor(CommonCommandProcessor): self.output(f"Set option {option_name} to {getattr(self.ctx, option_name)}") return True else: - known = (f"{option}:{otype}" for option, otype in self.simple_options.items()) + known = (f"{option}:{otype}" for option, otype in self.ctx.simple_options.items()) self.output(f"Unrecognized Option {option_name}, known: " f"{', '.join(known)}") return False diff --git a/Mystery.py b/Mystery.py index 3ee8976d..69d95533 100644 --- a/Mystery.py +++ b/Mystery.py @@ -7,11 +7,13 @@ import typing import os import ModuleUpdate +from BaseClasses import PlandoItem, PlandoConnection ModuleUpdate.update() +import Bosses from Utils import parse_yaml -from worlds.alttp.Rom import get_sprite_from_name +from worlds.alttp.Rom import Sprite from worlds.alttp.EntranceRandomizer import parse_arguments from worlds.alttp.Main import main as ERmain from worlds.alttp.Main import get_seed, seeddigits @@ -43,10 +45,13 @@ def mystery_argparse(): parser.add_argument('--create_diff', action="store_true") parser.add_argument('--yaml_output', default=0, type=lambda value: min(max(int(value), 0), 255), help='Output rolled mystery results to yaml up to specified number (made for async multiworld)') + parser.add_argument('--plando', default="bosses", + help='List of options that can be set manually. Can be combined, for example "bosses, items"') for player in range(1, multiargs.multi + 1): parser.add_argument(f'--p{player}', help=argparse.SUPPRESS) args = parser.parse_args() + args.plando: typing.Set[str] = {arg.strip().lower() for arg in args.plando.split(",")} return args @@ -57,12 +62,12 @@ def main(args=None, callback=ERmain): seed = get_seed(args.seed) random.seed(seed) - if args.race: - random.seed() # reset to time-based random source - seedname = "M" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)) print(f"Generating mystery for {args.multi} player{'s' if args.multi > 1 else ''}, {seedname} Seed {seed}") + if args.race: + random.seed() # reset to time-based random source + weights_cache = {} if args.weights: try: @@ -143,7 +148,8 @@ def main(args=None, callback=ERmain): if args.enemizercli: erargs.enemizercli = args.enemizercli - settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()} + settings_cache = {k: (roll_settings(v, args.plando) if args.samesettings else None) + for k, v in weights_cache.items()} player_path_cache = {} for player in range(1, args.multi + 1): player_path_cache[player] = getattr(args, f'p{player}') if getattr(args, f'p{player}') else args.weights @@ -166,8 +172,9 @@ def main(args=None, callback=ERmain): path = player_path_cache[player] if path: try: - settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path]) - if settings.sprite and not os.path.isfile(settings.sprite) and not get_sprite_from_name( + settings = settings_cache[path] if settings_cache[path] else \ + roll_settings(weights_cache[path], args.plando) + if settings.sprite and not os.path.isfile(settings.sprite) and not Sprite.get_sprite_from_name( settings.sprite): logging.warning( f"Warning: The chosen sprite, \"{settings.sprite}\", for yaml \"{path}\", does not exist.") @@ -238,6 +245,8 @@ def convert_to_on_off(value): def get_choice(option, root, value=None) -> typing.Any: if option not in root: return value + if type(root[option]) is list: + return interpret_on_off(random.choices(root[option])[0]) if type(root[option]) is not dict: return interpret_on_off(root[option]) if not root[option]: @@ -245,7 +254,7 @@ def get_choice(option, root, value=None) -> typing.Any: if any(root[option].values()): return interpret_on_off( random.choices(list(root[option].keys()), weights=list(map(int, root[option].values())))[0]) - raise RuntimeError(f"All options specified in {option} are weighted as zero.") + raise RuntimeError(f"All options specified in \"{option}\" are weighted as zero.") def handle_name(name: str): @@ -258,7 +267,27 @@ def prefer_int(input_data: str) -> typing.Union[str, int]: except: return input_data -def roll_settings(weights): + +available_boss_names: typing.Set[str] = {boss.lower() for boss in Bosses.boss_table if boss not in + {'Agahnim', 'Agahnim2', 'Ganon'}} + +boss_shuffle_options = {None: 'none', + 'none': 'none', + 'simple': 'basic', + 'full': 'normal', + 'random': 'chaos', + 'singularity': 'singularity', + 'duality': 'singularity' + } + + +def roll_percentage(percentage: typing.Union[int, float]) -> bool: + """Roll a percentage chance. + percentage is expected to be in range [0, 100]""" + return random.random() < (float(percentage) / 100) + + +def roll_settings(weights, plando_options: typing.Set[str] = frozenset(("bosses"))): ret = argparse.Namespace() if "linked_options" in weights: weights = weights.copy() # make sure we don't write back to other weights sets in same_settings @@ -266,8 +295,18 @@ def roll_settings(weights): if "name" not in option_set: raise ValueError("One of your linked options does not have a name.") try: - if random.random() < (float(option_set["percentage"]) / 100): + if roll_percentage(option_set["percentage"]): + logging.debug(f"Linked option {option_set['name']} triggered.") + logging.debug(f'Applying {option_set["options"]}') + new_options = set(option_set["options"]) - set(weights) weights.update(option_set["options"]) + if new_options: + for new_option in new_options: + logging.warning(f'Linked Suboption "{new_option}" of "{option_set["name"]}" did not ' + f'overwrite a root option. ' + f"This is probably in error.") + else: + logging.debug(f"linked option {option_set['name']} skipped.") except Exception as e: raise ValueError(f"Linked option {option_set['name']} is destroyed. " f"Please fix your linked option.") from e @@ -384,14 +423,24 @@ def roll_settings(weights): ret.item_functionality = get_choice('item_functionality', weights) - ret.shufflebosses = {None: 'none', - 'none': 'none', - 'simple': 'basic', - 'full': 'normal', - 'random': 'chaos', - 'singularity': 'singularity', - 'duality': 'singularity' - }[get_choice('boss_shuffle', weights)] + boss_shuffle = get_choice('boss_shuffle', weights) + + if boss_shuffle in boss_shuffle_options: + ret.shufflebosses = boss_shuffle_options[boss_shuffle] + elif "bosses" in plando_options: + options = boss_shuffle.lower().split(";") + remainder_shuffle = "none" # vanilla + bosses = [] + for boss in options: + if boss in boss_shuffle_options: + remainder_shuffle = boss + elif boss not in available_boss_names and not "-" in boss: + raise ValueError(f"Unknown Boss name or Boss shuffle option {boss}.") + else: + bosses.append(boss) + ret.shufflebosses = ";".join(bosses + [remainder_shuffle]) + else: + raise Exception(f"Boss Shuffle {boss_shuffle} is unknown and boss plando is turned off.") ret.enemy_shuffle = {'none': False, 'shuffled': 'shuffled', @@ -444,12 +493,22 @@ def roll_settings(weights): 'timed_countdown': 'timed-countdown', 'display': 'display'}[get_choice('timer', weights, False)] + ret.countdown_start_time = int(get_choice('countdown_start_time', weights, 10)) + ret.red_clock_time = int(get_choice('red_clock_time', weights, -2)) + ret.blue_clock_time = int(get_choice('blue_clock_time', weights, 2)) + ret.green_clock_time = int(get_choice('green_clock_time', weights, 4)) + ret.dungeon_counters = get_choice('dungeon_counters', weights, 'default') ret.progressive = convert_to_on_off(get_choice('progressive', weights, 'on')) ret.shuffle_prizes = get_choice('shuffle_prizes', weights, "g") + ret.required_medallions = (get_choice("misery_mire_medallion", weights, "random"), + get_choice("turtle_rock_medallion", weights, "random")) + for medallion in ret.required_medallions: + if medallion not in {"random", "Ether", "Bombos", "Quake"}: + raise Exception(f"unknown Medallion {medallion}") inventoryweights = weights.get('startinventory', {}) startitems = [] for item in inventoryweights.keys(): @@ -483,6 +542,46 @@ def roll_settings(weights): ret.local_items = ",".join(ret.local_items) + ret.non_local_items = set() + for item_name in weights.get('non_local_items', []): + items = item_name_groups.get(item_name, {item_name}) + for item in items: + if item in item_table: + ret.non_local_items.add(item) + else: + raise Exception(f"Could not force item {item} to be world-non-local, as it was not recognized.") + + ret.non_local_items = ",".join(ret.non_local_items) + + ret.plando_items = [] + if "items" in plando_options: + options = weights.get("plando_items", []) + for placement in options: + if roll_percentage(get_choice("percentage", placement, 100)): + item = get_choice("item", placement) + location = get_choice("location", placement) + from_pool = get_choice("from_pool", placement, True) + location_world = get_choice("world", placement, False) + ret.plando_items.append(PlandoItem(item, location, location_world, from_pool)) + + ret.plando_texts = {} + if "texts" in plando_options: + options = weights.get("plando_texts", []) + for placement in options: + if roll_percentage(get_choice("percentage", placement, 100)): + ret.plando_texts[str(get_choice("at", placement))] = str(get_choice("text", placement)) + + ret.plando_connections = [] + if "connections" in plando_options: + options = weights.get("plando_connections", []) + for placement in options: + if roll_percentage(get_choice("percentage", placement, 100)): + ret.plando_connections.append(PlandoConnection( + get_choice("entrance", placement), + get_choice("exit", placement), + get_choice("direction", placement, "both") + )) + if 'rom' in weights: romweights = weights['rom'] @@ -516,6 +615,11 @@ def roll_settings(weights): ret.heartbeep = convert_to_on_off(get_choice('heartbeep', romweights, "normal")) ret.ow_palettes = get_choice('ow_palettes', romweights, "default") ret.uw_palettes = get_choice('uw_palettes', romweights, "default") + ret.hud_palettes = get_choice('hud_palettes', romweights, "default") + ret.sword_palettes = get_choice('sword_palettes', romweights, "default") + ret.shield_palettes = get_choice('shield_palettes', romweights, "default") + ret.link_palettes = get_choice('link_palettes', romweights, "default") + else: ret.quickswap = True ret.sprite = "Link" diff --git a/Utils.py b/Utils.py index 01055770..d031986c 100644 --- a/Utils.py +++ b/Utils.py @@ -1,12 +1,18 @@ from __future__ import annotations + import typing def tuplize_version(version: str) -> typing.Tuple[int, ...]: - return tuple(int(piece, 10) for piece in version.split(".")) + return Version(*(int(piece, 10) for piece in version.split("."))) -__version__ = "1.0.0" +class Version(typing.NamedTuple): + major: int + minor: int + micro: int + +__version__ = "0.1.0" _version_tuple = tuplize_version(__version__) import os @@ -37,11 +43,11 @@ def int32_as_bytes(value): def pc_to_snes(value): - return ((value<<1) & 0x7F0000)|(value & 0x7FFF)|0x8000 + return ((value << 1) & 0x7F0000) | (value & 0x7FFF) | 0x8000 def snes_to_pc(value): - return ((value & 0x7F0000)>>1)|(value & 0x7FFF) + return ((value & 0x7F0000) >> 1) | (value & 0x7FFF) def parse_player_names(names, players, teams): @@ -82,6 +88,7 @@ def local_path(*path): return os.path.join(local_path.cached_path, *path) + local_path.cached_path = None @@ -93,8 +100,10 @@ def output_path(*path): os.makedirs(os.path.dirname(path), exist_ok=True) return path + output_path.cached_path = None + def open_file(filename): if sys.platform == 'win32': os.startfile(filename) @@ -102,9 +111,10 @@ def open_file(filename): open_command = 'open' if sys.platform == 'darwin' else 'xdg-open' subprocess.call([open_command, filename]) + def close_console(): if sys.platform == 'win32': - #windows + # windows import ctypes.wintypes try: ctypes.windll.kernel32.FreeConsole() @@ -138,6 +148,7 @@ class Hint(typing.NamedTuple): def __hash__(self): return hash((self.receiving_player, self.finding_player, self.location, self.item, self.entrance)) + def get_public_ipv4() -> str: import socket import urllib.request @@ -153,6 +164,7 @@ def get_public_ipv4() -> str: pass # we could be offline, in a local game, so no point in erroring out return ip + def get_public_ipv6() -> str: import socket import urllib.request @@ -165,6 +177,91 @@ def get_public_ipv6() -> str: pass # we could be offline, in a local game, or ipv6 may not be available return ip + +def get_default_options() -> dict: + if not hasattr(get_default_options, "options"): + # Refer to host.yaml for comments as to what all these options mean. + options = { + "general_options": { + "rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc", + "qusb2snes": "QUsb2Snes\\QUsb2Snes.exe", + "rom_start": True, + "output_path": "output", + }, + "server_options": { + "host": None, + "port": 38281, + "password": None, + "multidata": None, + "savefile": None, + "disable_save": False, + "loglevel": "info", + "server_password": None, + "disable_item_cheat": False, + "location_check_points": 1, + "hint_cost": 1000, + "forfeit_mode": "goal", + "remaining_mode": "goal", + "auto_shutdown": 0, + "compatibility": 2, + }, + "multi_mystery_options": { + "teams": 1, + "enemizer_path": "EnemizerCLI/EnemizerCLI.Core.exe", + "player_files_path": "Players", + "players": 0, + "weights_file_path": "weights.yaml", + "meta_file_path": "meta.yaml", + "player_name": "", + "create_spoiler": 1, + "zip_roms": 0, + "zip_diffs": 2, + "zip_spoiler": 0, + "zip_multidata": 1, + "zip_format": 1, + "race": 0, + "cpu_threads": 0, + "max_attempts": 0, + "take_first_working": False, + "keep_all_seeds": False, + "log_output_path": "Output Logs", + "log_level": None, + "plando_options": "bosses", + } + } + + get_default_options.options = options + return get_default_options.options + + +blacklisted_options = {"multi_mystery_options.cpu_threads", + "multi_mystery_options.max_attempts", + "multi_mystery_options.take_first_working", + "multi_mystery_options.keep_all_seeds", + "multi_mystery_options.log_output_path", + "multi_mystery_options.log_level"} + + +def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict: + import logging + for key, value in src.items(): + new_keys = keys.copy() + new_keys.append(key) + option_name = '.'.join(new_keys) + if key not in dest: + dest[key] = value + if filename.endswith("options.yaml") and option_name not in blacklisted_options: + logging.info(f"Warning: {filename} is missing {option_name}") + elif isinstance(value, dict): + if not isinstance(dest.get(key, None), dict): + if filename.endswith("options.yaml") and option_name not in blacklisted_options: + logging.info(f"Warning: {filename} has {option_name}, but it is not a dictionary. overwriting.") + dest[key] = value + else: + dest[key] = update_options(value, dest[key], filename, new_keys) + return dest + + def get_options() -> dict: if not hasattr(get_options, "options"): locations = ("options.yaml", "host.yaml", @@ -173,7 +270,9 @@ def get_options() -> dict: for location in locations: if os.path.exists(location): with open(location) as f: - get_options.options = parse_yaml(f.read()) + options = parse_yaml(f.read()) + + get_options.options = update_options(get_default_options(), options, location, list()) break else: raise FileNotFoundError(f"Could not find {locations[1]} to load options.") @@ -222,28 +321,32 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]: if hasattr(get_adjuster_settings, "adjuster_settings"): adjuster_settings = getattr(get_adjuster_settings, "adjuster_settings") else: - adjuster_settings = persistent_load().get("adjuster", {}).get("last_settings", {}) + adjuster_settings = persistent_load().get("adjuster", {}).get("last_settings_3", {}) + if adjuster_settings: import pprint import Patch adjuster_settings.rom = romfile adjuster_settings.baserom = Patch.get_base_rom_path() whitelist = {"disablemusic", "fastmenu", "heartbeep", "heartcolor", "ow_palettes", "quickswap", - "uw_palettes"} + "uw_palettes", "sprite"} printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist} - sprite = getattr(adjuster_settings, "sprite", None) - if sprite: - printed_options["sprite"] = adjuster_settings.sprite.name + if hasattr(get_adjuster_settings, "adjust_wanted"): adjust_wanted = getattr(get_adjuster_settings, "adjust_wanted") + elif persistent_load().get("adjuster", {}).get("never_adjust", False): # never adjust, per user request + return romfile, False else: adjust_wanted = input(f"Last used adjuster settings were found. Would you like to apply these? \n" f"{pprint.pformat(printed_options)}\n" - f"Enter yes or no: ") + f"Enter yes, no or never: ") if adjust_wanted and adjust_wanted.startswith("y"): adjusted = True import AdjusterMain _, romfile = AdjusterMain.adjust(adjuster_settings) + elif adjust_wanted and "never" in adjust_wanted: + persistent_store("adjuster", "never_adjust", True) + return romfile, False else: adjusted = False import logging @@ -255,7 +358,6 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]: return romfile, False - class ReceivedItem(typing.NamedTuple): item: int location: int diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index ad138bcc..a6c395c9 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -4,6 +4,7 @@ So unless you're Berserker you need to include license information.""" import os import uuid import base64 +import socket from pony.flask import Pony from flask import Flask, request, redirect, url_for, render_template, Response, session, abort, send_from_directory @@ -30,8 +31,8 @@ app.config["DEBUG"] = False app.config["PORT"] = 80 app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH'] = 4 * 1024 * 1024 # 4 megabyte limit -# if you want persistent sessions on your server, make sure you make this a constant in your config.yaml -app.config["SECRET_KEY"] = os.urandom(32) +# if you want to deploy, make sure you have a non-guessable secret key +app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8") # at what amount of worlds should scheduling be used, instead of rolling in the webthread app.config["JOB_THRESHOLD"] = 2 app.config['SESSION_PERMANENT'] = True @@ -47,6 +48,8 @@ app.config["PONY"] = { } app.config["MAX_ROLL"] = 20 app.config["CACHE_TYPE"] = "simple" +app.config["JSON_AS_ASCII"] = False + app.autoversion = True app.config["HOSTNAME"] = "berserkermulti.world" @@ -85,16 +88,21 @@ def tutorial(lang='en'): @app.route('/player-settings') +def player_settings_simple(): + return render_template("playerSettings.html") + + +@app.route('/weighted-settings') def player_settings(): - return render_template("player-settings.html") + return render_template("weightedSettings.html") @app.route('/seed/') -def view_seed(seed: UUID): +def viewSeed(seed: UUID): seed = Seed.get(id=seed) if not seed: abort(404) - return render_template("view_seed.html", seed=seed, + return render_template("viewSeed.html", seed=seed, rooms=[room for room in seed.rooms if room.owner == session["_id"]]) @@ -105,7 +113,7 @@ def new_room(seed: UUID): abort(404) room = Room(seed=seed, owner=session["_id"], tracker=uuid4()) commit() - return redirect(url_for("host_room", room=room.id)) + return redirect(url_for("hostRoom", room=room.id)) def _read_log(path: str): @@ -124,7 +132,7 @@ def display_log(room: UUID): @app.route('/hosted/', methods=['GET', 'POST']) -def host_room(room: UUID): +def hostRoom(room: UUID): room = Room.get(id=room) if room is None: return abort(404) @@ -137,7 +145,7 @@ def host_room(room: UUID): with db_session: room.last_activity = datetime.utcnow() # will trigger a spinup, if it's not already running - return render_template("host_room.html", room=room) + return render_template("hostRoom.html", room=room) @app.route('/favicon.ico') @@ -147,4 +155,5 @@ def favicon(): from WebHostLib.customserver import run_server_process -from . import tracker, upload, landing, check, generate, downloads # to trigger app routing picking up on it +from . import tracker, upload, landing, check, generate, downloads, api # to trigger app routing picking up on it +app.register_blueprint(api.api_endpoints) diff --git a/WebHostLib/api/__init__.py b/WebHostLib/api/__init__.py new file mode 100644 index 00000000..76878e49 --- /dev/null +++ b/WebHostLib/api/__init__.py @@ -0,0 +1,24 @@ +"""API endpoints package.""" +from uuid import UUID + +from flask import Blueprint, abort + +from ..models import Room + +api_endpoints = Blueprint('api', __name__, url_prefix="/api") + +from . import generate, user # trigger registration + + +# unsorted/misc endpoints + +@api_endpoints.route('/room_status/') +def room_info(room: UUID): + room = Room.get(id=room) + if room is None: + return abort(404) + return {"tracker": room.tracker, + "players": room.seed.multidata["names"], + "last_port": room.last_port, + "last_activity": room.last_activity, + "timeout": room.timeout} diff --git a/WebHostLib/api/generate.py b/WebHostLib/api/generate.py new file mode 100644 index 00000000..cc760559 --- /dev/null +++ b/WebHostLib/api/generate.py @@ -0,0 +1,73 @@ +import pickle +from uuid import UUID + +from . import api_endpoints +from flask import request, session, url_for +from pony.orm import commit + +from WebHostLib import app, Generation, STATE_QUEUED, Seed, STATE_ERROR +from WebHostLib.check import get_yaml_data, roll_options + + +@api_endpoints.route('/generate', methods=['POST']) +def generate_api(): + try: + options = {} + race = False + + if 'file' in request.files: + file = request.files['file'] + options = get_yaml_data(file) + if type(options) == str: + return {"text": options}, 400 + if "race" in request.form: + race = bool(0 if request.form["race"] in {"false"} else int(request.form["race"])) + + json_data = request.get_json() + if json_data: + if 'weights' in json_data: + # example: options = {"player1weights" : {}} + options = json_data["weights"] + if "race" in json_data: + race = bool(0 if json_data["race"] in {"false"} else int(json_data["race"])) + if not options: + return {"text": "No options found. Expected file attachment or json weights." + }, 400 + + if len(options) > app.config["MAX_ROLL"]: + return {"text": "Max size of multiworld exceeded", + "detail": app.config["MAX_ROLL"]}, 409 + + results, gen_options = roll_options(options) + if any(type(result) == str for result in results.values()): + return {"text": str(results), + "detail": results}, 400 + else: + gen = Generation( + options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}), + # convert to json compatible + meta=pickle.dumps({"race": race}), state=STATE_QUEUED, + owner=session["_id"]) + commit() + return {"text": f"Generation of seed {gen.id} started successfully.", + "detail": gen.id, + "encoded": app.url_map.converters["suuid"].to_url(None, gen.id), + "wait_api_url": url_for("api.wait_seed_api", seed=gen.id, _external=True), + "url": url_for("wait_seed", seed=gen.id, _external=True)}, 201 + except Exception as e: + return {"text": "Uncaught Exception:" + str(e)}, 500 + + +@api_endpoints.route('/status/') +def wait_seed_api(seed: UUID): + seed_id = seed + seed = Seed.get(id=seed_id) + if seed: + return {"text": "Generation done"}, 201 + generation = Generation.get(id=seed_id) + + if not generation: + return {"text": "Generation not found"}, 404 + elif generation.state == STATE_ERROR: + return {"text": "Generation failed"}, 500 + return {"text": "Generation running"}, 202 diff --git a/WebHostLib/api/user.py b/WebHostLib/api/user.py new file mode 100644 index 00000000..43a7bdcf --- /dev/null +++ b/WebHostLib/api/user.py @@ -0,0 +1,33 @@ +from flask import session, jsonify + +from WebHostLib.models import * +from . import api_endpoints + + +@api_endpoints.route('/get_rooms') +def get_rooms(): + response = [] + for room in select(room for room in Room if room.owner == session["_id"]): + response.append({ + "room_id": room.id, + "seed_id": room.seed.id, + "creation_time": room.creation_time, + "last_activity": room.last_activity, + "last_port": room.last_port, + "timeout": room.timeout, + "tracker": room.tracker, + "players": room.seed.multidata["names"] if room.seed.multidata else [["Singleplayer"]], + }) + return jsonify(response) + + +@api_endpoints.route('/get_seeds') +def get_seeds(): + response = [] + for seed in select(seed for seed in Seed if seed.owner == session["_id"]): + response.append({ + "seed_id": seed.id, + "creation_time": seed.creation_time, + "players": seed.multidata["names"] if seed.multidata else [["Singleplayer"]], + }) + return jsonify(response) \ No newline at end of file diff --git a/WebHostLib/check.py b/WebHostLib/check.py index 7b2e275a..86d79b02 100644 --- a/WebHostLib/check.py +++ b/WebHostLib/check.py @@ -28,8 +28,8 @@ def mysterycheck(): if type(options) == str: flash(options) else: - results, _ = roll_yamls(options) - return render_template("checkresult.html", results=results) + results, _ = roll_options(options) + return render_template("checkResult.html", results=results) return render_template("check.html") @@ -60,17 +60,20 @@ def get_yaml_data(file) -> Union[Dict[str, str], str]: return options -def roll_yamls(options: Dict[str, Union[str, str]]) -> Tuple[Dict[str, Union[str, bool]], Dict[str, dict]]: +def roll_options(options: Dict[str, Union[dict, str]]) -> Tuple[Dict[str, Union[str, bool]], Dict[str, dict]]: results = {} rolled_results = {} for filename, text in options.items(): try: - yaml_data = parse_yaml(text) + if type(text) is dict: + yaml_data = text + else: + yaml_data = parse_yaml(text) except Exception as e: results[filename] = f"Failed to parse YAML data in {filename}: {e}" else: try: - rolled_results[filename] = roll_settings(yaml_data) + rolled_results[filename] = roll_settings(yaml_data, plando_options={"bosses"}) except Exception as e: results[filename] = f"Failed to generate mystery in {filename}: {e}" else: diff --git a/WebHostLib/customserver.py b/WebHostLib/customserver.py index 858e8c28..fb6cbb16 100644 --- a/WebHostLib/customserver.py +++ b/WebHostLib/customserver.py @@ -48,7 +48,7 @@ class DBCommandProcessor(ServerCommandProcessor): class WebHostContext(Context): def __init__(self): - super(WebHostContext, self).__init__("", 0, "", 1, 40, True, "enabled", "enabled", 0, 2) + super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", 0, 2) self.main_loop = asyncio.get_running_loop() self.video = {} self.tags = ["AP", "WebHost"] diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index ec89659f..4faebe96 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -11,7 +11,7 @@ import pickle from .models import * from WebHostLib import app -from .check import get_yaml_data, roll_yamls +from .check import get_yaml_data, roll_options @app.route('/generate', methods=['GET', 'POST']) @@ -27,9 +27,9 @@ def generate(race=False): if type(options) == str: flash(options) else: - results, gen_options = roll_yamls(options) + results, gen_options = roll_options(options) if any(type(result) == str for result in results.values()): - return render_template("checkresult.html", results=results) + return render_template("checkResult.html", results=results) elif len(gen_options) > app.config["MAX_ROLL"]: flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players for now. " f"If you have a larger group, please generate it yourself and upload it.") @@ -43,9 +43,15 @@ def generate(race=False): return redirect(url_for("wait_seed", seed=gen.id)) else: - seed_id = gen_game({name: vars(options) for name, options in gen_options.items()}, - race=race, owner=session["_id"].int) - return redirect(url_for("view_seed", seed=seed_id)) + try: + seed_id = gen_game({name: vars(options) for name, options in gen_options.items()}, + race=race, owner=session["_id"].int) + except BaseException as e: + from .autolauncher import handle_generation_failure + handle_generation_failure(e) + return render_template("seedError.html", seed_error=(e.__class__.__name__ + ": "+ str(e))) + + return redirect(url_for("viewSeed", seed=seed_id)) return render_template("generate.html", race=race) @@ -90,13 +96,14 @@ def gen_game(gen_options, race=False, owner=None, sid=None): del (erargs.progression_balancing) ERmain(erargs, seed) - return upload_to_db(target.name, owner, sid) - except BaseException: + return upload_to_db(target.name, owner, sid, race) + except BaseException as e: if sid: with db_session: gen = Generation.get(id=sid) if gen is not None: gen.state = STATE_ERROR + gen.meta = (e.__class__.__name__ + ": "+ str(e)).encode() raise @@ -105,19 +112,20 @@ def wait_seed(seed: UUID): seed_id = seed seed = Seed.get(id=seed_id) if seed: - return redirect(url_for("view_seed", seed=seed_id)) + return redirect(url_for("viewSeed", seed=seed_id)) generation = Generation.get(id=seed_id) if not generation: return "Generation not found." elif generation.state == STATE_ERROR: - return "Generation failed, please retry." - return render_template("wait_seed.html", seed_id=seed_id) + return render_template("seedError.html", seed_error=generation.meta.decode()) + return render_template("waitSeed.html", seed_id=seed_id) -def upload_to_db(folder, owner, sid): +def upload_to_db(folder, owner, sid, race:bool): patches = set() spoiler = "" + multidata = None for file in os.listdir(folder): file = os.path.join(folder, file) @@ -129,7 +137,7 @@ def upload_to_db(folder, owner, sid): player_id=player_id, player_name = player_name)) elif file.endswith(".txt"): spoiler = open(file, "rt", encoding="utf-8-sig").read() - elif file.endswith(".multidata"): + elif file.endswith(".archipelago"): multidata = open(file, "rb").read() if multidata: with db_session: diff --git a/WebHostLib/models.py b/WebHostLib/models.py index 069dfca2..8065839f 100644 --- a/WebHostLib/models.py +++ b/WebHostLib/models.py @@ -52,5 +52,5 @@ class Generation(db.Entity): id = PrimaryKey(UUID, default=uuid4) owner = Required(UUID) options = Required(bytes, lazy=True) # these didn't work as JSON on mariaDB, so they're getting pickled now - meta = Required(bytes, lazy=True) + meta = Required(bytes, lazy=True) # if state is -1 (error) this will contain an utf-8 encoded error message state = Required(int, default=0, index=True) diff --git a/WebHostLib/requirements.txt b/WebHostLib/requirements.txt index 31ead753..f7a97f65 100644 --- a/WebHostLib/requirements.txt +++ b/WebHostLib/requirements.txt @@ -1,7 +1,7 @@ flask>=1.1.2 -pony>=0.7.13 +pony>=0.7.14 waitress>=1.4.4 flask-caching>=1.9.0 Flask-Autoversion>=0.2.0 -Flask-Compress>=1.5.0 +Flask-Compress>=1.8.0 Flask-Limiter>=1.4 diff --git a/WebHostLib/static/assets/autodatatable.js b/WebHostLib/static/assets/autodatatable.js new file mode 100644 index 00000000..a1437514 --- /dev/null +++ b/WebHostLib/static/assets/autodatatable.js @@ -0,0 +1,9 @@ +window.addEventListener('load', () => { + let tables = $(".autodatatable").DataTable({ + "paging": false, + "ordering": true, + "info": false, + "dom": "t", + }); + console.log(tables); +}); diff --git a/WebHostLib/static/assets/check.js b/WebHostLib/static/assets/check.js new file mode 100644 index 00000000..91f54c05 --- /dev/null +++ b/WebHostLib/static/assets/check.js @@ -0,0 +1,9 @@ +window.addEventListener('load', () => { + document.getElementById('check-button').addEventListener('click', () => { + document.getElementById('file-input').click(); + }); + + document.getElementById('file-input').addEventListener('change', () => { + document.getElementById('check-form').submit(); + }); +}); diff --git a/WebHostLib/static/assets/layout.js b/WebHostLib/static/assets/cookieNotice.js similarity index 60% rename from WebHostLib/static/assets/layout.js rename to WebHostLib/static/assets/cookieNotice.js index fedf419d..4149d076 100644 --- a/WebHostLib/static/assets/layout.js +++ b/WebHostLib/static/assets/cookieNotice.js @@ -4,14 +4,11 @@ window.addEventListener('load', () => { const cookieNotice = document.createElement('div'); cookieNotice.innerText = "This website uses cookies to store information about the games you play."; - cookieNotice.style.position = "fixed"; - cookieNotice.style.bottom = "0"; - cookieNotice.style.left = "0"; - cookieNotice.style.width = "100%"; - cookieNotice.style.lineHeight = "40px"; - cookieNotice.style.backgroundColor = "#c7cda5"; - cookieNotice.style.textAlign = "center"; - cookieNotice.style.cursor = "pointer"; + cookieNotice.setAttribute('id', 'cookie-notice'); + const closeButton = document.createElement('span'); + closeButton.setAttribute('id', 'close-button'); + closeButton.innerText = 'X'; + cookieNotice.appendChild(closeButton); document.body.appendChild(cookieNotice); cookieNotice.addEventListener('click', () => { localStorage.setItem('cookieNotice', "1"); diff --git a/WebHostLib/static/assets/generate.js b/WebHostLib/static/assets/generate.js index 189d46af..4e2251f6 100644 --- a/WebHostLib/static/assets/generate.js +++ b/WebHostLib/static/assets/generate.js @@ -1,9 +1,9 @@ window.addEventListener('load', () => { - document.getElementById('upload-button').addEventListener('click', () => { + document.getElementById('generate-game-button').addEventListener('click', () => { document.getElementById('file-input').click(); }); document.getElementById('file-input').addEventListener('change', () => { - document.getElementById('upload-form').submit(); + document.getElementById('generate-game-form').submit(); }); }); diff --git a/WebHostLib/static/assets/hostGame.js b/WebHostLib/static/assets/hostGame.js new file mode 100644 index 00000000..db1ab1dd --- /dev/null +++ b/WebHostLib/static/assets/hostGame.js @@ -0,0 +1,11 @@ +window.addEventListener('load', () => { + document.getElementById('host-game-button').addEventListener('click', () => { + document.getElementById('file-input').click(); + }); + + document.getElementById('file-input').addEventListener('change', () => { + document.getElementById('host-game-form').submit(); + }); + + adjustFooterHeight(); +}); diff --git a/WebHostLib/static/assets/playerSettings.js b/WebHostLib/static/assets/playerSettings.js new file mode 100644 index 00000000..e74bd47b --- /dev/null +++ b/WebHostLib/static/assets/playerSettings.js @@ -0,0 +1,188 @@ +window.addEventListener('load', () => { + Promise.all([fetchSettingData(), fetchSpriteData()]).then((results) => { + // Page setup + createDefaultSettings(results[0]); + buildUI(results[0]); + adjustHeaderWidth(); + + // Event listeners + document.getElementById('export-settings').addEventListener('click', () => exportSettings()); + document.getElementById('generate-race').addEventListener('click', () => generateGame(true)) + document.getElementById('generate-game').addEventListener('click', () => generateGame()); + + // Name input field + const playerSettings = JSON.parse(localStorage.getItem('playerSettings')); + const nameInput = document.getElementById('player-name'); + nameInput.addEventListener('keyup', (event) => updateSetting(event)); + nameInput.value = playerSettings.name; + + // Sprite options + const spriteData = JSON.parse(results[1]); + const spriteSelect = document.getElementById('sprite'); + spriteData.sprites.forEach((sprite) => { + if (sprite.name.trim().length === 0) { return; } + const option = document.createElement('option'); + option.setAttribute('value', sprite.name.trim()); + if (playerSettings.rom.sprite === sprite.name.trim()) { option.selected = true; } + option.innerText = sprite.name; + spriteSelect.appendChild(option); + }); + }).catch((error) => { + console.error(error); + }) +}); + +const fetchSettingData = () => new Promise((resolve, reject) => { + const ajax = new XMLHttpRequest(); + ajax.onreadystatechange = () => { + if (ajax.readyState !== 4) { return; } + if (ajax.status !== 200) { + reject(ajax.responseText); + return; + } + try{ resolve(JSON.parse(ajax.responseText)); } + catch(error){ reject(error); } + }; + ajax.open('GET', `${window.location.origin}/static/static/playerSettings.json`, true); + ajax.send(); +}); + +const createDefaultSettings = (settingData) => { + if (!localStorage.getItem('playerSettings')) { + const newSettings = {}; + for (let roSetting of Object.keys(settingData.readOnly)){ + newSettings[roSetting] = settingData.readOnly[roSetting]; + } + for (let generalOption of Object.keys(settingData.generalOptions)){ + newSettings[generalOption] = settingData.generalOptions[generalOption]; + } + for (let gameOption of Object.keys(settingData.gameOptions)){ + newSettings[gameOption] = settingData.gameOptions[gameOption].defaultValue; + } + newSettings.rom = {}; + for (let romOption of Object.keys(settingData.romOptions)){ + newSettings.rom[romOption] = settingData.romOptions[romOption].defaultValue; + } + localStorage.setItem('playerSettings', JSON.stringify(newSettings)); + } +}; + +const buildUI = (settingData) => { + // Game Options + const leftGameOpts = {}; + const rightGameOpts = {}; + Object.keys(settingData.gameOptions).forEach((key, index) => { + if (index < Object.keys(settingData.gameOptions).length / 2) { leftGameOpts[key] = settingData.gameOptions[key]; } + else { rightGameOpts[key] = settingData.gameOptions[key]; } + }); + document.getElementById('game-options-left').appendChild(buildOptionsTable(leftGameOpts)); + document.getElementById('game-options-right').appendChild(buildOptionsTable(rightGameOpts)); + + // ROM Options + const leftRomOpts = {}; + const rightRomOpts = {}; + Object.keys(settingData.romOptions).forEach((key, index) => { + if (index < Object.keys(settingData.romOptions).length / 2) { leftRomOpts[key] = settingData.romOptions[key]; } + else { rightRomOpts[key] = settingData.romOptions[key]; } + }); + document.getElementById('rom-options-left').appendChild(buildOptionsTable(leftRomOpts, true)); + document.getElementById('rom-options-right').appendChild(buildOptionsTable(rightRomOpts, true)); +}; + +const buildOptionsTable = (settings, romOpts = false) => { + const currentSettings = JSON.parse(localStorage.getItem('playerSettings')); + const table = document.createElement('table'); + const tbody = document.createElement('tbody'); + + Object.keys(settings).forEach((setting) => { + const tr = document.createElement('tr'); + + // td Left + const tdl = document.createElement('td'); + const label = document.createElement('label'); + label.setAttribute('for', setting); + label.setAttribute('data-tooltip', settings[setting].description); + label.innerText = `${settings[setting].friendlyName}:`; + tdl.appendChild(label); + tr.appendChild(tdl); + + // td Right + const tdr = document.createElement('td'); + const select = document.createElement('select'); + select.setAttribute('id', setting); + select.setAttribute('data-key', setting); + if (romOpts) { select.setAttribute('data-romOpt', '1'); } + settings[setting].options.forEach((opt) => { + const option = document.createElement('option'); + option.setAttribute('value', opt.value); + option.innerText = opt.name; + if ((isNaN(currentSettings[setting]) && (parseInt(opt.value, 10) === parseInt(currentSettings[setting]))) || + (opt.value === currentSettings[setting])) { + option.selected = true; + } + select.appendChild(option); + }); + select.addEventListener('change', (event) => updateSetting(event)); + tdr.appendChild(select); + tr.appendChild(tdr); + tbody.appendChild(tr); + }); + + table.appendChild(tbody); + return table; +}; + +const updateSetting = (event) => { + const options = JSON.parse(localStorage.getItem('playerSettings')); + if (event.target.getAttribute('data-romOpt')) { + options.rom[event.target.getAttribute('data-key')] = isNaN(event.target.value) ? + event.target.value : parseInt(event.target.value, 10); + } else { + options[event.target.getAttribute('data-key')] = isNaN(event.target.value) ? + event.target.value : parseInt(event.target.value, 10); + } + localStorage.setItem('playerSettings', JSON.stringify(options)); +}; + +const exportSettings = () => { + const settings = JSON.parse(localStorage.getItem('playerSettings')); + if (!settings.name || settings.name.trim().length === 0) { settings.name = "noname"; } + const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); + download(`${document.getElementById('player-name').value}.yaml`, yamlText); +}; + +/** Create an anchor and trigger a download of a text file. */ +const download = (filename, text) => { + const downloadLink = document.createElement('a'); + downloadLink.setAttribute('href','data:text/yaml;charset=utf-8,'+ encodeURIComponent(text)) + downloadLink.setAttribute('download', filename); + downloadLink.style.display = 'none'; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +}; + +const generateGame = (raceMode = false) => { + axios.post('/api/generate', { + weights: { player: localStorage.getItem('playerSettings') }, + presetData: { player: localStorage.getItem('playerSettings') }, + playerCount: 1, + race: raceMode ? '1' : '0', + }).then((response) => { + window.location.href = response.data.url; + }); +}; + +const fetchSpriteData = () => new Promise((resolve, reject) => { + const ajax = new XMLHttpRequest(); + ajax.onreadystatechange = () => { + if (ajax.readyState !== 4) { return; } + if (ajax.status !== 200) { + reject('Unable to fetch sprite data.'); + return; + } + resolve(ajax.responseText); + }; + ajax.open('GET', `${window.location.origin}/static/static/spriteData.json`, true); + ajax.send(); +}); diff --git a/WebHostLib/static/assets/styleController.js b/WebHostLib/static/assets/styleController.js new file mode 100644 index 00000000..924e86ee --- /dev/null +++ b/WebHostLib/static/assets/styleController.js @@ -0,0 +1,47 @@ +const adjustFooterHeight = () => { + // If there is no footer on this page, do nothing + const footer = document.getElementById('island-footer'); + if (!footer) { return; } + + // If the body is taller than the window, also do nothing + if (document.body.offsetHeight > window.innerHeight) { + footer.style.marginTop = '0'; + return; + } + + // Add a margin-top to the footer to position it at the bottom of the screen + const sibling = footer.previousElementSibling; + const margin = (window.innerHeight - sibling.offsetTop - sibling.offsetHeight - footer.offsetHeight); + if (margin < 1) { + footer.style.marginTop = '0'; + return; + } + footer.style.marginTop = `${margin}px`; +}; + +const adjustHeaderWidth = () => { + // If there is no header, do nothing + const header = document.getElementById('base-header'); + if (!header) { return; } + + const tempDiv = document.createElement('div'); + tempDiv.style.width = '100px'; + tempDiv.style.height = '100px'; + tempDiv.style.overflow = 'scroll'; + tempDiv.style.position = 'absolute'; + tempDiv.style.top = '-500px'; + document.body.appendChild(tempDiv); + const scrollbarWidth = tempDiv.offsetWidth - tempDiv.clientWidth; + document.body.removeChild(tempDiv); + + const documentRoot = document.compatMode === 'BackCompat' ? document.body : document.documentElement; + const margin = (documentRoot.scrollHeight > documentRoot.clientHeight) ? 0-scrollbarWidth : 0; + document.getElementById('base-header-right').style.marginRight = `${margin}px`; +}; + +window.addEventListener('load', () => { + window.addEventListener('resize', adjustFooterHeight); + window.addEventListener('resize', adjustHeaderWidth); + adjustFooterHeight(); + adjustHeaderWidth(); +}); diff --git a/WebHostLib/static/assets/tutorial.js b/WebHostLib/static/assets/tutorial.js index b05f16e3..8989f3ad 100644 --- a/WebHostLib/static/assets/tutorial.js +++ b/WebHostLib/static/assets/tutorial.js @@ -44,6 +44,7 @@ window.addEventListener('load', () => { // Populate page with HTML generated from markdown tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results); + adjustHeaderWidth(); // Reset the id of all header divs to something nicer const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')); diff --git a/WebHostLib/static/assets/tutorial/tutorial_en.md b/WebHostLib/static/assets/tutorial/tutorial_en.md index c6f1bd09..301315b7 100644 --- a/WebHostLib/static/assets/tutorial/tutorial_en.md +++ b/WebHostLib/static/assets/tutorial/tutorial_en.md @@ -50,30 +50,33 @@ each player to enjoy an experience customized for their taste, and different pla can all have different options. ### Where do I get a YAML file? -The [Player Settings](/player-settings) page on the website allows you to configure your personal settings -and download a `yaml` file. You may configure up to three presets on this page. +The [Generate Game](/player-settings) page on the website allows you to configure your personal settings and +export a YAML file from them. -### Your YAML file is weighted -The Player Settings page has many options which are primarily represented with sliders. This allows you to -choose how likely certain options are to occur relative to other options within a category. +### Advanced YAML configuration +A more advanced version of the YAML file can be created using the [Weighted Settings](/weighted-settings) page, +which allows you to configure up to three presets. The Weighted Settings page has many options which are +primarily represented with sliders. This allows you to choose how likely certain options are to occur relative +to other options within a category. For example, imagine the generator creates a bucket labeled "Map Shuffle", and places folded pieces of paper -into the bucket for each sub-option. Also imagine your chosen value for "On" is 20 and your value for "Off" is 40. +into the bucket for each sub-option. Also imagine your chosen value for "On" is 20, and your value for "Off" is 40. In this example, sixty pieces of paper are put into the bucket. Twenty for "On" and forty for "Off". When the generator is deciding whether or not to turn on map shuffle for your game, it reaches into this bucket and pulls out a piece of paper at random. In this example, you are much more likely to have map shuffle turned off. -If you never want an option to be chosen, simply set its value to zero. +If you never want an option to be chosen, simply set its value to zero. Remember that each setting must have at +lease one option set to a number greater than zero. ### Verifying your YAML file If you would like to validate your YAML file to make sure it works, you may do so on the [YAML Validator](/mysterycheck) page. ## Generating a Single-Player Game -1. Navigate to [the Generator Page](/generate) and upload your YAML file. +1. Navigate to the [Generate Game](/player-settings), configure your options, and click the "Generate Game" button. 2. You will be presented with a "Seed Info" page, where you can download your patch file. -3. Double-click on your patch file and the emulator should launch with your game automatically. As the +3. Double-click on your patch file, and the emulator should launch with your game automatically. As the Client is unnecessary for single player games, you may close it and the WebUI. ## Joining a MultiWorld Game @@ -122,10 +125,6 @@ done so already, please do this now. SD2SNES and FXPak Pro users may download th [here](https://github.com/RedGuyyyy/sd2snes/releases). Other hardware may find helpful information [on this page](http://usb2snes.com/#supported-platforms). -**To connect with hardware you must use an old version of QUsb2Snes -([v0.7.16](https://github.com/Skarsnik/QUsb2snes/releases/tag/v0.7.16)).** -Versions of QUsb2Snes later than this break compatibility with hardware for multiworld. - 1. Close your emulator, which may have auto-launched. 2. Close QUsb2Snes, which launched automatically with the client. 3. Launch the appropriate version of QUsb2Snes (v0.7.16). @@ -154,11 +153,30 @@ The recommended way to host a game is to use the hosting service provided on 3. Upload that zip file to the website linked above. 4. Wait a moment while the seed is generated. 5. When the seed is generated, you will be redirected to a "Seed Info" page. -6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players - so they may download their patch files from here. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, + so they may download their patch files from there. **Note:** The patch files provided on this page will allow players to automatically connect to the server, while the patch files on the "Seed Info" page will not. 7. Note that a link to a MultiWorld Tracker is at the top of the room page. You should also provide this link - to your players so they can watch the progress of the game. Any observers may also be given the link to + to your players, so they can watch the progress of the game. Any observers may also be given the link to this page. 8. Once all players have joined, you may begin playing. + +## Auto-Tracking +If you would like to use auto-tracking for your game, several pieces of software provide this functionality. +The recommended software for auto-tracking is currently +[OpenTracker](https://github.com/trippsc2/OpenTracker/releases). + +### Installation +1. Download the appropriate installation file for your computer (Windows users want the `.msi` file). +2. During the installation process, you may be asked to install the Microsoft Visual Studio Build Tools. A link + to this software is provided during the installation procedure, and it must be installed manually. + +### Enable auto-tracking +1. With OpenTracker launched, click the Tracking menu at the top of the window, then choose **AutoTracker...** +2. Click the **Get Devices** button +3. Select your SNES device from the drop-down list +4. If you would like to track small keys and dungeon items, check the box labeled **Race Illegal Tracking** +5. Click the **Start Autotracking** button +6. Close the AutoTracker window, as it is no longer necessary + diff --git a/WebHostLib/static/assets/tutorial/tutorial_es.md b/WebHostLib/static/assets/tutorial/tutorial_es.md index c38decf2..971ae073 100644 --- a/WebHostLib/static/assets/tutorial/tutorial_es.md +++ b/WebHostLib/static/assets/tutorial/tutorial_es.md @@ -43,11 +43,12 @@ Cada jugador en una partida de multiworld proveerá su propio fichero YAML. Esta que cada jugador disfrute de una experiencia personalizada a su gusto, y cada jugador dentro de la misma partida de multiworld puede tener diferentes opciones. ### Donde puedo obtener un fichero YAML? -La página "[Player Settings](/player-settings)" en el sitio web te permite configurar tu configuración personal y -descargar un fichero "YAML". Puedes tener hasta 3 configuraciones guardadas en esta página. +La página "[Generate Game](/player-settings)" en el sitio web te permite configurar tu configuración personal y +descargar un fichero "YAML". -### Tu fichero YAML esta ponderado -La página "Player settings" tiene muchas opciones representadas con controles deslizantes. Esto permite +### Configuración YAML avanzada +Una version mas avanzada del fichero Yaml puede ser creada usando la pagina ["Weighted settings"](/weighted-settings), +la cual te permite tener almacenadas hasta 3 preajustes. La pagina "Weighted Settings" tiene muchas opciones representadas con controles deslizantes. Esto permite elegir cuan probable los valores de una categoría pueden ser elegidos sobre otros de la misma. Por ejemplo, imagina que el generador crea un cubo llamado "map_shuffle", y pone trozos de papel doblado en él por cada sub-opción. @@ -58,16 +59,17 @@ Cuando el generador esta decidiendo si activar o no "map shuffle" para tu partid meterá la mano en el cubo y sacara un trozo de papel al azar. En este ejemplo, es mucho mas probable (2 de cada 3 veces (40/60)) que "map shuffle" esté desactivado. -Si quieres que una opción no pueda ser escogida, simplemente asigna el valor 0 a dicha opción. +Si quieres que una opción no pueda ser escogida, simplemente asigna el valor 0 a dicha opción. Recuerda que cada opción debe tener + al menos un valor mayor que cero, si no la generación fallará. ### Verificando tu archivo YAML Si quieres validar que tu fichero YAML para asegurarte que funciona correctamente, puedes hacerlo en la pagina [YAML Validator](/mysterycheck). ## Generar una partida para un jugador -1. Navega a [la pagina Generator](/generate) y carga tu fichero YAML. +1. Navega a [la pagina Generate game](/player-settings), configura tus opciones, haz click en el boton "Generate game". 2. Se te redigirá a una pagina "Seed Info", donde puedes descargar tu archivo de parche. -3. Haz doble click en tu fichero de parche y el emulador debería ejecutar tu juego automáticamente. Como el +3. Haz doble click en tu fichero de parche, y el emulador debería ejecutar tu juego automáticamente. Como el Cliente no es necesario para partidas de un jugador, puedes cerrarlo junto a la pagina web (que tiene como titulo "Multiworld WebUI") que se ha abierto automáticamente. ## Unirse a una partida MultiWorld @@ -113,11 +115,6 @@ Esta guía asume que ya has descargado el firmware correcto para tu dispositivo. Los usuarios de SD2SNES y FXPak Pro pueden descargar el firmware apropiado [aqui](https://github.com/RedGuyyyy/sd2snes/releases). Los usuarios de otros dispositivos pueden encontrar información [en esta página](http://usb2snes.com/#supported-platforms). - -**Para conectar con hardware debe usarse una version antigua de QUsb2Snes -([v0.7.16](https://github.com/Skarsnik/QUsb2snes/releases/tag/v0.7.16)).** -Las versiones mas actuales que esta son incompatibles con hardware para multiworld - 1. Cierra tu emulador, el cual debe haberse autoejecutado. 2. Cierra QUsb2Snes, el cual fue ejecutado junto al cliente. 3. Ejecuta la version correcta de QUsb2Snes (v0.7.16). @@ -152,4 +149,22 @@ La manera recomendad para hospedar una partida es usar el servicio proveído en mientras que los de la pagina "Seed info" no. 7. Hay un enlace a un MultiWorld Tracker en la parte superior de la pagina de la sala. Deberías pasar también este enlace a los jugadores para que puedan ver el progreso de la partida. A los observadores también se les puede pasar este enlace. -8. Una vez todos los jugadores se han unido, podeis empezar a jugar. \ No newline at end of file +8. Una vez todos los jugadores se han unido, podeis empezar a jugar. + +## Auto-Tracking +Si deseas usar auto-tracking para tu partida, varios programas ofrecen esta funcionalidad. +El programa recomentdado actualmente es: +[OpenTracker](https://github.com/trippsc2/OpenTracker/releases). + +### Instalación +1. Descarga el fichero de instalacion apropiado para tu ordenador (Usuarios de windows quieren el fichero ".msi"). +2. Durante el proceso de insatalación, puede que se te pida instalar Microsoft Visual Studio Build Tools. Un enlace + este programa se muestra durante la proceso, y debe ser ejecutado manualmente. + +### Activar auto-tracking +1. Con OpenTracker ejecutado, haz click en el menu Tracking en la parte superior de la ventana, y elige **AutoTracker...** +2. Click the **Get Devices** button +3. Selecciona tu "SNES device" de la lista +4. Si quieres que las llaves y los objetos de mazmorra tambien sean marcados, activa la caja con nombre **Race Illegal Tracking** +5. Haz click en el boton **Start Autotracking** +6. Cierra la ventana AutoTracker, ya que deja de ser necesaria \ No newline at end of file diff --git a/WebHostLib/static/assets/tutorial/tutorial_fr.md b/WebHostLib/static/assets/tutorial/tutorial_fr.md index 49994932..114a0b0d 100644 --- a/WebHostLib/static/assets/tutorial/tutorial_fr.md +++ b/WebHostLib/static/assets/tutorial/tutorial_fr.md @@ -9,7 +9,7 @@ ## Logiciels requis - [Utilitaires du MultiWorld](https://github.com/Berserker66/MultiWorld-Utilities/releases) - [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Inclus dans les utilitaires précédents) -- Une solution logicielle ou matérielle capable de charger et de jouer des fichiers ROM de SNES +- Une solution logicielle ou matérielle capable de charger et de lancer des fichiers ROM de SNES - Un émulateur capable d'éxécuter des scripts Lua ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), [BizHawk](http://tasvideos.org/BizHawk.html)) @@ -21,17 +21,17 @@ ### Installation sur Windows 1. Téléchargez et installez les utilitaires du MultiWorld à l'aide du lien au-dessus, faites attention à bien installer la version la plus récente. **Le fichier se situe dans la section "assets" en bas des informations de version**. Si vous voulez jouer des parties classiques de multiworld, -vous voudrez télécharger `Setup.BerserkerMultiWorld.exe` - - Si vous voulez jouer à la version alternative avec le mélangeur de portes dans les donjons, vous voudrez télécharger le fichier +téléchargez `Setup.BerserkerMultiWorld.exe` + - Si vous voulez jouer à la version alternative avec le mélangeur de portes dans les donjons, vous téléchargez le fichier `Setup.BerserkerMultiWorld.Doors.exe`. - Durant le processus d'installation, il vous sera demandé de localiser votre ROM v1.0 japonaise. Si vous avez déjà installé le logiciel auparavant et qu'il s'agit simplement d'une mise à jour, la localisation de la ROM originale ne sera pas requise. - Il vous sera peut-être également demandé d'installer Microsoft Visual C++. Si vous le possédez déjà (possiblement parce qu'un jeu Steam l'a déjà installé), l'installateur ne reproposera pas de l'installer. -2. Si vous utilisez un émulateur, vous devriez assigner votre émulateur capable d'éxécuter des scripts Lua comme programme +2. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme programme par défaut pour ouvrir vos ROMs. - 1. Extrayez votre dossier d'émulateur sur votre Bureau, ou quelque part dont vous vous souviendrez. + 1. Extrayez votre dossier d'émulateur sur votre Bureau, ou à un endroit dont vous vous souviendrez. 2. Faites un clic droit sur un fichier ROM et sélectionnez **Ouvrir avec...** 3. Cochez la case à côté de **Toujours utiliser cette application pour ouvrir les fichiers .sfc** 4. Descendez jusqu'en bas de la liste et sélectionnez **Rechercher une autre application sur ce PC** @@ -49,14 +49,12 @@ sur comment il devrait générer votre seed. Chaque joueur d'un multiwolrd devra joueur d'apprécier une expérience customisée selon ses goûts, et les différents joueurs d'un même multiworld peuvent avoir différentes options. ### Où est-ce que j'obtiens un fichier YAML ? -Un fichier YAML de base est disponible dans le dossier où les utilitaires du MultiWorld sont installés. Il est situé dans le dossier -`players` et se nomme `easy.yaml` -La page des [paramètres du joueur](/player-settings) vous permet de configurer vos paramètres personnels et de télécharger un fichier `yaml`. -Vous pouvez configurez jusqu'à trois pré-paramétrages sur cette page. +La page [Génération de partie](/player-settings) vous permet de configurer vos paramètres personnels et de les exporter vers un fichier YAML. -### Votre fichier YAML est pondéré -La page de paramétrage a de nombreuses options qui sont essentiellement représentées avec des curseurs glissants. Cela vous permet de choisir quelles -sont les chances qu'une certaine option apparaisse par rapport aux autres disponibles. +### Configuration avancée du fichier YAML +Une version plus avancée du fichier YAML peut être créée en utilisant la page des [paramètres de pondération](/weighted-settings), qui vous permet +de configurer jusqu'à trois préréglages. Cette page a de nombreuses options qui sont essentiellement représentées avec des curseurs glissants. +Cela vous permet de choisir quelles sont les chances qu'une certaine option apparaisse par rapport aux autres disponibles dans une même catégorie. Par exemple, imaginez que le générateur crée un seau étiqueté "Mélange des cartes", et qu'il place un morceau de papier pour chaque sous-option. Imaginez également que la valeur pour "On" est 20 et la valeur pour "Off" est 40. @@ -65,14 +63,15 @@ Dans cet exemple, il y a soixante morceaux de papier dans le seau : vingt pour " décide s'il doit oui ou non activer le mélange des cartes pour votre partie, , il tire aléatoirement un papier dans le seau. Dans cet exemple, il y a de plus grandes chances d'avoir le mélange de cartes désactivé. -S'il y a une option dont vous ne voulez jamais, mettez simplement sa valeur à zéro. +S'il y a une option dont vous ne voulez jamais, mettez simplement sa valeur à zéro. N'oubliez pas qu'il faut que pour chaque paramètre il faut +au moins une option qui soit paramétrée sur un nombre strictement positif. ### Vérifier son fichier YAML Si vous voulez valider votre fichier YAML pour être sûr qu'il fonctionne, vous pouvez le vérifier sur la page du [Validateur de YAML](/mysterycheck). ## Générer une partie pour un joueur -1. Aller sur la [page du générateur](/generate) et téléversez votre fichier YAML. +1. Aller sur la page [Génération de partie](/player-settings), configurez vos options, et cliquez sur le bouton "Generate Game". 2. Il vous sera alors présenté une page d'informations sur la seed, où vous pourrez télécharger votre patch. 3. Double-cliquez sur le patch et l'émulateur devrait se lancer automatiquement avec la seed. Etant donné que le client n'est pas requis pour les parties à un joueur, vous pouvez le fermer ainsi que l'interface Web (WebUI). @@ -120,10 +119,6 @@ Les utilisateurs de SD2SNES et de FXPak Pro peuvent télécharger le micro-logic [ici](https://github.com/RedGuyyyy/sd2snes/releases). Pour les autres solutions, de l'aide peut être trouvée [sur cette page](http://usb2snes.com/#supported-platforms). -**Pour vous connecter avec une solution matérielle vous devez utiliser une ancienne version de QUsb2Snes -([v0.7.16](https://github.com/Skarsnik/QUsb2snes/releases/tag/v0.7.16)).** -Les versions postérieures brisent la compatibilité avec le multiworld. - 1. Fermez votre émulateur, qui s'est potentiellement lancé automatiquement. 2. Fermez QUsb2Snes, qui s'est lancé automatiquement avec le client. 3. Lancez la version appropriée de QUsb2Snes (v0.7.16). @@ -149,13 +144,31 @@ La méthode recommandée pour héberger une partie est d'utiliser le service d'h 1. Récupérez les fichiers YAML des joueurs. 2. Créez une archive zip contenant ces fichiers YAML. -3. Téléversez l'archive zip sur le lien au-dessus. +3. Téléversez l'archive zip sur le lien ci-dessus. 4. Attendez un moment que les seed soient générées. -5. Lorsque les seeds sont générées, vous serez redirigé vers une page d'informations. +5. Lorsque les seeds sont générées, vous serez redirigé vers une page d'informations "Seed Info". 6. Cliquez sur "Create New Room". Cela vous amènera à la page du serveur. Fournissez le lien de cette page aux autres joueurs afin qu'ils puissent récupérer leurs patchs. **Note:** Les patchs fournis sur cette page permettront aux joueurs de se connecteur automatiquement au serveur, tandis que ceux de la page "Seed Info" non. 7. Remarquez qu'un lien vers le traqueur du MultiWorld est en haut de la page de la salle. Vous devriez également fournir ce lien aux joueurs - pour qu'ils puissent la progression de la partie. N'importe quel personne voulant observer devrait avoir accès à ce lien. -8. Une fois que tous les joueurs ont rejoint, vous pouvez commencer à jouer. \ No newline at end of file + pour qu'ils puissent suivre la progression de la partie. N'importe quel personne voulant observer devrait avoir accès à ce lien. +8. Une fois que tous les joueurs ont rejoint, vous pouvez commencer à jouer. + +## Auto-tracking +Si vous voulez utiliser l'auto-tracking, plusieurs logiciels offrent cette possibilité. +Le logiciel recommandé pour l'auto-tracking actuellement est +[OpenTracker](https://github.com/trippsc2/OpenTracker/releases). + +### Installation +1. Téléchargez le fichier d'installation approprié pour votre ordinateur (Les utilisateurs Windows voudront le fichier `.msi`). +2. Durant le processus d'installation, il vous sera peut-être demandé d'installer les outils "Microsoft Visual Studio Build Tools". Un +lien est fourni durant l'installation d'OpenTracker, et celle des outils doit se faire manuellement. + +### Activer l'auto-tracking +1. Une fois OpenTracker démarré, cliquez sur le menu "Tracking" en haut de la fenêtre, puis choisissez **AutoTracker...** +2. Appuyez sur le bouton **Get Devices** +3. Sélectionnez votre appareil SNES dans la liste déroulante. +4. Si vous voulez tracquer les petites clés ainsi que les objets des donjons, cochez la case **Race Illegal Tracking** +5. Cliquez sur le bouton **Start Autotracking** +6. Fermez la fenêtre "AutoTracker" maintenant, elle n'est plus nécessaire \ No newline at end of file diff --git a/WebHostLib/static/assets/uploads.js b/WebHostLib/static/assets/uploads.js deleted file mode 100644 index 14a63d44..00000000 --- a/WebHostLib/static/assets/uploads.js +++ /dev/null @@ -1,17 +0,0 @@ -window.addEventListener('load', () => { - document.getElementById('upload-button').addEventListener('click', () => { - document.getElementById('file-input').click(); - }); - - document.getElementById('file-input').addEventListener('change', () => { - document.getElementById('upload-form').submit(); - }); - - $("#uploads-table").DataTable({ - "paging": false, - "ordering": true, - "order": [[ 3, "desc" ]], - "info": false, - "dom": "t", - }); -}); diff --git a/WebHostLib/static/assets/userContent.js b/WebHostLib/static/assets/userContent.js new file mode 100644 index 00000000..dcc8f565 --- /dev/null +++ b/WebHostLib/static/assets/userContent.js @@ -0,0 +1,17 @@ +window.addEventListener('load', () => { + console.log("loaded"); + $("#rooms-table").DataTable({ + "paging": false, + "ordering": true, + "order": [[ 3, "desc" ]], + "info": false, + "dom": "t", + }); + $("#seeds-table").DataTable({ + "paging": false, + "ordering": true, + "order": [[ 2, "desc" ]], + "info": false, + "dom": "t", + }); +}); diff --git a/WebHostLib/static/assets/view_seed.js b/WebHostLib/static/assets/viewSeed.js similarity index 100% rename from WebHostLib/static/assets/view_seed.js rename to WebHostLib/static/assets/viewSeed.js diff --git a/WebHostLib/static/assets/player-settings.js b/WebHostLib/static/assets/weightedSettings.js similarity index 84% rename from WebHostLib/static/assets/player-settings.js rename to WebHostLib/static/assets/weightedSettings.js index 753edeb8..19b2f5e4 100644 --- a/WebHostLib/static/assets/player-settings.js +++ b/WebHostLib/static/assets/weightedSettings.js @@ -1,23 +1,20 @@ let spriteData = null; window.addEventListener('load', () => { - const gameSettings = document.getElementById('game-settings'); + const gameSettings = document.getElementById('weighted-settings'); Promise.all([fetchPlayerSettingsYaml(), fetchPlayerSettingsJson(), fetchSpriteData()]).then((results) => { // Load YAML into object const sourceData = jsyaml.safeLoad(results[0], { json: true }); // Update localStorage with three settings objects. Preserve original objects if present. for (let i=1; i<=3; i++) { - const localSettings = JSON.parse(localStorage.getItem(`playerSettings${i}`)); + const localSettings = JSON.parse(localStorage.getItem(`weightedSettings${i}`)); const updatedObj = localSettings ? Object.assign(sourceData, localSettings) : sourceData; - localStorage.setItem(`playerSettings${i}`, JSON.stringify(updatedObj)); + localStorage.setItem(`weightedSettings${i}`, JSON.stringify(updatedObj)); } - // Parse spriteData into useful sets - spriteData = JSON.parse(results[2]); - // Build the entire UI - buildUI(JSON.parse(results[1])); + buildUI(JSON.parse(results[1]), JSON.parse(results[2])); // Populate the UI and add event listeners populateSettings(); @@ -27,13 +24,17 @@ window.addEventListener('load', () => { document.getElementById('export-button').addEventListener('click', exportSettings); document.getElementById('reset-to-default').addEventListener('click', resetToDefaults); + adjustHeaderWidth(); }).catch((error) => { + console.error(error); gameSettings.innerHTML = `

Something went wrong while loading your game settings page.

${error}

Click here to return to safety!

` }); + document.getElementById('generate-game').addEventListener('click', () => generateGame()); + document.getElementById('generate-race').addEventListener('click', () => generateGame(true)); }); const fetchPlayerSettingsYaml = () => new Promise((resolve, reject) => { @@ -46,7 +47,7 @@ const fetchPlayerSettingsYaml = () => new Promise((resolve, reject) => { } resolve(ajax.responseText); }; - ajax.open('GET', `${window.location.origin}/static/static/playerSettings.yaml` ,true); + ajax.open('GET', `${window.location.origin}/static/static/weightedSettings.yaml` ,true); ajax.send(); }); @@ -60,7 +61,7 @@ const fetchPlayerSettingsJson = () => new Promise((resolve, reject) => { } resolve(ajax.responseText); }; - ajax.open('GET', `${window.location.origin}/static/static/playerSettings.json`, true); + ajax.open('GET', `${window.location.origin}/static/static/weightedSettings.json`, true); ajax.send(); }); @@ -81,7 +82,7 @@ const fetchSpriteData = () => new Promise((resolve, reject) => { const handleOptionChange = (event) => { if(!event.target.matches('.setting')) { return; } const presetNumber = document.getElementById('preset-number').value; - const settings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`)) + const settings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)) const settingString = event.target.getAttribute('data-setting'); document.getElementById(settingString).innerText = event.target.value; if(getSettingValue(settings, settingString) !== false){ @@ -105,7 +106,7 @@ const handleOptionChange = (event) => { } // Save the updated settings object bask to localStorage - localStorage.setItem(`playerSettings${presetNumber}`, JSON.stringify(settings)); + localStorage.setItem(`weightedSettings${presetNumber}`, JSON.stringify(settings)); }else{ console.warn(`Unknown setting string received: ${settingString}`) } @@ -113,7 +114,7 @@ const handleOptionChange = (event) => { const populateSettings = () => { const presetNumber = document.getElementById('preset-number').value; - const settings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`)) + const settings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)) const settingsInputs = Array.from(document.querySelectorAll('.setting')); settingsInputs.forEach((input) => { const settingString = input.getAttribute('data-setting'); @@ -146,13 +147,13 @@ const getSettingValue = (settings, keyString) => { const exportSettings = () => { const presetNumber = document.getElementById('preset-number').value; - const settings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`)); + const settings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)); const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); download(`${settings.description}.yaml`, yamlText); }; const resetToDefaults = () => { - [1, 2, 3].forEach((presetNumber) => localStorage.removeItem(`playerSettings${presetNumber}`)); + [1, 2, 3].forEach((presetNumber) => localStorage.removeItem(`weightedSettings${presetNumber}`)); location.reload(); }; @@ -167,7 +168,7 @@ const download = (filename, text) => { document.body.removeChild(downloadLink); }; -const buildUI = (settings) => { +const buildUI = (settings, spriteData) => { const settingsWrapper = document.getElementById('settings-wrapper'); const settingTypes = { gameOptions: 'Game Options', @@ -175,7 +176,7 @@ const buildUI = (settings) => { } Object.keys(settingTypes).forEach((settingTypeKey) => { - const sectionHeader = document.createElement('h1'); + const sectionHeader = document.createElement('h2'); sectionHeader.innerText = settingTypes[settingTypeKey]; settingsWrapper.appendChild(sectionHeader); @@ -200,7 +201,7 @@ const buildUI = (settings) => { }); // Build sprite options - const spriteOptionsHeader = document.createElement('h1'); + const spriteOptionsHeader = document.createElement('h2'); spriteOptionsHeader.innerText = 'Sprite Options'; settingsWrapper.appendChild(spriteOptionsHeader); @@ -224,7 +225,7 @@ const buildUI = (settings) => { tbody.setAttribute('id', 'sprites-tbody'); const currentPreset = document.getElementById('preset-number').value; - const playerSettings = JSON.parse(localStorage.getItem(`playerSettings${currentPreset}`)); + const playerSettings = JSON.parse(localStorage.getItem(`weightedSettings${currentPreset}`)); // Manually add a row for random sprites addSpriteRow(tbody, playerSettings, 'random'); @@ -241,7 +242,7 @@ const buildUI = (settings) => { settingsWrapper.appendChild(spriteOptionsWrapper); // Append sprite picker - settingsWrapper.appendChild(buildSpritePicker()); + settingsWrapper.appendChild(buildSpritePicker(spriteData)); }; const buildRangeSettings = (parentElement, settings) => { @@ -368,7 +369,7 @@ const addSpriteRow = (tbody, playerSettings, spriteName) => { const addSpriteOption = (event) => { const presetNumber = document.getElementById('preset-number').value; - const playerSettings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`)); + const playerSettings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)); const spriteName = event.target.getAttribute('data-sprite'); console.log(event.target); console.log(spriteName); @@ -380,7 +381,7 @@ const addSpriteOption = (event) => { // Add option to playerSettings object playerSettings.rom.sprite[event.target.getAttribute('data-sprite')] = 50; - localStorage.setItem(`playerSettings${presetNumber}`, JSON.stringify(playerSettings)); + localStorage.setItem(`weightedSettings${presetNumber}`, JSON.stringify(playerSettings)); // Add to #sprite-options-table const tbody = document.getElementById('sprites-tbody'); @@ -389,19 +390,19 @@ const addSpriteOption = (event) => { const removeSpriteOption = (event) => { const presetNumber = document.getElementById('preset-number').value; - const playerSettings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`)); + const playerSettings = JSON.parse(localStorage.getItem(`weightedSettings${presetNumber}`)); const spriteName = event.target.getAttribute('data-sprite'); // Remove option from playerSettings object delete playerSettings.rom.sprite[spriteName]; - localStorage.setItem(`playerSettings${presetNumber}`, JSON.stringify(playerSettings)); + localStorage.setItem(`weightedSettings${presetNumber}`, JSON.stringify(playerSettings)); // Remove from #sprite-options-table const tr = document.getElementById(event.target.getAttribute('data-row-id')); tr.parentNode.removeChild(tr); }; -const buildSpritePicker = () => { +const buildSpritePicker = (spriteData) => { const spritePicker = document.createElement('div'); spritePicker.setAttribute('id', 'sprite-picker'); @@ -412,18 +413,18 @@ const buildSpritePicker = () => { const sprites = document.createElement('div'); sprites.setAttribute('id', 'sprite-picker-sprites'); - Object.keys(spriteData).forEach((spriteName) => { + spriteData.sprites.forEach((sprite) => { const spriteImg = document.createElement('img'); - spriteImg.setAttribute('src', `static/static/sprites/${spriteName}.gif`); - spriteImg.setAttribute('data-sprite', spriteName); - spriteImg.setAttribute('alt', spriteName); + spriteImg.setAttribute('src', `static/static/sprites/${sprite.name}.gif`); + spriteImg.setAttribute('data-sprite', sprite.name); + spriteImg.setAttribute('alt', sprite.name); // Wrap the image in a span to allow for tooltip presence const imgWrapper = document.createElement('span'); imgWrapper.className = 'sprite-img-wrapper'; - imgWrapper.setAttribute('data-tooltip', spriteName); + imgWrapper.setAttribute('data-tooltip', `${sprite.name}${sprite.author ? `, by ${sprite.author}` : ''}`); imgWrapper.appendChild(spriteImg); - imgWrapper.setAttribute('data-sprite', spriteName); + imgWrapper.setAttribute('data-sprite', sprite.name); sprites.appendChild(imgWrapper); imgWrapper.addEventListener('click', addSpriteOption); }); @@ -431,3 +432,15 @@ const buildSpritePicker = () => { spritePicker.appendChild(sprites); return spritePicker; }; + +const generateGame = (raceMode = false) => { + const presetNumber = document.getElementById('preset-number').value; + axios.post('/api/generate', { + weights: { player: localStorage.getItem(`weightedSettings${presetNumber}`) }, + presetData: { player: localStorage.getItem(`weightedSettings${presetNumber}`) }, + playerCount: 1, + race: raceMode ? '1' : '0', + }).then((response) => { + window.location.href = response.data.url; + }); +}; diff --git a/WebHostLib/static/static/backgrounds/LICENSE b/WebHostLib/static/static/backgrounds/LICENSE new file mode 100644 index 00000000..71d218da --- /dev/null +++ b/WebHostLib/static/static/backgrounds/LICENSE @@ -0,0 +1,4 @@ +Copyright 2020 Berserker66 (Fabian Dill) +Copyright 2020 LegendaryLinux (Chris Wilson) + +All rights reserved. diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom-left-corner.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom-left-corner.png new file mode 100644 index 00000000..326670b7 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom-left-corner.png differ diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom-right-corner.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom-right-corner.png new file mode 100644 index 00000000..c8297d34 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom-right-corner.png differ diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom.png new file mode 100644 index 00000000..2a28958e Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-bottom.png differ diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-left.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-left.png new file mode 100644 index 00000000..9bc84ff6 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-left.png differ diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-right.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-right.png new file mode 100644 index 00000000..a1e9c7c8 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-right.png differ diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top-left-corner.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top-left-corner.png new file mode 100644 index 00000000..a40bca60 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top-left-corner.png differ diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top-right-corner.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top-right-corner.png new file mode 100644 index 00000000..b8a8c6a7 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top-right-corner.png differ diff --git a/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top.png b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top.png new file mode 100644 index 00000000..bb6ccec3 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/cliffs/grass/cliff-top.png differ diff --git a/WebHostLib/static/static/backgrounds/clouds/cloud-0001.png b/WebHostLib/static/static/backgrounds/clouds/cloud-0001.png new file mode 100644 index 00000000..dba338f5 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/clouds/cloud-0001.png differ diff --git a/WebHostLib/static/static/backgrounds/clouds/cloud-0002.png b/WebHostLib/static/static/backgrounds/clouds/cloud-0002.png new file mode 100644 index 00000000..33f09b19 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/clouds/cloud-0002.png differ diff --git a/WebHostLib/static/static/backgrounds/clouds/cloud-0003.png b/WebHostLib/static/static/backgrounds/clouds/cloud-0003.png new file mode 100644 index 00000000..f665015b Binary files /dev/null and b/WebHostLib/static/static/backgrounds/clouds/cloud-0003.png differ diff --git a/WebHostLib/static/static/backgrounds/dirt/dirt-0001.png b/WebHostLib/static/static/backgrounds/dirt/dirt-0001.png new file mode 100644 index 00000000..fabdaf4d Binary files /dev/null and b/WebHostLib/static/static/backgrounds/dirt/dirt-0001.png differ diff --git a/WebHostLib/static/static/backgrounds/dirt/dirt-0002.png b/WebHostLib/static/static/backgrounds/dirt/dirt-0002.png new file mode 100644 index 00000000..55af91b2 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/dirt/dirt-0002.png differ diff --git a/WebHostLib/static/static/backgrounds/dirt/dirt-0003.png b/WebHostLib/static/static/backgrounds/dirt/dirt-0003.png new file mode 100644 index 00000000..5eed4d4e Binary files /dev/null and b/WebHostLib/static/static/backgrounds/dirt/dirt-0003.png differ diff --git a/WebHostLib/static/static/backgrounds/dirt/dirt-0004.png b/WebHostLib/static/static/backgrounds/dirt/dirt-0004.png new file mode 100644 index 00000000..70213cd6 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/dirt/dirt-0004.png differ diff --git a/WebHostLib/static/static/backgrounds/dirt/dirt-0005-large.png b/WebHostLib/static/static/backgrounds/dirt/dirt-0005-large.png new file mode 100644 index 00000000..99f0db16 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/dirt/dirt-0005-large.png differ diff --git a/WebHostLib/static/static/backgrounds/dirt/dirt-grass-single.png b/WebHostLib/static/static/backgrounds/dirt/dirt-grass-single.png new file mode 100644 index 00000000..2a38799d Binary files /dev/null and b/WebHostLib/static/static/backgrounds/dirt/dirt-grass-single.png differ diff --git a/WebHostLib/static/static/backgrounds/dirt/rock-single.png b/WebHostLib/static/static/backgrounds/dirt/rock-single.png new file mode 100644 index 00000000..cc237d13 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/dirt/rock-single.png differ diff --git a/WebHostLib/static/static/backgrounds/footer/footer-0001.png b/WebHostLib/static/static/backgrounds/footer/footer-0001.png new file mode 100644 index 00000000..b863a3d4 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/footer/footer-0001.png differ diff --git a/WebHostLib/static/static/backgrounds/footer/footer-0002.png b/WebHostLib/static/static/backgrounds/footer/footer-0002.png new file mode 100644 index 00000000..90fdfe95 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/footer/footer-0002.png differ diff --git a/WebHostLib/static/static/backgrounds/footer/footer-0003.png b/WebHostLib/static/static/backgrounds/footer/footer-0003.png new file mode 100644 index 00000000..5fc31d1e Binary files /dev/null and b/WebHostLib/static/static/backgrounds/footer/footer-0003.png differ diff --git a/WebHostLib/static/static/backgrounds/footer/footer-0004.png b/WebHostLib/static/static/backgrounds/footer/footer-0004.png new file mode 100644 index 00000000..4a95ce9a Binary files /dev/null and b/WebHostLib/static/static/backgrounds/footer/footer-0004.png differ diff --git a/WebHostLib/static/static/backgrounds/footer/footer-0005.png b/WebHostLib/static/static/backgrounds/footer/footer-0005.png new file mode 100644 index 00000000..7b7cd502 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/footer/footer-0005.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-0001.png b/WebHostLib/static/static/backgrounds/grass/grass-0001.png new file mode 100644 index 00000000..638426b9 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-0001.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-0002.png b/WebHostLib/static/static/backgrounds/grass/grass-0002.png new file mode 100644 index 00000000..2742b21a Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-0002.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-0003.png b/WebHostLib/static/static/backgrounds/grass/grass-0003.png new file mode 100644 index 00000000..c446f36e Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-0003.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-0004.png b/WebHostLib/static/static/backgrounds/grass/grass-0004.png new file mode 100644 index 00000000..44f3e6f1 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-0004.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-0005.png b/WebHostLib/static/static/backgrounds/grass/grass-0005.png new file mode 100644 index 00000000..c5a4b331 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-0005.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-0006.png b/WebHostLib/static/static/backgrounds/grass/grass-0006.png new file mode 100644 index 00000000..f0d3109b Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-0006.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-0007-large.png b/WebHostLib/static/static/backgrounds/grass/grass-0007-large.png new file mode 100644 index 00000000..8ca0b5bb Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-0007-large.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-flower-single.png b/WebHostLib/static/static/backgrounds/grass/grass-flower-single.png new file mode 100644 index 00000000..e8a7d905 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-flower-single.png differ diff --git a/WebHostLib/static/static/backgrounds/grass/grass-single.png b/WebHostLib/static/static/backgrounds/grass/grass-single.png new file mode 100644 index 00000000..f60031d0 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/grass/grass-single.png differ diff --git a/WebHostLib/static/static/backgrounds/header/dirt-header.png b/WebHostLib/static/static/backgrounds/header/dirt-header.png new file mode 100644 index 00000000..7c9e298e Binary files /dev/null and b/WebHostLib/static/static/backgrounds/header/dirt-header.png differ diff --git a/WebHostLib/static/static/backgrounds/header/grass-header.png b/WebHostLib/static/static/backgrounds/header/grass-header.png new file mode 100644 index 00000000..c2acc588 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/header/grass-header.png differ diff --git a/WebHostLib/static/static/backgrounds/header/ocean-header.png b/WebHostLib/static/static/backgrounds/header/ocean-header.png new file mode 100644 index 00000000..a0ff51f9 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/header/ocean-header.png differ diff --git a/WebHostLib/static/static/backgrounds/oceans/oceans-0001.png b/WebHostLib/static/static/backgrounds/oceans/oceans-0001.png new file mode 100644 index 00000000..f85c3d91 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/oceans/oceans-0001.png differ diff --git a/WebHostLib/static/static/backgrounds/oceans/oceans-0002.png b/WebHostLib/static/static/backgrounds/oceans/oceans-0002.png new file mode 100644 index 00000000..5c22c0b9 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/oceans/oceans-0002.png differ diff --git a/WebHostLib/static/static/backgrounds/oceans/oceans-0003.png b/WebHostLib/static/static/backgrounds/oceans/oceans-0003.png new file mode 100644 index 00000000..95ad97dc Binary files /dev/null and b/WebHostLib/static/static/backgrounds/oceans/oceans-0003.png differ diff --git a/WebHostLib/static/static/backgrounds/oceans/oceans-0004.png b/WebHostLib/static/static/backgrounds/oceans/oceans-0004.png new file mode 100644 index 00000000..afb9e2cc Binary files /dev/null and b/WebHostLib/static/static/backgrounds/oceans/oceans-0004.png differ diff --git a/WebHostLib/static/static/backgrounds/oceans/oceans-0005.png b/WebHostLib/static/static/backgrounds/oceans/oceans-0005.png new file mode 100644 index 00000000..84679576 Binary files /dev/null and b/WebHostLib/static/static/backgrounds/oceans/oceans-0005.png differ diff --git a/WebHostLib/static/static/button-images/button-a.png b/WebHostLib/static/static/button-images/button-a.png new file mode 100644 index 00000000..f3872dfd Binary files /dev/null and b/WebHostLib/static/static/button-images/button-a.png differ diff --git a/WebHostLib/static/static/button-images/button-b.png b/WebHostLib/static/static/button-images/button-b.png new file mode 100644 index 00000000..65008eaf Binary files /dev/null and b/WebHostLib/static/static/button-images/button-b.png differ diff --git a/WebHostLib/static/static/button-images/button-c.png b/WebHostLib/static/static/button-images/button-c.png new file mode 100644 index 00000000..9e5f9f50 Binary files /dev/null and b/WebHostLib/static/static/button-images/button-c.png differ diff --git a/WebHostLib/static/static/decorations/island-a.png b/WebHostLib/static/static/decorations/island-a.png new file mode 100644 index 00000000..d931aed0 Binary files /dev/null and b/WebHostLib/static/static/decorations/island-a.png differ diff --git a/WebHostLib/static/static/decorations/island-b.png b/WebHostLib/static/static/decorations/island-b.png new file mode 100644 index 00000000..d6902281 Binary files /dev/null and b/WebHostLib/static/static/decorations/island-b.png differ diff --git a/WebHostLib/static/static/decorations/island-c.png b/WebHostLib/static/static/decorations/island-c.png new file mode 100644 index 00000000..790c7b01 Binary files /dev/null and b/WebHostLib/static/static/decorations/island-c.png differ diff --git a/WebHostLib/static/static/decorations/rock-a.png b/WebHostLib/static/static/decorations/rock-a.png new file mode 100644 index 00000000..25c62acd Binary files /dev/null and b/WebHostLib/static/static/decorations/rock-a.png differ diff --git a/WebHostLib/static/static/fonts/HyliaSerifBeta-Regular.otf b/WebHostLib/static/static/fonts/HyliaSerifBeta-Regular.otf new file mode 100644 index 00000000..5e2677d6 Binary files /dev/null and b/WebHostLib/static/static/fonts/HyliaSerifBeta-Regular.otf differ diff --git a/WebHostLib/static/static/playerSettings.json b/WebHostLib/static/static/playerSettings.json index 28ab3e04..d57fb589 100644 --- a/WebHostLib/static/static/playerSettings.json +++ b/WebHostLib/static/static/playerSettings.json @@ -1,1502 +1,701 @@ { - "gameOptions": { - "description": { - "keyString": "description", - "friendlyName": "Description", - "inputType": "text", - "description": "A short description of this preset. Useful if you have multiple files", - "defaultValue": "Preset Name" + "readOnly": { + "description": "Generated by MultiWorld website", + "triforce_pieces_mode": "available", + "triforce_pieces_available": 30, + "triforce_pieces_required": 20, + "shuffle_prizes": "none", + "timer": "none", + "glitch_boots": "on", + "key_drop_shuffle": "off", + "experimental": "off", + "debug": "off" }, - "name": { - "keyString": "name", - "friendlyName": "Player Name", - "inputType": "text", - "description": "Displayed in-game. Spaces will be replaced with underscores.", - "defaultValue": "Your Name" - }, - "glitches_required": { - "keyString": "glitches_required", - "friendlyName": "Glitches Required", - "description": "Determine the logic required to complete the seed.", - "inputType": "range", - "subOptions": { - "none": { - "keyString": "glitches_required.none", - "friendlyName": "None", - "description": "No glitches required.", - "defaultValue": 50 - }, - "minor_glitches": { - "keyString": "glitches_required.minor_glitches", - "friendlyName": "Minor Glitches", - "description": "Puts fake flipper, water-walk, super bunny, etc into logic", - "defaultValue": 0 - }, - "overworld_glitches": { - "keyString": "glitches_required.overworld_glitches", - "friendlyName": "Overworld Glitches", - "description": "Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches (fake flipper, super bunny shenanigans, water walk and etc.)", - "defaultValue": 0 - }, - "no_logic": { - "keyString": "glitches_required.no_logic", - "friendlyName": "No Logic", - "description": "Your items are placed with no regard to any logic. Your Fire Rod could be on your Trinexx.", - "defaultValue": 0 - } - } - }, - "dark_room_logic": { - "keyString": "dark_room_logic", - "friendlyName": "Dark Room Logic", - "description": "Logic to use for dark rooms.", - "inputType": "range", - "subOptions": { - "lamp": { - "keyString": "dark_room_logic.lamp", - "friendlyName": "Lamp Required", - "description": "The lamp is required for dark rooms to be considered in logic.", - "defaultValue": 50 - }, - "torches": { - "keyString": "dark_room_logic.torches", - "friendlyName": "Lamp or Torches", - "description": "In addition to the lamp, a fire rod and accessible torches may put dark rooms into logic.", - "defaultValue": 0 - }, - "none": { - "keyString": "dark_room_logic.none", - "friendlyName": "Always in Logic", - "description": "Dark rooms are always considered in logic, which may require you to navigate rooms in complete darkness.", - "defaultValue": 0 - } - } - }, - "map_shuffle": { - "keyString": "map_shuffle", - "friendlyName": "Map Shuffle", - "description": "Shuffle dungeon maps into the world and other dungeons, including other players' worlds.", - "inputType": "range", - "subOptions": { - "off": { - "keyString": "map_shuffle.off", - "friendlyName": "Off", - "description": "Disable map shuffle.", - "defaultValue": 50 - }, - "on": { - "keyString": "map_shuffle.on", - "friendlyName": "On", - "description": "Enable map shuffle.", - "defaultValue": 0 - } - } - }, - "compass_shuffle": { - "keyString": "compass_shuffle", - "friendlyName": "Compass Shuffle", - "description": "Shuffle compasses into the world and other dungeons, including other players' worlds", - "inputType": "range", - "subOptions": { - "off": { - "keyString": "compass_shuffle.off", - "friendlyName": "Off", - "description": "Disable compass shuffle.", - "defaultValue": 50 - }, - "on": { - "keyString": "compass_shuffle.on", - "friendlyName": "On", - "description": "Enable compass shuffle.", - "defaultValue": 0 - } - } - }, - "smallkey_shuffle": { - "keyString": "smallkey_shuffle", - "friendlyName": "Small Key Shuffle", - "description": "Shuffle small keys into the world and other dungeons, including other players' worlds.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "smallkey_shuffle.on", - "friendlyName": "On", - "description": "Enable small key shuffle.", - "defaultValue": 0 - }, - "off": { - "keyString": "smallkey_shuffle.off", - "friendlyName": "Off", - "description": "Disable small key shuffle.", - "defaultValue": 50 - }, - "universal": { - "keyString": "smallkey_shuffle.universal", - "friendlyName": "Universal", - "description": "Allows small keys to be used in any dungeon and adds keys to shops so you can buy more.", - "defaultValue": 0 - } - } - }, - "bigkey_shuffle": { - "keyString": "bigkey_shuffle", - "friendlyName": "Big Key Shuffle", - "description": "Shuffle big keys into the world and other dungeons, including other players' worlds.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "bigkey_shuffle.on", - "friendlyName": "On", - "description": "Enable big key shuffle.", - "defaultValue": 0 - }, - "off": { - "keyString": "bigkey_shuffle.off", - "friendlyName": "Off", - "description": "Disable big key shuffle.", - "defaultValue": 50 - } - } - }, - "local_keys": { - "keyString": "local_keys", - "friendlyName": "Local Keys", - "description": "Keep small keys and big keys local to your world.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "local_keys.on", - "friendlyName": "On", - "description": "Enable local keys.", - "defaultValue": 0 - }, - "off": { - "keyString": "local_keys.off", - "friendlyName": "Off", - "description": "Disable local keys.", - "defaultValue": 50 - } - } - }, - "dungeon_counters": { - "keyString": "dungeon_counters", - "friendlyName": "Dungeon Counters", - "description": "Determines when to show an on-screen counter for dungeon items.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "dungeon_counters.on", - "friendlyName": "Always On", - "description": "Always display amount of items checked in a dungeon.", - "defaultValue": 0 - }, - "pickup": { - "keyString": "dungeon_counters.pickup", - "friendlyName": "With Compass", - "description": "Show when compass is picked up.", - "defaultValue": 0 - }, - "default": { - "keyString": "dungeon_counters.default", - "friendlyName": "With Compass if Shuffled", - "description": "Show when the compass is picked up, if the compass was shuffled.", - "defaultValue": 0 - }, - "off": { - "keyString": "dungeon_counters.off", - "friendlyName": "Always Off", - "description": "Never show dungeon counters.", - "defaultValue": 50 - } - } - }, - "accessibility": { - "keyString": "accessibility", - "friendlyName": "Location Access", - "description": "Determines how much of the game is guaranteed to be reachable.", - "inputType": "range", - "subOptions": { - "items": { - "keyString": "accessibility.items", - "friendlyName": "All Items", - "description": "Guarantees you will be able to acquire all items, but you may not be able to access all locations.", - "defaultValue": 0 - }, - "locations": { - "keyString": "accessibility.locations", - "friendlyName": "All Locations", - "description": "Guarantees you will be able to access all locations, and therefore all items.", - "defaultValue": 50 - }, - "none": { - "keyString": "accessibility.none", - "friendlyName": "Required Only", - "description": "Guarantees only that the game is beatable. You may not be able to access all locations or acquire all items.", - "defaultValue": 0 - } - } - }, - "progressive": { - "keyString": "progressive", - "friendlyName": "Progressive Items", - "description": "Enable or disable the progressive acquisition of certain items (swords, shields, bow).", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "progressive.on", - "friendlyName": "On", - "description": "All relevant items are acquired progressively.", - "defaultValue": 50 - }, - "off": { - "keyString": "progressive.off", - "friendlyName": "Off", - "description": "All relevant items are acquired non-progressively (tempered sword may be in Link's House).", - "defaultValue": 0 - }, - "random": { - "keyString": "progressive.random", - "friendlyName": "Random", - "description": "The progressive nature of items is determined per-item pool. Gloves may be progressive, but swords may not be.", - "defaultValue": 0 - } - } - }, - "entrance_shuffle": { - "keyString": "entrance_shuffle", - "friendlyName": "Entrance Shuffle", - "description": "Determines how often and by what rules entrances are shuffled.", - "inputType": "range", - "subOptions": { - "none": { - "keyString": "entrance_shuffle.none", - "friendlyName": "Vanilla Entrances", - "description": "Vanilla game map. All entrances and exits lead to their original locations.", - "defaultValue": 50 - }, - "dungeonssimple": { - "keyString": "entrance_shuffle.dungeonssimple", - "friendlyName": "Dungeons Simple", - "description": "Shuffle whole dungeons amongst each other. Hyrule Castle would always be one dungeon.", - "defaultValue": 0 - }, - "dungeonsfull": { - "keyString": "entrance_shuffle.dungeonsfull", - "friendlyName": "Dungeons Full", - "description": "Shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle could be four different dungeons.", - "defaultValue": 0 - }, - "simple": { - "keyString": "entrance_shuffle.simple", - "friendlyName": "Simple Shuffle", - "description": "Entrances are grouped together before being randomized. This option uses the most strict grouping rules.", - "defaultValue": 0 - }, - "restricted": { - "keyString": "entrance_shuffle.restricted", - "friendlyName": "Restricted Shuffle", - "description": "Entrances are grouped together before being randomized. Grouping rules are less strict than Simple Shuffle.", - "defaultValue": 0 - }, - "full": { - "keyString": "entrance_shuffle.full", - "friendlyName": "Full Shuffle", - "description": "Entrances are grouped before being randomized. Grouping rules are less strict than Restricted Shuffle.", - "defaultValue": 0 - }, - "crossed": { - "keyString": "entrance_shuffle.crossed", - "friendlyName": "Crossed Shuffle", - "description": "Entrances are grouped before being randomized. Grouping rules are less strict than Full Shuffle.", - "defaultValue": 0 - }, - "insanity": { - "keyString": "entrance_shuffle.insanity", - "friendlyName": "Insanity Shuffle", - "description": "Very few entrance grouping rules are applied. Good luck.", - "defaultValue": 0 - } - } - }, + "generalOptions": { + "name": "PlayerName" + }, + "gameOptions": { "goals": { - "keyString": "goals", - "friendlyName": "Goals", - "description": "Determines how much work you need to put in to save Hyrule.", - "inputType": "range", - "subOptions": { - "ganon": { - "keyString": "goals.ganon", - "friendlyName": "Defeat Ganon", - "description": "Climb Ganon's Tower, defeat Agahnim, then defeat Ganon in his lair.", - "defaultValue": 50 + "type": "select", + "friendlyName": "Goal", + "description": "Choose the condition for winning the game", + "defaultValue": "ganon", + "options": [ + { + "name": "Kill Ganon", + "value": "ganon" }, - "fast_ganon": { - "keyString": "goals.fast_ganon", - "friendlyName": "Fast Ganon", - "description": "Kill Ganon in his lair. The hole is always open, but you may still require some crystals to damage him.", - "defaultValue": 0 + { + "name": "Fast Ganon (Pyramid Always Open)", + "value": "fast_ganon" }, - "dungeons": { - "keyString": "goals.dungeons", - "friendlyName": "All Dungeons", - "description": "Defeat the boss of all dungeons, defeat Agahnim in both Castle Tower and Ganon's Tower, then defeat Ganon in his lair.", - "defaultValue": 0 + { + "name": "All Dungeons", + "value": "dungeons" }, - "pedestal": { - "keyString": "goals.pedestal", - "friendlyName": "Pedestal", - "description": "Acquire all three pendants and pull the Triforce from the Master Sword Pedestal.", - "defaultValue": 0 + { + "name": "Master Sword Pedestal", + "value": "pedestal" }, - "ganon_pedestal": { - "keyString": "goals.ganon_pedestal", - "friendlyName": "Ganon Pedestal", - "description": "Accquire all three pendants, pull the Master Sword Pedestal, then defeat Ganon in his lair.", - "defaultValue": 0 + { + "name": "Master Sword Pedestal + Ganon", + "value": "ganon_pedestal" }, - "triforce_hunt": { - "keyString": "goals.triforce_hunt", - "friendlyName": "Triforce Hunt", - "description": "Collect enough pieces of the Triforce of Courage, which has been spread around the world, then turn them in to Murahadala, who is standing outside Hyrule Castle.", - "defaultValue": 0 + { + "name": "Triforce Hunt", + "value": "triforce_hunt" }, - "local_triforce_hunt": { - "keyString": "goals.local_triforce_hunt", - "friendlyName": "Local Triforce Hunt", - "description": "Same as Triforce Hunt, but the Triforce pieces are guaranteed to be in your world.", - "defaultValue": 0 - }, - "ganon_triforce_hunt": { - "keyString": "goals.ganon_triforce_hunt", - "friendlyName": "Triforce Hunt /w Ganon", - "description": "Same as Triforce Hunt, but you need to defeat Ganon in his lair instead of talking with Murahadala.", - "defaultValue": 0 - }, - "local_ganon_triforce_hunt": { - "keyString": "goals.local_ganon_triforce_hunt", - "friendlyName": "Local Triforce hunt /w Ganon", - "description": "Same as Local Triforce Hunt, but you need to defeat Ganon in his lair instead of talking with Murahadala.", - "defaultValue": 0 + { + "name": "Triforce Hunt + Ganon", + "value": "ganon_triforce_hunt" } - } - }, - "triforce_pieces_required": { - "keyString": "triforce_pieces_required", - "friendlyName": "Triforce Pieces Required", - "description": "Determines the total number of Triforce pieces required before speaking with Murahadala", - "inputType": "range", - "subOptions": { - "15": { - "keyString": "triforce_pieces_required.15", - "friendlyName": 15, - "description": "15 Triforce pieces are required before speaking with Murahadala.", - "defaultValue": 0 - }, - "20": { - "keyString": "triforce_pieces_required.20", - "friendlyName": 20, - "description": "20 Triforce pieces are required before speaking with Murahadala.", - "defaultValue": 50 - }, - "30": { - "keyString": "triforce_pieces_required.30", - "friendlyName": 30, - "description": "30 Triforce pieces are required before speaking with Murahadala.", - "defaultValue": 0 - }, - "40": { - "keyString": "triforce_pieces_required.40", - "friendlyName": 40, - "description": "40 Triforce pieces are required before speaking with Murahadala.", - "defaultValue": 0 - }, - "50": { - "keyString": "triforce_pieces_required.50", - "friendlyName": 50, - "description": "50 Triforce pieces are required before speaking with Murahadala.", - "defaultValue": 0 - } - } - }, - "triforce_pieces_mode": { - "keyString": "triforce_pieces_mode", - "friendlyName": "Triforce Piece Availability Mode", - "description": "Determines which of the following three options will be used to determine the total available triforce pieces.", - "inputType": "range", - "subOptions": { - "available": { - "keyString": "triforce_pieces_mode.available", - "friendlyName": "Exact Number", - "description": "Explicitly tell the generator how many triforce pieces to place throughout Hyrule.", - "defaultValue": 50 - }, - "extra": { - "keyString": "triforce_pieces_mode.extra", - "friendlyName": "Required Plus", - "description": "Set the number of triforce pieces in Hyrule equal to the number of required pieces plus a number specified by this option.", - "defaultValue": 0 - }, - "percentage": { - "keyString": "triforce_pieces_mode.percentage", - "friendlyName": "Percentage", - "description": "Set the number of triforce pieces in Hyrule equal to the number of required pieces plus a percentage specified by this option.", - "defaultValue": 0 - } - } - }, - "triforce_pieces_available": { - "keyString": "triforce_pieces_available", - "friendlyName": "Exact Number (Triforce Hunt)", - "description": "Only used if enabled in Triforce Piece Availability Mode.", - "inputType": "range", - "subOptions": { - "25": { - "keyString": "triforce_pieces_available.25", - "friendlyName": 25, - "description": "25 Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - }, - "30": { - "keyString": "triforce_pieces_available.30", - "friendlyName": 30, - "description": "30 Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 50 - }, - "40": { - "keyString": "triforce_pieces_available.40", - "friendlyName": 40, - "description": "40 Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - }, - "50": { - "keyString": "triforce_pieces_available.50", - "friendlyName": 50, - "description": "50 Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - } - } - }, - "triforce_pieces_extra": { - "keyString": "triforce_pieces_extra", - "friendlyName": "Required Plus (Triforce Hunt)", - "description": "Only used if enabled in Triforce Piece Availability Mode.", - "inputType": "range", - "subOptions": { - "0": { - "keyString": "triforce_pieces_extra.0", - "friendlyName": 0, - "description": "No extra Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - }, - "5": { - "keyString": "triforce_pieces_extra.5", - "friendlyName": 5, - "description": "5 extra Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - }, - "10": { - "keyString": "triforce_pieces_extra.10", - "friendlyName": 10, - "description": "10 extra Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 50 - }, - "15": { - "keyString": "triforce_pieces_extra.15", - "friendlyName": 15, - "description": "15 extra Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - }, - "20": { - "keyString": "triforce_pieces_extra.20", - "friendlyName": 20, - "description": "20 extra Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - } - } - }, - "triforce_pieces_percentage": { - "keyString": "triforce_pieces_percentage", - "friendlyName": "Percentage (Triforce Hunt)", - "description": "Only used if enabled in Triforce Piece Availability Mode.", - "inputType": "range", - "subOptions": { - "100": { - "keyString": "triforce_pieces_percentage.100", - "friendlyName": "0%", - "description": "No extra Triforce pieces will be hidden throughout Hyrule", - "defaultValue": 0 - }, - "150": { - "keyString": "triforce_pieces_percentage.150", - "friendlyName": "50%", - "description": "50% more triforce pieces than required will be placed throughout Hyrule.", - "defaultValue": 50 - }, - "200": { - "keyString": "triforce_pieces_percentage.200", - "friendlyName": "100%", - "description": "50% more triforce pieces than required will be placed throughout Hyrule.", - "defaultValue": 0 - } - } - }, - "tower_open": { - "keyString": "tower_open", - "friendlyName": "GT Crystals", - "description": "Determines the number of crystals required to open Ganon's Tower.", - "inputType": "range", - "subOptions": { - "0": { - "keyString": "tower_open.0", - "friendlyName": 0, - "description": "0 Crystals are required to open Ganon's Tower.", - "defaultValue": 80 - }, - "1": { - "keyString": "tower_open.1", - "friendlyName": 1, - "description": "1 Crystal is required to open Ganon's Tower.", - "defaultValue": 70 - }, - "2": { - "keyString": "tower_open.2", - "friendlyName": 2, - "description": "2 Crystals are required to open Ganon's Tower.", - "defaultValue": 60 - }, - "3": { - "keyString": "tower_open.3", - "friendlyName": 3, - "description": "3 Crystals are required to open Ganon's Tower.", - "defaultValue": 50 - }, - "4": { - "keyString": "tower_open.4", - "friendlyName": 4, - "description": "4 Crystals are required to open Ganon's Tower.", - "defaultValue": 40 - }, - "5": { - "keyString": "tower_open.5", - "friendlyName": 5, - "description": "5 Crystals are required to open Ganon's Tower.", - "defaultValue": 30 - }, - "6": { - "keyString": "tower_open.6", - "friendlyName": 6, - "description": "6 Crystals are required to open Ganon's Tower.", - "defaultValue": 20 - }, - "7": { - "keyString": "tower_open.7", - "friendlyName": 7, - "description": "7 Crystals are required to open Ganon's Tower.", - "defaultValue": 10 - }, - "random": { - "keyString": "tower_open.random", - "friendlyName": "Random", - "description": "Randomly determine the number of crystals necessary to open Ganon's Tower.", - "defaultValue": 0 - } - } - }, - "ganon_open": { - "keyString": "ganon_open", - "friendlyName": "Ganon Crystals", - "description": "Determines the number of crystals required before you are able to damage Ganon.", - "inputType": "range", - "subOptions": { - "0": { - "keyString": "ganon_open.0", - "friendlyName": 0, - "description": "0 Crystals are required to damage Ganon.", - "defaultValue": 80 - }, - "1": { - "keyString": "ganon_open.1", - "friendlyName": 1, - "description": "1 Crystal is required to damage Ganon.", - "defaultValue": 70 - }, - "2": { - "keyString": "ganon_open.2", - "friendlyName": 2, - "description": "2 Crystals are required to damage Ganon.", - "defaultValue": 60 - }, - "3": { - "keyString": "ganon_open.3", - "friendlyName": 3, - "description": "3 Crystals are required to damage Ganon.", - "defaultValue": 50 - }, - "4": { - "keyString": "ganon_open.4", - "friendlyName": 4, - "description": "4 Crystals are required to damage Ganon.", - "defaultValue": 40 - }, - "5": { - "keyString": "ganon_open.5", - "friendlyName": 5, - "description": "5 Crystals are required to damage Ganon.", - "defaultValue": 30 - }, - "6": { - "keyString": "ganon_open.6", - "friendlyName": 6, - "description": "6 Crystals are required to damage Ganon.", - "defaultValue": 20 - }, - "7": { - "keyString": "ganon_open.7", - "friendlyName": 7, - "description": "7 Crystals are required to damage Ganon.", - "defaultValue": 10 - }, - "random": { - "keyString": "ganon_open.random", - "friendlyName": "Random", - "description": "Randomly determine the number of crystals necessary to damage Ganon.", - "defaultValue": 0 - } - } + ] }, "mode": { - "keyString": "mode", - "friendlyName": "Game Mode", - "description": "Determines the mode, or world state, for your game.", - "inputType": "range", - "subOptions": { - "standard": { - "keyString": "mode.standard", - "friendlyName": "Standard Mode", - "description": "Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary.", - "defaultValue": 50 + "type": "select", + "friendlyName": "World State", + "description": "Choose the state of the game world", + "defaultValue": "standard", + "options": [ + { + "name": "Standard", + "value": "standard" }, - "open": { - "keyString": "mode.open", - "friendlyName": "Open Mode", - "description": "Begin the game from your choice of Link's House or the Sanctuary.", - "defaultValue": 50 + { + "name": "Open", + "value": "open" }, - "inverted": { - "keyString": "mode.inverted", - "friendlyName": "Inverted Mode", - "description": "Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered.", - "defaultValue": 0 + { + "name": "Inverted", + "value": "inverted" } - } + ] + }, + "accessibility": { + "type": "select", + "friendlyName": "Accessibility", + "description": "Choose how much of the world will be available", + "defaultValue": "locations", + "options": [ + { + "name": "Locations Guaranteed", + "value": "locations" + }, + { + "name": "Items Guaranteed", + "value": "items" + }, + { + "name": "Beatable Only", + "value": "none" + } + ] + }, + "progressive": { + "type": "select", + "friendlyName": "Progressive Items", + "description": "Turn progressive items on or off, or randomize them", + "defaultValue": "on", + "options": [ + { + "name": "All Progressive", + "value": "on" + }, + { + "name": "None Progressive", + "value": "off" + }, + { + "name": "Randomize Each", + "value": "random" + } + ] + }, + "tower_open": { + "type": "select", + "friendlyName": "Ganon's Tower Access", + "description": "Choose how many crystals are required to open Ganon's Tower", + "defaultValue": 7, + "options": [ + { + "name": "7 Crystals", + "value": 7 + }, + { + "name": "6 Crystals", + "value": 6 + }, + { + "name": "5 Crystals", + "value": 5 + }, + { + "name": "4 Crystals", + "value": 4 + }, + { + "name": "3 Crystals", + "value": 3 + }, + { + "name": "2 Crystals", + "value": 2 + }, + { + "name": "1 Crystals", + "value": 1 + }, + { + "name": "0 Crystals", + "value": 0 + }, + { + "name": "Random", + "value": "random" + } + ] + }, + "ganon_open": { + "type": "select", + "friendlyName": "Ganon Vulnerable", + "description": "Choose how many crystals are required to kill Ganon", + "defaultValue": 7, + "options": [ + { + "name": "7 Crystals", + "value": 7 + }, + { + "name": "6 Crystals", + "value": 6 + }, + { + "name": "5 Crystals", + "value": 5 + }, + { + "name": "4 Crystals", + "value": 4 + }, + { + "name": "3 Crystals", + "value": 3 + }, + { + "name": "2 Crystals", + "value": 2 + }, + { + "name": "1 Crystals", + "value": 1 + }, + { + "name": "0 Crystals", + "value": 0 + }, + { + "name": "Random", + "value": "random" + } + ] }, "retro": { - "keyString": "retro", + "type": "select", "friendlyName": "Retro Mode", - "description": "Makes the game similar to the first Legend of Zelda. You must buy a quiver to use the bow, take-any caves and an old-man cave are added to the world, and you may need to find your sword from the old man's cave.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "retro.on", - "friendlyName": "On", - "description": "Enable retro mode.", - "defaultValue": 0 + "description": "Choose if you want to play in retro mode", + "defaultValue": "off", + "options": [ + { + "name": "Disabled", + "value": "off" }, - "off": { - "keyString": "retro.off", - "friendlyName": "Off", - "description": "Disable retro mode.", - "defaultValue": 50 + { + "name": "Enabled", + "value": "on" } - } + ] }, "hints": { - "keyString": "hints", - "friendlyName": "Hint Type", - "description": "Determines the behavior of hint tiles in dungeons", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "hints.on", - "friendlyName": "Item Locations", - "description": "Hint tiles sometimes give item location hints.", - "defaultValue": 50 + "type": "select", + "friendlyName": "Hints", + "description": "Choose to enable or disable tile hints", + "defaultValue": "on", + "options": [ + { + "name": "Enabled", + "value": "on" }, - "off": { - "keyString": "hints.off", - "friendlyName": "Gameplay Tips", - "description": "Hint tiles provide gameplay tips.", - "defaultValue": 0 + { + "name": "Disabled", + "value": "off" } - } + ] }, "weapons": { - "keyString": "weapons", - "friendlyName": "Sword Placement", - "description": "Determines how swords are placed throughout the world.", - "inputType": "range", - "subOptions": { - "randomized": { - "keyString": "weapons.randomized", - "friendlyName": "Randomized", - "description": "Swords are placed randomly throughout the world.", - "defaultValue": 0 + "type": "select", + "friendlyName": "Sword Locations", + "description": "Choose where you will find your swords", + "defaultValue": "assured", + "options": [ + { + "name": "Assured", + "value": "assured" }, - "assured": { - "keyString": "weapons.assured", - "friendlyName": "Assured", - "description": "Begin the game with a sword. Other swords are placed randomly throughout the game world.", - "defaultValue": 50 + { + "name": "Vanilla", + "value": "vanilla" }, - "vanilla": { - "keyString": "weapons.vanilla", - "friendlyName": "Vanilla Locations", - "description": "Swords are placed in vanilla locations in your own game (uncle, pedestal, smiths, pyramid fairy).", - "defaultValue": 0 + { + "name": "Swordless", + "value": "swordless" }, - "swordless": { - "keyString": "weapons.swordless", - "friendlyName": "Swordless", - "description": "Your swords are replaced with rupees. Gameplay changes are made to accommodate this change.", - "defaultValue": 0 + { + "name": "Randomized", + "value": "randomized" } + ] + }, + "glitches_required":{ + "type": "select", + "friendlyName": "Glitches Required", + "description": "Choose which glitches will be considered in-logic", + "defaultValue": "none", + "options": [ + { + "name": "None", + "value": "none" + }, + { + "name": "Minor Glitches", + "value": "minor_glitches" + }, + { + "name": "Overworld Glitches", + "value": "overworld_glitches" + }, + { + "name": "No Logic", + "value": "no_logic" } + ] + }, + "dark_room_logic": { + "type": "select", + "friendlyName": "Dark Room Logic", + "description": "Choose your logical access to dark rooms", + "defaultValue": "lamp", + "options": [ + { + "name": "Lamp Required", + "value": "lamp" + }, + { + "name": "Torches Lightable", + "value": "torches" + }, + { + "name": "Always In-Logic", + "value": "none" + } + ] + }, + "dungeon_items": { + "type": "select", + "friendlyName": "Dungeon Item Shuffle", + "description": "Choose which dungeon items you want shuffled", + "defaultValue": "none", + "options": [ + { + "name": "None", + "value": "none" + }, + { + "name": "Map & Compass", + "value": "mc" + }, + { + "name": "Small Keys Only", + "value": "s" + }, + { + "name": "Big Keys Only", + "value": "b" + }, + { + "name": "Small and Big Keys", + "value": "sb" + }, + { + "name": "Full Keysanity", + "value": "mscb" + }, + { + "name": "Universal Small Keys", + "value": "u" + } + ] + }, + "entrance_shuffle": { + "type": "select", + "friendlyName": "Entrance Shuffle", + "description": "Shuffles the game map. Not recommended for beginners", + "defaultValue": "none", + "options": [ + { + "name": "None", + "value": "none" + }, + { + "name": "Only Dungeons, Simple", + "value": "dungeonssimple" + }, + { + "name": "Only Dungeons, Full", + "value": "dungeonsfull" + }, + { + "name": "Simple", + "value": "simple" + }, + { + "name": "Restricted", + "value": "restricted" + }, + { + "name": "Full", + "value": "full" + }, + { + "name": "Crossed", + "value": "crossed" + }, + { + "name": "Insanity", + "value": "insanity" + } + ] }, "item_pool": { - "keyString": "item_pool", + "type": "select", "friendlyName": "Item Pool", - "description": "Determines the availability of upgrades, progressive items, and convenience items.", - "inputType": "range", - "subOptions": { - "easy": { - "keyString": "item_pool.easy", - "friendlyName": "Easy", - "description": "Double the number of available upgrades and progressive items.", - "defaultValue": 0 + "description": "Changes the available upgrade items (1/2 Magic, hearts, sword upgrades, etc)", + "defaultValue": "normal", + "options": [ + { + "name": "Easy", + "value": "easy" }, - "normal": { - "keyString": "item_pool.normal", - "friendlyName": "Normal", - "description": "Item availability remains unchanged from the vanilla game.", - "defaultValue": 50 + { + "name": "Normal", + "value": "normal" }, - "hard": { - "keyString": "item_pool.hard", - "friendlyName": "Hard", - "description": "Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless).", - "defaultValue": 0 + { + "name": "Hard", + "value": "hard" }, - "expert": { - "keyString": "item_pool.expert", - "friendlyName": "Expert", - "description": "Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless).", - "defaultValue": 0 - }, - "crowd_control": { - "keyString": "item_pool.crowd_control", - "friendlyName": "Crowd Control", - "description": "Configures the item pool for the crowd control extension. Do not use this unless you are using crowd control.", - "defaultValue": 0 + { + "name": "Expert", + "value": "expert" } - } + ] }, "item_functionality": { - "keyString": "item_functionality", + "type": "select", "friendlyName": "Item Functionality", - "description": "Alters the usefulness of various items in the game.", - "inputType": "range", - "subOptions": { - "easy": { - "keyString": "item_functionality.easy", - "friendlyName": "Easy", - "description": "Increases helpfulness of items. Medallions are usable everywhere, even without a sword. Hammer can be used in place of master sword to beat ganon and collect the tablets.", - "defaultValue": 0 + "description": "Changes the abilities of your items", + "defaultValue": "normal", + "options": [ + { + "name": "Easy", + "value": "easy" }, - "normal": { - "keyString": "item_functionality.normal", - "friendlyName": "Normal", - "description": "Item functionality remains unchanged from the vanilla game.", - "defaultValue": 50 + { + "name": "Normal", + "value": "normal" }, - "hard": { - "keyString": "item_functionality.hard", - "friendlyName": "Hard", - "description": "Reduced helpfulness of items. Potions are less effective, you can't catch faeries, the Magic Cape uses double magic, the Cane of Byrna does not grant invulnerability, boomerangs do not stun, and silver arrows are disabled outside ganon.", - "defaultValue": 0 + { + "name": "Hard", + "value": "hard" }, - "expert": { - "keyString": "item_functionality.expert", - "friendlyName": "Expert", - "description": "Vastly reduces the helpfulness of items. Potions are barely effective, you can't catch faeries, the Magic Cape uses double magic, the Cane of Byrna does not grant invulnerability, boomerangs and hookshot do not stun, and the silver arrows are disabled outside ganon.", - "defaultValue": 0 + { + "name": "Expert", + "value": "expert" } - } - }, - "progression_balancing": { - "keyString": "progression_balancing", - "friendlyName": "Progression Balancing", - "description": "A system to reduce time spent in BK mode. It moves your items into an earlier access sphere to make it more likely you have access to progression items.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "progression_balancing.on", - "friendlyName": "On", - "description": "Enable progression balancing.", - "defaultValue": 50 - }, - "off": { - "keyString": "progression_balancing.off", - "friendlyName": "Off", - "description": "Disable progression balancing.", - "defaultValue": 0 - } - } - }, - "boss_shuffle": { - "keyString": "boss_shuffle", - "friendlyName": "Boss Shuffle", - "description": "Determines which boss appears in which dungeon.", - "inputType": "range", - "subOptions": { - "none": { - "keyString": "boss_shuffle.none", - "friendlyName": "None", - "description": "Bosses appear in vanilla locations.", - "defaultValue": 50 - }, - "simple": { - "keyString": "boss_shuffle.simple", - "friendlyName": "Simple", - "description": "Existing bosses except Ganon and Agahnim are shuffled throughout dungeons.", - "defaultValue": 0 - }, - "full": { - "keyString": "boss_shuffle.full", - "friendlyName": "Full", - "description": "Bosses are shuffled, and three of them may occur twice.", - "defaultValue": 0 - }, - "random": { - "keyString": "boss_shuffle.random", - "friendlyName": "Random", - "description": "Any boss may appear any number of times.", - "defaultValue": 0 - }, - "singularity": { - "keyString": "boss_shuffle.singularity", - "friendlyName": "Singularity", - "description": "Picks a boss at random and puts it in every dungeon it can appear in. Remaining dungeons bosses are chosen at random.", - "defaultValue": 0 - } - } + ] }, "enemy_shuffle": { - "keyString": "enemy_shuffle", + "type": "select", "friendlyName": "Enemy Shuffle", - "description": "Randomizes which enemies appear throughout the game.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "enemy_shuffle.on", - "friendlyName": "On", - "description": "Enable enemy shuffle.", - "defaultValue": 0 + "description": "Randomize the enemies which appear throughout the game", + "defaultValue": "off", + "options": [ + { + "name": "Disabled", + "value": "off" }, - "off": { - "keyString": "enemy_shuffle.off", - "friendlyName": "Off", - "description": "Disable enemy shuffle.", - "defaultValue": 50 + { + "name": "Enabled", + "value": "on" } - } + ] }, - "killable_thieves": { - "keyString": "killable_thieves", - "friendlyName": "Killable Thieves", - "description": "Determines whether thieves may be killed or not.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "killable_thieves.on", - "friendlyName": "On", - "description": "Thieves are mortal.", - "defaultValue": 0 + "boss_shuffle": { + "type": "select", + "friendlyName": "Boss Shuffle", + "description": "Shuffle the bosses within dungeons", + "defaultValue": "none", + "options": [ + { + "name": "Disabled", + "value": "none" }, - "off": { - "keyString": "killable_thieves.off", - "friendlyName": "Off", - "description": "Thieves are invulnerable.", - "defaultValue": 50 + { + "name": "Simple", + "value": "simple" + }, + { + "name": "Full", + "value": "full" + }, + { + "name": "Singularity", + "value": "singularity" + }, + { + "name": "Random", + "value": "random" } - } - }, - "tile_shuffle": { - "keyString": "tile_shuffle", - "friendlyName": "Tile Shuffle", - "description": "Randomizes tile layouts in rooms where floor tiles attack you.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "tile_shuffle.on", - "friendlyName": "On", - "description": "Enable tile shuffle.", - "defaultValue": 0 - }, - "off": { - "keyString": "tile_shuffle.off", - "friendlyName": "Off", - "description": "Disable tile shuffle.", - "defaultValue": 50 - } - } - }, - "bush_shuffle": { - "keyString": "bush_shuffle", - "friendlyName": "Bush Shuffle", - "description": "Randomize the chance that bushes around Hyrule have enemies hiding under them.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "bush_shuffle.on", - "friendlyName": "On", - "description": "Enable bush shuffle.", - "defaultValue": 0 - }, - "off": { - "keyString": "bush_shuffle.off", - "friendlyName": "Off", - "description": "Disable bush shuffle.", - "defaultValue": 50 - } - } - }, - "enemy_damage": { - "keyString": "enemy_damage", - "friendlyName": "Enemy Damage", - "description": "Randomizes how much damage enemies can deal to you.", - "inputType": "range", - "subOptions": { - "default": { - "keyString": "enemy_damage.default", - "friendlyName": "Vanilla Damage", - "description": "Enemies deal the same damage as in the vanilla game.", - "defaultValue": 50 - }, - "shuffled": { - "keyString": "enemy_damage.shuffled", - "friendlyName": "Shuffled", - "description": "Enemies deal zero to four hearts of damage, and armor reduces this damage.", - "defaultValue": 0 - }, - "random": { - "keyString": "enemy_damage.random", - "friendlyName": "Random", - "description": "Enemies may deal zero through eight hearts of damage, and armor re-shuffles how much damage you take from each enemy.", - "defaultValue": 0 - } - } - }, - "enemy_health": { - "keyString": "enemy_health", - "friendlyName": "Enemy Health", - "description": "Randomizes the amount of health enemies have. Does not affect bosses.", - "inputType": "range", - "subOptions": { - "default": { - "keyString": "enemy_health.default", - "friendlyName": "Vanilla", - "description": "Enemies have the same amount of health as in the vanilla game.", - "defaultValue": 50 - }, - "easy": { - "keyString": "enemy_health.easy", - "friendlyName": "Reduced", - "description": "Enemies have generally reduced health.", - "defaultValue": 0 - }, - "hard": { - "keyString": "enemy_health.hard", - "friendlyName": "Increased", - "description": "Enemies have generally increased health.", - "defaultValue": 0 - }, - "expert": { - "keyString": "enemy_health.expert", - "friendlyName": "Armor-Plated", - "description": "Enemies will be very heard to defeat.", - "defaultValue": 0 - } - } - }, - "pot_shuffle": { - "keyString": "pot_shuffle", - "friendlyName": "Pot Shuffle", - "description": "Keys, items, and buttons hidden under pots in dungeons may be shuffled with other pots in their super-tile.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "pot_shuffle.on", - "friendlyName": "On", - "description": "Enable pot shuffle.", - "defaultValue": 0 - }, - "off": { - "keyString": "pot_shuffle.off", - "friendlyName": "Off", - "description": "Disable pot shuffle.", - "defaultValue": 50 - } - } - }, - "beemizer": { - "keyString": "beemizer", - "friendlyName": "Beemizer", - "description": "Remove items from the global item pool and replace them with single bees and bee traps.", - "inputType": "range", - "subOptions": { - "0": { - "keyString": "beemizer.0", - "friendlyName": "Level 0", - "description": "No bee traps are placed.", - "defaultValue": 50 - }, - "1": { - "keyString": "beemizer.1", - "friendlyName": "Level 1", - "description": "25% of the non-essential item pool is replaced with bee traps.", - "defaultValue": 1 - }, - "2": { - "keyString": "beemizer.2", - "friendlyName": "Level 2", - "description": "60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees.", - "defaultValue": 2 - }, - "3": { - "keyString": "beemizer.3", - "friendlyName": "Level 3", - "description": "100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees.", - "defaultValue": 3 - }, - "4": { - "keyString": "beemizer.4", - "friendlyName": "Level 4", - "description": "100% of the non-essential item pool is replaced with bee traps.", - "defaultValue": 4 - } - } + ] }, "shop_shuffle": { - "keyString": "shop_shuffle", + "type": "select", "friendlyName": "Shop Shuffle", - "description": "Alters the inventory and prices of shops.", - "inputType": "range", - "subOptions": { - "none": { - "keyString": "shop_shuffle.none", - "friendlyName": "Vanilla Shops", - "description": "Shop contents are left unchanged.", - "defaultValue": 50 + "description": "Shuffles the content and prices of shops throughout Hyrule", + "defaultValue": "none", + "options": [ + { + "name": "None", + "value": "none" }, - "i": { - "keyString": "shop_shuffle.i", - "friendlyName": "Inventory Shuffle", - "description": "Randomizes the inventories of shops.", - "defaultValue": 0 + { + "name": "Inventory", + "value": "i" }, - "p": { - "keyString": "shop_shuffle.p", - "friendlyName": "Price Shuffle", - "description": "Randomizes the price of items sold in shops.", - "defaultValue": 0 + { + "name": "Prices", + "value": "p" }, - "u": { - "keyString": "shop_shuffle.u", - "friendlyName": "Capacity Upgrades", - "description": "Shuffles capacity upgrades throughout the game world.", - "defaultValue": 0 + { + "name": "Capacity Upgrades", + "value": "u" }, - "ip": { - "keyString": "shop_shuffle.ip", - "friendlyName": "Inventory & Prices", - "description": "Shuffles the inventory and randomizes the prices of items in shops.", - "defaultValue": 0 + { + "name": "Inventory and Prices", + "value": "ip" }, - "uip": { - "keyString": "shop_shuffle.uip", - "friendlyName": "Full Shuffle", - "description": "Shuffles the inventory and randomizes the prices of items in shops. Also distributes capacity upgrades throughout the world.", - "defaultValue": 0 + { + "name": "Inventory, Prices, and Upgrades", + "value": "ipu" } - } - }, - "shuffle_prizes": { - "keyString": "shuffle_prizes", - "friendlyName": "Prize Shuffle", - "description": "Alters the Prizes from pulling, bonking, enemy kills, digging, and hoarders", - "inputType": "range", - "subOptions": { - "none": { - "keyString": "shuffle_prizes.none", - "friendlyName": "None", - "description": "All prizes from pulling, bonking, enemy kills, digging, hoarders are vanilla.", - "defaultValue": 0 - }, - "g": { - "keyString": "shuffle_prizes.g", - "friendlyName": "\"General\" prize shuffle", - "description": "Shuffles the prizes from pulling, enemy kills, digging, hoarders", - "defaultValue": 50 - }, - "b": { - "keyString": "shuffle_prizes.b", - "friendlyName": "Bonk prize shuffle", - "description": "Shuffles the prizes from bonking into trees.", - "defaultValue": 0 - }, - "bg": { - "keyString": "shuffle_prizes.bg", - "friendlyName": "Both", - "description": "Shuffles both of the options.", - "defaultValue": 0 - } - } - }, - "timer": { - "keyString": "timer", - "friendlyName": "Timed Modes", - "description": "Add a timer to the game UI, and cause it to have various effects.", - "inputType": "range", - "subOptions": { - "none": { - "keyString": "timer.none", - "friendlyName": "Disabled", - "description": "No timed mode is applied to the game.", - "defaultValue": 50 - }, - "timed": { - "keyString": "timer.timed", - "friendlyName": "Timed Mode", - "description": "Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end.", - "defaultValue": 0 - }, - "timed_ohko": { - "keyString": "timer.timed_ohko", - "friendlyName": "Timed OHKO", - "description": "Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit.", - "defaultValue": 0 - }, - "ohko": { - "keyString": "timer.ohko", - "friendlyName": "One-Hit KO", - "description": "Timer always at zero. Permanent OHKO.", - "defaultValue": 0 - }, - "timed_countdown": { - "keyString": "timer.timed_countdown", - "friendlyName": "Timed Countdown", - "description": "Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.", - "defaultValue": 0 - }, - "display": { - "keyString": "timer.display", - "friendlyName": "Timer Only", - "description": "Displays a timer, but otherwise does not affect gameplay or the item pool.", - "defaultValue": 0 - } - } - }, - "glitch_boots": { - "keyString": "glitch_boots", - "friendlyName": "Glitch Boots", - "description": "Start with Pegasus Boots in any glitched logic mode that makes use of them.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "glitch_boots.on", - "friendlyName": "On", - "description": "Enable glitch boots.", - "defaultValue": 50 - }, - "off": { - "keyString": "glitch_boots.off", - "friendlyName": "Off", - "description": "Disable glitch boots.", - "defaultValue": 0 - } - } - }, - "door_shuffle": { - "keyString": "door_shuffle", - "friendlyName": "Door Shuffle", - "description": "Shuffles the interior layout of dungeons. Only available if the host rolls the game using the doors version of the generator.", - "inputType": "range", - "subOptions": { - "vanilla": { - "keyString": "door_shuffle.vanilla", - "friendlyName": "Vanilla", - "description": "Doors within dungeons remain unchanged from the vanilla game.", - "defaultValue": 50 - }, - "basic": { - "keyString": "door_shuffle.basic", - "friendlyName": "Basic", - "description": "Dungeons are shuffled within themselves.", - "defaultValue": 0 - }, - "crossed": { - "keyString": "door_shuffle.crossed", - "friendlyName": "Crossed", - "description": "Dungeons are shuffled across each other. Eastern may contain POD, Mire, and Hera.", - "defaultValue": 0 - } - } - }, - "intensity": { - "keyString": "intensity", - "friendlyName": "Door Shuffle Intensity Level", - "description": "Specifies what types of doors will be shuffled.", - "inputType": "range", - "subOptions": { - "1": { - "keyString": "intensity.1", - "friendlyName": "Level 1", - "description": "Doors and spiral staircases will be shuffled amongst themselves.", - "defaultValue": 50 - }, - "2": { - "keyString": "intensity.2", - "friendlyName": "Level 2", - "description": "Doors, open edges, and straight stair cases are shuffled amongst each other. Spiral staircases will be shuffled amongst themselves.", - "defaultValue": 0 - }, - "3": { - "keyString": "intensity.3", - "friendlyName": "Level 3", - "description": "Level 2 plus lobby shuffling, which means any non-dead-end supertile with a south-facing door may become a dungeon entrance.", - "defaultValue": 0 - }, - "random": { - "keyString": "intensity.random", - "friendlyName": "Random", - "description": "Randomly chooses an intensity level from 1-3.", - "defaultValue": 0 - } - } + ] } }, "romOptions": { "disablemusic": { - "keyString": "rom.disablemusic", + "type": "select", "friendlyName": "Game Music", - "description": "Enable or disable all in-game music. Sound-effects are unaffected.", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "rom.disablemusic.on", - "friendlyName": "Disabled", - "description": "Disables in-game music.", - "defaultValue": 0 + "description": "Choose to enable or disable in-game music", + "defaultValue": "off", + "options": [ + { + "name": "Enabled", + "value": "off" }, - "off": { - "keyString": "rom.disablemusic.off", - "friendlyName": "Enabled", - "description": "Enables in-game music.", - "defaultValue": 50 + { + "name": "Disabled", + "value": "on" } - } + ] }, "quickswap": { - "keyString": "rom.quickswap", + "type": "select", "friendlyName": "Item Quick-Swap", - "description": "Quickly change items by pressing the L+R shoulder buttons. Pressing L+R at the same time toggles the in-slot item (arrows and silver arrows, for example).", - "inputType": "range", - "subOptions": { - "on": { - "keyString": "rom.quickswap.on", - "friendlyName": "Enabled", - "description": "Enable quick-swap.", - "defaultValue": 0 + "description": "Enable or disable quick-swap using the L+R buttons", + "defaultValue": "on", + "options": [ + { + "name": "Enabled", + "value": "on" }, - "off": { - "keyString": "rom.quickswap.off", - "friendlyName": "Disabled", - "description": "Disable quick-swap.", - "defaultValue": 50 + { + "name": "Disabled", + "value": "off" } - } + ] }, "menuspeed": { - "keyString": "menuspeed", + "type": "select", "friendlyName": "Menu Speed", - "description": "Choose how fast the in-game menu opens and closes.", - "inputType": "range", - "subOptions": { - "normal": { - "keyString": "rom.menuspeed.normal", - "friendlyName": "Vanilla", - "description": "Menu speed is unchanged from the vanilla game.", - "defaultValue": 50 + "description": "Changes the animation speed of the in-game menu", + "defaultValue": "normal", + "options": [ + { + "name": "Normal", + "value": "normal" }, - "instant": { - "keyString": "rom.menuspeed.instant", - "friendlyName": "Instant", - "description": "The in-game menu appears and disappears instantly.", - "defaultValue": 0 + { + "name": "Instant", + "value": "instant" }, - "double": { - "keyString": "rom.menuspeed.double", - "friendlyName": "Double Speed", - "description": "The in-game menu animation moves at double speed.", - "defaultValue": 0 + { + "name": "Double", + "value": "double" }, - "triple": { - "keyString": "rom.menuspeed.triple", - "friendlyName": "Triple Speed", - "description": "The in-game menu animation moves at triple speed.", - "defaultValue": 0 + { + "name": "Triple", + "value": "triple" }, - "quadruple": { - "keyString": "rom.menuspeed.quadruple", - "friendlyName": "Quadruple Speed", - "description": "The in-game menu animation moves at quadruple speed.", - "defaultValue": 0 + { + "name": "Quadruple", + "value": "quadruple" }, - "half": { - "keyString": "rom.menuspeed.half", - "friendlyName": "Half Speed", - "description": "The in-game menu animation moves at half speed.", - "defaultValue": 0 + { + "name": "Half-Speed", + "value": "half" } - } - }, - "heartcolor": { - "keyString": "rom.heartcolor", - "friendlyName": "Heart Color", - "description": "Changes the color of your in-game health hearts.", - "inputType": "range", - "subOptions": { - "red": { - "keyString": "rom.heartcolor.red", - "friendlyName": "Red", - "description": "Health hearts are red.", - "defaultValue": 50 - }, - "blue": { - "keyString": "rom.heartcolor.blue", - "friendlyName": "Blue", - "description": "Health hearts are blue.", - "defaultValue": 0 - }, - "green": { - "keyString": "rom.heartcolor.green", - "friendlyName": "Green", - "description": "Health hearts are green.", - "defaultValue": 0 - }, - "yellow": { - "keyString": "rom.heartcolor.yellow", - "friendlyName": "Yellow", - "description": "Health hearts are yellow.", - "defaultValue": 0 - }, - "random": { - "keyString": "rom.heartcolor.random", - "friendlyName": "Random", - "description": "Health heart color is chosen randomly from red, green, blue, and yellow.", - "defaultValue": 0 - } - } + ] }, "heartbeep": { - "keyString": "rom.heartbeep", - "friendlyName": "Heart Beep Speed", - "description": "Controls the frequency of the low-health beeping.", - "inputType": "range", - "subOptions": { - "double": { - "keyString": "rom.heartbeep.double", - "friendlyName": "Double", - "description": "Doubles the frequency of the low-health beep.", - "defaultValue": 0 + "type": "select", + "friendlyName": "Heart-Beep Speed", + "description": "Change the frequency of the heart beep alert when you are at low health", + "defaultValue": "normal", + "options": [ + { + "name": "Double Speed", + "value": "double" }, - "normal": { - "keyString": "rom.heartbeep.normal", - "friendlyName": "Vanilla", - "description": "Heart beep frequency is unchanged from the vanilla game.", - "defaultValue": 50 + { + "name": "Normal", + "value": "normal" }, - "half": { - "keyString": "rom.heartbeep.half", - "friendlyName": "Half Speed", - "description": "Heart beep plays at half-speed.", - "defaultValue": 0 + { + "name": "Half-Speed", + "value": "half" }, - "quarter": { - "keyString": "rom.heartbeep.quarter", - "friendlyName": "Quarter Speed", - "description": "Heart beep plays at one quarter-speed.", - "defaultValue": 0 + { + "name": "Quarter-Speed", + "value": "quarter" }, - "off": { - "keyString": "rom.heartbeep.off", - "friendlyName": "Disabled", - "description": "Disables the low-health heart beep.", - "defaultValue": 0 + { + "name": "Disabled", + "value": "off" } - } + ] + }, + "heartcolor": { + "type": "select", + "friendlyName": "Heart Color", + "description": "Change the color of your hearts in-game", + "defaultValue": "red", + "options": [ + { + "name": "Red", + "value": "red" + }, + { + "name": "Blue", + "value": "blue" + }, + { + "name": "Green", + "value": "green" + }, + { + "name": "Yellow", + "value": "yellow" + }, + { + "name": "Random", + "value": "random" + } + ] }, "ow_palettes": { - "keyString": "rom.ow_palettes", + "type": "select", "friendlyName": "Overworld Palette", - "description": "Randomize the colors of the overworld, within reason.", - "inputType": "range", - "subOptions": { - "default": { - "keyString": "rom.ow_palettes.default", - "friendlyName": "Vanilla", - "description": "Overworld colors will remain unchanged.", - "defaultValue": 50 + "description": "Change the colors of the overworld", + "defaultValue": "default", + "options": [ + { + "name": "Vanilla", + "value": "default" }, - "random": { - "keyString": "rom.ow_palettes.random", - "friendlyName": "Random", - "description": "Shuffles the colors of the overworld palette.", - "defaultValue": 0 - }, - "blackout": { - "keyString": "rom.ow_palettes.blackout", - "friendlyName": "Blackout", - "description": "Never use this. Makes all overworld palette colors black.", - "defaultValue": 0 + { + "name": "Randomized", + "value": "random" } - } + ] }, "uw_palettes": { - "keyString": "rom.uw_palettes", - "friendlyName": "Underworld Palettes", - "description": "Randomize the colors of the underworld (caves, dungeons, etc.), within reason.", - "inputType": "range", - "subOptions": { - "default": { - "keyString": "rom.uw_palettes.default", - "friendlyName": "Vanilla", - "description": "Underworld colors will remain unchanged.", - "defaultValue": 50 + "type": "select", + "friendlyName": "Underworld Palette", + "description": "Change the colors of the underworld", + "defaultValue": "default", + "options": [ + { + "name": "Vanilla", + "value": "default" }, - "random": { - "keyString": "rom.uw_palettes.random", - "friendlyName": "Random", - "description": "Shuffles the colors of the underworld palette.", - "defaultValue": 0 - }, - "blackout": { - "keyString": "rom.uw_palettes.blackout", - "friendlyName": "Blackout", - "description": "Never use this. Makes all underworld palette colors black.", - "defaultValue": 0 + { + "name": "Randomized", + "value": "random" } - } + ] + }, + "hud_palettes": { + "type": "select", + "friendlyName": "HUD Palette", + "description": "Change the colors of the user-interface", + "defaultValue": "default", + "options": [ + { + "name": "Vanilla", + "value": "default" + }, + { + "name": "Randomized", + "value": "random" + } + ] + }, + "sword_palettes": { + "type": "select", + "friendlyName": "Sword Palette", + "description": "Change the colors of the swords, within reason", + "defaultValue": "default", + "options": [ + { + "name": "Vanilla", + "value": "default" + }, + { + "name": "Randomized", + "value": "random" + } + ] + }, + "sprite": { + "type": "select", + "friendlyName": "Sprite", + "description": "Choose a sprite to play as!", + "defaultValue": "link", + "options": [ + { + "name": "Random", + "value": "random" + } + ] } } } diff --git a/WebHostLib/static/static/spriteData.json b/WebHostLib/static/static/spriteData.json index f54078e9..4f7ab8a4 100644 --- a/WebHostLib/static/static/spriteData.json +++ b/WebHostLib/static/static/spriteData.json @@ -1 +1,1609 @@ -{"Link": "001.link.1.zspr", "Four Swords Link": "4slink-armors.1.zspr", "Abigail": "abigail.1.zspr", "Adol": "adol.1.zspr", "Aggretsuko": "aggretsuko.1.zspr", "Alice": "alice.1.zspr", "Angry Video Game Nerd": "angry-video-game-nerd.1.zspr", "Arcane": "arcane.1.zspr", "Ark": "ark.2.zspr", "Arrghus": "arrghus.2.zspr", "Astronaut": "astronaut.1.zspr", "Badeline": "badeline.1.zspr", "Bananas In Pyjamas": "bananas-in-pyjamas.1.zspr", "Bandit": "bandit.1.zspr", "Batman": "batman.1.zspr", "Beau": "beau.1.zspr", "Bewp": "bewp.1.zspr", "Big Key": "bigkey.1.zspr", "Birb": "birb.1.zspr", "Birdo": "birdo.1.zspr", "Black Mage": "blackmage.1.zspr", "Blacksmith Link": "blacksmithlink.1.zspr", "Blossom": "blossom.1.zspr", "Bob": "bob.1.zspr", "Boo 2": "boo-two.1.zspr", "Boo": "boo.2.zspr", "Bottle o' Goo": "bottle_o_goo.1.zspr", "": "botw-zelda.1.zspr", "Bowser": "bowser.1.zspr", "Branch": "branch.1.zspr", "Brian": "brian.1.zspr", "Broccoli": "broccoli.1.zspr", "Bronzor": "bronzor.1.zspr", "B.S. Boy": "bsboy.1.zspr", "B.S. Girl": "bsgirl.1.zspr", "Bubbles": "bubbles.1.zspr", "Bullet Bill": "bullet_bill.1.zspr", "Buttercup": "buttercup.1.zspr", "Cactuar": "cactuar.1.zspr", "Cadence": "cadence.1.zspr", "CarlSagan42": "carlsagan42.1.zspr", "Casual Zelda": "casual-zelda.1.zspr", "Marvin the Cat": "cat.3.zspr", "Cat Boo": "catboo.1.zspr", "CD-i Link": "cdilink.1.zspr", "Celes": "celes.1.zspr", "Charizard": "charizard.1.zspr", "Cheep Cheep": "cheepcheep.1.zspr", "Chibity": "chibity.1.zspr", "Cirno": "cirno.1.zspr", "Clifford": "clifford.1.zspr", "Clyde": "clyde.1.zspr", "Conker": "conker.1.zspr", "Cornelius": "cornelius.1.zspr", "Untitled": "corona.1.zspr", "Cucco": "cucco.1.zspr", "Cursor": "cursor.1.zspr", "Dark Panda": "dark-panda.1.zspr", "Dark Boy": "darkboy.1.zspr", "Dark Girl": "darkgirl.1.zspr", "Dark Link (Tunic)": "darklink-tunic.1.zspr", "Dark Link": "darklink.1.zspr", "Dark Swatchy": "darkswatchy.1.zspr", "Dark Zelda": "darkzelda.1.zspr", "Dark Zora": "darkzora.2.zspr", "Deadpool (Mythic)": "deadpool-mythic.1.zspr", "Deadpool (SirCzah)": "deadpool.1.zspr", "Deadrock": "deadrock.1.zspr", "Decidueye": "decidueye.1.zspr", "Demon Link": "demonlink.1.zspr", "Dragonite": "dragonite.2.zspr", "Drake The Dragon": "drake.1.zspr", "D.Owls": "d_owls.1.zspr", "Eggplant": "eggplant.1.zspr", "EmoSaru": "emosaru.1.zspr", "Ezlo": "ezlo.1.zspr", "Fierce Deity Link": "fierce-deity-link.1.zspr", "Finn Merten": "finn.3.zspr", "Finny Bear": "finny_bear.1.zspr", "Floodgate Fish": "fish_floodgate.1.zspr", "Flavor Guy": "flavor_guy.1.zspr", "Fox Link": "foxlink.1.zspr", "Freya Crescent": "freya.1.zspr", "Frisk": "frisk.1.zspr", "Frog Link": "froglink.3.zspr", "Fujin": "fujin.2.zspr", "Future Trunks": "future_trunks.1.zspr", "Gamer": "gamer.1.zspr", "Mini Ganon": "ganon.1.zspr", "Ganondorf": "ganondorf.1.zspr", "Garfield": "garfield.2.zspr", "Garnet": "garnet.1.zspr", "Garo Master": "garomaster.1.zspr", "GBC Link": "gbc-link.1.zspr", "Geno": "geno.1.zspr", "Gobli": "gobli.1.zspr", "Goomba": "goomba.1.zspr", "Goose": "goose.1.zspr", "GrandPOOBear": "grandpoobear.2.zspr", "Gruncle Stan": "grunclestan.1.zspr", "GuizDP": "guiz.1.zspr", "Hardhat Beetle": "hardhat_beetle.1.zspr", "Hat Kid": "hat-kid.1.zspr", "Headless Link": "headlesslink.1.zspr", "Hello Kitty": "hello_kitty.1.zspr", "Hidari": "hidari.1.zspr", "Hint Tile": "hint_tile.1.zspr", "Hitsuyan1337": "hitsuyan.1.zspr", "Hoarder (Bush)": "hoarder-bush.1.zspr", "Hoarder (Pot)": "hoarder-pot.1.zspr", "Hoarder (Rock)": "hoarder-rock.1.zspr", "Homer Simpson": "homer.1.zspr", "Hyrule Knight": "hyruleknight.1.zspr", "iBazly": "ibazly.1.zspr", "Ignignokt": "ignignokt.2.zspr", "Informant Woman": "informant_woman.1.zspr", "Inkling": "inkling.1.zspr", "Invisible Link": "invisibleman.1.zspr", "Jack Frost": "jack-frost.1.zspr", "Jason Frudnick": "jason_frudnick.1.zspr", "Jasp": "jasp.1.zspr", "Jogurt": "jogurt.1.zspr", "Katsura": "katsura.1.zspr", "Kecleon": "kecleon.1.zspr", "Kenny McCormick": "kenny_mccormick.1.zspr", "Ketchup": "ketchup.1.zspr", "Kholdstare": "kholdstare.1.zspr", "King Gothalion": "king_gothalion.1.zspr", "King Graham v1.1": "king_graham.1.zspr", "Kirby": "kirby-meta.1.zspr", "Kore8": "kore8.1.zspr", "Lakitu": "lakitu.1.zspr", "Lapras": "lapras.1.zspr", "Lest": "lest.1.zspr", "Lily": "lily.1.zspr", "Linja": "linja.1.zspr", "Hat Color Link": "linkhatcolor.1.zspr", "Tunic Color Link": "linktuniccolor.1.zspr", "Pony": "littlepony.1.zspr", "Figaro Merchant": "locke_merchant.1.zspr", "Lucario": "lucario.1.zspr", "Luigi": "luigi.1.zspr", "Madeline": "madeline.1.zspr", "Magus": "magus.1.zspr", "Maiden": "maiden.1.zspr", "Mallow (Cat)": "mallow-cat.1.zspr", "Manga Link": "mangalink.1.zspr", "Maple Queen": "maplequeen.2.zspr", "Marin": "marin.2.zspr", "Mario (Classic)": "mario-classic.2.zspr", "Mario and Cappy": "mariocappy.1.zspr", "Tanooki Mario": "mario_tanooki.1.zspr", "Marisa Kirisame": "marisa.1.zspr", "Matthias": "matthias.1.zspr", "Meatwad": "meatwad.1.zspr", "Medallions": "medallions.1.zspr", "Medli": "medli.1.zspr", "Megaman X": "megaman-x.2.zspr", "Baby Metroid": "metroid.1.zspr", "MewLp": "mew.1.zspr", "Mike Jones": "mike-jones.2.zspr", "Minish Cap Link": "minishcaplink.2.zspr", "Minish Link": "minish_link.1.zspr", "missingno": "missingno.1.zspr", "Modern Link": "modernlink.1.zspr", "Mog": "mog.2.zspr", "Momiji Inubashiri": "momiji.1.zspr", "Moosh": "moosh.1.zspr", "Mouse": "mouse.1.zspr", "Ms. Paint Dog": "ms-paintdog.1.zspr", "Power Up with Pride Mushroom": "mushy.1.zspr", "Nature Link": "naturelink.1.zspr", "Navi": "navi.1.zspr", "Navirou": "navirou.1.zspr", "Ned Flanders": "ned-flanders.1.zspr", "Negative Link": "negativelink.1.zspr", "Neosad": "neosad.1.zspr", " NES Link": "neslink.1.zspr", "Ness (Earthbound)": "ness.1.zspr", "Nia": "nia.1.zspr", "Niko": "niko.1.zspr", "Old Man": "oldman.2.zspr", "Ori": "ori.2.zspr", "Outline Link": "outlinelink.1.zspr", "Parallel Worlds Link": "parallelworldslink.1.zspr", "Paula": "paula.1.zspr", "Princess Peach": "peach.1.zspr", "Penguin Link": "penguinlink.1.zspr", "Pete (Harvest Moon)": "pete.1.zspr", "Phoenix Wright": "phoenix-wright.1.zspr", "Pikachu": "pikachu.1.zspr", "Pink Ribbon Link": "pinkribbonlink.2.zspr", "Piranha Plant": "piranha_plant.1.zspr", "Plague Knight": "plagueknight.1.zspr", "Pokey": "pokey.1.zspr", "Popoi": "popoi.1.zspr", "Poppy": "poppy.1.zspr", "Porg Knight": "porg_knight.1.zspr", "Powerpuff Girl": "powerpuff_girl.1.zspr", "Pride Link": "pridelink.2.zspr", "Primm": "primm.1.zspr", "Princess Bubblegum": "princess_bubblegum.1.zspr", "Psyduck": "psyduck.2.zspr", "The Pug": "pug.1.zspr", "Purple Chest": "purplechest-bottle.1.zspr", "Pyro": "pyro.1.zspr", "Rainbow Link": "rainbowlink.1.zspr", "Remeer": "remeer.1.zspr", "Rick (Redacted)": "rick.1.zspr", "Robo-Link 9000": "robotlink.1.zspr", "Rocko": "rocko.1.zspr", "Rottytops": "rottytops.1.zspr", "Roy Koopa": "roykoopa.1.zspr", "Rumia": "rumia.1.zspr", "Rydia": "rydia.1.zspr", "Ryu": "ryu.1.zspr", "Sailor Moon": "sailormoon.1.zspr", "Saitama": "saitama.1.zspr", "Samus (Super Metroid)": "samus-sm.1.zspr", "Samus": "samus.2.zspr", "Samus (Classic)": "samus_classic.1.zspr", "Santa Link": "santalink.2.zspr", "Scholar": "scholar.1.zspr", "Selan": "selan.1.zspr", "SevenS1ns": "sevens1ns.1.zspr", "Shadow": "shadow.1.zspr", "Shadow Sakura": "shadowsaku.2.zspr", "Shantae": "shantae.1.zspr", "Shuppet": "shuppet.1.zspr", "Shy Gal": "shy-gal.1.zspr", "Shy Guy": "shy-guy.1.zspr", "SighnWaive": "sighn_waive.1.zspr", "SNES Controller": "snes-controller.1.zspr", "Soda Can": "sodacan.1.zspr", "Solaire of Astora": "solaire.1.zspr", "Hyrule Soldier": "soldiersprite.1.zspr", "Sonic the Hedgehog": "sonic.1.zspr", "Sora": "sora.1.zspr", "Sora (KH1)": "sora_kh1.1.zspr", "Squall": "squall.1.zspr", "Squirrel": "squirrel.1.zspr", "Squirtle": "squirtle.1.zspr", "Stalfos": "stalfos.1.zspr", "Stan": "stan.1.zspr", "Static Link": "staticlink.1.zspr", "Stick Man": "stick_man.1.zspr", "Super Bomb": "superbomb.1.zspr", "Super Bunny": "superbunny.2.zspr", "Super Meat Boy": "supermeatboy.1.zspr", "Swatchy": "swatchy.1.zspr", "TASBot": "tasbot.1.zspr", "Tea Time": "teatime.1.zspr", "Terra (Esper)": "terra.1.zspr", "Tetra Sheet": "tetra.1.zspr", "TGH": "tgh.1.zspr", "Thief": "thief.1.zspr", "Thomcrow": "thomcrow.1.zspr", "Tile": "tile.2.zspr", "Tingle": "tingle.1.zspr", "TMNT": "tmnt.1.zspr", "Toad": "toad.2.zspr", "Toadette": "toadette.2.zspr", "Captain Toadette": "toadette_captain.1.zspr", "TotemLinks": "totem-links.1.zspr", "Trogdor the Burninator": "trogdor.1.zspr", "TP Zelda": "twilightprincesszelda.2.zspr", "TwoFaced": "two_faced.1.zspr", "Ty the Tasmanian Tiger": "ty.1.zspr", "Ultros": "ultros.1.zspr", "Valeera": "valeera.1.zspr", "VanillaLink": "vanillalink.1.zspr", "Vaporeon": "vaporeon.1.zspr", "Vegeta": "vegeta.1.zspr", "Vera": "vera.1.zspr", "Vitreous": "vitreous.1.zspr", "Vivi": "vivi.1.zspr", "Vivian": "vivian.1.zspr", "Wario": "wario.1.zspr", "Will": "will.1.zspr", "Wizzrobe": "wizzrobe.2.zspr", "Wolf Link (Festive)": "wolf_link.1.zspr", "Wolf Link (TP)": "wolf_link_tp.1.zspr", "Yoshi": "yoshi.1.zspr", "Yunica Tovah": "yunica.1.zspr", "Zandra": "zandra.1.zspr", "Zebra Unicorn": "zebraunicorn.1.zspr", "Zeckemyro": "zeck.1.zspr", "Zelda": "zelda.1.zspr", "Zero Suit Samus": "zerosuitsamus.2.zspr", "Zora": "zora.2.zspr"} +{ + "sprites": [ + { + "file": "neslink.1.zspr", + "author": "MikeTrethewey/Fatmanspanda", + "name": " NES Link" + }, + { + "file": "abigail.1.zspr", + "author": "Fish_waffle64", + "name": "Abigail" + }, + { + "file": "adol.1.zspr", + "author": "Yuushia", + "name": "Adol" + }, + { + "file": "aggretsuko.1.zspr", + "author": "skovacs1", + "name": "Aggretsuko" + }, + { + "file": "alice.1.zspr", + "author": "Artheau", + "name": "Alice" + }, + { + "file": "angry-video-game-nerd.1.zspr", + "author": "ABOhiccups", + "name": "Angry Video Game Nerd" + }, + { + "file": "arcane.1.zspr", + "author": "MM102", + "name": "Arcane" + }, + { + "file": "ark.1.zspr", + "author": "wzl", + "name": "Ark (Cape)" + }, + { + "file": "ark-dorana.1.zspr", + "author": "Matt Dorana", + "name": "Ark (No Cape)" + }, + { + "file": "arrghus.2.zspr", + "author": "fatmanspanda", + "name": "Arrghus" + }, + { + "file": "astronaut.1.zspr", + "author": "Malmo", + "name": "Astronaut" + }, + { + "file": "asuna.1.zspr", + "author": "Natsuru Kiyohoshi", + "name": "Asuna" + }, + { + "file": "bsboy.1.zspr", + "author": "InTheBeef", + "name": "B.S. Boy" + }, + { + "file": "bsgirl.1.zspr", + "author": "InTheBeef", + "name": "B.S. Girl" + }, + { + "file": "metroid.1.zspr", + "author": "Jam", + "name": "Baby Metroid" + }, + { + "file": "badeline.1.zspr", + "author": "Jam", + "name": "Badeline" + }, + { + "file": "bananas-in-pyjamas.1.zspr", + "author": "codemann8", + "name": "Bananas In Pyjamas" + }, + { + "file": "bandit.1.zspr", + "author": "Fenrika", + "name": "Bandit" + }, + { + "file": "batman.1.zspr", + "author": "Ninjakauz", + "name": "Batman" + }, + { + "file": "beau.1.zspr", + "author": "Achy", + "name": "Beau" + }, + { + "file": "bewp.1.zspr", + "author": "Valechec", + "name": "Bewp" + }, + { + "file": "bigkey.1.zspr", + "author": "Fouton", + "name": "Big Key" + }, + { + "file": "birb.1.zspr", + "author": "Andrew Copple", + "name": "Birb" + }, + { + "file": "birdo.1.zspr", + "author": "BlackTycoon", + "name": "Birdo" + }, + { + "file": "blackmage.1.zspr", + "author": "TheRedMage", + "name": "Black Mage" + }, + { + "file": "blacksmithlink.1.zspr", + "author": "Glan", + "name": "Blacksmith Link" + }, + { + "file": "blazer.1.zspr", + "author": "Herowho", + "name": "Blazer" + }, + { + "file": "blossom.1.zspr", + "author": "Artheau", + "name": "Blossom" + }, + { + "file": "bob.1.zspr", + "author": "fatmanspanda", + "name": "Bob" + }, + { + "file": "bobross.1.zspr", + "author": "CaptainApathy", + "name": "Bob Ross" + }, + { + "file": "boo.2.zspr", + "author": "Zarby89", + "name": "Boo" + }, + { + "file": "boo-two.1.zspr", + "author": "Achy", + "name": "Boo 2" + }, + { + "file": "botw-link.1.zspr", + "author": "Pasta La Vista", + "name": "BotW Link" + }, + { + "file": "botw-zelda.1.zspr", + "author": "Roo", + "name": "BotW Zelda" + }, + { + "file": "bottle_o_goo.1.zspr", + "author": "Fish_waffle64", + "name": "Bottle o' Goo" + }, + { + "file": "bowser.1.zspr", + "author": "Artheau", + "name": "Bowser" + }, + { + "file": "bowsette.1.zspr", + "author": "Sarah Shinespark", + "name": "Bowsette" + }, + { + "file": "bowsette-red.1.zspr", + "author": "Sarah Shinespark", + "name": "Bowsette Red" + }, + { + "file": "branch.1.zspr", + "author": "cbass601", + "name": "Branch" + }, + { + "file": "brian.1.zspr", + "author": "Herowho", + "name": "Brian" + }, + { + "file": "broccoli.1.zspr", + "author": "fatmanspanda", + "name": "Broccoli" + }, + { + "file": "bronzor.1.zspr", + "author": "fatmanspanda", + "name": "Bronzor" + }, + { + "file": "bubbles.1.zspr", + "author": "Artheau", + "name": "Bubbles" + }, + { + "file": "bullet_bill.1.zspr", + "author": "Artheau", + "name": "Bullet Bill" + }, + { + "file": "buttercup.1.zspr", + "author": "Artheau", + "name": "Buttercup" + }, + { + "file": "cdilink.1.zspr", + "author": "SnipSlum", + "name": "CD-i Link" + }, + { + "file": "cactuar.1.zspr", + "author": "RyuTech", + "name": "Cactuar" + }, + { + "file": "cadence.1.zspr", + "author": "Fish_waffle64", + "name": "Cadence" + }, + { + "file": "toadette_captain.1.zspr", + "author": "Devan2002", + "name": "Captain Toadette" + }, + { + "file": "carlsagan42.1.zspr", + "author": "FedoraFriday", + "name": "CarlSagan42" + }, + { + "file": "casual-zelda.1.zspr", + "author": "Fish_waffle64", + "name": "Casual Zelda" + }, + { + "file": "catboo.1.zspr", + "author": "JaySee87", + "name": "Cat Boo" + }, + { + "file": "celes.1.zspr", + "author": "Deagans", + "name": "Celes" + }, + { + "file": "charizard.1.zspr", + "author": "Charmander106", + "name": "Charizard" + }, + { + "file": "cheepcheep.1.zspr", + "author": "Faw", + "name": "Cheep Cheep" + }, + { + "file": "chibity.1.zspr", + "author": "Ecyro", + "name": "Chibity" + }, + { + "file": "chrizzz.1.zspr", + "author": "Chrizzz", + "name": "Chrizzz" + }, + { + "file": "cirno.1.zspr", + "author": "Achy", + "name": "Cirno" + }, + { + "file": "clifford.1.zspr", + "author": "PlaguedOne", + "name": "Clifford" + }, + { + "file": "clyde.1.zspr", + "author": "Artheau", + "name": "Clyde" + }, + { + "file": "conker.1.zspr", + "author": "Charmander106/SePH", + "name": "Conker" + }, + { + "file": "cornelius.1.zspr", + "author": "Lori", + "name": "Cornelius" + }, + { + "file": "cucco.1.zspr", + "author": "MikeTrethewey", + "name": "Cucco" + }, + { + "file": "cursor.1.zspr", + "author": "PlaguedOne", + "name": "Cursor" + }, + { + "file": "d_owls.2.zspr", + "author": "D.Owls", + "name": "D.Owls" + }, + { + "file": "slime.1.zspr", + "author": "KamenRideDecade", + "name": "DQ Slime" + }, + { + "file": "darkboy.1.zspr", + "author": "iBazly", + "name": "Dark Boy" + }, + { + "file": "darkgirl.1.zspr", + "author": "iBazly", + "name": "Dark Girl" + }, + { + "file": "darklink.1.zspr", + "author": "iBazly", + "name": "Dark Link" + }, + { + "file": "darklink-tunic.1.zspr", + "author": "Damon", + "name": "Dark Link (Tunic)" + }, + { + "file": "dark-panda.1.zspr", + "author": "MM102", + "name": "Dark Panda" + }, + { + "file": "darkswatchy.1.zspr", + "author": "Mike Trethewey", + "name": "Dark Swatchy" + }, + { + "file": "darkzelda.1.zspr", + "author": "iBazly", + "name": "Dark Zelda" + }, + { + "file": "darkzora.2.zspr", + "author": "iBazly", + "name": "Dark Zora" + }, + { + "file": "deadpool-mythic.1.zspr", + "author": "Mythic", + "name": "Deadpool (Mythic)" + }, + { + "file": "deadpool.1.zspr", + "author": "SirCzah", + "name": "Deadpool (SirCzah)" + }, + { + "file": "deadrock.1.zspr", + "author": "Glan", + "name": "Deadrock" + }, + { + "file": "decidueye.1.zspr", + "author": "Achy", + "name": "Decidueye" + }, + { + "file": "dekar.1.zspr", + "author": "The3X", + "name": "Dekar" + }, + { + "file": "demonlink.1.zspr", + "author": "Krelbel", + "name": "Demon Link" + }, + { + "file": "dragonite.2.zspr", + "author": "Fish_waffle64", + "name": "Dragonite" + }, + { + "file": "drake.1.zspr", + "author": "No Body The Dragon", + "name": "Drake The Dragon" + }, + { + "file": "eggplant.1.zspr", + "author": "PlaguedOne", + "name": "Eggplant" + }, + { + "file": "emosaru.1.zspr", + "author": "Achy", + "name": "EmoSaru" + }, + { + "file": "ezlo.1.zspr", + "author": "cbass601", + "name": "Ezlo" + }, + { + "file": "fierce-deity-link.2.zspr", + "author": "jeffreygriggs2", + "name": "Fierce Deity Link" + }, + { + "file": "locke_merchant.1.zspr", + "author": "Artheau", + "name": "Figaro Merchant" + }, + { + "file": "finn.3.zspr", + "author": "Devan2002", + "name": "Finn Merten" + }, + { + "file": "finny_bear.1.zspr", + "author": "skovacs1", + "name": "Finny Bear" + }, + { + "file": "flavor_guy.1.zspr", + "author": "PlaguedOne", + "name": "Flavor Guy" + }, + { + "file": "fish_floodgate.1.zspr", + "author": "Delphi1024", + "name": "Floodgate Fish" + }, + { + "file": "4slink-armors.1.zspr", + "author": "Mike Trethewey", + "name": "Four Swords Link" + }, + { + "file": "foxlink.1.zspr", + "author": "InTheBeef", + "name": "Fox Link" + }, + { + "file": "freya.1.zspr", + "author": "Demoncraze", + "name": "Freya Crescent" + }, + { + "file": "frisk.1.zspr", + "author": "Original by Toby Fox, sprite edit by MisterKerr", + "name": "Frisk" + }, + { + "file": "froglink.3.zspr", + "author": "Mike Trethewey", + "name": "Frog Link" + }, + { + "file": "fujin.2.zspr", + "author": "FujinAkari", + "name": "Fujin" + }, + { + "file": "future_trunks.1.zspr", + "author": "Merciter", + "name": "Future Trunks" + }, + { + "file": "gbc-link.1.zspr", + "author": "skovacs1", + "name": "GBC Link" + }, + { + "file": "gamer.1.zspr", + "author": "Unknown", + "name": "Gamer" + }, + { + "file": "ganondorf.1.zspr", + "author": "Fish_waffle64", + "name": "Ganondorf" + }, + { + "file": "garfield.2.zspr", + "author": "Fwiller", + "name": "Garfield" + }, + { + "file": "garnet.1.zspr", + "author": "Artheau", + "name": "Garnet" + }, + { + "file": "garomaster.1.zspr", + "author": "Herowho", + "name": "Garo Master" + }, + { + "file": "geno.1.zspr", + "author": "FedoraFriday", + "name": "Geno" + }, + { + "file": "gliitchwiitch.1.zspr", + "author": "Ivy-IV", + "name": "GliitchWiitch" + }, + { + "file": "gobli.1.zspr", + "author": "Lantis", + "name": "Gobli" + }, + { + "file": "goomba.1.zspr", + "author": "SirCzah", + "name": "Goomba" + }, + { + "file": "goose.1.zspr", + "author": "Jam", + "name": "Goose" + }, + { + "file": "grandpoobear.2.zspr", + "author": "proximitysound", + "name": "GrandPOOBear" + }, + { + "file": "gretis.1.zspr", + "author": "SnakeGrunger", + "name": "Gretis" + }, + { + "file": "grunclestan.1.zspr", + "author": "SirCzah", + "name": "Gruncle Stan" + }, + { + "file": "guiz.1.zspr", + "author": "GuizDP", + "name": "GuizDP" + }, + { + "file": "hanna.1.zspr", + "author": "Maya-Neko", + "name": "Hanna" + }, + { + "file": "hardhat_beetle.1.zspr", + "author": "Artheau", + "name": "Hardhat Beetle" + }, + { + "file": "linkhatcolor.1.zspr", + "author": "Damon", + "name": "Hat Color Link" + }, + { + "file": "hat-kid.1.zspr", + "author": "skovacs1", + "name": "Hat Kid" + }, + { + "file": "headlesslink.1.zspr", + "author": "fatmanspanda", + "name": "Headless Link" + }, + { + "file": "hello_kitty.1.zspr", + "author": "qeeen", + "name": "Hello Kitty" + }, + { + "file": "hidari.1.zspr", + "author": "Hidari", + "name": "Hidari" + }, + { + "file": "hint_tile.1.zspr", + "author": "PlaguedOne", + "name": "Hint Tile" + }, + { + "file": "luffy.1.zspr", + "author": "BOtheMighty", + "name": "Hitsuyan1337" + }, + { + "file": "hoarder-bush.1.zspr", + "author": "Restomak", + "name": "Hoarder (Bush)" + }, + { + "file": "hoarder-pot.1.zspr", + "author": "Restomak", + "name": "Hoarder (Pot)" + }, + { + "file": "hoarder-rock.1.zspr", + "author": "Restomak", + "name": "Hoarder (Rock)" + }, + { + "file": "homer.1.zspr", + "author": "Fwiller", + "name": "Homer Simpson" + }, + { + "file": "hotdog.1.zspr", + "author": "Xag & Tylo", + "name": "Hotdog" + }, + { + "file": "hyruleknight.1.zspr", + "author": "InTheBeef", + "name": "Hyrule Knight" + }, + { + "file": "soldiersprite.1.zspr", + "author": "InTheBeef", + "name": "Hyrule Soldier" + }, + { + "file": "ignignokt.2.zspr", + "author": "fatmanspanda", + "name": "Ignignokt" + }, + { + "file": "crewmate.1.zspr", + "author": "Fish_waffle64", + "name": "Imposter" + }, + { + "file": "informant_woman.1.zspr", + "author": "Herowho", + "name": "Informant Woman" + }, + { + "file": "inkling.1.zspr", + "author": "RyuTech", + "name": "Inkling" + }, + { + "file": "invisibleman.1.zspr", + "author": "Mike Trethewey", + "name": "Invisible Link" + }, + { + "file": "jack-frost.1.zspr", + "author": "xypotion", + "name": "Jack Frost" + }, + { + "file": "jason_frudnick.1.zspr", + "author": "Artheau", + "name": "Jason Frudnick" + }, + { + "file": "jasp.1.zspr", + "author": "Chonixtu", + "name": "Jasp" + }, + { + "file": "jogurt.1.zspr", + "author": "Nakuri", + "name": "Jogurt" + }, + { + "file": "katsura.1.zspr", + "author": "atth3h3art0fwinter", + "name": "Katsura" + }, + { + "file": "kecleon.1.zspr", + "author": "Gylergin", + "name": "Kecleon" + }, + { + "file": "kenny_mccormick.1.zspr", + "author": "Artheau", + "name": "Kenny McCormick" + }, + { + "file": "ketchup.1.zspr", + "author": "t0uchan", + "name": "Ketchup" + }, + { + "file": "kholdstare.1.zspr", + "author": "fatmanspanda", + "name": "Kholdstare" + }, + { + "file": "king_gothalion.1.zspr", + "author": "kickpixel", + "name": "King Gothalion" + }, + { + "file": "king_graham.1.zspr", + "author": "MisterKerr", + "name": "King Graham v1.1" + }, + { + "file": "kirby-meta.1.zspr", + "author": "KHRoxas", + "name": "Kirby" + }, + { + "file": "kore8.1.zspr", + "author": "Skewer", + "name": "Kore8" + }, + { + "file": "korok.1.zspr", + "author": "atth3h3art0fwinter", + "name": "Korok" + }, + { + "file": "lakitu.1.zspr", + "author": "SirCzah", + "name": "Lakitu" + }, + { + "file": "lapras.1.zspr", + "author": "Fish_waffle64", + "name": "Lapras" + }, + { + "file": "lest.1.zspr", + "author": "PrideToRuleEarth", + "name": "Lest" + }, + { + "file": "lily.1.zspr", + "author": "ScatlinkSean", + "name": "Lily" + }, + { + "file": "linja.1.zspr", + "author": "Razhagal", + "name": "Linja" + }, + { + "file": "001.link.1.zspr", + "author": "Nintendo", + "name": "Link" + }, + { + "file": "link-redrawn.1.zspr", + "author": "Spiffy", + "name": "Link Redrawn" + }, + { + "file": "little-hylian.1.zspr", + "author": "MM102", + "name": "Little Hylian" + }, + { + "file": "locke.1.zspr", + "author": "Rose", + "name": "Locke" + }, + { + "file": "lucario.1.zspr", + "author": "Achy", + "name": "Lucario" + }, + { + "file": "luigi.1.zspr", + "author": "Achy", + "name": "Luigi" + }, + { + "file": "luna-maindo.1.zspr", + "author": "IkkyLights", + "name": "Luna Maindo" + }, + { + "file": "madeline.1.zspr", + "author": "Jam", + "name": "Madeline" + }, + { + "file": "magus.1.zspr", + "author": "PlaguedOne", + "name": "Magus" + }, + { + "file": "maiden.1.zspr", + "author": "Plan", + "name": "Maiden" + }, + { + "file": "mallow-cat.1.zspr", + "author": "FedoraFriday", + "name": "Mallow (Cat)" + }, + { + "file": "mangalink.1.zspr", + "author": "fatmanspanda", + "name": "Manga Link" + }, + { + "file": "maplequeen.2.zspr", + "author": "Zarby89", + "name": "Maple Queen" + }, + { + "file": "marin.2.zspr", + "author": "Nocturnesthesia", + "name": "Marin" + }, + { + "file": "mario-classic.2.zspr", + "author": "Damon", + "name": "Mario (Classic)" + }, + { + "file": "mariocappy.1.zspr", + "author": "Damon", + "name": "Mario and Cappy" + }, + { + "file": "marisa.1.zspr", + "author": "Achy", + "name": "Marisa Kirisame" + }, + { + "file": "cat.3.zspr", + "author": "Fish_waffle64", + "name": "Marvin the Cat" + }, + { + "file": "matthias.1.zspr", + "author": "Marcus Bolduc", + "name": "Matthias" + }, + { + "file": "meatwad.1.zspr", + "author": "fatmanspanda", + "name": "Meatwad" + }, + { + "file": "medallions.1.zspr", + "author": "Mike Trethewey", + "name": "Medallions" + }, + { + "file": "medli.1.zspr", + "author": "Kzinssie", + "name": "Medli" + }, + { + "file": "megaman-x.2.zspr", + "author": "PlaguedOne", + "name": "Megaman X" + }, + { + "file": "mew.1.zspr", + "author": "MewLp", + "name": "MewLp" + }, + { + "file": "mike-jones.2.zspr", + "author": "Fish_waffle64", + "name": "Mike Jones" + }, + { + "file": "ganon.1.zspr", + "author": "atth3h3art0fwinter", + "name": "Mini Ganon" + }, + { + "file": "minishcaplink.2.zspr", + "author": "InTheBeef", + "name": "Minish Cap Link" + }, + { + "file": "minish_link.1.zspr", + "author": "Artheau", + "name": "Minish Link" + }, + { + "file": "moblin.1.zspr", + "author": "Noctai_", + "name": "MoblinSprite" + }, + { + "file": "modernlink.1.zspr", + "author": "RyuTech", + "name": "Modern Link" + }, + { + "file": "mog.2.zspr", + "author": "Krelbel", + "name": "Mog" + }, + { + "file": "momiji.1.zspr", + "author": "Ardaceus", + "name": "Momiji Inubashiri" + }, + { + "file": "moosh.1.zspr", + "author": "PlaguedOne", + "name": "Moosh" + }, + { + "file": "mouse.1.zspr", + "author": "Malthaez", + "name": "Mouse" + }, + { + "file": "ms-paintdog.1.zspr", + "author": "TehRealSalt", + "name": "Ms. Paint Dog" + }, + { + "file": "naturelink.1.zspr", + "author": "iBazly", + "name": "Nature Link" + }, + { + "file": "navi.1.zspr", + "author": "qwertymodo", + "name": "Navi" + }, + { + "file": "navirou.2.zspr", + "author": "Lori", + "name": "Navirou" + }, + { + "file": "ned-flanders.1.zspr", + "author": "JJ0033LL", + "name": "Ned Flanders" + }, + { + "file": "negativelink.1.zspr", + "author": "iBazly", + "name": "Negative Link" + }, + { + "file": "neosad.1.zspr", + "author": "Andrew Copple", + "name": "Neosad" + }, + { + "file": "ness.1.zspr", + "author": "Lantis", + "name": "Ness (Earthbound)" + }, + { + "file": "nia.1.zspr", + "author": "Mojonbo", + "name": "Nia" + }, + { + "file": "niddraig.1.zspr", + "author": "Jakebob", + "name": "Niddraig" + }, + { + "file": "niko.1.zspr", + "author": "ScatlinkSean", + "name": "Niko" + }, + { + "file": "oldman.2.zspr", + "author": "Zarby89", + "name": "Old Man" + }, + { + "file": "ori.2.zspr", + "author": "Phant", + "name": "Ori" + }, + { + "file": "outlinelink.1.zspr", + "author": "VT", + "name": "Outline Link" + }, + { + "file": "parallelworldslink.1.zspr", + "author": "SePH/InTheBeef", + "name": "Parallel Worlds Link" + }, + { + "file": "paula.1.zspr", + "author": "Fish_waffle64", + "name": "Paula" + }, + { + "file": "penguinlink.1.zspr", + "author": "Fish_waffle64", + "name": "Penguin Link" + }, + { + "file": "pete.1.zspr", + "author": "Lantis", + "name": "Pete (Harvest Moon)" + }, + { + "file": "phoenix-wright.1.zspr", + "author": "SnipSlum", + "name": "Phoenix Wright" + }, + { + "file": "pikachu.1.zspr", + "author": "toucansham", + "name": "Pikachu" + }, + { + "file": "pinkribbonlink.2.zspr", + "author": "fatmanspanda", + "name": "Pink Ribbon Link" + }, + { + "file": "piranha_plant.1.zspr", + "author": "lecremateur", + "name": "Piranha Plant" + }, + { + "file": "plagueknight.1.zspr", + "author": "Jenichi", + "name": "Plague Knight" + }, + { + "file": "pokey.1.zspr", + "author": "fatmanspanda", + "name": "Pokey" + }, + { + "file": "littlepony.1.zspr", + "author": "Botchos", + "name": "Pony" + }, + { + "file": "popoi.1.zspr", + "author": "ItsSupercar", + "name": "Popoi" + }, + { + "file": "poppy.1.zspr", + "author": "cbass601", + "name": "Poppy" + }, + { + "file": "porg_knight.1.zspr", + "author": "PorgCollector", + "name": "Porg Knight" + }, + { + "file": "mushy.1.zspr", + "author": "Achy", + "name": "Power Up with Pride Mushroom" + }, + { + "file": "powerpuff_girl.1.zspr", + "author": "Jenichi", + "name": "Powerpuff Girl" + }, + { + "file": "pridelink.2.zspr", + "author": "proximitysound", + "name": "Pride Link" + }, + { + "file": "primm.1.zspr", + "author": "Artheau", + "name": "Primm" + }, + { + "file": "princess_bubblegum.1.zspr", + "author": "Devan2002", + "name": "Princess Bubblegum" + }, + { + "file": "peach.1.zspr", + "author": "RoPan", + "name": "Princess Peach" + }, + { + "file": "psyduck.2.zspr", + "author": "skovacs1", + "name": "Psyduck" + }, + { + "file": "purplechest-bottle.1.zspr", + "author": "Mike Trethewey", + "name": "Purple Chest" + }, + { + "file": "pyro.1.zspr", + "author": "malmo", + "name": "Pyro" + }, + { + "file": "rainbowlink.1.zspr", + "author": "fatmanspanda", + "name": "Rainbow Link" + }, + { + "file": "rat.1.zspr", + "author": "atth3h3art0fwinter", + "name": "Rat" + }, + { + "file": "red-mage.1.zspr", + "author": "TheRedMage", + "name": "Red Mage" + }, + { + "file": "remeer.1.zspr", + "author": "Herowho", + "name": "Remeer" + }, + { + "file": "rick.1.zspr", + "author": "Eric the Terrible/Devan 2002", + "name": "Rick (Redacted)" + }, + { + "file": "robotlink.1.zspr", + "author": "fatmanspanda", + "name": "Robo-Link 9000" + }, + { + "file": "rocko.1.zspr", + "author": "toucansham", + "name": "Rocko" + }, + { + "file": "rottytops.1.zspr", + "author": "PlaguedOne", + "name": "Rottytops" + }, + { + "file": "rover.1.zspr", + "author": "NO Body The Dragon", + "name": "Rover" + }, + { + "file": "roykoopa.1.zspr", + "author": "Achy", + "name": "Roy Koopa" + }, + { + "file": "rumia.1.zspr", + "author": "Achy", + "name": "Rumia" + }, + { + "file": "rydia.1.zspr", + "author": "Sho", + "name": "Rydia" + }, + { + "file": "ryu.1.zspr", + "author": "PlaguedOne", + "name": "Ryu" + }, + { + "file": "snes-controller.1.zspr", + "author": "Cbass601", + "name": "SNES Controller" + }, + { + "file": "sailormoon.1.zspr", + "author": "Jenichi", + "name": "Sailor Moon" + }, + { + "file": "saitama.1.zspr", + "author": "Dabeanjelly/Ath3h3art0fwinter", + "name": "Saitama" + }, + { + "file": "samus.2.zspr", + "author": "Fish_waffle64", + "name": "Samus" + }, + { + "file": "samus_classic.1.zspr", + "author": "Fish_waffle64", + "name": "Samus (Classic)" + }, + { + "file": "samus-sm.1.zspr", + "author": "Ben G", + "name": "Samus (Super Metroid)" + }, + { + "file": "santalink.2.zspr", + "author": "HOHOHO", + "name": "Santa Link" + }, + { + "file": "scholar.1.zspr", + "author": "Damon", + "name": "Scholar" + }, + { + "file": "selan.1.zspr", + "author": "atth3h3art0fwinter", + "name": "Selan" + }, + { + "file": "sevens1ns.1.zspr", + "author": "Hroun", + "name": "SevenS1ns" + }, + { + "file": "shadow.1.zspr", + "author": "CGG Zayik", + "name": "Shadow" + }, + { + "file": "shadowsaku.2.zspr", + "author": "iBazly", + "name": "Shadow Sakura" + }, + { + "file": "shantae.1.zspr", + "author": "skovacs1", + "name": "Shantae" + }, + { + "file": "shuppet.1.zspr", + "author": "fatmanspanda", + "name": "Shuppet" + }, + { + "file": "shy-gal.1.zspr", + "author": "FedoraFriday", + "name": "Shy Gal" + }, + { + "file": "shy-guy.1.zspr", + "author": "skovacs1", + "name": "Shy Guy" + }, + { + "file": "sighn_waive.1.zspr", + "author": "GenoCL", + "name": "SighnWaive" + }, + { + "file": "slowpoke.1.zspr", + "author": "Joey Rat", + "name": "Slowpoke" + }, + { + "file": "sodacan.1.zspr", + "author": "Zarby89", + "name": "Soda Can" + }, + { + "file": "solaire.1.zspr", + "author": "Knilip", + "name": "Solaire of Astora" + }, + { + "file": "sonic.1.zspr", + "author": "Osaka", + "name": "Sonic the Hedgehog" + }, + { + "file": "sora.1.zspr", + "author": "roxas232", + "name": "Sora" + }, + { + "file": "sora_kh1.1.zspr", + "author": "ScatlinkSean", + "name": "Sora (KH1)" + }, + { + "file": "spongebob.1.zspr", + "author": "JJ0033LL", + "name": "SpongeBob SquarePants" + }, + { + "file": "squall.1.zspr", + "author": "Maessan", + "name": "Squall" + }, + { + "file": "squirrel.1.zspr", + "author": "Fish_waffle64", + "name": "Squirrel" + }, + { + "file": "squirtle.1.zspr", + "author": "Numberplay", + "name": "Squirtle" + }, + { + "file": "stalfos.1.zspr", + "author": "Artheau", + "name": "Stalfos" + }, + { + "file": "stan.1.zspr", + "author": "Kan", + "name": "Stan" + }, + { + "file": "staticlink.1.zspr", + "author": "fatmanspanda", + "name": "Static Link" + }, + { + "file": "steamedhams.1.zspr", + "author": "AFewGoodTaters", + "name": "Steamed Ham" + }, + { + "file": "stick_man.1.zspr", + "author": "skovacs1", + "name": "Stick Man" + }, + { + "file": "superbomb.1.zspr", + "author": "Ninjakauz", + "name": "Super Bomb" + }, + { + "file": "superbunny.2.zspr", + "author": "TheOkayGuy", + "name": "Super Bunny" + }, + { + "file": "supermeatboy.1.zspr", + "author": "Achy", + "name": "Super Meat Boy" + }, + { + "file": "susie.1.zspr", + "author": "ZandraVandra", + "name": "Susie" + }, + { + "file": "swatchy.1.zspr", + "author": "Mike Trethewey", + "name": "Swatchy" + }, + { + "file": "tasbot.1.zspr", + "author": "GenoCL", + "name": "TASBot" + }, + { + "file": "tgh.1.zspr", + "author": "Drew Wise, pizza_for_free", + "name": "TGH" + }, + { + "file": "tmnt.1.zspr", + "author": "SirCzah", + "name": "TMNT" + }, + { + "file": "twilightprincesszelda.2.zspr", + "author": "Fish_waffle64", + "name": "TP Zelda" + }, + { + "file": "mario_tanooki.1.zspr", + "author": "Nocturnesthesia", + "name": "Tanooki Mario" + }, + { + "file": "teatime.1.zspr", + "author": "SirCzah", + "name": "Tea Time" + }, + { + "file": "terra.1.zspr", + "author": "All-in-one Mighty", + "name": "Terra (Esper)" + }, + { + "file": "tetra.1.zspr", + "author": "Ferelheart", + "name": "Tetra Sheet" + }, + { + "file": "pug.1.zspr", + "author": "Achy", + "name": "The Pug" + }, + { + "file": "thief.1.zspr", + "author": "Devan2002", + "name": "Thief" + }, + { + "file": "thomcrow.1.zspr", + "author": "Thom", + "name": "Thomcrow" + }, + { + "file": "tile.2.zspr", + "author": "fatmanspanda", + "name": "Tile" + }, + { + "file": "tingle.1.zspr", + "author": "Xenobond", + "name": "Tingle" + }, + { + "file": "toad.2.zspr", + "author": "Zarby89", + "name": "Toad" + }, + { + "file": "toadette.2.zspr", + "author": "Devan2002", + "name": "Toadette" + }, + { + "file": "totem-links.1.zspr", + "author": "Yotohan", + "name": "TotemLinks" + }, + { + "file": "trogdor.1.zspr", + "author": "MikeTrethewey/Spanda", + "name": "Trogdor the Burninator" + }, + { + "file": "linktuniccolor.1.zspr", + "author": "Damon", + "name": "Tunic Color Link" + }, + { + "file": "two_faced.1.zspr", + "author": "Devan2002", + "name": "TwoFaced" + }, + { + "file": "ty.1.zspr", + "author": "Fish_waffle64", + "name": "Ty the Tasmanian Tiger" + }, + { + "file": "ultros.1.zspr", + "author": "PlaguedOne", + "name": "Ultros" + }, + { + "file": "corona.1.zspr", + "author": "Unknown", + "name": "Untitled" + }, + { + "file": "valeera.1.zspr", + "author": "Glan", + "name": "Valeera" + }, + { + "file": "vanillalink.1.zspr", + "author": "Jenichi", + "name": "VanillaLink" + }, + { + "file": "vaporeon.1.zspr", + "author": "Aquana", + "name": "Vaporeon" + }, + { + "file": "vegeta.1.zspr", + "author": "Merciter", + "name": "Vegeta" + }, + { + "file": "vera.1.zspr", + "author": "aitchFactor", + "name": "Vera" + }, + { + "file": "vitreous.1.zspr", + "author": "Glan", + "name": "Vitreous" + }, + { + "file": "vivi.1.zspr", + "author": "RyuTech", + "name": "Vivi" + }, + { + "file": "vivian.1.zspr", + "author": "SirCzah", + "name": "Vivian" + }, + { + "file": "wario.1.zspr", + "author": "Deagans", + "name": "Wario" + }, + { + "file": "will.1.zspr", + "author": "Xenobond", + "name": "Will" + }, + { + "file": "wizzrobe.2.zspr", + "author": "iBazly", + "name": "Wizzrobe" + }, + { + "file": "wolf_link.1.zspr", + "author": "Fish/Beef-Chan", + "name": "Wolf Link (Festive)" + }, + { + "file": "wolf_link_tp.1.zspr", + "author": "Gfish59", + "name": "Wolf Link (TP)" + }, + { + "file": "yoshi.1.zspr", + "author": "Yotohan", + "name": "Yoshi" + }, + { + "file": "yunica.1.zspr", + "author": "Fish_waffle64", + "name": "Yunica Tovah" + }, + { + "file": "zandra.1.zspr", + "author": "ZandraVandra", + "name": "Zandra" + }, + { + "file": "zebraunicorn.1.zspr", + "author": "Brass Man", + "name": "Zebra Unicorn" + }, + { + "file": "zeck.1.zspr", + "author": "aitchFactor", + "name": "Zeckemyro" + }, + { + "file": "zelda.1.zspr", + "author": "Myriachan", + "name": "Zelda" + }, + { + "file": "zerosuitsamus.2.zspr", + "author": "Fish_waffle64", + "name": "Zero Suit Samus" + }, + { + "file": "zora.2.zspr", + "author": "Zarby, InTheBeef", + "name": "Zora" + }, + { + "file": "boco.1.zspr", + "author": "", + "name": "boco" + }, + { + "file": "hollow-knight.1.zspr", + "author": "Chew_Terr", + "name": "hollow_test" + }, + { + "file": "ibazly.1.zspr", + "author": "Achy", + "name": "iBazly" + }, + { + "file": "missingno.1.zspr", + "author": "", + "name": "missingno" + } + ] +} \ No newline at end of file diff --git a/WebHostLib/static/static/sprites/Ark (Cape).gif b/WebHostLib/static/static/sprites/Ark (Cape).gif new file mode 100644 index 00000000..a4b66812 Binary files /dev/null and b/WebHostLib/static/static/sprites/Ark (Cape).gif differ diff --git a/WebHostLib/static/static/sprites/Ark.gif b/WebHostLib/static/static/sprites/Ark (No Cape).gif similarity index 100% rename from WebHostLib/static/static/sprites/Ark.gif rename to WebHostLib/static/static/sprites/Ark (No Cape).gif diff --git a/WebHostLib/static/static/sprites/Asuna.gif b/WebHostLib/static/static/sprites/Asuna.gif new file mode 100644 index 00000000..5b08b251 Binary files /dev/null and b/WebHostLib/static/static/sprites/Asuna.gif differ diff --git a/WebHostLib/static/static/sprites/Blazer.gif b/WebHostLib/static/static/sprites/Blazer.gif new file mode 100644 index 00000000..9deaefb6 Binary files /dev/null and b/WebHostLib/static/static/sprites/Blazer.gif differ diff --git a/WebHostLib/static/static/sprites/Bob Ross.gif b/WebHostLib/static/static/sprites/Bob Ross.gif new file mode 100644 index 00000000..bd432ac0 Binary files /dev/null and b/WebHostLib/static/static/sprites/Bob Ross.gif differ diff --git a/WebHostLib/static/static/sprites/BotW Link.gif b/WebHostLib/static/static/sprites/BotW Link.gif new file mode 100644 index 00000000..53635c19 Binary files /dev/null and b/WebHostLib/static/static/sprites/BotW Link.gif differ diff --git a/WebHostLib/static/static/sprites/.gif b/WebHostLib/static/static/sprites/BotW Zelda.gif similarity index 100% rename from WebHostLib/static/static/sprites/.gif rename to WebHostLib/static/static/sprites/BotW Zelda.gif diff --git a/WebHostLib/static/static/sprites/Bowsette Red.gif b/WebHostLib/static/static/sprites/Bowsette Red.gif new file mode 100644 index 00000000..afe4bd29 Binary files /dev/null and b/WebHostLib/static/static/sprites/Bowsette Red.gif differ diff --git a/WebHostLib/static/static/sprites/Bowsette.gif b/WebHostLib/static/static/sprites/Bowsette.gif new file mode 100644 index 00000000..c40f7dd4 Binary files /dev/null and b/WebHostLib/static/static/sprites/Bowsette.gif differ diff --git a/WebHostLib/static/static/sprites/Chrizzz.gif b/WebHostLib/static/static/sprites/Chrizzz.gif new file mode 100644 index 00000000..bf962524 Binary files /dev/null and b/WebHostLib/static/static/sprites/Chrizzz.gif differ diff --git a/WebHostLib/static/static/sprites/DQ Slime.gif b/WebHostLib/static/static/sprites/DQ Slime.gif new file mode 100644 index 00000000..c9678bb1 Binary files /dev/null and b/WebHostLib/static/static/sprites/DQ Slime.gif differ diff --git a/WebHostLib/static/static/sprites/Dekar.gif b/WebHostLib/static/static/sprites/Dekar.gif new file mode 100644 index 00000000..6c51f092 Binary files /dev/null and b/WebHostLib/static/static/sprites/Dekar.gif differ diff --git a/WebHostLib/static/static/sprites/Fierce Deity Link.gif b/WebHostLib/static/static/sprites/Fierce Deity Link.gif index f566d416..aede93df 100644 Binary files a/WebHostLib/static/static/sprites/Fierce Deity Link.gif and b/WebHostLib/static/static/sprites/Fierce Deity Link.gif differ diff --git a/WebHostLib/static/static/sprites/GliitchWiitch.gif b/WebHostLib/static/static/sprites/GliitchWiitch.gif new file mode 100644 index 00000000..9d9a8623 Binary files /dev/null and b/WebHostLib/static/static/sprites/GliitchWiitch.gif differ diff --git a/WebHostLib/static/static/sprites/Gretis.gif b/WebHostLib/static/static/sprites/Gretis.gif new file mode 100644 index 00000000..d4cfbd8a Binary files /dev/null and b/WebHostLib/static/static/sprites/Gretis.gif differ diff --git a/WebHostLib/static/static/sprites/Hanna.gif b/WebHostLib/static/static/sprites/Hanna.gif new file mode 100644 index 00000000..c818f050 Binary files /dev/null and b/WebHostLib/static/static/sprites/Hanna.gif differ diff --git a/WebHostLib/static/static/sprites/Hotdog.gif b/WebHostLib/static/static/sprites/Hotdog.gif new file mode 100644 index 00000000..ec7c7b36 Binary files /dev/null and b/WebHostLib/static/static/sprites/Hotdog.gif differ diff --git a/WebHostLib/static/static/sprites/Imposter.gif b/WebHostLib/static/static/sprites/Imposter.gif new file mode 100644 index 00000000..de6cfc73 Binary files /dev/null and b/WebHostLib/static/static/sprites/Imposter.gif differ diff --git a/WebHostLib/static/static/sprites/Korok.gif b/WebHostLib/static/static/sprites/Korok.gif new file mode 100644 index 00000000..1e45dd46 Binary files /dev/null and b/WebHostLib/static/static/sprites/Korok.gif differ diff --git a/WebHostLib/static/static/sprites/Link Redrawn.gif b/WebHostLib/static/static/sprites/Link Redrawn.gif new file mode 100644 index 00000000..669c4ee7 Binary files /dev/null and b/WebHostLib/static/static/sprites/Link Redrawn.gif differ diff --git a/WebHostLib/static/static/sprites/Little Hylian.gif b/WebHostLib/static/static/sprites/Little Hylian.gif new file mode 100644 index 00000000..4b252c09 Binary files /dev/null and b/WebHostLib/static/static/sprites/Little Hylian.gif differ diff --git a/WebHostLib/static/static/sprites/Locke.gif b/WebHostLib/static/static/sprites/Locke.gif new file mode 100644 index 00000000..f9a2dc21 Binary files /dev/null and b/WebHostLib/static/static/sprites/Locke.gif differ diff --git a/WebHostLib/static/static/sprites/Luna Maindo.gif b/WebHostLib/static/static/sprites/Luna Maindo.gif new file mode 100644 index 00000000..e801545b Binary files /dev/null and b/WebHostLib/static/static/sprites/Luna Maindo.gif differ diff --git a/WebHostLib/static/static/sprites/MoblinSprite.gif b/WebHostLib/static/static/sprites/MoblinSprite.gif new file mode 100644 index 00000000..43480319 Binary files /dev/null and b/WebHostLib/static/static/sprites/MoblinSprite.gif differ diff --git a/WebHostLib/static/static/sprites/Navirou.gif b/WebHostLib/static/static/sprites/Navirou.gif index b16c1ab4..fc03423f 100644 Binary files a/WebHostLib/static/static/sprites/Navirou.gif and b/WebHostLib/static/static/sprites/Navirou.gif differ diff --git a/WebHostLib/static/static/sprites/Niddraig.gif b/WebHostLib/static/static/sprites/Niddraig.gif new file mode 100644 index 00000000..70368029 Binary files /dev/null and b/WebHostLib/static/static/sprites/Niddraig.gif differ diff --git a/WebHostLib/static/static/sprites/Rat.gif b/WebHostLib/static/static/sprites/Rat.gif new file mode 100644 index 00000000..78567a46 Binary files /dev/null and b/WebHostLib/static/static/sprites/Rat.gif differ diff --git a/WebHostLib/static/static/sprites/Red Mage.gif b/WebHostLib/static/static/sprites/Red Mage.gif new file mode 100644 index 00000000..c355c484 Binary files /dev/null and b/WebHostLib/static/static/sprites/Red Mage.gif differ diff --git a/WebHostLib/static/static/sprites/Rover.gif b/WebHostLib/static/static/sprites/Rover.gif new file mode 100644 index 00000000..ac38f258 Binary files /dev/null and b/WebHostLib/static/static/sprites/Rover.gif differ diff --git a/WebHostLib/static/static/sprites/Slowpoke.gif b/WebHostLib/static/static/sprites/Slowpoke.gif new file mode 100644 index 00000000..d7ae78d8 Binary files /dev/null and b/WebHostLib/static/static/sprites/Slowpoke.gif differ diff --git a/WebHostLib/static/static/sprites/SpongeBob SquarePants.gif b/WebHostLib/static/static/sprites/SpongeBob SquarePants.gif new file mode 100644 index 00000000..cc2c108d Binary files /dev/null and b/WebHostLib/static/static/sprites/SpongeBob SquarePants.gif differ diff --git a/WebHostLib/static/static/sprites/Steamed Ham.gif b/WebHostLib/static/static/sprites/Steamed Ham.gif new file mode 100644 index 00000000..4785d66c Binary files /dev/null and b/WebHostLib/static/static/sprites/Steamed Ham.gif differ diff --git a/WebHostLib/static/static/sprites/Susie.gif b/WebHostLib/static/static/sprites/Susie.gif new file mode 100644 index 00000000..58d3843d Binary files /dev/null and b/WebHostLib/static/static/sprites/Susie.gif differ diff --git a/WebHostLib/static/static/sprites/boco.gif b/WebHostLib/static/static/sprites/boco.gif new file mode 100644 index 00000000..0bcc8f33 Binary files /dev/null and b/WebHostLib/static/static/sprites/boco.gif differ diff --git a/WebHostLib/static/static/sprites/hollow_test.gif b/WebHostLib/static/static/sprites/hollow_test.gif new file mode 100644 index 00000000..248f663c Binary files /dev/null and b/WebHostLib/static/static/sprites/hollow_test.gif differ diff --git a/WebHostLib/static/static/weightedSettings.json b/WebHostLib/static/static/weightedSettings.json new file mode 100644 index 00000000..e6ee539f --- /dev/null +++ b/WebHostLib/static/static/weightedSettings.json @@ -0,0 +1,1878 @@ +{ + "gameOptions": { + "description": { + "keyString": "description", + "friendlyName": "Description", + "inputType": "text", + "description": "A short description of this preset. Useful if you have multiple files", + "defaultValue": "Preset Name" + }, + "name": { + "keyString": "name", + "friendlyName": "Player Name", + "inputType": "text", + "description": "Displayed in-game. Spaces will be replaced with underscores.", + "defaultValue": "Your Name" + }, + "glitches_required": { + "keyString": "glitches_required", + "friendlyName": "Glitches Required", + "description": "Determine the logic required to complete the seed.", + "inputType": "range", + "subOptions": { + "none": { + "keyString": "glitches_required.none", + "friendlyName": "None", + "description": "No glitches required.", + "defaultValue": 50 + }, + "minor_glitches": { + "keyString": "glitches_required.minor_glitches", + "friendlyName": "Minor Glitches", + "description": "Puts fake flipper, water-walk, super bunny, etc into logic", + "defaultValue": 0 + }, + "overworld_glitches": { + "keyString": "glitches_required.overworld_glitches", + "friendlyName": "Overworld Glitches", + "description": "Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches (fake flipper, super bunny shenanigans, water walk and etc.)", + "defaultValue": 0 + }, + "no_logic": { + "keyString": "glitches_required.no_logic", + "friendlyName": "No Logic", + "description": "Your items are placed with no regard to any logic. Your Fire Rod could be on your Trinexx.", + "defaultValue": 0 + } + } + }, + "dark_room_logic": { + "keyString": "dark_room_logic", + "friendlyName": "Dark Room Logic", + "description": "Logic to use for dark rooms.", + "inputType": "range", + "subOptions": { + "lamp": { + "keyString": "dark_room_logic.lamp", + "friendlyName": "Lamp Required", + "description": "The lamp is required for dark rooms to be considered in logic.", + "defaultValue": 50 + }, + "torches": { + "keyString": "dark_room_logic.torches", + "friendlyName": "Lamp or Torches", + "description": "In addition to the lamp, a fire rod and accessible torches may put dark rooms into logic.", + "defaultValue": 0 + }, + "none": { + "keyString": "dark_room_logic.none", + "friendlyName": "Always in Logic", + "description": "Dark rooms are always considered in logic, which may require you to navigate rooms in complete darkness.", + "defaultValue": 0 + } + } + }, + "map_shuffle": { + "keyString": "map_shuffle", + "friendlyName": "Map Shuffle", + "description": "Shuffle dungeon maps into the world and other dungeons, including other players' worlds.", + "inputType": "range", + "subOptions": { + "off": { + "keyString": "map_shuffle.off", + "friendlyName": "Off", + "description": "Disable map shuffle.", + "defaultValue": 50 + }, + "on": { + "keyString": "map_shuffle.on", + "friendlyName": "On", + "description": "Enable map shuffle.", + "defaultValue": 0 + } + } + }, + "compass_shuffle": { + "keyString": "compass_shuffle", + "friendlyName": "Compass Shuffle", + "description": "Shuffle compasses into the world and other dungeons, including other players' worlds", + "inputType": "range", + "subOptions": { + "off": { + "keyString": "compass_shuffle.off", + "friendlyName": "Off", + "description": "Disable compass shuffle.", + "defaultValue": 50 + }, + "on": { + "keyString": "compass_shuffle.on", + "friendlyName": "On", + "description": "Enable compass shuffle.", + "defaultValue": 0 + } + } + }, + "smallkey_shuffle": { + "keyString": "smallkey_shuffle", + "friendlyName": "Small Key Shuffle", + "description": "Shuffle small keys into the world and other dungeons, including other players' worlds.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "smallkey_shuffle.on", + "friendlyName": "On", + "description": "Enable small key shuffle.", + "defaultValue": 0 + }, + "off": { + "keyString": "smallkey_shuffle.off", + "friendlyName": "Off", + "description": "Disable small key shuffle.", + "defaultValue": 50 + }, + "universal": { + "keyString": "smallkey_shuffle.universal", + "friendlyName": "Universal", + "description": "Allows small keys to be used in any dungeon and adds keys to shops so you can buy more.", + "defaultValue": 0 + } + } + }, + "bigkey_shuffle": { + "keyString": "bigkey_shuffle", + "friendlyName": "Big Key Shuffle", + "description": "Shuffle big keys into the world and other dungeons, including other players' worlds.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "bigkey_shuffle.on", + "friendlyName": "On", + "description": "Enable big key shuffle.", + "defaultValue": 0 + }, + "off": { + "keyString": "bigkey_shuffle.off", + "friendlyName": "Off", + "description": "Disable big key shuffle.", + "defaultValue": 50 + } + } + }, + "local_keys": { + "keyString": "local_keys", + "friendlyName": "Local Keys", + "description": "Keep small keys and big keys local to your world.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "local_keys.on", + "friendlyName": "On", + "description": "Enable local keys.", + "defaultValue": 0 + }, + "off": { + "keyString": "local_keys.off", + "friendlyName": "Off", + "description": "Disable local keys.", + "defaultValue": 50 + } + } + }, + "dungeon_counters": { + "keyString": "dungeon_counters", + "friendlyName": "Dungeon Counters", + "description": "Determines when to show an on-screen counter for dungeon items.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "dungeon_counters.on", + "friendlyName": "Always On", + "description": "Always display amount of items checked in a dungeon.", + "defaultValue": 0 + }, + "pickup": { + "keyString": "dungeon_counters.pickup", + "friendlyName": "With Compass", + "description": "Show when compass is picked up.", + "defaultValue": 0 + }, + "default": { + "keyString": "dungeon_counters.default", + "friendlyName": "With Compass if Shuffled", + "description": "Show when the compass is picked up, if the compass was shuffled.", + "defaultValue": 0 + }, + "off": { + "keyString": "dungeon_counters.off", + "friendlyName": "Always Off", + "description": "Never show dungeon counters.", + "defaultValue": 50 + } + } + }, + "accessibility": { + "keyString": "accessibility", + "friendlyName": "Location Access", + "description": "Determines how much of the game is guaranteed to be reachable.", + "inputType": "range", + "subOptions": { + "items": { + "keyString": "accessibility.items", + "friendlyName": "All Items", + "description": "Guarantees you will be able to acquire all items, but you may not be able to access all locations.", + "defaultValue": 0 + }, + "locations": { + "keyString": "accessibility.locations", + "friendlyName": "All Locations", + "description": "Guarantees you will be able to access all locations, and therefore all items.", + "defaultValue": 50 + }, + "none": { + "keyString": "accessibility.none", + "friendlyName": "Required Only", + "description": "Guarantees only that the game is beatable. You may not be able to access all locations or acquire all items.", + "defaultValue": 0 + } + } + }, + "progressive": { + "keyString": "progressive", + "friendlyName": "Progressive Items", + "description": "Enable or disable the progressive acquisition of certain items (swords, shields, bow).", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "progressive.on", + "friendlyName": "On", + "description": "All relevant items are acquired progressively.", + "defaultValue": 50 + }, + "off": { + "keyString": "progressive.off", + "friendlyName": "Off", + "description": "All relevant items are acquired non-progressively (tempered sword may be in Link's House).", + "defaultValue": 0 + }, + "random": { + "keyString": "progressive.random", + "friendlyName": "Random", + "description": "The progressive nature of items is determined per-item pool. Gloves may be progressive, but swords may not be.", + "defaultValue": 0 + } + } + }, + "entrance_shuffle": { + "keyString": "entrance_shuffle", + "friendlyName": "Entrance Shuffle", + "description": "Determines how often and by what rules entrances are shuffled.", + "inputType": "range", + "subOptions": { + "none": { + "keyString": "entrance_shuffle.none", + "friendlyName": "Vanilla Entrances", + "description": "Vanilla game map. All entrances and exits lead to their original locations.", + "defaultValue": 50 + }, + "dungeonssimple": { + "keyString": "entrance_shuffle.dungeonssimple", + "friendlyName": "Dungeons Simple", + "description": "Shuffle whole dungeons amongst each other. Hyrule Castle would always be one dungeon.", + "defaultValue": 0 + }, + "dungeonsfull": { + "keyString": "entrance_shuffle.dungeonsfull", + "friendlyName": "Dungeons Full", + "description": "Shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle could be four different dungeons.", + "defaultValue": 0 + }, + "simple": { + "keyString": "entrance_shuffle.simple", + "friendlyName": "Simple Shuffle", + "description": "Entrances are grouped together before being randomized. This option uses the most strict grouping rules.", + "defaultValue": 0 + }, + "restricted": { + "keyString": "entrance_shuffle.restricted", + "friendlyName": "Restricted Shuffle", + "description": "Entrances are grouped together before being randomized. Grouping rules are less strict than Simple Shuffle.", + "defaultValue": 0 + }, + "full": { + "keyString": "entrance_shuffle.full", + "friendlyName": "Full Shuffle", + "description": "Entrances are grouped before being randomized. Grouping rules are less strict than Restricted Shuffle.", + "defaultValue": 0 + }, + "crossed": { + "keyString": "entrance_shuffle.crossed", + "friendlyName": "Crossed Shuffle", + "description": "Entrances are grouped before being randomized. Grouping rules are less strict than Full Shuffle.", + "defaultValue": 0 + }, + "insanity": { + "keyString": "entrance_shuffle.insanity", + "friendlyName": "Insanity Shuffle", + "description": "Very few entrance grouping rules are applied. Good luck.", + "defaultValue": 0 + } + } + }, + "goals": { + "keyString": "goals", + "friendlyName": "Goals", + "description": "Determines how much work you need to put in to save Hyrule.", + "inputType": "range", + "subOptions": { + "ganon": { + "keyString": "goals.ganon", + "friendlyName": "Defeat Ganon", + "description": "Climb Ganon's Tower, defeat Agahnim, then defeat Ganon in his lair.", + "defaultValue": 50 + }, + "fast_ganon": { + "keyString": "goals.fast_ganon", + "friendlyName": "Fast Ganon", + "description": "Kill Ganon in his lair. The hole is always open, but you may still require some crystals to damage him.", + "defaultValue": 0 + }, + "dungeons": { + "keyString": "goals.dungeons", + "friendlyName": "All Dungeons", + "description": "Defeat the boss of all dungeons, defeat Agahnim in both Castle Tower and Ganon's Tower, then defeat Ganon in his lair.", + "defaultValue": 0 + }, + "pedestal": { + "keyString": "goals.pedestal", + "friendlyName": "Pedestal", + "description": "Acquire all three pendants and pull the Triforce from the Master Sword Pedestal.", + "defaultValue": 0 + }, + "ganon_pedestal": { + "keyString": "goals.ganon_pedestal", + "friendlyName": "Ganon Pedestal", + "description": "Accquire all three pendants, pull the Master Sword Pedestal, then defeat Ganon in his lair.", + "defaultValue": 0 + }, + "triforce_hunt": { + "keyString": "goals.triforce_hunt", + "friendlyName": "Triforce Hunt", + "description": "Collect enough pieces of the Triforce of Courage, which has been spread around the world, then turn them in to Murahadala, who is standing outside Hyrule Castle.", + "defaultValue": 0 + }, + "local_triforce_hunt": { + "keyString": "goals.local_triforce_hunt", + "friendlyName": "Local Triforce Hunt", + "description": "Same as Triforce Hunt, but the Triforce pieces are guaranteed to be in your world.", + "defaultValue": 0 + }, + "ganon_triforce_hunt": { + "keyString": "goals.ganon_triforce_hunt", + "friendlyName": "Triforce Hunt /w Ganon", + "description": "Same as Triforce Hunt, but you need to defeat Ganon in his lair instead of talking with Murahadala.", + "defaultValue": 0 + }, + "local_ganon_triforce_hunt": { + "keyString": "goals.local_ganon_triforce_hunt", + "friendlyName": "Local Triforce hunt /w Ganon", + "description": "Same as Local Triforce Hunt, but you need to defeat Ganon in his lair instead of talking with Murahadala.", + "defaultValue": 0 + } + } + }, + "triforce_pieces_required": { + "keyString": "triforce_pieces_required", + "friendlyName": "Triforce Pieces Required", + "description": "Determines the total number of Triforce pieces required before speaking with Murahadala", + "inputType": "range", + "subOptions": { + "15": { + "keyString": "triforce_pieces_required.15", + "friendlyName": 15, + "description": "15 Triforce pieces are required before speaking with Murahadala.", + "defaultValue": 0 + }, + "20": { + "keyString": "triforce_pieces_required.20", + "friendlyName": 20, + "description": "20 Triforce pieces are required before speaking with Murahadala.", + "defaultValue": 50 + }, + "30": { + "keyString": "triforce_pieces_required.30", + "friendlyName": 30, + "description": "30 Triforce pieces are required before speaking with Murahadala.", + "defaultValue": 0 + }, + "40": { + "keyString": "triforce_pieces_required.40", + "friendlyName": 40, + "description": "40 Triforce pieces are required before speaking with Murahadala.", + "defaultValue": 0 + }, + "50": { + "keyString": "triforce_pieces_required.50", + "friendlyName": 50, + "description": "50 Triforce pieces are required before speaking with Murahadala.", + "defaultValue": 0 + } + } + }, + "triforce_pieces_mode": { + "keyString": "triforce_pieces_mode", + "friendlyName": "Triforce Piece Availability Mode", + "description": "Determines which of the following three options will be used to determine the total available triforce pieces.", + "inputType": "range", + "subOptions": { + "available": { + "keyString": "triforce_pieces_mode.available", + "friendlyName": "Exact Number", + "description": "Explicitly tell the generator how many triforce pieces to place throughout Hyrule.", + "defaultValue": 50 + }, + "extra": { + "keyString": "triforce_pieces_mode.extra", + "friendlyName": "Required Plus", + "description": "Set the number of triforce pieces in Hyrule equal to the number of required pieces plus a number specified by this option.", + "defaultValue": 0 + }, + "percentage": { + "keyString": "triforce_pieces_mode.percentage", + "friendlyName": "Percentage", + "description": "Set the number of triforce pieces in Hyrule equal to the number of required pieces plus a percentage specified by this option.", + "defaultValue": 0 + } + } + }, + "triforce_pieces_available": { + "keyString": "triforce_pieces_available", + "friendlyName": "Exact Number (Triforce Hunt)", + "description": "Only used if enabled in Triforce Piece Availability Mode.", + "inputType": "range", + "subOptions": { + "25": { + "keyString": "triforce_pieces_available.25", + "friendlyName": 25, + "description": "25 Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + }, + "30": { + "keyString": "triforce_pieces_available.30", + "friendlyName": 30, + "description": "30 Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 50 + }, + "40": { + "keyString": "triforce_pieces_available.40", + "friendlyName": 40, + "description": "40 Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + }, + "50": { + "keyString": "triforce_pieces_available.50", + "friendlyName": 50, + "description": "50 Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + } + } + }, + "triforce_pieces_extra": { + "keyString": "triforce_pieces_extra", + "friendlyName": "Required Plus (Triforce Hunt)", + "description": "Only used if enabled in Triforce Piece Availability Mode.", + "inputType": "range", + "subOptions": { + "0": { + "keyString": "triforce_pieces_extra.0", + "friendlyName": 0, + "description": "No extra Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + }, + "5": { + "keyString": "triforce_pieces_extra.5", + "friendlyName": 5, + "description": "5 extra Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + }, + "10": { + "keyString": "triforce_pieces_extra.10", + "friendlyName": 10, + "description": "10 extra Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 50 + }, + "15": { + "keyString": "triforce_pieces_extra.15", + "friendlyName": 15, + "description": "15 extra Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + }, + "20": { + "keyString": "triforce_pieces_extra.20", + "friendlyName": 20, + "description": "20 extra Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + } + } + }, + "triforce_pieces_percentage": { + "keyString": "triforce_pieces_percentage", + "friendlyName": "Percentage (Triforce Hunt)", + "description": "Only used if enabled in Triforce Piece Availability Mode.", + "inputType": "range", + "subOptions": { + "100": { + "keyString": "triforce_pieces_percentage.100", + "friendlyName": "0%", + "description": "No extra Triforce pieces will be hidden throughout Hyrule", + "defaultValue": 0 + }, + "150": { + "keyString": "triforce_pieces_percentage.150", + "friendlyName": "50%", + "description": "50% more triforce pieces than required will be placed throughout Hyrule.", + "defaultValue": 50 + }, + "200": { + "keyString": "triforce_pieces_percentage.200", + "friendlyName": "100%", + "description": "50% more triforce pieces than required will be placed throughout Hyrule.", + "defaultValue": 0 + } + } + }, + "tower_open": { + "keyString": "tower_open", + "friendlyName": "GT Crystals", + "description": "Determines the number of crystals required to open Ganon's Tower.", + "inputType": "range", + "subOptions": { + "0": { + "keyString": "tower_open.0", + "friendlyName": 0, + "description": "0 Crystals are required to open Ganon's Tower.", + "defaultValue": 80 + }, + "1": { + "keyString": "tower_open.1", + "friendlyName": 1, + "description": "1 Crystal is required to open Ganon's Tower.", + "defaultValue": 70 + }, + "2": { + "keyString": "tower_open.2", + "friendlyName": 2, + "description": "2 Crystals are required to open Ganon's Tower.", + "defaultValue": 60 + }, + "3": { + "keyString": "tower_open.3", + "friendlyName": 3, + "description": "3 Crystals are required to open Ganon's Tower.", + "defaultValue": 50 + }, + "4": { + "keyString": "tower_open.4", + "friendlyName": 4, + "description": "4 Crystals are required to open Ganon's Tower.", + "defaultValue": 40 + }, + "5": { + "keyString": "tower_open.5", + "friendlyName": 5, + "description": "5 Crystals are required to open Ganon's Tower.", + "defaultValue": 30 + }, + "6": { + "keyString": "tower_open.6", + "friendlyName": 6, + "description": "6 Crystals are required to open Ganon's Tower.", + "defaultValue": 20 + }, + "7": { + "keyString": "tower_open.7", + "friendlyName": 7, + "description": "7 Crystals are required to open Ganon's Tower.", + "defaultValue": 10 + }, + "random": { + "keyString": "tower_open.random", + "friendlyName": "Random", + "description": "Randomly determine the number of crystals necessary to open Ganon's Tower.", + "defaultValue": 0 + } + } + }, + "ganon_open": { + "keyString": "ganon_open", + "friendlyName": "Ganon Crystals", + "description": "Determines the number of crystals required before you are able to damage Ganon.", + "inputType": "range", + "subOptions": { + "0": { + "keyString": "ganon_open.0", + "friendlyName": 0, + "description": "0 Crystals are required to damage Ganon.", + "defaultValue": 80 + }, + "1": { + "keyString": "ganon_open.1", + "friendlyName": 1, + "description": "1 Crystal is required to damage Ganon.", + "defaultValue": 70 + }, + "2": { + "keyString": "ganon_open.2", + "friendlyName": 2, + "description": "2 Crystals are required to damage Ganon.", + "defaultValue": 60 + }, + "3": { + "keyString": "ganon_open.3", + "friendlyName": 3, + "description": "3 Crystals are required to damage Ganon.", + "defaultValue": 50 + }, + "4": { + "keyString": "ganon_open.4", + "friendlyName": 4, + "description": "4 Crystals are required to damage Ganon.", + "defaultValue": 40 + }, + "5": { + "keyString": "ganon_open.5", + "friendlyName": 5, + "description": "5 Crystals are required to damage Ganon.", + "defaultValue": 30 + }, + "6": { + "keyString": "ganon_open.6", + "friendlyName": 6, + "description": "6 Crystals are required to damage Ganon.", + "defaultValue": 20 + }, + "7": { + "keyString": "ganon_open.7", + "friendlyName": 7, + "description": "7 Crystals are required to damage Ganon.", + "defaultValue": 10 + }, + "random": { + "keyString": "ganon_open.random", + "friendlyName": "Random", + "description": "Randomly determine the number of crystals necessary to damage Ganon.", + "defaultValue": 0 + } + } + }, + "mode": { + "keyString": "mode", + "friendlyName": "Game Mode", + "description": "Determines the mode, or world state, for your game.", + "inputType": "range", + "subOptions": { + "standard": { + "keyString": "mode.standard", + "friendlyName": "Standard Mode", + "description": "Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary.", + "defaultValue": 50 + }, + "open": { + "keyString": "mode.open", + "friendlyName": "Open Mode", + "description": "Begin the game from your choice of Link's House or the Sanctuary.", + "defaultValue": 50 + }, + "inverted": { + "keyString": "mode.inverted", + "friendlyName": "Inverted Mode", + "description": "Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered.", + "defaultValue": 0 + } + } + }, + "retro": { + "keyString": "retro", + "friendlyName": "Retro Mode", + "description": "Makes the game similar to the first Legend of Zelda. You must buy a quiver to use the bow, take-any caves and an old-man cave are added to the world, and you may need to find your sword from the old man's cave.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "retro.on", + "friendlyName": "On", + "description": "Enable retro mode.", + "defaultValue": 0 + }, + "off": { + "keyString": "retro.off", + "friendlyName": "Off", + "description": "Disable retro mode.", + "defaultValue": 50 + } + } + }, + "hints": { + "keyString": "hints", + "friendlyName": "Hint Type", + "description": "Determines the behavior of hint tiles in dungeons", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "hints.on", + "friendlyName": "Item Locations", + "description": "Hint tiles sometimes give item location hints.", + "defaultValue": 50 + }, + "off": { + "keyString": "hints.off", + "friendlyName": "Gameplay Tips", + "description": "Hint tiles provide gameplay tips.", + "defaultValue": 0 + } + } + }, + "weapons": { + "keyString": "weapons", + "friendlyName": "Sword Placement", + "description": "Determines how swords are placed throughout the world.", + "inputType": "range", + "subOptions": { + "randomized": { + "keyString": "weapons.randomized", + "friendlyName": "Randomized", + "description": "Swords are placed randomly throughout the world.", + "defaultValue": 0 + }, + "assured": { + "keyString": "weapons.assured", + "friendlyName": "Assured", + "description": "Begin the game with a sword. Other swords are placed randomly throughout the game world.", + "defaultValue": 50 + }, + "vanilla": { + "keyString": "weapons.vanilla", + "friendlyName": "Vanilla Locations", + "description": "Swords are placed in vanilla locations in your own game (uncle, pedestal, smiths, pyramid fairy).", + "defaultValue": 0 + }, + "swordless": { + "keyString": "weapons.swordless", + "friendlyName": "Swordless", + "description": "Your swords are replaced with rupees. Gameplay changes are made to accommodate this change.", + "defaultValue": 0 + } + } + }, + "item_pool": { + "keyString": "item_pool", + "friendlyName": "Item Pool", + "description": "Determines the availability of upgrades, progressive items, and convenience items.", + "inputType": "range", + "subOptions": { + "easy": { + "keyString": "item_pool.easy", + "friendlyName": "Easy", + "description": "Double the number of available upgrades and progressive items.", + "defaultValue": 0 + }, + "normal": { + "keyString": "item_pool.normal", + "friendlyName": "Normal", + "description": "Item availability remains unchanged from the vanilla game.", + "defaultValue": 50 + }, + "hard": { + "keyString": "item_pool.hard", + "friendlyName": "Hard", + "description": "Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless).", + "defaultValue": 0 + }, + "expert": { + "keyString": "item_pool.expert", + "friendlyName": "Expert", + "description": "Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless).", + "defaultValue": 0 + } + } + }, + "item_functionality": { + "keyString": "item_functionality", + "friendlyName": "Item Functionality", + "description": "Alters the usefulness of various items in the game.", + "inputType": "range", + "subOptions": { + "easy": { + "keyString": "item_functionality.easy", + "friendlyName": "Easy", + "description": "Increases helpfulness of items. Medallions are usable everywhere, even without a sword. Hammer can be used in place of master sword to beat ganon and collect the tablets.", + "defaultValue": 0 + }, + "normal": { + "keyString": "item_functionality.normal", + "friendlyName": "Normal", + "description": "Item functionality remains unchanged from the vanilla game.", + "defaultValue": 50 + }, + "hard": { + "keyString": "item_functionality.hard", + "friendlyName": "Hard", + "description": "Reduced helpfulness of items. Potions are less effective, you can't catch faeries, the Magic Cape uses double magic, the Cane of Byrna does not grant invulnerability, boomerangs do not stun, and silver arrows are disabled outside ganon.", + "defaultValue": 0 + }, + "expert": { + "keyString": "item_functionality.expert", + "friendlyName": "Expert", + "description": "Vastly reduces the helpfulness of items. Potions are barely effective, you can't catch faeries, the Magic Cape uses double magic, the Cane of Byrna does not grant invulnerability, boomerangs and hookshot do not stun, and the silver arrows are disabled outside ganon.", + "defaultValue": 0 + } + } + }, + "progression_balancing": { + "keyString": "progression_balancing", + "friendlyName": "Progression Balancing", + "description": "A system to reduce time spent in BK mode. It moves your items into an earlier access sphere to make it more likely you have access to progression items.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "progression_balancing.on", + "friendlyName": "On", + "description": "Enable progression balancing.", + "defaultValue": 50 + }, + "off": { + "keyString": "progression_balancing.off", + "friendlyName": "Off", + "description": "Disable progression balancing.", + "defaultValue": 0 + } + } + }, + "boss_shuffle": { + "keyString": "boss_shuffle", + "friendlyName": "Boss Shuffle", + "description": "Determines which boss appears in which dungeon.", + "inputType": "range", + "subOptions": { + "none": { + "keyString": "boss_shuffle.none", + "friendlyName": "None", + "description": "Bosses appear in vanilla locations.", + "defaultValue": 50 + }, + "simple": { + "keyString": "boss_shuffle.simple", + "friendlyName": "Simple", + "description": "Existing bosses except Ganon and Agahnim are shuffled throughout dungeons.", + "defaultValue": 0 + }, + "full": { + "keyString": "boss_shuffle.full", + "friendlyName": "Full", + "description": "Bosses are shuffled, and three of them may occur twice.", + "defaultValue": 0 + }, + "random": { + "keyString": "boss_shuffle.random", + "friendlyName": "Random", + "description": "Any boss may appear any number of times.", + "defaultValue": 0 + }, + "singularity": { + "keyString": "boss_shuffle.singularity", + "friendlyName": "Singularity", + "description": "Picks a boss at random and puts it in every dungeon it can appear in. Remaining dungeons bosses are chosen at random.", + "defaultValue": 0 + } + } + }, + "enemy_shuffle": { + "keyString": "enemy_shuffle", + "friendlyName": "Enemy Shuffle", + "description": "Randomizes which enemies appear throughout the game.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "enemy_shuffle.on", + "friendlyName": "On", + "description": "Enable enemy shuffle.", + "defaultValue": 0 + }, + "off": { + "keyString": "enemy_shuffle.off", + "friendlyName": "Off", + "description": "Disable enemy shuffle.", + "defaultValue": 50 + } + } + }, + "killable_thieves": { + "keyString": "killable_thieves", + "friendlyName": "Killable Thieves", + "description": "Determines whether thieves may be killed or not.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "killable_thieves.on", + "friendlyName": "On", + "description": "Thieves are mortal.", + "defaultValue": 0 + }, + "off": { + "keyString": "killable_thieves.off", + "friendlyName": "Off", + "description": "Thieves are invulnerable.", + "defaultValue": 50 + } + } + }, + "tile_shuffle": { + "keyString": "tile_shuffle", + "friendlyName": "Tile Shuffle", + "description": "Randomizes tile layouts in rooms where floor tiles attack you.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "tile_shuffle.on", + "friendlyName": "On", + "description": "Enable tile shuffle.", + "defaultValue": 0 + }, + "off": { + "keyString": "tile_shuffle.off", + "friendlyName": "Off", + "description": "Disable tile shuffle.", + "defaultValue": 50 + } + } + }, + "bush_shuffle": { + "keyString": "bush_shuffle", + "friendlyName": "Bush Shuffle", + "description": "Randomize the chance that bushes around Hyrule have enemies hiding under them.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "bush_shuffle.on", + "friendlyName": "On", + "description": "Enable bush shuffle.", + "defaultValue": 0 + }, + "off": { + "keyString": "bush_shuffle.off", + "friendlyName": "Off", + "description": "Disable bush shuffle.", + "defaultValue": 50 + } + } + }, + "enemy_damage": { + "keyString": "enemy_damage", + "friendlyName": "Enemy Damage", + "description": "Randomizes how much damage enemies can deal to you.", + "inputType": "range", + "subOptions": { + "default": { + "keyString": "enemy_damage.default", + "friendlyName": "Vanilla Damage", + "description": "Enemies deal the same damage as in the vanilla game.", + "defaultValue": 50 + }, + "shuffled": { + "keyString": "enemy_damage.shuffled", + "friendlyName": "Shuffled", + "description": "Enemies deal zero to four hearts of damage, and armor reduces this damage.", + "defaultValue": 0 + }, + "random": { + "keyString": "enemy_damage.random", + "friendlyName": "Random", + "description": "Enemies may deal zero through eight hearts of damage, and armor re-shuffles how much damage you take from each enemy.", + "defaultValue": 0 + } + } + }, + "enemy_health": { + "keyString": "enemy_health", + "friendlyName": "Enemy Health", + "description": "Randomizes the amount of health enemies have. Does not affect bosses.", + "inputType": "range", + "subOptions": { + "default": { + "keyString": "enemy_health.default", + "friendlyName": "Vanilla", + "description": "Enemies have the same amount of health as in the vanilla game.", + "defaultValue": 50 + }, + "easy": { + "keyString": "enemy_health.easy", + "friendlyName": "Reduced", + "description": "Enemies have generally reduced health.", + "defaultValue": 0 + }, + "hard": { + "keyString": "enemy_health.hard", + "friendlyName": "Increased", + "description": "Enemies have generally increased health.", + "defaultValue": 0 + }, + "expert": { + "keyString": "enemy_health.expert", + "friendlyName": "Armor-Plated", + "description": "Enemies will be very heard to defeat.", + "defaultValue": 0 + } + } + }, + "pot_shuffle": { + "keyString": "pot_shuffle", + "friendlyName": "Pot Shuffle", + "description": "Keys, items, and buttons hidden under pots in dungeons may be shuffled with other pots in their super-tile.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "pot_shuffle.on", + "friendlyName": "On", + "description": "Enable pot shuffle.", + "defaultValue": 0 + }, + "off": { + "keyString": "pot_shuffle.off", + "friendlyName": "Off", + "description": "Disable pot shuffle.", + "defaultValue": 50 + } + } + }, + "beemizer": { + "keyString": "beemizer", + "friendlyName": "Beemizer", + "description": "Remove items from the global item pool and replace them with single bees and bee traps.", + "inputType": "range", + "subOptions": { + "0": { + "keyString": "beemizer.0", + "friendlyName": "Level 0", + "description": "No bee traps are placed.", + "defaultValue": 50 + }, + "1": { + "keyString": "beemizer.1", + "friendlyName": "Level 1", + "description": "25% of the non-essential item pool is replaced with bee traps.", + "defaultValue": 1 + }, + "2": { + "keyString": "beemizer.2", + "friendlyName": "Level 2", + "description": "60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees.", + "defaultValue": 2 + }, + "3": { + "keyString": "beemizer.3", + "friendlyName": "Level 3", + "description": "100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees.", + "defaultValue": 3 + }, + "4": { + "keyString": "beemizer.4", + "friendlyName": "Level 4", + "description": "100% of the non-essential item pool is replaced with bee traps.", + "defaultValue": 4 + } + } + }, + "shop_shuffle": { + "keyString": "shop_shuffle", + "friendlyName": "Shop Shuffle", + "description": "Alters the inventory and prices of shops.", + "inputType": "range", + "subOptions": { + "none": { + "keyString": "shop_shuffle.none", + "friendlyName": "Vanilla Shops", + "description": "Shop contents are left unchanged.", + "defaultValue": 50 + }, + "i": { + "keyString": "shop_shuffle.i", + "friendlyName": "Inventory Shuffle", + "description": "Randomizes the inventories of shops.", + "defaultValue": 0 + }, + "p": { + "keyString": "shop_shuffle.p", + "friendlyName": "Price Shuffle", + "description": "Randomizes the price of items sold in shops.", + "defaultValue": 0 + }, + "u": { + "keyString": "shop_shuffle.u", + "friendlyName": "Capacity Upgrades", + "description": "Shuffles capacity upgrades throughout the game world.", + "defaultValue": 0 + }, + "ip": { + "keyString": "shop_shuffle.ip", + "friendlyName": "Inventory & Prices", + "description": "Shuffles the inventory and randomizes the prices of items in shops.", + "defaultValue": 0 + }, + "uip": { + "keyString": "shop_shuffle.uip", + "friendlyName": "Full Shuffle", + "description": "Shuffles the inventory and randomizes the prices of items in shops. Also distributes capacity upgrades throughout the world.", + "defaultValue": 0 + } + } + }, + "shuffle_prizes": { + "keyString": "shuffle_prizes", + "friendlyName": "Prize Shuffle", + "description": "Alters the Prizes from pulling, bonking, enemy kills, digging, and hoarders", + "inputType": "range", + "subOptions": { + "none": { + "keyString": "shuffle_prizes.none", + "friendlyName": "None", + "description": "All prizes from pulling, bonking, enemy kills, digging, hoarders are vanilla.", + "defaultValue": 0 + }, + "g": { + "keyString": "shuffle_prizes.g", + "friendlyName": "\"General\" prize shuffle", + "description": "Shuffles the prizes from pulling, enemy kills, digging, hoarders", + "defaultValue": 50 + }, + "b": { + "keyString": "shuffle_prizes.b", + "friendlyName": "Bonk prize shuffle", + "description": "Shuffles the prizes from bonking into trees.", + "defaultValue": 0 + }, + "bg": { + "keyString": "shuffle_prizes.bg", + "friendlyName": "Both", + "description": "Shuffles both of the options.", + "defaultValue": 0 + } + } + }, + "timer": { + "keyString": "timer", + "friendlyName": "Timed Modes", + "description": "Add a timer to the game UI, and cause it to have various effects.", + "inputType": "range", + "subOptions": { + "none": { + "keyString": "timer.none", + "friendlyName": "Disabled", + "description": "No timed mode is applied to the game.", + "defaultValue": 50 + }, + "timed": { + "keyString": "timer.timed", + "friendlyName": "Timed Mode", + "description": "Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end.", + "defaultValue": 0 + }, + "timed_ohko": { + "keyString": "timer.timed_ohko", + "friendlyName": "Timed OHKO", + "description": "Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit.", + "defaultValue": 0 + }, + "ohko": { + "keyString": "timer.ohko", + "friendlyName": "One-Hit KO", + "description": "Timer always at zero. Permanent OHKO.", + "defaultValue": 0 + }, + "timed_countdown": { + "keyString": "timer.timed_countdown", + "friendlyName": "Timed Countdown", + "description": "Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.", + "defaultValue": 0 + }, + "display": { + "keyString": "timer.display", + "friendlyName": "Timer Only", + "description": "Displays a timer, but otherwise does not affect gameplay or the item pool.", + "defaultValue": 0 + } + } + }, + "countdown_start_time": { + "keyString": "countdown_start_time", + "friendlyName": "Countdown Starting Time", + "description": "The amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes.", + "inputType": "range", + "subOptions": { + "0": { + "keyString": "countdown_start_time.0", + "friendlyName": 0, + "description": "Start with no time on the timer. In Timed OHKO mode, start in OHKO mode.", + "defaultValue": 0 + }, + "10": { + "keyString": "countdown_start_time.10", + "friendlyName": 10, + "description": "Start with 10 minutes on the timer.", + "defaultValue": 50 + }, + "20": { + "keyString": "countdown_start_time.20", + "friendlyName": 20, + "description": "Start with 20 minutes on the timer.", + "defaultValue": 0 + }, + "30": { + "keyString": "countdown_start_time.30", + "friendlyName": 30, + "description": "Start with 30 minutes on the timer.", + "defaultValue": 0 + }, + "60": { + "keyString": "countdown_start_time.60", + "friendlyName": 60, + "description": "Start with an hour on the timer.", + "defaultValue": 0 + } + } + }, + "red_clock_time": { + "keyString": "red_clock_time", + "friendlyName": "Red Clock Time", + "description": "The amount of time, in minutes, to add to or subtract from the timer upon picking up a red clock.", + "inputType": "range", + "subOptions": { + "-2": { + "keyString": "red_clock_time.-2", + "friendlyName": -2, + "description": "Subtract 2 minutes from the timer upon picking up a red clock.", + "defaultValue": 0 + }, + "1": { + "keyString": "red_clock_time.1", + "friendlyName": 1, + "description": "Add a minute to the timer upon picking up a red clock.", + "defaultValue": 50 + } + } + }, + "blue_clock_time": { + "keyString": "blue_clock_time", + "friendlyName": "Blue Clock Time", + "description": "The amount of time, in minutes, to add to or subtract from the timer upon picking up a blue clock.", + "inputType": "range", + "subOptions": { + "1": { + "keyString": "blue_clock_time.1", + "friendlyName": 1, + "description": "Add a minute to the timer upon picking up a blue clock.", + "defaultValue": 0 + }, + "2": { + "keyString": "blue_clock_time.2", + "friendlyName": 2, + "description": "Add 2 minutes to the timer upon picking up a blue clock.", + "defaultValue": 50 + } + } + }, + "green_clock_time": { + "keyString": "green_clock_time", + "friendlyName": "Green Clock Time", + "description": "The amount of time, in minutes, to add to or subtract from the timer upon picking up a green clock.", + "inputType": "range", + "subOptions": { + "4": { + "keyString": "green_clock_time.4", + "friendlyName": 4, + "description": "Add 4 minutes to the timer upon picking up a green clock.", + "defaultValue": 50 + }, + "10": { + "keyString": "green_clock_time.10", + "friendlyName": 10, + "description": "Add 10 minutes to the timer upon picking up a green clock.", + "defaultValue": 0 + }, + "15": { + "keyString": "green_clock_time.15", + "friendlyName": 15, + "description": "Add 15 minutes to the timer upon picking up a green clock.", + "defaultValue": 0 + } + } + }, + "glitch_boots": { + "keyString": "glitch_boots", + "friendlyName": "Glitch Boots", + "description": "Start with Pegasus Boots in any glitched logic mode that makes use of them.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "glitch_boots.on", + "friendlyName": "On", + "description": "Enable glitch boots.", + "defaultValue": 50 + }, + "off": { + "keyString": "glitch_boots.off", + "friendlyName": "Off", + "description": "Disable glitch boots.", + "defaultValue": 0 + } + } + }, + "door_shuffle": { + "keyString": "door_shuffle", + "friendlyName": "Door Shuffle", + "description": "Shuffles the interior layout of dungeons. Only available if the host rolls the game using the doors version of the generator.", + "inputType": "range", + "subOptions": { + "vanilla": { + "keyString": "door_shuffle.vanilla", + "friendlyName": "Vanilla", + "description": "Doors within dungeons remain unchanged from the vanilla game.", + "defaultValue": 50 + }, + "basic": { + "keyString": "door_shuffle.basic", + "friendlyName": "Basic", + "description": "Dungeons are shuffled within themselves.", + "defaultValue": 0 + }, + "crossed": { + "keyString": "door_shuffle.crossed", + "friendlyName": "Crossed", + "description": "Dungeons are shuffled across each other. Eastern may contain POD, Mire, and Hera.", + "defaultValue": 0 + } + } + }, + "intensity": { + "keyString": "intensity", + "friendlyName": "Door Shuffle Intensity Level", + "description": "Specifies what types of doors will be shuffled.", + "inputType": "range", + "subOptions": { + "1": { + "keyString": "intensity.1", + "friendlyName": "Level 1", + "description": "Doors and spiral staircases will be shuffled amongst themselves.", + "defaultValue": 50 + }, + "2": { + "keyString": "intensity.2", + "friendlyName": "Level 2", + "description": "Doors, open edges, and straight stair cases are shuffled amongst each other. Spiral staircases will be shuffled amongst themselves.", + "defaultValue": 0 + }, + "3": { + "keyString": "intensity.3", + "friendlyName": "Level 3", + "description": "Level 2 plus lobby shuffling, which means any non-dead-end supertile with a south-facing door may become a dungeon entrance.", + "defaultValue": 0 + }, + "random": { + "keyString": "intensity.random", + "friendlyName": "Random", + "description": "Randomly chooses an intensity level from 1-3.", + "defaultValue": 0 + } + } + }, + "key_drop_shuffle": { + "keyString": "key_drop_shuffle", + "friendlyName": "Key Drop Shuffle", + "description": "Allows the small/big keys dropped by enemies/pots to be shuffled into the item pool. This extends the number of checks from 216 to 249", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "key_drop_shuffle.on", + "friendlyName": "Enabled", + "description": "Enables key drop shuffle", + "defaultValue": 0 + }, + "off": { + "keyString": "key_drop_shuffle.off", + "friendlyName": "Disabled", + "description": "Disables key drop shuffle", + "defaultValue": 50 + } + } + } + }, + "romOptions": { + "disablemusic": { + "keyString": "rom.disablemusic", + "friendlyName": "Game Music", + "description": "Enable or disable all in-game music. Sound-effects are unaffected.", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "rom.disablemusic.on", + "friendlyName": "Disabled", + "description": "Disables in-game music.", + "defaultValue": 0 + }, + "off": { + "keyString": "rom.disablemusic.off", + "friendlyName": "Enabled", + "description": "Enables in-game music.", + "defaultValue": 50 + } + } + }, + "quickswap": { + "keyString": "rom.quickswap", + "friendlyName": "Item Quick-Swap", + "description": "Quickly change items by pressing the L+R shoulder buttons. Pressing L+R at the same time toggles the in-slot item (arrows and silver arrows, for example).", + "inputType": "range", + "subOptions": { + "on": { + "keyString": "rom.quickswap.on", + "friendlyName": "Enabled", + "description": "Enable quick-swap.", + "defaultValue": 0 + }, + "off": { + "keyString": "rom.quickswap.off", + "friendlyName": "Disabled", + "description": "Disable quick-swap.", + "defaultValue": 50 + } + } + }, + "menuspeed": { + "keyString": "menuspeed", + "friendlyName": "Menu Speed", + "description": "Choose how fast the in-game menu opens and closes.", + "inputType": "range", + "subOptions": { + "normal": { + "keyString": "rom.menuspeed.normal", + "friendlyName": "Vanilla", + "description": "Menu speed is unchanged from the vanilla game.", + "defaultValue": 50 + }, + "instant": { + "keyString": "rom.menuspeed.instant", + "friendlyName": "Instant", + "description": "The in-game menu appears and disappears instantly.", + "defaultValue": 0 + }, + "double": { + "keyString": "rom.menuspeed.double", + "friendlyName": "Double Speed", + "description": "The in-game menu animation moves at double speed.", + "defaultValue": 0 + }, + "triple": { + "keyString": "rom.menuspeed.triple", + "friendlyName": "Triple Speed", + "description": "The in-game menu animation moves at triple speed.", + "defaultValue": 0 + }, + "quadruple": { + "keyString": "rom.menuspeed.quadruple", + "friendlyName": "Quadruple Speed", + "description": "The in-game menu animation moves at quadruple speed.", + "defaultValue": 0 + }, + "half": { + "keyString": "rom.menuspeed.half", + "friendlyName": "Half Speed", + "description": "The in-game menu animation moves at half speed.", + "defaultValue": 0 + } + } + }, + "heartcolor": { + "keyString": "rom.heartcolor", + "friendlyName": "Heart Color", + "description": "Changes the color of your in-game health hearts.", + "inputType": "range", + "subOptions": { + "red": { + "keyString": "rom.heartcolor.red", + "friendlyName": "Red", + "description": "Health hearts are red.", + "defaultValue": 50 + }, + "blue": { + "keyString": "rom.heartcolor.blue", + "friendlyName": "Blue", + "description": "Health hearts are blue.", + "defaultValue": 0 + }, + "green": { + "keyString": "rom.heartcolor.green", + "friendlyName": "Green", + "description": "Health hearts are green.", + "defaultValue": 0 + }, + "yellow": { + "keyString": "rom.heartcolor.yellow", + "friendlyName": "Yellow", + "description": "Health hearts are yellow.", + "defaultValue": 0 + }, + "random": { + "keyString": "rom.heartcolor.random", + "friendlyName": "Random", + "description": "Health heart color is chosen randomly from red, green, blue, and yellow.", + "defaultValue": 0 + } + } + }, + "heartbeep": { + "keyString": "rom.heartbeep", + "friendlyName": "Heart Beep Speed", + "description": "Controls the frequency of the low-health beeping.", + "inputType": "range", + "subOptions": { + "double": { + "keyString": "rom.heartbeep.double", + "friendlyName": "Double", + "description": "Doubles the frequency of the low-health beep.", + "defaultValue": 0 + }, + "normal": { + "keyString": "rom.heartbeep.normal", + "friendlyName": "Vanilla", + "description": "Heart beep frequency is unchanged from the vanilla game.", + "defaultValue": 50 + }, + "half": { + "keyString": "rom.heartbeep.half", + "friendlyName": "Half Speed", + "description": "Heart beep plays at half-speed.", + "defaultValue": 0 + }, + "quarter": { + "keyString": "rom.heartbeep.quarter", + "friendlyName": "Quarter Speed", + "description": "Heart beep plays at one quarter-speed.", + "defaultValue": 0 + }, + "off": { + "keyString": "rom.heartbeep.off", + "friendlyName": "Disabled", + "description": "Disables the low-health heart beep.", + "defaultValue": 0 + } + } + }, + "ow_palettes": { + "keyString": "rom.ow_palettes", + "friendlyName": "Overworld Palette", + "description": "Randomize the colors of the overworld, within reason.", + "inputType": "range", + "subOptions": { + "default": { + "keyString": "rom.ow_palettes.default", + "friendlyName": "Vanilla", + "description": "Overworld colors will remain unchanged.", + "defaultValue": 50 + }, + "random": { + "keyString": "rom.ow_palettes.random", + "friendlyName": "Random", + "description": "Shuffles the colors of the overworld palette.", + "defaultValue": 0 + }, + "blackout": { + "keyString": "rom.ow_palettes.blackout", + "friendlyName": "Blackout", + "description": "Never use this. Makes all overworld palette colors black.", + "defaultValue": 0 + }, + "grayscale": { + "keyString": "rom.ow_palettes.grayscale", + "friendlyName": "Grayscale", + "description": "Removes all saturation of colors.", + "defaultValue": 0 + }, + "negative": { + "keyString": "rom.ow_palettes.negative", + "friendlyName": "Negative", + "description": "Invert all colors", + "defaultValue": 0 + }, + "classic": { + "keyString": "rom.ow_palettes.classic", + "friendlyName": "Classic", + "description": "Produces results similar to the website.", + "defaultValue": 0 + }, + "dizzy": { + "keyString": "rom.ow_palettes.dizzy", + "friendlyName": "Dizzy", + "description": "No logic in colors but saturation and lightness are conserved.", + "defaultValue": 0 + }, + "sick": { + "keyString": "rom.ow_palettes.sick", + "friendlyName": "Sick", + "description": "No logic in colors but lightness is conserved.", + "defaultValue": 0 + }, + "puke": { + "keyString": "rom.ow_palettes.Puke", + "friendlyName": "Puke", + "description": "No logic at all.", + "defaultValue": 0 + } + } + }, + "uw_palettes": { + "keyString": "rom.uw_palettes", + "friendlyName": "Underworld Palettes", + "description": "Randomize the colors of the underworld (caves, dungeons, etc.), within reason.", + "inputType": "range", + "subOptions": { + "default": { + "keyString": "rom.uw_palettes.default", + "friendlyName": "Vanilla", + "description": "Underworld colors will remain unchanged.", + "defaultValue": 50 + }, + "random": { + "keyString": "rom.uw_palettes.random", + "friendlyName": "Random", + "description": "Shuffles the colors of the underworld palette.", + "defaultValue": 0 + }, + "blackout": { + "keyString": "rom.uw_palettes.blackout", + "friendlyName": "Blackout", + "description": "Never use this. Makes all underworld palette colors black.", + "defaultValue": 0 + }, + "grayscale": { + "keyString": "rom.uw_palettes.grayscale", + "friendlyName": "Grayscale", + "description": "Removes all saturation of colors.", + "defaultValue": 0 + }, + "negative": { + "keyString": "rom.uw_palettes.negative", + "friendlyName": "Negative", + "description": "Invert all colors", + "defaultValue": 0 + }, + "classic": { + "keyString": "rom.uw_palettes.classic", + "friendlyName": "Classic", + "description": "Produces results similar to the website.", + "defaultValue": 0 + }, + "dizzy": { + "keyString": "rom.uw_palettes.dizzy", + "friendlyName": "Dizzy", + "description": "No logic in colors but saturation and lightness are conserved.", + "defaultValue": 0 + }, + "sick": { + "keyString": "rom.uw_palettes.sick", + "friendlyName": "Sick", + "description": "No logic in colors but lightness is conserved.", + "defaultValue": 0 + }, + "puke": { + "keyString": "rom.uw_palettes.Puke", + "friendlyName": "Puke", + "description": "No logic at all.", + "defaultValue": 0 + } + } + }, + "hud_palettes": { + "keyString": "rom.hud_palettes", + "friendlyName": "HUD Palettes", + "description": "Randomize the colors of the HUD (user interface), within reason.", + "inputType": "range", + "subOptions": { + "default": { + "keyString": "rom.hud_palettes.default", + "friendlyName": "Vanilla", + "description": "HUD colors will remain unchanged.", + "defaultValue": 50 + }, + "random": { + "keyString": "rom.hud_palettes.random", + "friendlyName": "Random", + "description": "Shuffles the colors of the HUD palette.", + "defaultValue": 0 + }, + "blackout": { + "keyString": "rom.hud_palettes.blackout", + "friendlyName": "Blackout", + "description": "Never use this. Makes all HUD palette colors black.", + "defaultValue": 0 + }, + "grayscale": { + "keyString": "rom.hud_palettes.grayscale", + "friendlyName": "Grayscale", + "description": "Removes all saturation of colors.", + "defaultValue": 0 + }, + "negative": { + "keyString": "rom.hud_palettes.negative", + "friendlyName": "Negative", + "description": "Invert all colors", + "defaultValue": 0 + }, + "classic": { + "keyString": "rom.hud_palettes.classic", + "friendlyName": "Classic", + "description": "Produces results similar to the website.", + "defaultValue": 0 + }, + "dizzy": { + "keyString": "rom.hud_palettes.dizzy", + "friendlyName": "Dizzy", + "description": "No logic in colors but saturation and lightness are conserved.", + "defaultValue": 0 + }, + "sick": { + "keyString": "rom.hud_palettes.sick", + "friendlyName": "Sick", + "description": "No logic in colors but lightness is conserved.", + "defaultValue": 0 + }, + "puke": { + "keyString": "rom.hud_palettes.Puke", + "friendlyName": "Puke", + "description": "No logic at all.", + "defaultValue": 0 + } + } + }, + "shield_palettes": { + "keyString": "rom.shield_palettes", + "friendlyName": "Shield Palettes", + "description": "Randomize the colors of the shield, within reason.", + "inputType": "range", + "subOptions": { + "default": { + "keyString": "rom.shield_palettes.default", + "friendlyName": "Vanilla", + "description": "Shield colors will remain unchanged.", + "defaultValue": 50 + }, + "random": { + "keyString": "rom.shield_palettes.random", + "friendlyName": "Random", + "description": "Shuffles the colors of the shield palette.", + "defaultValue": 0 + }, + "blackout": { + "keyString": "rom.shield_palettes.blackout", + "friendlyName": "Blackout", + "description": "Never use this. Makes all shield palette colors black.", + "defaultValue": 0 + }, + "grayscale": { + "keyString": "rom.shield_palettes.grayscale", + "friendlyName": "Grayscale", + "description": "Removes all saturation of colors.", + "defaultValue": 0 + }, + "negative": { + "keyString": "rom.shield_palettes.negative", + "friendlyName": "Negative", + "description": "Invert all colors", + "defaultValue": 0 + }, + "classic": { + "keyString": "rom.shield_palettes.classic", + "friendlyName": "Classic", + "description": "Produces results similar to the website.", + "defaultValue": 0 + }, + "dizzy": { + "keyString": "rom.shield_palettes.dizzy", + "friendlyName": "Dizzy", + "description": "No logic in colors but saturation and lightness are conserved.", + "defaultValue": 0 + }, + "sick": { + "keyString": "rom.shield_palettes.sick", + "friendlyName": "Sick", + "description": "No logic in colors but lightness is conserved.", + "defaultValue": 0 + }, + "puke": { + "keyString": "rom.shield_palettes.Puke", + "friendlyName": "Puke", + "description": "No logic at all.", + "defaultValue": 0 + } + } + }, + "sword_palettes": { + "keyString": "rom.sword_palettes", + "friendlyName": "Sword Palettes", + "description": "Randomize the colors of the sword, within reason.", + "inputType": "range", + "subOptions": { + "default": { + "keyString": "rom.sword_palettes.default", + "friendlyName": "Vanilla", + "description": "Sword colors will remain unchanged.", + "defaultValue": 50 + }, + "random": { + "keyString": "rom.sword_palettes.random", + "friendlyName": "Random", + "description": "Shuffles the colors of the sword palette.", + "defaultValue": 0 + }, + "blackout": { + "keyString": "rom.sword_palettes.blackout", + "friendlyName": "Blackout", + "description": "Never use this. Makes all sword palette colors black.", + "defaultValue": 0 + }, + "grayscale": { + "keyString": "rom.sword_palettes.grayscale", + "friendlyName": "Grayscale", + "description": "Removes all saturation of colors.", + "defaultValue": 0 + }, + "negative": { + "keyString": "rom.sword_palettes.negative", + "friendlyName": "Negative", + "description": "Invert all colors", + "defaultValue": 0 + }, + "classic": { + "keyString": "rom.sword_palettes.classic", + "friendlyName": "Classic", + "description": "Produces results similar to the website.", + "defaultValue": 0 + }, + "dizzy": { + "keyString": "rom.sword_palettes.dizzy", + "friendlyName": "Dizzy", + "description": "No logic in colors but saturation and lightness are conserved.", + "defaultValue": 0 + }, + "sick": { + "keyString": "rom.sword_palettes.sick", + "friendlyName": "Sick", + "description": "No logic in colors but lightness is conserved.", + "defaultValue": 0 + }, + "puke": { + "keyString": "rom.sword_palettes.Puke", + "friendlyName": "Puke", + "description": "No logic at all.", + "defaultValue": 0 + } + } + } + } +} diff --git a/WebHostLib/static/static/playerSettings.yaml b/WebHostLib/static/static/weightedSettings.yaml similarity index 92% rename from WebHostLib/static/static/playerSettings.yaml rename to WebHostLib/static/static/weightedSettings.yaml index ee71ec85..b6e7747f 100644 --- a/WebHostLib/static/static/playerSettings.yaml +++ b/WebHostLib/static/static/weightedSettings.yaml @@ -165,7 +165,6 @@ item_pool: normal: 50 # Item availability remains unchanged from vanilla game hard: 0 # Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless) expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless) - crowd_control: 0 # Sets up the item pool for the crowd control extension. Do not use it without crowd control item_functionality: easy: 0 # Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere. normal: 50 # Vanilla item functionality @@ -232,6 +231,22 @@ timer: ohko: 0 # Timer always at zero. Permanent OHKO. timed_countdown: 0 # Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though. display: 0 # Displays a timer, but otherwise does not affect gameplay or the item pool. +countdown_start_time: # For timed_ohko and timed_countdown timer modes, the amount of time in minutes to start with + 0: 0 # For timed_ohko, starts in OHKO mode when starting the game + 10: 50 + 20: 0 + 30: 0 + 60: 0 +red_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock + -2: 50 + 1: 0 +blue_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock + 1: 0 + 2: 50 +green_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock + 4: 50 + 10: 0 + 15: 0 # Can be uncommented to use it # local_items: # Force certain items to appear in your world only, not across the multiworld. Recognizes some group names, like "Swords" # - "Moon Pearl" @@ -289,6 +304,9 @@ intensity: # Only available if the host uses the doors branch, it is ignored oth 2: 0 # And shuffles open edges and straight staircases 3: 0 # And shuffles dungeon lobbies random: 0 # Picks one of those at random +key_drop_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise + on: 0 # Enables the small keys dropped by enemies or under pots, and the big key dropped by the Ball & Chain guard to be shuffled into the pool. This extends the number of checks to 249. + off: 50 experimental: # Only available if the host uses the doors branch, it is ignored otherwise on: 0 # Enables experimental features. Currently, this is just the dungeon keys in chest counter. off: 50 @@ -355,7 +373,39 @@ rom: default: 50 # No changes random: 0 # Shuffle the colors blackout: 0 # Never use this + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 uw_palettes: # Change the colors of caves and dungeons default: 50 # No changes random: 0 # Shuffle the colors blackout: 0 # Never use this + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 + hud_palettes: # Change the colors of the hud + default: 50 # No changes + random: 0 # Shuffle the colors + blackout: 0 # Never use this + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 + sword_palettes: # Change the colors of swords + default: 50 # No changes + random: 0 # Shuffle the colors + blackout: 0 # Never use this + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 diff --git a/WebHostLib/static/styles/uploads.css b/WebHostLib/static/styles/check.css similarity index 60% rename from WebHostLib/static/styles/uploads.css rename to WebHostLib/static/styles/check.css index 47f8baf2..39671d87 100644 --- a/WebHostLib/static/styles/uploads.css +++ b/WebHostLib/static/styles/check.css @@ -1,16 +1,22 @@ -#uploads-wrapper{ +#check-wrapper{ display: flex; flex-direction: row; justify-content: center; flex-wrap: wrap; } -#uploads-form-wrapper{ +#check{ + width: 620px; + height: 280px; + text-align: center; +} + +#check-form-wrapper{ width: 100%; text-align: center; margin-bottom: 1rem; } -#upload-form{ +#check-form{ display: none; } diff --git a/WebHostLib/static/styles/checkResult.css b/WebHostLib/static/styles/checkResult.css new file mode 100644 index 00000000..1b22b206 --- /dev/null +++ b/WebHostLib/static/styles/checkResult.css @@ -0,0 +1,18 @@ +#check-result-wrapper{ + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; +} + +#check-result{ + width: 540px; +} + +#check-result table{ + text-align: left; +} + +#check-result table th, #check-result table td{ + padding-right: 20px; +} diff --git a/WebHostLib/static/styles/cookieNotice.css b/WebHostLib/static/styles/cookieNotice.css new file mode 100644 index 00000000..cb6b1109 --- /dev/null +++ b/WebHostLib/static/styles/cookieNotice.css @@ -0,0 +1,16 @@ +#cookie-notice{ + display: block; + position: fixed; + bottom: 0; + left: 0; + width: 100%; + line-height: 40px; + background-color: #c7cda5; + text-align: center; + cursor: pointer; +} + +#cookie-notice #close-button{ + float: right; + padding-right: 10px; +} diff --git a/WebHostLib/static/styles/generate.css b/WebHostLib/static/styles/generate.css new file mode 100644 index 00000000..d7406682 --- /dev/null +++ b/WebHostLib/static/styles/generate.css @@ -0,0 +1,30 @@ +#generate-game-wrapper{ + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; +} + +#generate-game{ + width: 700px; + min-height: 360px; + text-align: center; +} + +#generate-game p{ + text-align: left; +} + +#generate-game button{ + margin-top: 5px; +} + +#generate-game-form-wrapper{ + width: 100%; + text-align: center; + margin-bottom: 1rem; +} + +#generate-game-form{ + display: none; +} diff --git a/WebHostLib/static/styles/globalStyles.css b/WebHostLib/static/styles/globalStyles.css new file mode 100644 index 00000000..26b6c48a --- /dev/null +++ b/WebHostLib/static/styles/globalStyles.css @@ -0,0 +1,117 @@ +@font-face { + font-family: "HyliaSerif"; + src: url('../static/fonts/HyliaSerifBeta-Regular.otf') format("opentype"); +} + +html{ + background-image: url('../static/backgrounds/oceans/oceans-0002.png'); + background-repeat: repeat; + background-size: 250px 250px; + font-family: 'Jost', sans-serif; + font-size: 1.1rem; + color: #000000; +} + +body{ + margin: 0; +} + +a{ + color: #ffef00; +} + +button{ + font-family: Jost, sans-serif; + font-weight: 500; + font-size: 0.9rem; + padding: 10px 17px 11px 16px; /* top right bottom left */ + border-radius: 4px; + border-top: 1px solid rgba(0, 0, 0, 0.5); + border-left: 1px solid rgba(0, 0, 0, 0.5); + border-right: 2px solid rgba(0, 0, 0, 0.5); + border-bottom: 2px solid rgba(0, 0, 0, 0.5); + font-kerning: auto; + cursor: pointer; +} + +button:active{ + border-right: 1px solid rgba(0, 0, 0, 0.5); + border-bottom: 1px solid rgba(0, 0, 0, 0.5); + padding-right: 16px; + margin-right: 2px; + padding-bottom: 10px; + margin-bottom: 2px; +} + +button.button-grass{ + border: 1px solid black; +} + +button.button-dirt{ + border: 1px solid black; +} + +h1, h2, h3, h4, h5, h6{ + font-family: HyliaSerif, sans-serif; + font-weight: normal; + margin: 0; + color: #032605; +} + +h1{ font-size: 3rem; } +h2{ font-size: 2rem; } +h3{ font-size: 1.75rem; } +h4{ + font-size: 1.5rem; + margin-bottom:0.5rem; +} +h5, h6{ + font-size: 1.25rem; + margin-bottom: 0.5rem; +} + +.grass-island{ + background: + url('../static/backgrounds/cliffs/grass/cliff-top-left-corner.png') top left no-repeat, + url('../static/backgrounds/cliffs/grass/cliff-top-right-corner.png') top right no-repeat, + url('../static/backgrounds/cliffs/grass/cliff-bottom-left-corner.png') bottom left no-repeat, + url('../static/backgrounds/cliffs/grass/cliff-bottom-right-corner.png') bottom right no-repeat, + url('../static/backgrounds/cliffs/grass/cliff-top.png') top repeat-x, + url('../static/backgrounds/cliffs/grass/cliff-bottom.png') bottom repeat-x, + url('../static/backgrounds/cliffs/grass/cliff-left.png') left repeat-y, + url('../static/backgrounds/cliffs/grass/cliff-right.png') right repeat-y, + url('../static/backgrounds/grass/grass-0007-large.png') repeat; + + background-size: + 140px 120px, /* top-left */ + 140px 120px, /* top-right */ + 140px 140px, /* bottom-left */ + 140px 140px, /* bottom-right */ + 20px 71px, /* top */ + 20px 100px, /* bottom */ + 71px 20px, /* left */ + 71px 20px, /* right */ + 525px 525px; /* center */ + + min-width: 280px; + min-height: 280px; + + padding-left: 120px; + padding-right: 120px; + padding-top: 100px; + padding-bottom: 120px; +} + +.user-message{ + width: 50%; + min-width: 500px; + padding-top: 5px; + padding-bottom: 5px; + text-align: center; + margin-left: auto; + margin-right: auto; + color: #000000; + border-radius: 4px; + margin-bottom: 20px; + background-color: #ffff00; +} diff --git a/WebHostLib/static/styles/header/baseHeader.css b/WebHostLib/static/styles/header/baseHeader.css new file mode 100644 index 00000000..1d501917 --- /dev/null +++ b/WebHostLib/static/styles/header/baseHeader.css @@ -0,0 +1,46 @@ +html{ + padding-top: 110px; +} + +#base-header{ + display: flex; + flex-direction: row; + justify-content: space-between; + background-size: auto 90px; + width: calc(100% - 20px); + height: 90px; + position: fixed; + top: 0; + left: 0; + font-family: HyliaSerif, sans-serif; + padding: 10px; + line-height: 2rem; + z-index: 9999; +} + +#base-header #base-header-left{ + display: flex; + flex-direction: row; +} + +#base-header #base-header-left a{ + margin-right: 0; + padding-right: 7px; +} + +#base-header #site-title{ + margin-right: 7px; +} + +#base-header #site-title img{ + width: 40px; + height: 40px; +} + +#base-header a{ + color: #2f6b83; + text-decoration: none; + cursor: pointer; + margin-right: 1.2rem; + font-size: 1.2rem; +} diff --git a/WebHostLib/static/styles/header/dirtHeader.css b/WebHostLib/static/styles/header/dirtHeader.css new file mode 100644 index 00000000..b2df59a0 --- /dev/null +++ b/WebHostLib/static/styles/header/dirtHeader.css @@ -0,0 +1,3 @@ +#base-header{ + background: url('../../static/backgrounds/header/dirt-header.png') repeat-x; +} diff --git a/WebHostLib/static/styles/header/grassHeader.css b/WebHostLib/static/styles/header/grassHeader.css new file mode 100644 index 00000000..e5943404 --- /dev/null +++ b/WebHostLib/static/styles/header/grassHeader.css @@ -0,0 +1,3 @@ +#base-header{ + background: url('../../static/backgrounds/header/grass-header.png') repeat-x; +} diff --git a/WebHostLib/static/styles/header/oceanHeader.css b/WebHostLib/static/styles/header/oceanHeader.css new file mode 100644 index 00000000..05d80293 --- /dev/null +++ b/WebHostLib/static/styles/header/oceanHeader.css @@ -0,0 +1,3 @@ +#base-header{ + background: url('../../static/backgrounds/header/ocean-header.png') repeat-x; +} diff --git a/WebHostLib/static/styles/hostGame.css b/WebHostLib/static/styles/hostGame.css new file mode 100644 index 00000000..751dc7c8 --- /dev/null +++ b/WebHostLib/static/styles/hostGame.css @@ -0,0 +1,30 @@ +#host-game-wrapper{ + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; +} + +#host-game{ + width: 700px; + min-height: 240px; + text-align: center; +} + +#host-game.wider{ + width: 980px; +} + +#host-game-form-wrapper{ + width: 100%; + text-align: center; + margin-bottom: 1rem; +} + +#host-game-form{ + display: none; +} + +#host-game button{ + margin-top: 5px; +} diff --git a/WebHostLib/static/styles/hostRoom.css b/WebHostLib/static/styles/hostRoom.css new file mode 100644 index 00000000..ac839d1e --- /dev/null +++ b/WebHostLib/static/styles/hostRoom.css @@ -0,0 +1,26 @@ +html{ + background-image: url('../static/backgrounds/grass/grass-0007-large.png'); + background-repeat: repeat; + background-size: 650px 650px; +} + +#host-room{ + width: calc(100% - 5rem); + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; +} + +#host-room a{ + color: #ffef00; +} + +#host-room input[type=text]{ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + width: 500px; +} diff --git a/WebHostLib/static/styles/host_room.css b/WebHostLib/static/styles/host_room.css deleted file mode 100644 index 12993f7a..00000000 --- a/WebHostLib/static/styles/host_room.css +++ /dev/null @@ -1,5 +0,0 @@ -#host-room input[type=text]{ - width: calc(100% - 6px); - padding: 0.125rem; - height: 1.5rem; -} diff --git a/WebHostLib/static/styles/islandFooter.css b/WebHostLib/static/styles/islandFooter.css new file mode 100644 index 00000000..96611f4e --- /dev/null +++ b/WebHostLib/static/styles/islandFooter.css @@ -0,0 +1,17 @@ +#island-footer{ + background: url('../static/backgrounds/footer/footer-0001.png') repeat-x bottom; + background-size: auto 90px; + display: flex; + flex-direction: row; + justify-content: space-between; + width: calc(100% - 0.5rem); + padding-left: 0.5rem; + padding-top: 80px; + color: #dfedc6 +} + +#island-footer #links a{ + padding-right: 0.5rem; + padding-left: 0.5rem; + color: #dfedc6; +} diff --git a/WebHostLib/static/styles/landing.css b/WebHostLib/static/styles/landing.css index e77f3006..23da6690 100644 --- a/WebHostLib/static/styles/landing.css +++ b/WebHostLib/static/styles/landing.css @@ -1,35 +1,301 @@ +html{ + overflow-x: hidden; +} + #landing-wrapper{ display: flex; - flex-direction: row; + flex-direction: column; justify-content: center; flex-wrap: wrap; + margin-top: 60px; +} + +#landing-header{ + margin-left: auto; + margin-right: auto; + margin-bottom: 50px; + text-align: center; +} + +#landing-header h1{ + color: #ffffff; + font-size: 3.5rem; + text-shadow: 1px 1px 7px #000000; + -webkit-text-stroke: 1px #00582e; + z-index: 10; +} + +#landing-header h4{ + color: #ffffff; + font-size: 1.75rem; + margin-bottom: 0; + text-shadow: 1px 1px 7px #000000; + font-kerning: none; + z-index: 10; +} + +#landing-links{ + margin-left: auto; + margin-right: auto; + height: 350px; +} + +#landing-links a{ + position: absolute; + display: block; + text-align: center; + background-repeat: no-repeat; + font-family: HyliaSerif, sans-serif; + font-kerning: none; + text-decoration: none; + text-shadow: 1px 1px 7px #000000; + color: #ffffff; + font-size: 1.4rem; +} + +#uploads-button{ + top: 65px; + left: calc(50% - 416px - 200px - 75px); + background-image: url("/static/static/button-images/button-a.png"); + background-size: 200px auto; + width: 200px; + height: calc(156px - 40px); + padding-top: 40px; +} + +#setup-guide-button{ + top: 270px; + left: calc(50% - 416px - 200px + 140px); + background-image: url("/static/static/button-images/button-b.png"); + background-size: 260px auto; + width: 260px; + height: calc(130px - 35px); + padding-top: 35px; +} + +#player-settings-button{ + top: 350px; + left: calc(50% - 100px); + background-image: url("/static/static/button-images/button-a.png"); + background-size: 200px auto; + width: 200px; + height: calc(156px - 38px); + padding-top: 38px; +} + +#discord-button{ + top: 250px; + left: calc(50% + 416px - 166px); + background-image: url("/static/static/button-images/button-c.png"); + background-size: 250px auto; + width: calc(250px - 20px); + height: calc(180px - 90px); + padding-top: 90px; + padding-left: 20px; +} + +#generate-button{ + top: 75px; + left: calc(50% + 416px + 75px); + background-image: url("/static/static/button-images/button-b.png"); + background-size: 260px auto; + width: 260px; + height: calc(130px - 35px); + padding-top: 35px; +} + +#landing-clouds{ + z-index: 5; +} + +#landing-clouds #cloud1{ + position: absolute; + left: 10px; + top: 265px; + width: 400px; + height: 350px; + + animation-name: c1-float; + animation-duration: 20s; + animation-iteration-count: infinite; +} + +#landing-clouds #cloud2{ + position: absolute; + right: 82px; + top: 320px; + width: 300px; + height: 300px; + + animation-name: c2-float; + animation-duration: 20s; + animation-iteration-count: infinite; +} + +#landing-clouds #cloud3{ + position: absolute; + right: 24px; + top: 570px; + width: 200px; + height: 325px; + + animation-name: c3-float; + animation-duration: 20s; + animation-iteration-count: infinite; +} + +@keyframes c1-float{ + from{ + left: 10px; + top: 265px; + } + 25%{ + left: 14px; + top: 267px; + } + 50%{ + left: 17px; + top: 265px; + } + 75%{ + left: 14px; + top: 262px; + } + to{ + left: 10px; + top: 265px; + } +} + +@keyframes c2-float { + from{ + right: 82px; + top: 320px; + } + 25%{ + right: 85px; + top: 323px; + } + 50%{ + right: 81px; + top: 326px; + } + 75%{ + right: 79px; + top: 326px; + } + to{ + right: 82px; + top: 320px; + } +} + +@keyframes c3-float { + from{ + right: 24px; + top: 570px; + } + 25%{ + right: 26px; + top: 567px; + } + 50%{ + right: 25px; + top: 570px; + } + 75%{ + right: 22px; + top: 572px; + } + to{ + right: 24px; + top: 570px; + } } #landing{ - margin-right: 0.5em; - margin-bottom: 0.5em; + width: 700px; + min-height: 280px; + margin-left: auto; + margin-right: auto; } -#landing-header-links{ - width: 100%; - text-align: center; - margin: 0; +#landing #first-line{ + font-weight: 500; } -#landing-header-links a{ - margin-left: 1em; - margin-right: 1em; +#landing .variable{ + color: #ffff00; } -#landing-buttons{ - display: flex; - flex-direction: row; - justify-content: center; - flex-wrap: wrap; - margin-top: 1rem; - margin-bottom: 1rem; +.landing-deco{ + position: absolute; } -iframe{ - border: none; +.landing-deco.deco-island{ + width: 110px; +} + +.landing-deco.deco-rock{ + width: 30px; +} + +#landing-deco-1{ + top: 430px; + left: calc(50% - 276px); +} + +#landing-deco-2{ + top: 200px; + left: calc(50% + 150px); +} + +#landing-deco-3{ + top: 300px; + left: calc(50% - 150px); +} + +#landing-deco-4{ + top: 240px; + left: calc(50% - 580px); +} + +#landing-deco-5{ + top: 40px; + left: calc(50% + 450px); +} + +#landing-deco-6{ + top: 412px; + left: calc(50% + 196px); +} + +@media all and (max-width: 1520px){ + #landing-clouds #cloud1, #landing-clouds #cloud2, #landing-clouds #cloud3{ + display: none; + } + + #landing-header{ order: 1; } + #landing-links{ order: 3; } + #landing-clouds{ order: 4; } + #decorations{ order: 5; } + #landing{ order: 2; } + + #landing-links{ + height: auto; + flex-direction: column; + } + + #landing-links a{ + position: relative; + margin-left: auto; + margin-right: auto; + margin-bottom: 1rem; + top: auto; + left: auto; + } + + .landing-deco{ + display: none; + } } diff --git a/WebHostLib/static/styles/layout.css b/WebHostLib/static/styles/layout.css deleted file mode 100644 index 41260f1c..00000000 --- a/WebHostLib/static/styles/layout.css +++ /dev/null @@ -1,58 +0,0 @@ -/** Global colors for all pages */ -body{ - background-color: #dce2bd; - font-size: 1.2rem; - font-family: "Segoe UI", Arial, sans-serif; -} - -/** Button Styles */ -button, input[type=submit]{ - border: 1px solid #7d8c35; - border-radius: 4px; - width: 200px; - height: 75px; - margin-left: 0.25rem; - margin-right: 0.25rem; - background-color: #dce2bd; - font-family: inherit; - font-size: 1.5rem; - cursor: pointer; -} - -button:hover, input[type=submit]:hover{ - background-color: #e0e7bd; -} - -/** Content styles */ -.main-content{ - max-width: 1000px; - border-radius: 8px; - background-color: #bbb288; - padding: 0.5em 1.5rem 1.5rem; - color: #282b28; -} - -.main-content h3{ - margin: 0; - font-size: 3rem; - text-align: center; - font-weight: normal; -} - -.main-content a{ - color: #34768a; - text-decoration: none; -} - -/** Tooltip styles */ -[data-tooltop]{ - position: relative; - z-index: 10; - cursor: pointer; -} - -[data-tooltip]:before, [data-tooltip]:after{ - visibility: hidden; - opacity: 0; - pointer-events: none; -} diff --git a/WebHostLib/static/styles/player-settings.css b/WebHostLib/static/styles/player-settings.css deleted file mode 100644 index 47e9b8ab..00000000 --- a/WebHostLib/static/styles/player-settings.css +++ /dev/null @@ -1,89 +0,0 @@ -#game-settings{ - margin-left: auto; - margin-right: auto; -} - -#game-settings code{ - background-color: #dbe1bc; - border-radius: 4px; - padding-left: 0.25rem; - padding-right: 0.25rem; -} - -#game-settings .instructions{ - text-align: left; -} - -#game-settings #settings-wrapper .setting-wrapper{ - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 100%; -} - -#game-settings #settings-wrapper .setting-wrapper .title-span{ - font-weight: bold; -} - -#game-settings #settings-wrapper{ - margin-top: 1.5rem; -} - -#game-settings #settings-wrapper #sprite-picker{ - margin-bottom: 2rem; -} - -#game-settings #settings-wrapper #sprite-picker #sprite-picker-sprites{ - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: flex-start; -} - -#game-settings #settings-wrapper #sprite-picker .sprite-img-wrapper{ - cursor: pointer; - margin: 10px; -} - -/* Center tooltip text for sprite images */ -#game-settings #settings-wrapper #sprite-picker .sprite-img-wrapper::after{ - text-align: center; -} - -#game-settings #settings-wrapper #sprite-picker .sprite-img-wrapper img{ - width: 32px; - height: 48px; -} - -#game-settings table.option-set{ - width: 100%; - margin-bottom: 1.5rem; -} - -#game-settings table.option-set td.option-name{ - width: 150px; - font-weight: bold; - font-size: 1rem; - line-height: 2rem; -} - -#game-settings table.option-set td.option-name .delete-button{ - cursor: pointer; -} - -#game-settings table.option-set td.option-value{ - line-height: 2rem; -} - -#game-settings table.option-set td.option-value input[type=range]{ - width: 90%; - min-width: 300px; - vertical-align: middle; -} - -#game-settings #game-settings-button-row{ - display: flex; - flex-direction: row; - justify-content: space-between; - width: 100%; -} diff --git a/WebHostLib/static/styles/playerSettings.css b/WebHostLib/static/styles/playerSettings.css new file mode 100644 index 00000000..33a76d3d --- /dev/null +++ b/WebHostLib/static/styles/playerSettings.css @@ -0,0 +1,115 @@ +html{ + background-image: url('../static/backgrounds/grass/grass-0007-large.png'); + background-repeat: repeat; + background-size: 650px 650px; +} + +#player-settings{ + max-width: 1000px; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; +} + +#player-settings #player-settings-button-row{ + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 15px; +} + +#player-settings code{ + background-color: #d9cd8e; + border-radius: 4px; + padding-left: 0.25rem; + padding-right: 0.25rem; + color: #000000; +} + +#player-settings h1{ + font-size: 2.5rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffffff; + text-shadow: 1px 1px 4px #000000; +} + +#player-settings h2{ + font-size: 2rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffe993; + text-transform: lowercase; + text-shadow: 1px 1px 2px #000000; +} + +#player-settings h3, #player-settings h4, #player-settings h5, #player-settings h6{ + color: #ffffff; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); +} + +#player-settings a{ + color: #ffef00; +} + +#player-settings input:not([type]){ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; +} + +#player-settings input:not([type]):focus{ + border: 1px solid #ffffff; +} + +#player-settings select{ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; + background-color: #ffffff; +} + +#player-settings #game-options, #player-settings #rom-options{ + display: flex; + flex-direction: row; +} + +#player-settings .left, #player-settings .right{ + flex-grow: 1; +} + +#player-settings table select{ + width: 250px; +} + +#player-settings table label{ + display: block; + min-width: 200px; + margin-right: 4px; + cursor: default; +} + +@media all and (max-width: 1000px), all and (orientation: portrait){ + #player-settings #game-options, #player-settings #rom-options{ + justify-content: flex-start; + flex-wrap: wrap; + } + + #player-settings .left, #player-settings .right{ + flex-grow: unset; + } + + #game-options table label, #rom-options table label{ + display: block; + min-width: 200px; + } +} diff --git a/WebHostLib/static/styles/tablepage.css b/WebHostLib/static/styles/tablepage.css index da81621c..87d683da 100644 --- a/WebHostLib/static/styles/tablepage.css +++ b/WebHostLib/static/styles/tablepage.css @@ -19,7 +19,7 @@ table.dataTable thead .sorting, table.dataTable thead .sorting_asc, table.dataTa } table.dataTable thead{ - background-color: #b0a77d; + /* background-color: #b0a77d; */ } table.dataTable thead tr th{ @@ -32,5 +32,5 @@ table.dataTable tbody tr{ } table.dataTable tbody tr:hover{ - background-color: #e2eabb; + /* background-color: #e2eabb; */ } diff --git a/WebHostLib/static/styles/tooltip.css b/WebHostLib/static/styles/tooltip.css index 5c61098a..a5d8c759 100644 --- a/WebHostLib/static/styles/tooltip.css +++ b/WebHostLib/static/styles/tooltip.css @@ -67,6 +67,12 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top, line-height: 1.2; } +[data-tooltip]:before, [data-tooltip]:after{ + visibility: hidden; + opacity: 0; + pointer-events: none; +} + [data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after, .tooltip-top:before, .tooltip-top:after { bottom: 100%; diff --git a/WebHostLib/static/styles/tracker.css b/WebHostLib/static/styles/tracker.css index 022b493b..60dc9fbe 100644 --- a/WebHostLib/static/styles/tracker.css +++ b/WebHostLib/static/styles/tracker.css @@ -1,8 +1,16 @@ +html{ + background-image: url('../static/backgrounds/dirt/dirt-0005-large.png'); + background-repeat: repeat; + background-size: 900px 900px; +} + #tracker-wrapper { display: flex; flex-direction: column; justify-content: space-between; - width: 100%; + margin-left: auto; + margin-right: auto; + width: calc(100% - 1rem); } .table-wrapper{ @@ -18,9 +26,15 @@ line-height: 20px; } +#tracker-header-bar .info{ + color: #ffffff; +} + #search{ + border: 1px solid #000000; + border-radius: 3px; + padding: 3px; width: 200px; - height: 20px; margin-bottom: 0.5rem; margin-right: 1rem; } @@ -33,6 +47,14 @@ div.dataTables_wrapper.no-footer .dataTables_scrollBody{ border: none; } +table.dataTable tbody{ + background-color: #dce2bd; +} + +table.dataTable tbody tr:hover{ + background-color: #e2eabb; +} + table.dataTable tbody td{ padding: 4px 6px; } diff --git a/WebHostLib/static/styles/tutorial.css b/WebHostLib/static/styles/tutorial.css index dd7b6a51..c79bfed1 100644 --- a/WebHostLib/static/styles/tutorial.css +++ b/WebHostLib/static/styles/tutorial.css @@ -1,36 +1,62 @@ +html{ + background-image: url('../static/backgrounds/grass/grass-0007-large.png'); + background-repeat: repeat; + background-size: 650px 650px; +} + #tutorial-wrapper{ + display: flex; + flex-direction: column; + max-width: 70rem; margin-left: auto; margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem 1rem 3rem; + color: #eeffeb; +} + +#tutorial-wrapper a{ + color: #ffef00; } #tutorial-wrapper h1{ font-size: 2.5rem; font-weight: normal; - border-bottom: 1px solid #9f916a; + border-bottom: 1px solid #ffffff; cursor: pointer; width: 100%; + margin-bottom: 0.5rem; + color: #ffffff; + text-shadow: 1px 1px 4px #000000; } #tutorial-wrapper h2{ font-size: 2rem; font-weight: normal; - border-bottom: 1px solid #9f916a; + border-bottom: 1px solid #ffffff; cursor: pointer; width: 100%; + margin-bottom: 0.5rem; + color: #ffe993; + text-transform: lowercase; + text-shadow: 1px 1px 2px #000000; } #tutorial-wrapper h3{ - font-size: 1.75rem; + font-size: 1.70rem; font-weight: normal; text-align: left; cursor: pointer; width: 100%; + margin-bottom: 0.5rem; } #tutorial-wrapper h4{ font-size: 1.5rem; font-weight: normal; cursor: pointer; + margin-bottom: 0.5rem; } #tutorial-wrapper h5{ @@ -46,6 +72,11 @@ color: #434343; } +#tutorial-wrapper h3, #tutorial-wrapper h4, #tutorial-wrapper h5,#tutorial-wrapper h6{ + color: #ffffff; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); +} + #tutorial-wrapper ul{ } @@ -58,22 +89,20 @@ } -#tutorial-wrapper a{ - -} - #tutorial-wrapper pre{ padding: 0.5rem 0.25rem; - background-color: #dce2bd; + background-color: #ffeeab; border: 1px solid #9f916a; border-radius: 6px; + color: #000000; } #tutorial-wrapper code{ - background-color: #dce2bd; + background-color: #ffeeab; border-radius: 4px; padding-left: 0.25rem; padding-right: 0.25rem; + color: #000000; } #tutorial-wrapper #tutorial-video-container{ diff --git a/WebHostLib/static/styles/userContent.css b/WebHostLib/static/styles/userContent.css new file mode 100644 index 00000000..84774726 --- /dev/null +++ b/WebHostLib/static/styles/userContent.css @@ -0,0 +1,54 @@ +#user-content-wrapper{ + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; +} + +#user-content{ + min-width: 900px; + text-align: center; +} + +#user-content h1, #user-content h2{ + margin-bottom: 6px; +} + +#user-content h2{ + margin-top: 10px; +} + +#user-content table{ + margin-left: auto; + margin-right: auto; + text-align: left; +} + +#user-content table th, #user-content table td{ + padding-right: 20px; +} + +#user-content .center{ + text-align: center; +} + +#user-content-table{ + margin-right: auto; + text-align: left; +} + +#user-content .table td.center{ + text-align: center; +} + +#user-content table.dataTable{ + width: unset; +} + +table.dataTable thead th{ + padding: 0 20px 0 0; +} + +table.dataTable tbody td{ + padding: 6px 20px 0 0; +} diff --git a/WebHostLib/static/styles/viewSeed.css b/WebHostLib/static/styles/viewSeed.css new file mode 100644 index 00000000..148725cb --- /dev/null +++ b/WebHostLib/static/styles/viewSeed.css @@ -0,0 +1,26 @@ +#view-seed-wrapper{ + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; +} + +#view-seed{ + width: 620px; + min-height: 360px; + text-align: center; +} + +#view-seed table td{ + vertical-align: top; +} + +#view-seed h3{ + margin-bottom: 0.5rem; +} + +#view-seed table{ + text-align: left; + margin-left: auto; + margin-right: auto; +} diff --git a/WebHostLib/static/styles/view_seed.css b/WebHostLib/static/styles/view_seed.css deleted file mode 100644 index 418817b2..00000000 --- a/WebHostLib/static/styles/view_seed.css +++ /dev/null @@ -1,9 +0,0 @@ -#view-seed-wrapper{ - display: flex; - flex-direction: row; - justify-content: center; -} - -#view-seed-wrapper table td{ - vertical-align: top; -} diff --git a/WebHostLib/static/styles/waitSeed.css b/WebHostLib/static/styles/waitSeed.css new file mode 100644 index 00000000..85d281b2 --- /dev/null +++ b/WebHostLib/static/styles/waitSeed.css @@ -0,0 +1,15 @@ +#wait-seed-wrapper{ + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + max-width: 620px; + margin-left: auto; + margin-right: auto; +} + +#wait-seed{ + width: 620px; + min-height: 360px; + text-align: center; +} diff --git a/WebHostLib/static/styles/weightedSettings.css b/WebHostLib/static/styles/weightedSettings.css new file mode 100644 index 00000000..4f788931 --- /dev/null +++ b/WebHostLib/static/styles/weightedSettings.css @@ -0,0 +1,153 @@ +html{ + background-image: url('../static/backgrounds/grass/grass-0007-large.png'); + background-repeat: repeat; + background-size: 650px 650px; +} + +#weighted-settings{ + width: 60rem; + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0.15); + border-radius: 8px; + padding: 1rem; + color: #eeffeb; +} + +#weighted-settings code{ + background-color: #d9cd8e; + border-radius: 4px; + padding-left: 0.25rem; + padding-right: 0.25rem; + color: #000000; +} + +#weighted-settings h1{ + font-size: 2.5rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffffff; + text-shadow: 1px 1px 4px #000000; +} + +#weighted-settings h2{ + font-size: 2rem; + font-weight: normal; + border-bottom: 1px solid #ffffff; + width: 100%; + margin-bottom: 0.5rem; + color: #ffe993; + text-transform: lowercase; + text-shadow: 1px 1px 2px #000000; +} + +#weighted-settings h3, #weighted-settings h4, #weighted-settings h5, #weighted-settings h6{ + color: #ffffff; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); +} + +#weighted-settings .instructions{ + text-align: left; +} + +#weighted-settings #settings-wrapper .setting-wrapper{ + display: flex; + flex-direction: column; + justify-content: flex-start; + width: 100%; +} + +#weighted-settings #settings-wrapper .setting-wrapper .title-span{ + font-weight: 600; + font-size: 1.25rem; +} + +#weighted-settings #settings-wrapper{ + margin-top: 1.5rem; +} + +#weighted-settings #settings-wrapper #sprite-picker{ + margin-bottom: 2rem; +} + +#weighted-settings #settings-wrapper #sprite-picker #sprite-picker-sprites{ + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; +} + +#weighted-settings #settings-wrapper #sprite-picker .sprite-img-wrapper{ + cursor: pointer; + margin: 10px; + image-rendering: pixelated; +} + +/* Center tooltip text for sprite images */ +#weighted-settings #settings-wrapper #sprite-picker .sprite-img-wrapper::after{ + text-align: center; +} + +#weighted-settings #settings-wrapper #sprite-picker .sprite-img-wrapper img{ + width: 32px; + height: 48px; +} + +#weighted-settings table.option-set{ + width: 100%; + margin-bottom: 1.5rem; +} + +#weighted-settings table.option-set td.option-name{ + width: 150px; + font-weight: 400; + font-size: 1rem; + line-height: 2rem; +} + +#weighted-settings table.option-set td.option-name .delete-button{ + cursor: pointer; +} + +#weighted-settings table.option-set td.option-value{ + line-height: 2rem; +} + +#weighted-settings table.option-set td.option-value input[type=range]{ + width: 90%; + min-width: 300px; + vertical-align: middle; +} + +#weighted-settings #weighted-settings-button-row{ + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; +} + +#weighted-settings a{ + color: #ffef00; +} + +#weighted-settings input:not([type]){ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; +} + +#weighted-settings input:not([type]):focus{ + border: 1px solid #ffffff; +} + +#weighted-settings select{ + border: 1px solid #000000; + padding: 3px; + border-radius: 3px; + min-width: 150px; + background-color: #ffffff; +} + diff --git a/WebHostLib/templates/autotablepage.html b/WebHostLib/templates/autotablepage.html new file mode 100644 index 00000000..0bb30f87 --- /dev/null +++ b/WebHostLib/templates/autotablepage.html @@ -0,0 +1,5 @@ +{% extends "tablepage.html" %} +{% block head %} + {{ super() }} + +{% endblock %} diff --git a/WebHostLib/templates/check.html b/WebHostLib/templates/check.html index 511bb13f..78b014eb 100644 --- a/WebHostLib/templates/check.html +++ b/WebHostLib/templates/check.html @@ -1,23 +1,28 @@ -{% extends 'layout.html' %} +{% extends 'pageWrapper.html' %} {% block head %} {{ super() }} Mystery Check Result - - + + {% endblock %} {% block body %} -
-
+ {% include 'header/oceanHeader.html' %} +
+

Upload Yaml

-

This page checks a .yaml file for you, to be used as options for a mystery multiworld. You can also upload a .zip with multiple YAMLs.

-
-
+

+ This page checks a .yaml file for you, to be used as options for a mystery multiworld. + You can also upload a .zip with multiple YAMLs. +

+
+ - +
+ {% include 'islandFooter.html' %} {% endblock %} diff --git a/WebHostLib/templates/checkResult.html b/WebHostLib/templates/checkResult.html new file mode 100644 index 00000000..5bc4fa9d --- /dev/null +++ b/WebHostLib/templates/checkResult.html @@ -0,0 +1,34 @@ +{% extends 'autotablepage.html' %} + +{% block head %} + {{ super() }} + Mystery YAML Test Roll Results + +{% endblock %} + +{% block body %} + {% include 'header/oceanHeader.html' %} +
+
+

Verification Results

+

The results of your requested file check are below.

+ + + + + + + + + {% for filename, resulttext in results.items() %} + + + + + {% endfor %} + +
FileResult
{{ filename }}{{ "Valid" if resulttext == True else resulttext }}
+
+
+ {% include 'islandFooter.html' %} +{% endblock %} diff --git a/WebHostLib/templates/checkresult.html b/WebHostLib/templates/checkresult.html deleted file mode 100644 index d32aa8a0..00000000 --- a/WebHostLib/templates/checkresult.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'layout.html' %} - -{% block head %} - {{ super() }} - Upload Mystery YAML -{% endblock %} - -{% block body %} - {% for filename, resulttext in results.items() %} - {{ filename }}: {{ "Looks ok" if resulttext == True else resulttext }}
- {% endfor %} -{% endblock %} diff --git a/WebHostLib/templates/generate.html b/WebHostLib/templates/generate.html index e33a106c..865f0c23 100644 --- a/WebHostLib/templates/generate.html +++ b/WebHostLib/templates/generate.html @@ -1,42 +1,46 @@ -{% extends 'layout.html' %} +{% extends 'pageWrapper.html' %} {% block head %} {{ super() }} Generate Game - + {% endblock %} {% block body %} -
-
-

Upload YAML(s){% if race %} (Race Mode){% endif %}

+ {% include 'header/oceanHeader.html' %} +
+
+

Upload Config{% if race %} (Race Mode){% endif %}

+

+ This page allows you to generate a game by uploading a yaml file or a zip file containing yaml files. + If you do not have a config (yaml) file yet, you may create one on the + Player Settings page. If you would like more advanced options, + the Weighted Settings page might be what you're looking for. +

- This page accepts a yaml file containing generator options. - You can find a documented example at playerSettings.yaml. - This file can be saved as .yaml, edited to your liking and then supplied to the generator. - You can also upload a .zip with multiple YAMLs. - A proper menu is in the works. {% if race -%} - Race Mode means the spoiler log will be unavailable. + This game will be generated in race mode, meaning the spoiler log will be unavailable, + roms will be encrypted, and single-player games will have no multidata files. {%- else -%} - You can go to Race Mode to create a game without - spoiler log. + If you would like to generate a race game, + click here. Race games are generated without + a spoiler log, the ROMs are encrypted, and single-player games will not include a multidata file. {%- endif -%}

After generation is complete, you will have the option to download a patch file. - This patch file can be opened with the Client to create a rom file. - In-Browser patching will come. + This patch file can be opened with the + client, which can be + used to to create a rom file. In-browser patching is planned for the future.

-
-
+
+ - +
+ {% include 'islandFooter.html' %} {% endblock %} diff --git a/WebHostLib/templates/header/baseHeader.html b/WebHostLib/templates/header/baseHeader.html new file mode 100644 index 00000000..1c58aa2a --- /dev/null +++ b/WebHostLib/templates/header/baseHeader.html @@ -0,0 +1,21 @@ +{% block head %} + +{% endblock %} + +{% block header %} +
+ + +
+{% endblock %} diff --git a/WebHostLib/templates/header/dirtHeader.html b/WebHostLib/templates/header/dirtHeader.html new file mode 100644 index 00000000..4b237333 --- /dev/null +++ b/WebHostLib/templates/header/dirtHeader.html @@ -0,0 +1,5 @@ +{% block head %} + +{% endblock %} + +{% include 'header/baseHeader.html' %} diff --git a/WebHostLib/templates/header/grassHeader.html b/WebHostLib/templates/header/grassHeader.html new file mode 100644 index 00000000..20ad404e --- /dev/null +++ b/WebHostLib/templates/header/grassHeader.html @@ -0,0 +1,5 @@ +{% block head %} + +{% endblock %} + +{% include 'header/baseHeader.html' %} diff --git a/WebHostLib/templates/header/oceanHeader.html b/WebHostLib/templates/header/oceanHeader.html new file mode 100644 index 00000000..1bcfae5b --- /dev/null +++ b/WebHostLib/templates/header/oceanHeader.html @@ -0,0 +1,5 @@ +{% block head %} + +{% endblock %} + +{% include 'header/baseHeader.html' %} diff --git a/WebHostLib/templates/hostGame.html b/WebHostLib/templates/hostGame.html new file mode 100644 index 00000000..a8d6e5b1 --- /dev/null +++ b/WebHostLib/templates/hostGame.html @@ -0,0 +1,30 @@ +{% extends 'pageWrapper.html' %} + +{% block head %} + {{ super() }} + Upload Multidata + + +{% endblock %} + +{% block body %} + {% include 'header/oceanHeader.html' %} + +
+
+

Host Game

+

+ To host a game, you need to upload a .multidata file or a .zip file
+ created by the multiworld generator. +

+
+
+ +
+ +
+
+
+ + {% include 'islandFooter.html' %} +{% endblock %} diff --git a/WebHostLib/templates/host_room.html b/WebHostLib/templates/hostRoom.html similarity index 85% rename from WebHostLib/templates/host_room.html rename to WebHostLib/templates/hostRoom.html index a519fde1..e0108559 100644 --- a/WebHostLib/templates/host_room.html +++ b/WebHostLib/templates/hostRoom.html @@ -1,18 +1,19 @@ -{% extends 'layout.html' %} +{% extends 'pageWrapper.html' %} {% import "macros.html" as macros %} {% block head %} Multiworld {{ room.id|suuid }} - + {% endblock %} {% block body %} + {% include 'header/grassHeader.html' %}
{% if room.owner == session["_id"] %} - Room created from Seed #{{ room.seed.id|suuid }} + Room created from Seed #{{ room.seed.id|suuid }}
{% endif %} {% if room.tracker %} - This room has a Multiworld Tracker enabled. + This room has a Multiworld Tracker enabled.
{% endif %} This room will be closed after {{ room.timeout//60//60 }} hours of inactivity. Should you wish to continue diff --git a/WebHostLib/templates/islandFooter.html b/WebHostLib/templates/islandFooter.html new file mode 100644 index 00000000..f6ae7a2c --- /dev/null +++ b/WebHostLib/templates/islandFooter.html @@ -0,0 +1,18 @@ +{% block footer %} + +{% endblock %} + +{% block head %} + +{% endblock %} diff --git a/WebHostLib/templates/landing.html b/WebHostLib/templates/landing.html index 68222103..e015b065 100644 --- a/WebHostLib/templates/landing.html +++ b/WebHostLib/templates/landing.html @@ -1,59 +1,61 @@ -{% extends 'layout.html' %} +{% extends 'pageWrapper.html' %} {% block head %} - Berserker's Multiworld + MultiWorld {% endblock %} {% block body %}
-
-
-

Berserker's Multiworld

- -
- +
+

the legend of zelda: a link to the past

+

MULTIWORLD RANDOMIZER

+
+ +
+ + + +
+
+ + + + + + +
+
+

Welcome to the Archipelago Multiworld Randomizer!

This is a randomizer for The Legend of Zelda: A Link to the Past.

-

This is also a multiworld, meaning your items may be placed into other players' worlds, and - other players's items may be placed into your world. When a player picks up an item which does - not belong to them, it is sent back to the player it belongs to over the internet.

-

This website allows you to host a multiworld game, and provides item and location trackers for all - games hosted here.

-

Currently, a locally installed client is required to play. This client should handle patching - your ROM files and connecting to the multiworld server. More information on how to set up a local - client may be found on the - Setup Guide.

-

This website is under active development. As such, your hosted rooms may occasionally disappear, - and there may be bugs. If you do happen to find a bug, please report it - here.

-

{{ seeds }} games were created and {{ rooms }} hosted in the last 7 days.

+

It is also a multi-world, meaning Link's items may have been placed into other players' games. + When a player picks up an item which does not belong to them, it is sent back to the player + it belongs to.

+

On this website you are able to generate and host multiworld games, and item and location + trackers are provided for games hosted here.

+

+ This project is the cumulative effort of many + talented people. + Together, they have spent countless hours creating a huge repository of + source code which has turned + our crazy idea into a reality. +

+

+ {{ seeds }} + games were created and + {{ rooms }} + were hosted in the last 7 days. +

-
+ {% include 'islandFooter.html' %} {% endblock %} diff --git a/WebHostLib/templates/macros.html b/WebHostLib/templates/macros.html index 1bf4af28..97e554ce 100644 --- a/WebHostLib/templates/macros.html +++ b/WebHostLib/templates/macros.html @@ -1,7 +1,7 @@ {% macro list_rooms(rooms) -%} @@ -15,4 +15,4 @@ {% endfor %} {% endif %} -{%- endmacro -%} \ No newline at end of file +{%- endmacro -%} diff --git a/WebHostLib/templates/layout.html b/WebHostLib/templates/pageWrapper.html similarity index 50% rename from WebHostLib/templates/layout.html rename to WebHostLib/templates/pageWrapper.html index 4118ec9e..1872382a 100644 --- a/WebHostLib/templates/layout.html +++ b/WebHostLib/templates/pageWrapper.html @@ -2,11 +2,15 @@ - + + - + + + + {% block head %} - Berserker's Multiworld + MultiWorld {% endblock %} @@ -15,7 +19,7 @@ {% if messages %}
{% for message in messages %} - +
{{ message }}
{% endfor %}
{% endif %} diff --git a/WebHostLib/templates/playerSettings.html b/WebHostLib/templates/playerSettings.html new file mode 100644 index 00000000..8f3e584a --- /dev/null +++ b/WebHostLib/templates/playerSettings.html @@ -0,0 +1,45 @@ +{% extends 'pageWrapper.html' %} + +{% block head %} + Player Settings + + + + +{% endblock %} + +{% block body %} + {% include 'header/grassHeader.html' %} +
+

Start Game

+

Choose the options you would like to play with! You may generate a single-player game from this page, + or download a settings file you can use to participate in a MultiWorld. If you would like to make + your settings extra random, check out the advanced weighted settings + page. There, you will find examples of all available sprites as well.

+ +

A list of all games you have generated can be found here.

+ +


+ +

+ +

Game Options

+
+
+
+
+ +

ROM Options

+
+
+
+
+ +
+ + + +
+
+{% endblock %} diff --git a/WebHostLib/templates/seedError.html b/WebHostLib/templates/seedError.html new file mode 100644 index 00000000..b0414f83 --- /dev/null +++ b/WebHostLib/templates/seedError.html @@ -0,0 +1,19 @@ +{% extends 'pageWrapper.html' %} +{% import "macros.html" as macros %} + +{% block head %} + Generation failed, please retry. + +{% endblock %} + +{% block body %} + {% include 'header/oceanHeader.html' %} +
+
+

Generation failed

+

please retry

+ {{ seed_error }} +
+
+ {% include 'islandFooter.html' %} +{% endblock %} diff --git a/WebHostLib/templates/tablepage.html b/WebHostLib/templates/tablepage.html index 0867b2f7..70a4c4d3 100644 --- a/WebHostLib/templates/tablepage.html +++ b/WebHostLib/templates/tablepage.html @@ -1,4 +1,4 @@ -{% extends "layout.html" %} +{% extends "pageWrapper.html" %} {% block head %} {% endblock %} + {% block body %} + {% include 'header/dirtHeader.html' %}
@@ -18,7 +20,7 @@ Multistream - This tracker will automatically update itself periodically. + This tracker will automatically update itself periodically.
{% for team, players in inventory.items() %} @@ -122,7 +124,7 @@ {{ player_names[(team, loop.index)]|e }} {%- for area in ordered_areas -%} {%- set checks_done = checks[area] -%} - {%- set checks_total = checks_in_area[area] -%} + {%- set checks_total = checks_in_area[player][area] -%} {%- if checks_done == checks_total -%} {{ checks_done }}/{{ checks_total }} diff --git a/WebHostLib/templates/tutorial.html b/WebHostLib/templates/tutorial.html index 9e751464..eb6a39d9 100644 --- a/WebHostLib/templates/tutorial.html +++ b/WebHostLib/templates/tutorial.html @@ -1,6 +1,7 @@ -{% extends 'layout.html' %} +{% extends 'pageWrapper.html' %} {% block head %} + {% include 'header/grassHeader.html' %} Setup Tutorial -{% endblock %} - -{% block body %} -
-
-

Upload Multidata

-

To host a game, you need to upload a .multidata file or a .zip file created by the - multiworld generator.

-
-
- -
- -
- - {% if rooms %} -

Your Rooms:

- - - - - - - - - - - - - {% for room in rooms %} - - - - - - - - {% endfor %} - -
SeedRoomPlayersCreatedLast Activity
{{ room.seed.id|suuid }} - {{ room.id|suuid }}{{ room.seed.multidata.names[0]|length }} Total: - {{ room.seed.multidata.names[0]|join(", ")|truncate(256, False, " ...") }}{{ room.creation_time.strftime("%Y-%m-%d %H:%M") }}{{ room.last_activity.strftime("%Y-%m-%d %H:%M") }}
- {% else %} -

No rooms owned by you were found. Upload a file to get started.

- {% endif %} -
-
-{% endblock %} diff --git a/WebHostLib/templates/userContent.html b/WebHostLib/templates/userContent.html new file mode 100644 index 00000000..59c81f83 --- /dev/null +++ b/WebHostLib/templates/userContent.html @@ -0,0 +1,79 @@ +{% extends 'tablepage.html' %} + +{% block head %} + {{ super() }} + Generate Game + + +{% endblock %} + +{% block body %} + {% include 'header/oceanHeader.html' %} +
+
+

User Content

+ Below is a list of all the content you have generated on this site. Rooms and seeds are listed separately. + +

Your Rooms

+ {% if rooms %} + + + + + + + + + + + + {% for room in rooms %} + + + + + + + + {% endfor %} + +
SeedRoomPlayersCreated (UTC)Last Activity (UTC)
{{ room.seed.id|suuid }}{{ room.id|suuid }}{{ room.seed.multidata.names[0]|length }}{{ room.creation_time.strftime("%Y-%m-%d %H:%M") }}{{ room.last_activity.strftime("%Y-%m-%d %H:%M") }}
+ {% else %} + You have not created any rooms yet! + {% endif %} + +

Your Seeds

+ {% if seeds %} + + + + + + + + + + {% for seed in seeds %} + + + + + + {% endfor %} + +
SeedPlayersCreated (UTC)
{{ seed.id|suuid }}{% if seed.multidata %}{{ seed.multidata.names[0]|length }}{% else %}1{% endif %} + {{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }}
+ {% else %} + You have no generated any seeds yet! + {% endif %} +
+
+ {% include 'islandFooter.html' %} +{% endblock %} diff --git a/WebHostLib/templates/view_seed.html b/WebHostLib/templates/viewSeed.html similarity index 65% rename from WebHostLib/templates/view_seed.html rename to WebHostLib/templates/viewSeed.html index 0db581ee..a6916bec 100644 --- a/WebHostLib/templates/view_seed.html +++ b/WebHostLib/templates/viewSeed.html @@ -1,16 +1,21 @@ -{% extends 'layout.html' %} +{% extends 'pageWrapper.html' %} {% import "macros.html" as macros %} {% block head %} - Multiworld Seed {{ seed.id|suuid }} - - + View Seed {{ seed.id|suuid }} + + {% endblock %} {% block body %} + {% include 'header/oceanHeader.html' %}
-
-

Seed Info

+
+

Seed Info

+ {% if not seed.multidata and not seed.spoiler %} +

Single Player Race Rom: No spoiler or multidata exists, parts of the rom are encrypted and rooms + cannot be created.

+ {% endif %} @@ -27,6 +32,7 @@ {% endif %} + {% if seed.multidata %} + {% else %} + + + + + {% endif %}
Download
Players:  @@ -55,8 +61,26 @@ {% endcall %}
Patches:  + +
+ {% include 'islandFooter.html' %} {% endblock %} diff --git a/WebHostLib/templates/waitSeed.html b/WebHostLib/templates/waitSeed.html new file mode 100644 index 00000000..40c0c160 --- /dev/null +++ b/WebHostLib/templates/waitSeed.html @@ -0,0 +1,19 @@ +{% extends 'pageWrapper.html' %} +{% import "macros.html" as macros %} + +{% block head %} + Generation in Progress + + +{% endblock %} + +{% block body %} + {% include 'header/oceanHeader.html' %} +
+
+

Generation in Progress

+ Waiting for game to generate, this page auto-refreshes to check. +
+
+ {% include 'islandFooter.html' %} +{% endblock %} diff --git a/WebHostLib/templates/wait_seed.html b/WebHostLib/templates/wait_seed.html deleted file mode 100644 index fc2d5c3b..00000000 --- a/WebHostLib/templates/wait_seed.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'layout.html' %} -{% import "macros.html" as macros %} - -{% block head %} - Multiworld Seed {{ seed_id|suuid }} (generating...) - - -{% endblock %} - -{% block body %} -
-
- Waiting for game to generate, this page auto-refreshes to check. -
-
-{% endblock %} diff --git a/WebHostLib/templates/player-settings.html b/WebHostLib/templates/weightedSettings.html similarity index 78% rename from WebHostLib/templates/player-settings.html rename to WebHostLib/templates/weightedSettings.html index 3b9ce687..3bbf0c9f 100644 --- a/WebHostLib/templates/player-settings.html +++ b/WebHostLib/templates/weightedSettings.html @@ -1,19 +1,23 @@ -{% extends 'layout.html' %} +{% extends 'pageWrapper.html' %} {% block head %} Player Settings - + + - + {% endblock %} {% block body %} -
-

Player Settings

+ {% include 'header/grassHeader.html' %} +
+

Weighted Settings

- This page is used to configure your player settings. You have three presets you can control, which + This page is used to configure your weighted settings. You have three presets you can control, which you can access using the dropdown menu below. These settings will be usable when generating a single player game, or you can export them to a .yaml file and use them in a multiworld. + If you already have a settings file you would like to validate, you may do so on the + verification page.
@@ -61,9 +65,11 @@
-
+
+ +
{% endblock %} diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index 3b179ed7..358f953b 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -178,6 +178,25 @@ default_locations = { 60121, 60124, 60127, 1573217, 60130, 60133, 60136, 60139, 60142, 60145, 60148, 60151, 60157}, 'Total': set()} +key_only_locations = { + 'Light World': set(), + 'Dark World': set(), + 'Desert Palace': {0x140031, 0x14002b, 0x140061, 0x140028}, + 'Eastern Palace': {0x14005b, 0x140049}, + 'Hyrule Castle': {0x140037, 0x140034, 0x14000d, 0x14003d}, + 'Agahnims Tower': {0x140061, 0x140052}, + 'Tower of Hera': set(), + 'Swamp Palace': {0x140019, 0x140016, 0x140013, 0x140010, 0x14000a}, + 'Thieves Town': {0x14005e, 0x14004f}, + 'Skull Woods': {0x14002e, 0x14001c}, + 'Ice Palace': {0x140004, 0x140022, 0x140025, 0x140046}, + 'Misery Mire': {0x140055, 0x14004c, 0x140064}, + 'Turtle Rock': {0x140058, 0x140007}, + 'Palace of Darkness': set(), + 'Ganons Tower': {0x140040, 0x140043, 0x14003a, 0x14001f}, + 'Total': set() +} + key_locations = {"Desert Palace", "Eastern Palace", "Hyrule Castle", "Agahnims Tower", "Tower of Hera", "Swamp Palace", "Thieves Town", "Skull Woods", "Ice Palace", "Misery Mire", "Turtle Rock", "Palace of Darkness", "Ganons Tower"} @@ -189,6 +208,10 @@ for area, locations in default_locations.items(): for location in locations: location_to_area[location] = area +for area, locations in key_only_locations.items(): + for location in locations: + location_to_area[location] = area + checks_in_area = {area: len(checks) for area, checks in default_locations.items()} checks_in_area["Total"] = 216 @@ -233,6 +256,14 @@ def render_timedelta(delta: datetime.timedelta): _multidata_cache = {} +def get_location_table(checks_table: dict) -> dict: + loc_to_area = {} + for area, locations in checks_table.items(): + if area == "Total": + continue + for location in locations: + loc_to_area[location] = area + return loc_to_area def get_static_room_data(room: Room): result = _multidata_cache.get(room.seed.id, None) @@ -242,22 +273,41 @@ def get_static_room_data(room: Room): # in > 100 players this can take a bit of time and is the main reason for the cache locations = {tuple(k): tuple(v) for k, v in multidata['locations']} names = multidata["names"] + seed_checks_in_area = checks_in_area.copy() use_door_tracker = False if "tags" in multidata: use_door_tracker = "DR" in multidata["tags"] - result = locations, names, use_door_tracker + if use_door_tracker: + for area, checks in key_only_locations.items(): + seed_checks_in_area[area] += len(checks) + seed_checks_in_area["Total"] = 249 + if "checks_in_area" not in multidata: + player_checks_in_area = {playernumber: (seed_checks_in_area if use_door_tracker and + (0x140031, playernumber) in locations else checks_in_area) + for playernumber in range(1, len(names[0]) + 1)} + player_location_to_area = {playernumber: location_to_area + for playernumber in range(1, len(names[0]) + 1)} + + else: + player_checks_in_area = {playernumber: {areaname: len(multidata["checks_in_area"][f'{playernumber}'][areaname]) + if areaname != "Total" else multidata["checks_in_area"][f'{playernumber}']["Total"] + for areaname in ordered_areas} + for playernumber in range(1, len(names[0]) + 1)} + player_location_to_area = {playernumber: get_location_table(multidata["checks_in_area"][f'{playernumber}']) + for playernumber in range(1, len(names[0]) + 1)} + result = locations, names, use_door_tracker, player_checks_in_area, player_location_to_area _multidata_cache[room.seed.id] = result return result @app.route('/tracker/') @cache.memoize(timeout=30) # update every 30 seconds -def get_tracker(tracker: UUID): +def getTracker(tracker: UUID): room = Room.get(tracker=tracker) if not room: abort(404) - locations, names, use_door_tracker = get_static_room_data(room) + locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area = get_static_room_data(room) inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in range(1, len(team) + 1)} for teamnumber, team in enumerate(names)} @@ -278,9 +328,12 @@ def get_tracker(tracker: UUID): for item_id in precollected: attribute_item(inventory, team, player, item_id) for location in locations_checked: + if (location, player) not in locations or location not in player_location_to_area[player]: + continue + item, recipient = locations[location, player] attribute_item(inventory, team, recipient, item) - checks_done[team][player][location_to_area[location]] += 1 + checks_done[team][player][player_location_to_area[player][location]] += 1 checks_done[team][player]["Total"] += 1 for (team, player), game_state in room.multisave.get("client_game_state", []): @@ -309,7 +362,7 @@ def get_tracker(tracker: UUID): lookup_id_to_name=Items.lookup_id_to_name, player_names=player_names, tracking_names=tracking_names, tracking_ids=tracking_ids, room=room, icons=icons, multi_items=multi_items, checks_done=checks_done, ordered_areas=ordered_areas, - checks_in_area=checks_in_area, activity_timers=activity_timers, + checks_in_area=seed_checks_in_area, activity_timers=activity_timers, key_locations=key_locations, small_key_ids=small_key_ids, big_key_ids=big_key_ids, video=video, big_key_locations=key_locations if use_door_tracker else big_key_locations, hints=hints, long_player_names = long_player_names) diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 0613b291..8fce1add 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -55,7 +55,7 @@ def uploads(): for patch in patches: patch.seed = seed - return redirect(url_for("view_seed", seed=seed.id)) + return redirect(url_for("viewSeed", seed=seed.id)) else: flash("No multidata was found in the zip file, which is required.") else: @@ -66,12 +66,18 @@ def uploads(): else: seed = Seed(multidata=multidata, owner=session["_id"]) commit() # place into DB and generate ids - return redirect(url_for("view_seed", seed=seed.id)) + return redirect(url_for("viewSeed", seed=seed.id)) else: flash("Not recognized file format. Awaiting a .multidata file.") + return render_template("hostGame.html") + + +@app.route('/user-content', methods=['GET']) +def user_content(): rooms = select(room for room in Room if room.owner == session["_id"]) - return render_template("uploads.html", rooms=rooms) + seeds = select(seed for seed in Seed if seed.owner == session["_id"]) + return render_template("userContent.html", rooms=rooms, seeds=seeds) def allowed_file(filename): - return filename.endswith(('multidata', ".zip")) \ No newline at end of file + return filename.endswith(('multidata', ".zip")) diff --git a/data/basepatch.apbp b/data/basepatch.apbp index 98ecc814..564edb17 100644 Binary files a/data/basepatch.apbp and b/data/basepatch.apbp differ diff --git a/data/sprites/alttpr/.gitignore b/data/sprites/alttpr/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/data/sprites/alttpr/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/data/sprites/alttpr/001.link.1.zspr b/data/sprites/alttpr/001.link.1.zspr deleted file mode 100644 index 4537afa8..00000000 Binary files a/data/sprites/alttpr/001.link.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/4slink-armors.1.zspr b/data/sprites/alttpr/4slink-armors.1.zspr deleted file mode 100644 index 7a944113..00000000 Binary files a/data/sprites/alttpr/4slink-armors.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/abigail.1.zspr b/data/sprites/alttpr/abigail.1.zspr deleted file mode 100644 index 526990c5..00000000 Binary files a/data/sprites/alttpr/abigail.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/adol.1.zspr b/data/sprites/alttpr/adol.1.zspr deleted file mode 100644 index da8210a9..00000000 Binary files a/data/sprites/alttpr/adol.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/aggretsuko.1.zspr b/data/sprites/alttpr/aggretsuko.1.zspr deleted file mode 100644 index c23d9d83..00000000 Binary files a/data/sprites/alttpr/aggretsuko.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/alice.1.zspr b/data/sprites/alttpr/alice.1.zspr deleted file mode 100644 index 4c673acd..00000000 Binary files a/data/sprites/alttpr/alice.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/angry-video-game-nerd.1.zspr b/data/sprites/alttpr/angry-video-game-nerd.1.zspr deleted file mode 100644 index 79aee561..00000000 Binary files a/data/sprites/alttpr/angry-video-game-nerd.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/arcane.1.zspr b/data/sprites/alttpr/arcane.1.zspr deleted file mode 100644 index b0fd4760..00000000 Binary files a/data/sprites/alttpr/arcane.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ark.2.zspr b/data/sprites/alttpr/ark.2.zspr deleted file mode 100644 index 916b036a..00000000 Binary files a/data/sprites/alttpr/ark.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/arrghus.2.zspr b/data/sprites/alttpr/arrghus.2.zspr deleted file mode 100644 index 2064009d..00000000 Binary files a/data/sprites/alttpr/arrghus.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/astronaut.1.zspr b/data/sprites/alttpr/astronaut.1.zspr deleted file mode 100644 index a4db3020..00000000 Binary files a/data/sprites/alttpr/astronaut.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/badeline.1.zspr b/data/sprites/alttpr/badeline.1.zspr deleted file mode 100644 index b9fb1346..00000000 Binary files a/data/sprites/alttpr/badeline.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bananas-in-pyjamas.1.zspr b/data/sprites/alttpr/bananas-in-pyjamas.1.zspr deleted file mode 100644 index f75af2b2..00000000 Binary files a/data/sprites/alttpr/bananas-in-pyjamas.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bandit.1.zspr b/data/sprites/alttpr/bandit.1.zspr deleted file mode 100644 index 5b3288f8..00000000 Binary files a/data/sprites/alttpr/bandit.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/batman.1.zspr b/data/sprites/alttpr/batman.1.zspr deleted file mode 100644 index a4a1e9c0..00000000 Binary files a/data/sprites/alttpr/batman.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/beau.1.zspr b/data/sprites/alttpr/beau.1.zspr deleted file mode 100644 index 8d8d2079..00000000 Binary files a/data/sprites/alttpr/beau.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bewp.1.zspr b/data/sprites/alttpr/bewp.1.zspr deleted file mode 100644 index 265d2e1a..00000000 Binary files a/data/sprites/alttpr/bewp.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bigkey.1.zspr b/data/sprites/alttpr/bigkey.1.zspr deleted file mode 100644 index eab4854e..00000000 Binary files a/data/sprites/alttpr/bigkey.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/birb.1.zspr b/data/sprites/alttpr/birb.1.zspr deleted file mode 100644 index d6d86bb6..00000000 Binary files a/data/sprites/alttpr/birb.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/birdo.1.zspr b/data/sprites/alttpr/birdo.1.zspr deleted file mode 100644 index 54c49747..00000000 Binary files a/data/sprites/alttpr/birdo.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/blackmage.1.zspr b/data/sprites/alttpr/blackmage.1.zspr deleted file mode 100644 index d9b56288..00000000 Binary files a/data/sprites/alttpr/blackmage.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/blacksmithlink.1.zspr b/data/sprites/alttpr/blacksmithlink.1.zspr deleted file mode 100644 index e9aeb31a..00000000 Binary files a/data/sprites/alttpr/blacksmithlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/blossom.1.zspr b/data/sprites/alttpr/blossom.1.zspr deleted file mode 100644 index 57f4918c..00000000 Binary files a/data/sprites/alttpr/blossom.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bob.1.zspr b/data/sprites/alttpr/bob.1.zspr deleted file mode 100644 index 25fc0410..00000000 Binary files a/data/sprites/alttpr/bob.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/boo-two.1.zspr b/data/sprites/alttpr/boo-two.1.zspr deleted file mode 100644 index a5c5463c..00000000 Binary files a/data/sprites/alttpr/boo-two.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/boo.2.zspr b/data/sprites/alttpr/boo.2.zspr deleted file mode 100644 index 24c74bde..00000000 Binary files a/data/sprites/alttpr/boo.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bottle_o_goo.1.zspr b/data/sprites/alttpr/bottle_o_goo.1.zspr deleted file mode 100644 index 28ca1f9b..00000000 Binary files a/data/sprites/alttpr/bottle_o_goo.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/botw-zelda.1.zspr b/data/sprites/alttpr/botw-zelda.1.zspr deleted file mode 100644 index 630adfa6..00000000 Binary files a/data/sprites/alttpr/botw-zelda.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bowser.1.zspr b/data/sprites/alttpr/bowser.1.zspr deleted file mode 100644 index 1cc256d8..00000000 Binary files a/data/sprites/alttpr/bowser.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/branch.1.zspr b/data/sprites/alttpr/branch.1.zspr deleted file mode 100644 index b7926418..00000000 Binary files a/data/sprites/alttpr/branch.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/brian.1.zspr b/data/sprites/alttpr/brian.1.zspr deleted file mode 100644 index 013a2207..00000000 Binary files a/data/sprites/alttpr/brian.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/broccoli.1.zspr b/data/sprites/alttpr/broccoli.1.zspr deleted file mode 100644 index e335df01..00000000 Binary files a/data/sprites/alttpr/broccoli.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bronzor.1.zspr b/data/sprites/alttpr/bronzor.1.zspr deleted file mode 100644 index d1afd117..00000000 Binary files a/data/sprites/alttpr/bronzor.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bsboy.1.zspr b/data/sprites/alttpr/bsboy.1.zspr deleted file mode 100644 index 7d00be74..00000000 Binary files a/data/sprites/alttpr/bsboy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bsgirl.1.zspr b/data/sprites/alttpr/bsgirl.1.zspr deleted file mode 100644 index 82923751..00000000 Binary files a/data/sprites/alttpr/bsgirl.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bubbles.1.zspr b/data/sprites/alttpr/bubbles.1.zspr deleted file mode 100644 index bbba3b75..00000000 Binary files a/data/sprites/alttpr/bubbles.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/bullet_bill.1.zspr b/data/sprites/alttpr/bullet_bill.1.zspr deleted file mode 100644 index 5b561b9e..00000000 Binary files a/data/sprites/alttpr/bullet_bill.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/buttercup.1.zspr b/data/sprites/alttpr/buttercup.1.zspr deleted file mode 100644 index bd066c27..00000000 Binary files a/data/sprites/alttpr/buttercup.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cactuar.1.zspr b/data/sprites/alttpr/cactuar.1.zspr deleted file mode 100644 index 51c32893..00000000 Binary files a/data/sprites/alttpr/cactuar.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cadence.1.zspr b/data/sprites/alttpr/cadence.1.zspr deleted file mode 100644 index 05f174f7..00000000 Binary files a/data/sprites/alttpr/cadence.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/carlsagan42.1.zspr b/data/sprites/alttpr/carlsagan42.1.zspr deleted file mode 100644 index 2632cb6f..00000000 Binary files a/data/sprites/alttpr/carlsagan42.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/casual-zelda.1.zspr b/data/sprites/alttpr/casual-zelda.1.zspr deleted file mode 100644 index 80257f11..00000000 Binary files a/data/sprites/alttpr/casual-zelda.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cat.3.zspr b/data/sprites/alttpr/cat.3.zspr deleted file mode 100644 index 69946d73..00000000 Binary files a/data/sprites/alttpr/cat.3.zspr and /dev/null differ diff --git a/data/sprites/alttpr/catboo.1.zspr b/data/sprites/alttpr/catboo.1.zspr deleted file mode 100644 index 45a4fa81..00000000 Binary files a/data/sprites/alttpr/catboo.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cdilink.1.zspr b/data/sprites/alttpr/cdilink.1.zspr deleted file mode 100644 index 3236f799..00000000 Binary files a/data/sprites/alttpr/cdilink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/celes.1.zspr b/data/sprites/alttpr/celes.1.zspr deleted file mode 100644 index ac0c1226..00000000 Binary files a/data/sprites/alttpr/celes.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/charizard.1.zspr b/data/sprites/alttpr/charizard.1.zspr deleted file mode 100644 index babed511..00000000 Binary files a/data/sprites/alttpr/charizard.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cheepcheep.1.zspr b/data/sprites/alttpr/cheepcheep.1.zspr deleted file mode 100644 index a49545f2..00000000 Binary files a/data/sprites/alttpr/cheepcheep.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/chibity.1.zspr b/data/sprites/alttpr/chibity.1.zspr deleted file mode 100644 index 949dbe2e..00000000 Binary files a/data/sprites/alttpr/chibity.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cirno.1.zspr b/data/sprites/alttpr/cirno.1.zspr deleted file mode 100644 index 75de8ab6..00000000 Binary files a/data/sprites/alttpr/cirno.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/clifford.1.zspr b/data/sprites/alttpr/clifford.1.zspr deleted file mode 100644 index 73f848c1..00000000 Binary files a/data/sprites/alttpr/clifford.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/clyde.1.zspr b/data/sprites/alttpr/clyde.1.zspr deleted file mode 100644 index b590a2ef..00000000 Binary files a/data/sprites/alttpr/clyde.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/conker.1.zspr b/data/sprites/alttpr/conker.1.zspr deleted file mode 100644 index 121d5233..00000000 Binary files a/data/sprites/alttpr/conker.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cornelius.1.zspr b/data/sprites/alttpr/cornelius.1.zspr deleted file mode 100644 index 4c58f356..00000000 Binary files a/data/sprites/alttpr/cornelius.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/corona.1.zspr b/data/sprites/alttpr/corona.1.zspr deleted file mode 100644 index 2ed39a78..00000000 Binary files a/data/sprites/alttpr/corona.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cucco.1.zspr b/data/sprites/alttpr/cucco.1.zspr deleted file mode 100644 index f237de4a..00000000 Binary files a/data/sprites/alttpr/cucco.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/cursor.1.zspr b/data/sprites/alttpr/cursor.1.zspr deleted file mode 100644 index 45bc9739..00000000 Binary files a/data/sprites/alttpr/cursor.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/d_owls.1.zspr b/data/sprites/alttpr/d_owls.1.zspr deleted file mode 100644 index 147283ea..00000000 Binary files a/data/sprites/alttpr/d_owls.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/dark-panda.1.zspr b/data/sprites/alttpr/dark-panda.1.zspr deleted file mode 100644 index 1b39b747..00000000 Binary files a/data/sprites/alttpr/dark-panda.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/darkboy.1.zspr b/data/sprites/alttpr/darkboy.1.zspr deleted file mode 100644 index de55ebbe..00000000 Binary files a/data/sprites/alttpr/darkboy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/darkgirl.1.zspr b/data/sprites/alttpr/darkgirl.1.zspr deleted file mode 100644 index 8fd848fd..00000000 Binary files a/data/sprites/alttpr/darkgirl.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/darklink-tunic.1.zspr b/data/sprites/alttpr/darklink-tunic.1.zspr deleted file mode 100644 index fe417308..00000000 Binary files a/data/sprites/alttpr/darklink-tunic.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/darklink.1.zspr b/data/sprites/alttpr/darklink.1.zspr deleted file mode 100644 index b0d9a95f..00000000 Binary files a/data/sprites/alttpr/darklink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/darkswatchy.1.zspr b/data/sprites/alttpr/darkswatchy.1.zspr deleted file mode 100644 index 88677425..00000000 Binary files a/data/sprites/alttpr/darkswatchy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/darkzelda.1.zspr b/data/sprites/alttpr/darkzelda.1.zspr deleted file mode 100644 index 519c278a..00000000 Binary files a/data/sprites/alttpr/darkzelda.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/darkzora.2.zspr b/data/sprites/alttpr/darkzora.2.zspr deleted file mode 100644 index fbb15c69..00000000 Binary files a/data/sprites/alttpr/darkzora.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/deadpool-mythic.1.zspr b/data/sprites/alttpr/deadpool-mythic.1.zspr deleted file mode 100644 index abcda926..00000000 Binary files a/data/sprites/alttpr/deadpool-mythic.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/deadpool.1.zspr b/data/sprites/alttpr/deadpool.1.zspr deleted file mode 100644 index 3d2e87f7..00000000 Binary files a/data/sprites/alttpr/deadpool.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/deadrock.1.zspr b/data/sprites/alttpr/deadrock.1.zspr deleted file mode 100644 index cc28cd79..00000000 Binary files a/data/sprites/alttpr/deadrock.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/decidueye.1.zspr b/data/sprites/alttpr/decidueye.1.zspr deleted file mode 100644 index 1c769e62..00000000 Binary files a/data/sprites/alttpr/decidueye.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/demonlink.1.zspr b/data/sprites/alttpr/demonlink.1.zspr deleted file mode 100644 index 2daf7359..00000000 Binary files a/data/sprites/alttpr/demonlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/dragonite.2.zspr b/data/sprites/alttpr/dragonite.2.zspr deleted file mode 100644 index 37d95ad0..00000000 Binary files a/data/sprites/alttpr/dragonite.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/drake.1.zspr b/data/sprites/alttpr/drake.1.zspr deleted file mode 100644 index 1be94a75..00000000 Binary files a/data/sprites/alttpr/drake.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/eggplant.1.zspr b/data/sprites/alttpr/eggplant.1.zspr deleted file mode 100644 index c33c2008..00000000 Binary files a/data/sprites/alttpr/eggplant.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/emosaru.1.zspr b/data/sprites/alttpr/emosaru.1.zspr deleted file mode 100644 index a636dad9..00000000 Binary files a/data/sprites/alttpr/emosaru.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ezlo.1.zspr b/data/sprites/alttpr/ezlo.1.zspr deleted file mode 100644 index 54596847..00000000 Binary files a/data/sprites/alttpr/ezlo.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/fierce-deity-link.1.zspr b/data/sprites/alttpr/fierce-deity-link.1.zspr deleted file mode 100644 index 770b89de..00000000 Binary files a/data/sprites/alttpr/fierce-deity-link.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/finn.3.zspr b/data/sprites/alttpr/finn.3.zspr deleted file mode 100644 index 265b197c..00000000 Binary files a/data/sprites/alttpr/finn.3.zspr and /dev/null differ diff --git a/data/sprites/alttpr/finny_bear.1.zspr b/data/sprites/alttpr/finny_bear.1.zspr deleted file mode 100644 index 9c3a530b..00000000 Binary files a/data/sprites/alttpr/finny_bear.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/fish_floodgate.1.zspr b/data/sprites/alttpr/fish_floodgate.1.zspr deleted file mode 100644 index 86684e7d..00000000 Binary files a/data/sprites/alttpr/fish_floodgate.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/flavor_guy.1.zspr b/data/sprites/alttpr/flavor_guy.1.zspr deleted file mode 100644 index 5e1df365..00000000 Binary files a/data/sprites/alttpr/flavor_guy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/foxlink.1.zspr b/data/sprites/alttpr/foxlink.1.zspr deleted file mode 100644 index d6eaf433..00000000 Binary files a/data/sprites/alttpr/foxlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/freya.1.zspr b/data/sprites/alttpr/freya.1.zspr deleted file mode 100644 index b43338d5..00000000 Binary files a/data/sprites/alttpr/freya.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/frisk.1.zspr b/data/sprites/alttpr/frisk.1.zspr deleted file mode 100644 index d521cae3..00000000 Binary files a/data/sprites/alttpr/frisk.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/froglink.3.zspr b/data/sprites/alttpr/froglink.3.zspr deleted file mode 100644 index f5c46d82..00000000 Binary files a/data/sprites/alttpr/froglink.3.zspr and /dev/null differ diff --git a/data/sprites/alttpr/fujin.2.zspr b/data/sprites/alttpr/fujin.2.zspr deleted file mode 100644 index 9254ff7b..00000000 Binary files a/data/sprites/alttpr/fujin.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/future_trunks.1.zspr b/data/sprites/alttpr/future_trunks.1.zspr deleted file mode 100644 index 456e64c7..00000000 Binary files a/data/sprites/alttpr/future_trunks.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/gamer.1.zspr b/data/sprites/alttpr/gamer.1.zspr deleted file mode 100644 index 9f78d894..00000000 Binary files a/data/sprites/alttpr/gamer.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ganon.1.zspr b/data/sprites/alttpr/ganon.1.zspr deleted file mode 100644 index a6adda43..00000000 Binary files a/data/sprites/alttpr/ganon.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ganondorf.1.zspr b/data/sprites/alttpr/ganondorf.1.zspr deleted file mode 100644 index 5bb6f548..00000000 Binary files a/data/sprites/alttpr/ganondorf.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/garfield.2.zspr b/data/sprites/alttpr/garfield.2.zspr deleted file mode 100644 index 6ca890e8..00000000 Binary files a/data/sprites/alttpr/garfield.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/garnet.1.zspr b/data/sprites/alttpr/garnet.1.zspr deleted file mode 100644 index 858497c7..00000000 Binary files a/data/sprites/alttpr/garnet.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/garomaster.1.zspr b/data/sprites/alttpr/garomaster.1.zspr deleted file mode 100644 index 65b9959d..00000000 Binary files a/data/sprites/alttpr/garomaster.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/gbc-link.1.zspr b/data/sprites/alttpr/gbc-link.1.zspr deleted file mode 100644 index e98a6d08..00000000 Binary files a/data/sprites/alttpr/gbc-link.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/geno.1.zspr b/data/sprites/alttpr/geno.1.zspr deleted file mode 100644 index 3d747a2a..00000000 Binary files a/data/sprites/alttpr/geno.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/gobli.1.zspr b/data/sprites/alttpr/gobli.1.zspr deleted file mode 100644 index 51dd1192..00000000 Binary files a/data/sprites/alttpr/gobli.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/goomba.1.zspr b/data/sprites/alttpr/goomba.1.zspr deleted file mode 100644 index 0438682f..00000000 Binary files a/data/sprites/alttpr/goomba.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/goose.1.zspr b/data/sprites/alttpr/goose.1.zspr deleted file mode 100644 index d2ffb5ba..00000000 Binary files a/data/sprites/alttpr/goose.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/grandpoobear.2.zspr b/data/sprites/alttpr/grandpoobear.2.zspr deleted file mode 100644 index 72663680..00000000 Binary files a/data/sprites/alttpr/grandpoobear.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/grunclestan.1.zspr b/data/sprites/alttpr/grunclestan.1.zspr deleted file mode 100644 index cf371839..00000000 Binary files a/data/sprites/alttpr/grunclestan.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/guiz.1.zspr b/data/sprites/alttpr/guiz.1.zspr deleted file mode 100644 index 995c08ad..00000000 Binary files a/data/sprites/alttpr/guiz.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hardhat_beetle.1.zspr b/data/sprites/alttpr/hardhat_beetle.1.zspr deleted file mode 100644 index 80b63af1..00000000 Binary files a/data/sprites/alttpr/hardhat_beetle.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hat-kid.1.zspr b/data/sprites/alttpr/hat-kid.1.zspr deleted file mode 100644 index d0341060..00000000 Binary files a/data/sprites/alttpr/hat-kid.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/headlesslink.1.zspr b/data/sprites/alttpr/headlesslink.1.zspr deleted file mode 100644 index 8a9b3ce4..00000000 Binary files a/data/sprites/alttpr/headlesslink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hello_kitty.1.zspr b/data/sprites/alttpr/hello_kitty.1.zspr deleted file mode 100644 index a2f5df06..00000000 Binary files a/data/sprites/alttpr/hello_kitty.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hidari.1.zspr b/data/sprites/alttpr/hidari.1.zspr deleted file mode 100644 index 54a4d0da..00000000 Binary files a/data/sprites/alttpr/hidari.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hint_tile.1.zspr b/data/sprites/alttpr/hint_tile.1.zspr deleted file mode 100644 index 9cfd7e90..00000000 Binary files a/data/sprites/alttpr/hint_tile.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hitsuyan.1.zspr b/data/sprites/alttpr/hitsuyan.1.zspr deleted file mode 100644 index 0661577b..00000000 Binary files a/data/sprites/alttpr/hitsuyan.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hoarder-bush.1.zspr b/data/sprites/alttpr/hoarder-bush.1.zspr deleted file mode 100644 index d2adb227..00000000 Binary files a/data/sprites/alttpr/hoarder-bush.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hoarder-pot.1.zspr b/data/sprites/alttpr/hoarder-pot.1.zspr deleted file mode 100644 index 717e4199..00000000 Binary files a/data/sprites/alttpr/hoarder-pot.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hoarder-rock.1.zspr b/data/sprites/alttpr/hoarder-rock.1.zspr deleted file mode 100644 index a93fd1c4..00000000 Binary files a/data/sprites/alttpr/hoarder-rock.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/homer.1.zspr b/data/sprites/alttpr/homer.1.zspr deleted file mode 100644 index ee8b5f5f..00000000 Binary files a/data/sprites/alttpr/homer.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/hyruleknight.1.zspr b/data/sprites/alttpr/hyruleknight.1.zspr deleted file mode 100644 index a8815bc3..00000000 Binary files a/data/sprites/alttpr/hyruleknight.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ibazly.1.zspr b/data/sprites/alttpr/ibazly.1.zspr deleted file mode 100644 index 01114c9e..00000000 Binary files a/data/sprites/alttpr/ibazly.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ignignokt.2.zspr b/data/sprites/alttpr/ignignokt.2.zspr deleted file mode 100644 index f06d07cc..00000000 Binary files a/data/sprites/alttpr/ignignokt.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/informant_woman.1.zspr b/data/sprites/alttpr/informant_woman.1.zspr deleted file mode 100644 index 6465a0e9..00000000 Binary files a/data/sprites/alttpr/informant_woman.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/inkling.1.zspr b/data/sprites/alttpr/inkling.1.zspr deleted file mode 100644 index 6b39e4a7..00000000 Binary files a/data/sprites/alttpr/inkling.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/invisibleman.1.zspr b/data/sprites/alttpr/invisibleman.1.zspr deleted file mode 100644 index 7993c500..00000000 Binary files a/data/sprites/alttpr/invisibleman.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/jack-frost.1.zspr b/data/sprites/alttpr/jack-frost.1.zspr deleted file mode 100644 index 12dd417a..00000000 Binary files a/data/sprites/alttpr/jack-frost.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/jason_frudnick.1.zspr b/data/sprites/alttpr/jason_frudnick.1.zspr deleted file mode 100644 index 2411759c..00000000 Binary files a/data/sprites/alttpr/jason_frudnick.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/jasp.1.zspr b/data/sprites/alttpr/jasp.1.zspr deleted file mode 100644 index 6dc74496..00000000 Binary files a/data/sprites/alttpr/jasp.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/jogurt.1.zspr b/data/sprites/alttpr/jogurt.1.zspr deleted file mode 100644 index b229060c..00000000 Binary files a/data/sprites/alttpr/jogurt.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/katsura.1.zspr b/data/sprites/alttpr/katsura.1.zspr deleted file mode 100644 index 422a0faf..00000000 Binary files a/data/sprites/alttpr/katsura.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/kecleon.1.zspr b/data/sprites/alttpr/kecleon.1.zspr deleted file mode 100644 index 5e1786ba..00000000 Binary files a/data/sprites/alttpr/kecleon.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/kenny_mccormick.1.zspr b/data/sprites/alttpr/kenny_mccormick.1.zspr deleted file mode 100644 index c66a74a5..00000000 Binary files a/data/sprites/alttpr/kenny_mccormick.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ketchup.1.zspr b/data/sprites/alttpr/ketchup.1.zspr deleted file mode 100644 index 9dbb326c..00000000 Binary files a/data/sprites/alttpr/ketchup.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/kholdstare.1.zspr b/data/sprites/alttpr/kholdstare.1.zspr deleted file mode 100644 index 393a491d..00000000 Binary files a/data/sprites/alttpr/kholdstare.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/king_gothalion.1.zspr b/data/sprites/alttpr/king_gothalion.1.zspr deleted file mode 100644 index 65c73f04..00000000 Binary files a/data/sprites/alttpr/king_gothalion.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/king_graham.1.zspr b/data/sprites/alttpr/king_graham.1.zspr deleted file mode 100644 index 28b75cf1..00000000 Binary files a/data/sprites/alttpr/king_graham.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/kirby-meta.1.zspr b/data/sprites/alttpr/kirby-meta.1.zspr deleted file mode 100644 index 9cb132b3..00000000 Binary files a/data/sprites/alttpr/kirby-meta.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/kore8.1.zspr b/data/sprites/alttpr/kore8.1.zspr deleted file mode 100644 index a1db104a..00000000 Binary files a/data/sprites/alttpr/kore8.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/lakitu.1.zspr b/data/sprites/alttpr/lakitu.1.zspr deleted file mode 100644 index 24d0f12e..00000000 Binary files a/data/sprites/alttpr/lakitu.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/lapras.1.zspr b/data/sprites/alttpr/lapras.1.zspr deleted file mode 100644 index bcec01b2..00000000 Binary files a/data/sprites/alttpr/lapras.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/lest.1.zspr b/data/sprites/alttpr/lest.1.zspr deleted file mode 100644 index 99764924..00000000 Binary files a/data/sprites/alttpr/lest.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/lily.1.zspr b/data/sprites/alttpr/lily.1.zspr deleted file mode 100644 index 5cb5d2aa..00000000 Binary files a/data/sprites/alttpr/lily.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/linja.1.zspr b/data/sprites/alttpr/linja.1.zspr deleted file mode 100644 index 414efaf7..00000000 Binary files a/data/sprites/alttpr/linja.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/linkhatcolor.1.zspr b/data/sprites/alttpr/linkhatcolor.1.zspr deleted file mode 100644 index af53898d..00000000 Binary files a/data/sprites/alttpr/linkhatcolor.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/linktuniccolor.1.zspr b/data/sprites/alttpr/linktuniccolor.1.zspr deleted file mode 100644 index 305a9f8f..00000000 Binary files a/data/sprites/alttpr/linktuniccolor.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/littlepony.1.zspr b/data/sprites/alttpr/littlepony.1.zspr deleted file mode 100644 index 0ed4b1b1..00000000 Binary files a/data/sprites/alttpr/littlepony.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/locke_merchant.1.zspr b/data/sprites/alttpr/locke_merchant.1.zspr deleted file mode 100644 index bfd87c7d..00000000 Binary files a/data/sprites/alttpr/locke_merchant.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/lucario.1.zspr b/data/sprites/alttpr/lucario.1.zspr deleted file mode 100644 index 44ce395e..00000000 Binary files a/data/sprites/alttpr/lucario.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/luigi.1.zspr b/data/sprites/alttpr/luigi.1.zspr deleted file mode 100644 index 1a1dc552..00000000 Binary files a/data/sprites/alttpr/luigi.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/madeline.1.zspr b/data/sprites/alttpr/madeline.1.zspr deleted file mode 100644 index 8256e6a3..00000000 Binary files a/data/sprites/alttpr/madeline.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/magus.1.zspr b/data/sprites/alttpr/magus.1.zspr deleted file mode 100644 index 171980ef..00000000 Binary files a/data/sprites/alttpr/magus.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/maiden.1.zspr b/data/sprites/alttpr/maiden.1.zspr deleted file mode 100644 index e0297901..00000000 Binary files a/data/sprites/alttpr/maiden.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mallow-cat.1.zspr b/data/sprites/alttpr/mallow-cat.1.zspr deleted file mode 100644 index 395684b2..00000000 Binary files a/data/sprites/alttpr/mallow-cat.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mangalink.1.zspr b/data/sprites/alttpr/mangalink.1.zspr deleted file mode 100644 index adb57b99..00000000 Binary files a/data/sprites/alttpr/mangalink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/maplequeen.2.zspr b/data/sprites/alttpr/maplequeen.2.zspr deleted file mode 100644 index 35b7deec..00000000 Binary files a/data/sprites/alttpr/maplequeen.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/marin.2.zspr b/data/sprites/alttpr/marin.2.zspr deleted file mode 100644 index 72a06ecf..00000000 Binary files a/data/sprites/alttpr/marin.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mario-classic.2.zspr b/data/sprites/alttpr/mario-classic.2.zspr deleted file mode 100644 index 6443e327..00000000 Binary files a/data/sprites/alttpr/mario-classic.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mario_tanooki.1.zspr b/data/sprites/alttpr/mario_tanooki.1.zspr deleted file mode 100644 index 255350dd..00000000 Binary files a/data/sprites/alttpr/mario_tanooki.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mariocappy.1.zspr b/data/sprites/alttpr/mariocappy.1.zspr deleted file mode 100644 index b888396d..00000000 Binary files a/data/sprites/alttpr/mariocappy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/marisa.1.zspr b/data/sprites/alttpr/marisa.1.zspr deleted file mode 100644 index 16b2a803..00000000 Binary files a/data/sprites/alttpr/marisa.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/matthias.1.zspr b/data/sprites/alttpr/matthias.1.zspr deleted file mode 100644 index 062dae6d..00000000 Binary files a/data/sprites/alttpr/matthias.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/meatwad.1.zspr b/data/sprites/alttpr/meatwad.1.zspr deleted file mode 100644 index a09a4adf..00000000 Binary files a/data/sprites/alttpr/meatwad.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/medallions.1.zspr b/data/sprites/alttpr/medallions.1.zspr deleted file mode 100644 index dc4b04d1..00000000 Binary files a/data/sprites/alttpr/medallions.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/medli.1.zspr b/data/sprites/alttpr/medli.1.zspr deleted file mode 100644 index 59284a36..00000000 Binary files a/data/sprites/alttpr/medli.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/megaman-x.2.zspr b/data/sprites/alttpr/megaman-x.2.zspr deleted file mode 100644 index ffe75595..00000000 Binary files a/data/sprites/alttpr/megaman-x.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/metroid.1.zspr b/data/sprites/alttpr/metroid.1.zspr deleted file mode 100644 index d81187cb..00000000 Binary files a/data/sprites/alttpr/metroid.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mew.1.zspr b/data/sprites/alttpr/mew.1.zspr deleted file mode 100644 index a06dc8d6..00000000 Binary files a/data/sprites/alttpr/mew.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mike-jones.2.zspr b/data/sprites/alttpr/mike-jones.2.zspr deleted file mode 100644 index 550fb213..00000000 Binary files a/data/sprites/alttpr/mike-jones.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/minish_link.1.zspr b/data/sprites/alttpr/minish_link.1.zspr deleted file mode 100644 index 4b342c1a..00000000 Binary files a/data/sprites/alttpr/minish_link.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/minishcaplink.2.zspr b/data/sprites/alttpr/minishcaplink.2.zspr deleted file mode 100644 index aaca256b..00000000 Binary files a/data/sprites/alttpr/minishcaplink.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/missingno.1.zspr b/data/sprites/alttpr/missingno.1.zspr deleted file mode 100644 index 68e61b9b..00000000 Binary files a/data/sprites/alttpr/missingno.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/modernlink.1.zspr b/data/sprites/alttpr/modernlink.1.zspr deleted file mode 100644 index 6d5e68a4..00000000 Binary files a/data/sprites/alttpr/modernlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mog.2.zspr b/data/sprites/alttpr/mog.2.zspr deleted file mode 100644 index a6ed2225..00000000 Binary files a/data/sprites/alttpr/mog.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/momiji.1.zspr b/data/sprites/alttpr/momiji.1.zspr deleted file mode 100644 index 86a18586..00000000 Binary files a/data/sprites/alttpr/momiji.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/moosh.1.zspr b/data/sprites/alttpr/moosh.1.zspr deleted file mode 100644 index 0a1e167a..00000000 Binary files a/data/sprites/alttpr/moosh.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mouse.1.zspr b/data/sprites/alttpr/mouse.1.zspr deleted file mode 100644 index 16ba884d..00000000 Binary files a/data/sprites/alttpr/mouse.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ms-paintdog.1.zspr b/data/sprites/alttpr/ms-paintdog.1.zspr deleted file mode 100644 index 75f5f541..00000000 Binary files a/data/sprites/alttpr/ms-paintdog.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/mushy.1.zspr b/data/sprites/alttpr/mushy.1.zspr deleted file mode 100644 index a6c924a5..00000000 Binary files a/data/sprites/alttpr/mushy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/naturelink.1.zspr b/data/sprites/alttpr/naturelink.1.zspr deleted file mode 100644 index bdfd0efb..00000000 Binary files a/data/sprites/alttpr/naturelink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/navi.1.zspr b/data/sprites/alttpr/navi.1.zspr deleted file mode 100644 index 4621bf4a..00000000 Binary files a/data/sprites/alttpr/navi.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/navirou.1.zspr b/data/sprites/alttpr/navirou.1.zspr deleted file mode 100644 index 9da7e76e..00000000 Binary files a/data/sprites/alttpr/navirou.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ned-flanders.1.zspr b/data/sprites/alttpr/ned-flanders.1.zspr deleted file mode 100644 index 78ed5bc4..00000000 Binary files a/data/sprites/alttpr/ned-flanders.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/negativelink.1.zspr b/data/sprites/alttpr/negativelink.1.zspr deleted file mode 100644 index a3dd1566..00000000 Binary files a/data/sprites/alttpr/negativelink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/neosad.1.zspr b/data/sprites/alttpr/neosad.1.zspr deleted file mode 100644 index 7e95d7f7..00000000 Binary files a/data/sprites/alttpr/neosad.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/neslink.1.zspr b/data/sprites/alttpr/neslink.1.zspr deleted file mode 100644 index 805b3162..00000000 Binary files a/data/sprites/alttpr/neslink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ness.1.zspr b/data/sprites/alttpr/ness.1.zspr deleted file mode 100644 index b8b3de81..00000000 Binary files a/data/sprites/alttpr/ness.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/nia.1.zspr b/data/sprites/alttpr/nia.1.zspr deleted file mode 100644 index 5d01ba4b..00000000 Binary files a/data/sprites/alttpr/nia.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/niko.1.zspr b/data/sprites/alttpr/niko.1.zspr deleted file mode 100644 index 5d39e6bb..00000000 Binary files a/data/sprites/alttpr/niko.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/oldman.2.zspr b/data/sprites/alttpr/oldman.2.zspr deleted file mode 100644 index 1d47cdac..00000000 Binary files a/data/sprites/alttpr/oldman.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ori.2.zspr b/data/sprites/alttpr/ori.2.zspr deleted file mode 100644 index 10c1e462..00000000 Binary files a/data/sprites/alttpr/ori.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/outlinelink.1.zspr b/data/sprites/alttpr/outlinelink.1.zspr deleted file mode 100644 index 50ae98bc..00000000 Binary files a/data/sprites/alttpr/outlinelink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/parallelworldslink.1.zspr b/data/sprites/alttpr/parallelworldslink.1.zspr deleted file mode 100644 index 71a9bdc1..00000000 Binary files a/data/sprites/alttpr/parallelworldslink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/paula.1.zspr b/data/sprites/alttpr/paula.1.zspr deleted file mode 100644 index 657752ea..00000000 Binary files a/data/sprites/alttpr/paula.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/peach.1.zspr b/data/sprites/alttpr/peach.1.zspr deleted file mode 100644 index 7973f952..00000000 Binary files a/data/sprites/alttpr/peach.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/penguinlink.1.zspr b/data/sprites/alttpr/penguinlink.1.zspr deleted file mode 100644 index 2fe01e49..00000000 Binary files a/data/sprites/alttpr/penguinlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/pete.1.zspr b/data/sprites/alttpr/pete.1.zspr deleted file mode 100644 index a3135615..00000000 Binary files a/data/sprites/alttpr/pete.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/phoenix-wright.1.zspr b/data/sprites/alttpr/phoenix-wright.1.zspr deleted file mode 100644 index d7cb0be2..00000000 Binary files a/data/sprites/alttpr/phoenix-wright.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/pikachu.1.zspr b/data/sprites/alttpr/pikachu.1.zspr deleted file mode 100644 index 0b8a88c4..00000000 Binary files a/data/sprites/alttpr/pikachu.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/pinkribbonlink.2.zspr b/data/sprites/alttpr/pinkribbonlink.2.zspr deleted file mode 100644 index ba516f18..00000000 Binary files a/data/sprites/alttpr/pinkribbonlink.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/piranha_plant.1.zspr b/data/sprites/alttpr/piranha_plant.1.zspr deleted file mode 100644 index 59bf4d0d..00000000 Binary files a/data/sprites/alttpr/piranha_plant.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/plagueknight.1.zspr b/data/sprites/alttpr/plagueknight.1.zspr deleted file mode 100644 index 258bed7b..00000000 Binary files a/data/sprites/alttpr/plagueknight.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/pokey.1.zspr b/data/sprites/alttpr/pokey.1.zspr deleted file mode 100644 index 4de17faf..00000000 Binary files a/data/sprites/alttpr/pokey.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/popoi.1.zspr b/data/sprites/alttpr/popoi.1.zspr deleted file mode 100644 index 663d4dc1..00000000 Binary files a/data/sprites/alttpr/popoi.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/poppy.1.zspr b/data/sprites/alttpr/poppy.1.zspr deleted file mode 100644 index 80d4ca69..00000000 Binary files a/data/sprites/alttpr/poppy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/porg_knight.1.zspr b/data/sprites/alttpr/porg_knight.1.zspr deleted file mode 100644 index 4d6f9635..00000000 Binary files a/data/sprites/alttpr/porg_knight.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/powerpuff_girl.1.zspr b/data/sprites/alttpr/powerpuff_girl.1.zspr deleted file mode 100644 index fbf3c694..00000000 Binary files a/data/sprites/alttpr/powerpuff_girl.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/pridelink.2.zspr b/data/sprites/alttpr/pridelink.2.zspr deleted file mode 100644 index 66231013..00000000 Binary files a/data/sprites/alttpr/pridelink.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/primm.1.zspr b/data/sprites/alttpr/primm.1.zspr deleted file mode 100644 index e9ff2d05..00000000 Binary files a/data/sprites/alttpr/primm.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/princess_bubblegum.1.zspr b/data/sprites/alttpr/princess_bubblegum.1.zspr deleted file mode 100644 index c46dcc0f..00000000 Binary files a/data/sprites/alttpr/princess_bubblegum.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/psyduck.2.zspr b/data/sprites/alttpr/psyduck.2.zspr deleted file mode 100644 index c9e17117..00000000 Binary files a/data/sprites/alttpr/psyduck.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/pug.1.zspr b/data/sprites/alttpr/pug.1.zspr deleted file mode 100644 index 60692711..00000000 Binary files a/data/sprites/alttpr/pug.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/purplechest-bottle.1.zspr b/data/sprites/alttpr/purplechest-bottle.1.zspr deleted file mode 100644 index 8daed4d6..00000000 Binary files a/data/sprites/alttpr/purplechest-bottle.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/pyro.1.zspr b/data/sprites/alttpr/pyro.1.zspr deleted file mode 100644 index 9037c8e4..00000000 Binary files a/data/sprites/alttpr/pyro.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/rainbowlink.1.zspr b/data/sprites/alttpr/rainbowlink.1.zspr deleted file mode 100644 index bc8443f9..00000000 Binary files a/data/sprites/alttpr/rainbowlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/remeer.1.zspr b/data/sprites/alttpr/remeer.1.zspr deleted file mode 100644 index 8d7f245a..00000000 Binary files a/data/sprites/alttpr/remeer.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/rick.1.zspr b/data/sprites/alttpr/rick.1.zspr deleted file mode 100644 index 93a163f6..00000000 Binary files a/data/sprites/alttpr/rick.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/robotlink.1.zspr b/data/sprites/alttpr/robotlink.1.zspr deleted file mode 100644 index 8a1eed43..00000000 Binary files a/data/sprites/alttpr/robotlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/rocko.1.zspr b/data/sprites/alttpr/rocko.1.zspr deleted file mode 100644 index ab34f635..00000000 Binary files a/data/sprites/alttpr/rocko.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/rottytops.1.zspr b/data/sprites/alttpr/rottytops.1.zspr deleted file mode 100644 index d4007ffb..00000000 Binary files a/data/sprites/alttpr/rottytops.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/roykoopa.1.zspr b/data/sprites/alttpr/roykoopa.1.zspr deleted file mode 100644 index e1f9699f..00000000 Binary files a/data/sprites/alttpr/roykoopa.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/rumia.1.zspr b/data/sprites/alttpr/rumia.1.zspr deleted file mode 100644 index dc037ae2..00000000 Binary files a/data/sprites/alttpr/rumia.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/rydia.1.zspr b/data/sprites/alttpr/rydia.1.zspr deleted file mode 100644 index ff98ab56..00000000 Binary files a/data/sprites/alttpr/rydia.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ryu.1.zspr b/data/sprites/alttpr/ryu.1.zspr deleted file mode 100644 index 5c6d5411..00000000 Binary files a/data/sprites/alttpr/ryu.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/sailormoon.1.zspr b/data/sprites/alttpr/sailormoon.1.zspr deleted file mode 100644 index 1120d3f4..00000000 Binary files a/data/sprites/alttpr/sailormoon.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/saitama.1.zspr b/data/sprites/alttpr/saitama.1.zspr deleted file mode 100644 index acd9170d..00000000 Binary files a/data/sprites/alttpr/saitama.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/samus-sm.1.zspr b/data/sprites/alttpr/samus-sm.1.zspr deleted file mode 100644 index c8fde01b..00000000 Binary files a/data/sprites/alttpr/samus-sm.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/samus.2.zspr b/data/sprites/alttpr/samus.2.zspr deleted file mode 100644 index 81b0912f..00000000 Binary files a/data/sprites/alttpr/samus.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/samus_classic.1.zspr b/data/sprites/alttpr/samus_classic.1.zspr deleted file mode 100644 index 6559e25c..00000000 Binary files a/data/sprites/alttpr/samus_classic.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/santalink.2.zspr b/data/sprites/alttpr/santalink.2.zspr deleted file mode 100644 index 0e78fedb..00000000 Binary files a/data/sprites/alttpr/santalink.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/scholar.1.zspr b/data/sprites/alttpr/scholar.1.zspr deleted file mode 100644 index bf697f16..00000000 Binary files a/data/sprites/alttpr/scholar.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/selan.1.zspr b/data/sprites/alttpr/selan.1.zspr deleted file mode 100644 index eb3b0318..00000000 Binary files a/data/sprites/alttpr/selan.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/sevens1ns.1.zspr b/data/sprites/alttpr/sevens1ns.1.zspr deleted file mode 100644 index d59a1b52..00000000 Binary files a/data/sprites/alttpr/sevens1ns.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/shadow.1.zspr b/data/sprites/alttpr/shadow.1.zspr deleted file mode 100644 index fcd0d49b..00000000 Binary files a/data/sprites/alttpr/shadow.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/shadowsaku.2.zspr b/data/sprites/alttpr/shadowsaku.2.zspr deleted file mode 100644 index 8972f9f2..00000000 Binary files a/data/sprites/alttpr/shadowsaku.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/shantae.1.zspr b/data/sprites/alttpr/shantae.1.zspr deleted file mode 100644 index 03a1c7b9..00000000 Binary files a/data/sprites/alttpr/shantae.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/shuppet.1.zspr b/data/sprites/alttpr/shuppet.1.zspr deleted file mode 100644 index 55a51ae9..00000000 Binary files a/data/sprites/alttpr/shuppet.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/shy-gal.1.zspr b/data/sprites/alttpr/shy-gal.1.zspr deleted file mode 100644 index b86b27bc..00000000 Binary files a/data/sprites/alttpr/shy-gal.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/shy-guy.1.zspr b/data/sprites/alttpr/shy-guy.1.zspr deleted file mode 100644 index 43ee0fe4..00000000 Binary files a/data/sprites/alttpr/shy-guy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/sighn_waive.1.zspr b/data/sprites/alttpr/sighn_waive.1.zspr deleted file mode 100644 index d961dc4d..00000000 Binary files a/data/sprites/alttpr/sighn_waive.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/snes-controller.1.zspr b/data/sprites/alttpr/snes-controller.1.zspr deleted file mode 100644 index 5dd70f39..00000000 Binary files a/data/sprites/alttpr/snes-controller.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/sodacan.1.zspr b/data/sprites/alttpr/sodacan.1.zspr deleted file mode 100644 index 93e6fb1e..00000000 Binary files a/data/sprites/alttpr/sodacan.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/solaire.1.zspr b/data/sprites/alttpr/solaire.1.zspr deleted file mode 100644 index e216a7d9..00000000 Binary files a/data/sprites/alttpr/solaire.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/soldiersprite.1.zspr b/data/sprites/alttpr/soldiersprite.1.zspr deleted file mode 100644 index d5e8ee35..00000000 Binary files a/data/sprites/alttpr/soldiersprite.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/sonic.1.zspr b/data/sprites/alttpr/sonic.1.zspr deleted file mode 100644 index 55724219..00000000 Binary files a/data/sprites/alttpr/sonic.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/sora.1.zspr b/data/sprites/alttpr/sora.1.zspr deleted file mode 100644 index c8d656fd..00000000 Binary files a/data/sprites/alttpr/sora.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/sora_kh1.1.zspr b/data/sprites/alttpr/sora_kh1.1.zspr deleted file mode 100644 index e77c922d..00000000 Binary files a/data/sprites/alttpr/sora_kh1.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/squall.1.zspr b/data/sprites/alttpr/squall.1.zspr deleted file mode 100644 index b9cd9556..00000000 Binary files a/data/sprites/alttpr/squall.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/squirrel.1.zspr b/data/sprites/alttpr/squirrel.1.zspr deleted file mode 100644 index 64e399a1..00000000 Binary files a/data/sprites/alttpr/squirrel.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/squirtle.1.zspr b/data/sprites/alttpr/squirtle.1.zspr deleted file mode 100644 index 274bf1c7..00000000 Binary files a/data/sprites/alttpr/squirtle.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/stalfos.1.zspr b/data/sprites/alttpr/stalfos.1.zspr deleted file mode 100644 index d4787a3b..00000000 Binary files a/data/sprites/alttpr/stalfos.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/stan.1.zspr b/data/sprites/alttpr/stan.1.zspr deleted file mode 100644 index 5fd3eb11..00000000 Binary files a/data/sprites/alttpr/stan.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/staticlink.1.zspr b/data/sprites/alttpr/staticlink.1.zspr deleted file mode 100644 index d0f1bc06..00000000 Binary files a/data/sprites/alttpr/staticlink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/stick_man.1.zspr b/data/sprites/alttpr/stick_man.1.zspr deleted file mode 100644 index b891586f..00000000 Binary files a/data/sprites/alttpr/stick_man.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/superbomb.1.zspr b/data/sprites/alttpr/superbomb.1.zspr deleted file mode 100644 index 1ed38ae3..00000000 Binary files a/data/sprites/alttpr/superbomb.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/superbunny.2.zspr b/data/sprites/alttpr/superbunny.2.zspr deleted file mode 100644 index b842d1c3..00000000 Binary files a/data/sprites/alttpr/superbunny.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/supermeatboy.1.zspr b/data/sprites/alttpr/supermeatboy.1.zspr deleted file mode 100644 index ad4368bb..00000000 Binary files a/data/sprites/alttpr/supermeatboy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/swatchy.1.zspr b/data/sprites/alttpr/swatchy.1.zspr deleted file mode 100644 index 46795e9b..00000000 Binary files a/data/sprites/alttpr/swatchy.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/tasbot.1.zspr b/data/sprites/alttpr/tasbot.1.zspr deleted file mode 100644 index b7278587..00000000 Binary files a/data/sprites/alttpr/tasbot.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/teatime.1.zspr b/data/sprites/alttpr/teatime.1.zspr deleted file mode 100644 index 8953bc79..00000000 Binary files a/data/sprites/alttpr/teatime.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/terra.1.zspr b/data/sprites/alttpr/terra.1.zspr deleted file mode 100644 index e24ca87a..00000000 Binary files a/data/sprites/alttpr/terra.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/tetra.1.zspr b/data/sprites/alttpr/tetra.1.zspr deleted file mode 100644 index 77525f08..00000000 Binary files a/data/sprites/alttpr/tetra.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/tgh.1.zspr b/data/sprites/alttpr/tgh.1.zspr deleted file mode 100644 index 929b8705..00000000 Binary files a/data/sprites/alttpr/tgh.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/thief.1.zspr b/data/sprites/alttpr/thief.1.zspr deleted file mode 100644 index b6b0ffef..00000000 Binary files a/data/sprites/alttpr/thief.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/thomcrow.1.zspr b/data/sprites/alttpr/thomcrow.1.zspr deleted file mode 100644 index 81bba95d..00000000 Binary files a/data/sprites/alttpr/thomcrow.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/tile.2.zspr b/data/sprites/alttpr/tile.2.zspr deleted file mode 100644 index 38332bb0..00000000 Binary files a/data/sprites/alttpr/tile.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/tingle.1.zspr b/data/sprites/alttpr/tingle.1.zspr deleted file mode 100644 index 9a53f8d2..00000000 Binary files a/data/sprites/alttpr/tingle.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/tmnt.1.zspr b/data/sprites/alttpr/tmnt.1.zspr deleted file mode 100644 index 8f01c1db..00000000 Binary files a/data/sprites/alttpr/tmnt.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/toad.2.zspr b/data/sprites/alttpr/toad.2.zspr deleted file mode 100644 index 6abca2d7..00000000 Binary files a/data/sprites/alttpr/toad.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/toadette.2.zspr b/data/sprites/alttpr/toadette.2.zspr deleted file mode 100644 index 8c6498b2..00000000 Binary files a/data/sprites/alttpr/toadette.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/toadette_captain.1.zspr b/data/sprites/alttpr/toadette_captain.1.zspr deleted file mode 100644 index e69f74a7..00000000 Binary files a/data/sprites/alttpr/toadette_captain.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/totem-links.1.zspr b/data/sprites/alttpr/totem-links.1.zspr deleted file mode 100644 index e4ac6abc..00000000 Binary files a/data/sprites/alttpr/totem-links.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/trogdor.1.zspr b/data/sprites/alttpr/trogdor.1.zspr deleted file mode 100644 index b37191ac..00000000 Binary files a/data/sprites/alttpr/trogdor.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/twilightprincesszelda.2.zspr b/data/sprites/alttpr/twilightprincesszelda.2.zspr deleted file mode 100644 index 2487f44a..00000000 Binary files a/data/sprites/alttpr/twilightprincesszelda.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/two_faced.1.zspr b/data/sprites/alttpr/two_faced.1.zspr deleted file mode 100644 index d504c321..00000000 Binary files a/data/sprites/alttpr/two_faced.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ty.1.zspr b/data/sprites/alttpr/ty.1.zspr deleted file mode 100644 index 1091b298..00000000 Binary files a/data/sprites/alttpr/ty.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/ultros.1.zspr b/data/sprites/alttpr/ultros.1.zspr deleted file mode 100644 index bd312843..00000000 Binary files a/data/sprites/alttpr/ultros.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/valeera.1.zspr b/data/sprites/alttpr/valeera.1.zspr deleted file mode 100644 index 090a6631..00000000 Binary files a/data/sprites/alttpr/valeera.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/vanillalink.1.zspr b/data/sprites/alttpr/vanillalink.1.zspr deleted file mode 100644 index 409171fa..00000000 Binary files a/data/sprites/alttpr/vanillalink.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/vaporeon.1.zspr b/data/sprites/alttpr/vaporeon.1.zspr deleted file mode 100644 index 55372722..00000000 Binary files a/data/sprites/alttpr/vaporeon.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/vegeta.1.zspr b/data/sprites/alttpr/vegeta.1.zspr deleted file mode 100644 index b4f46019..00000000 Binary files a/data/sprites/alttpr/vegeta.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/vera.1.zspr b/data/sprites/alttpr/vera.1.zspr deleted file mode 100644 index b8914365..00000000 Binary files a/data/sprites/alttpr/vera.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/vitreous.1.zspr b/data/sprites/alttpr/vitreous.1.zspr deleted file mode 100644 index 947eff30..00000000 Binary files a/data/sprites/alttpr/vitreous.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/vivi.1.zspr b/data/sprites/alttpr/vivi.1.zspr deleted file mode 100644 index a7ad1a5f..00000000 Binary files a/data/sprites/alttpr/vivi.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/vivian.1.zspr b/data/sprites/alttpr/vivian.1.zspr deleted file mode 100644 index 9de1061f..00000000 Binary files a/data/sprites/alttpr/vivian.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/wario.1.zspr b/data/sprites/alttpr/wario.1.zspr deleted file mode 100644 index f1a5aab7..00000000 Binary files a/data/sprites/alttpr/wario.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/will.1.zspr b/data/sprites/alttpr/will.1.zspr deleted file mode 100644 index d3794969..00000000 Binary files a/data/sprites/alttpr/will.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/wizzrobe.2.zspr b/data/sprites/alttpr/wizzrobe.2.zspr deleted file mode 100644 index f79195d2..00000000 Binary files a/data/sprites/alttpr/wizzrobe.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/wolf_link.1.zspr b/data/sprites/alttpr/wolf_link.1.zspr deleted file mode 100644 index 5ee07dcb..00000000 Binary files a/data/sprites/alttpr/wolf_link.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/wolf_link_tp.1.zspr b/data/sprites/alttpr/wolf_link_tp.1.zspr deleted file mode 100644 index 9627cd73..00000000 Binary files a/data/sprites/alttpr/wolf_link_tp.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/yoshi.1.zspr b/data/sprites/alttpr/yoshi.1.zspr deleted file mode 100644 index 189ea390..00000000 Binary files a/data/sprites/alttpr/yoshi.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/yunica.1.zspr b/data/sprites/alttpr/yunica.1.zspr deleted file mode 100644 index 57f1f416..00000000 Binary files a/data/sprites/alttpr/yunica.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/zandra.1.zspr b/data/sprites/alttpr/zandra.1.zspr deleted file mode 100644 index d7e5012c..00000000 Binary files a/data/sprites/alttpr/zandra.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/zebraunicorn.1.zspr b/data/sprites/alttpr/zebraunicorn.1.zspr deleted file mode 100644 index c06130ff..00000000 Binary files a/data/sprites/alttpr/zebraunicorn.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/zeck.1.zspr b/data/sprites/alttpr/zeck.1.zspr deleted file mode 100644 index 630acd1a..00000000 Binary files a/data/sprites/alttpr/zeck.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/zelda.1.zspr b/data/sprites/alttpr/zelda.1.zspr deleted file mode 100644 index 26ba1a05..00000000 Binary files a/data/sprites/alttpr/zelda.1.zspr and /dev/null differ diff --git a/data/sprites/alttpr/zerosuitsamus.2.zspr b/data/sprites/alttpr/zerosuitsamus.2.zspr deleted file mode 100644 index 0e5a7d17..00000000 Binary files a/data/sprites/alttpr/zerosuitsamus.2.zspr and /dev/null differ diff --git a/data/sprites/alttpr/zora.2.zspr b/data/sprites/alttpr/zora.2.zspr deleted file mode 100644 index 1ca568e1..00000000 Binary files a/data/sprites/alttpr/zora.2.zspr and /dev/null differ diff --git a/data/tiles/JK.json b/data/tiles/JK.json new file mode 100644 index 00000000..019ce97c --- /dev/null +++ b/data/tiles/JK.json @@ -0,0 +1,69 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 5 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 8, + "y": 2 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 8, + "y": 6 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 5, + "y": 4 + } + ] +} \ No newline at end of file diff --git a/data/tiles/LTTP.json b/data/tiles/LTTP.json new file mode 100644 index 00000000..594fd11d --- /dev/null +++ b/data/tiles/LTTP.json @@ -0,0 +1,81 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 4 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 8, + "y": 7 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 7, + "y": 8 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 8, + "y": 6 + } + ] +} \ No newline at end of file diff --git a/data/tiles/NO.json b/data/tiles/NO.json new file mode 100644 index 00000000..611d390d --- /dev/null +++ b/data/tiles/NO.json @@ -0,0 +1,85 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 5 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 1, + "y": 2 + }, + { + "x": 8, + "y": 5 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 8, + "y": 2 + }, + { + "x": 8, + "y": 3 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 6, + "y": 3 + } + ] +} \ No newline at end of file diff --git a/data/tiles/TILE.json b/data/tiles/TILE.json new file mode 100644 index 00000000..acfb35d1 --- /dev/null +++ b/data/tiles/TILE.json @@ -0,0 +1,85 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 2 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 1, + "y": 2 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 7, + "y": 8 + }, + { + "x": 8, + "y": 6 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 6, + "y": 8 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 8, + "y": 8 + } + ] +} \ No newline at end of file diff --git a/data/tiles/YMCA.json b/data/tiles/YMCA.json new file mode 100644 index 00000000..e7c1bc28 --- /dev/null +++ b/data/tiles/YMCA.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 8, + "y": 8 + }, + { + "x": 8, + "y": 7 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 6, + "y": 8 + } + ] +} \ No newline at end of file diff --git a/data/tiles/arrghus.json b/data/tiles/arrghus.json new file mode 100644 index 00000000..1fe1d011 --- /dev/null +++ b/data/tiles/arrghus.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 4, + "y": 1 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 5, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/bomb.json b/data/tiles/bomb.json new file mode 100644 index 00000000..aaa37091 --- /dev/null +++ b/data/tiles/bomb.json @@ -0,0 +1,73 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 3 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 8, + "y": 2 + } + ] +} \ No newline at end of file diff --git a/data/tiles/boot.json b/data/tiles/boot.json new file mode 100644 index 00000000..541fe14a --- /dev/null +++ b/data/tiles/boot.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 6, + "y": 7 + }, + { + "x": 8, + "y": 6 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 8, + "y": 2 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 7, + "y": 8 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 8, + "y": 7 + }, + { + "x": 8, + "y": 8 + }, + { + "x": 8, + "y": 3 + }, + { + "x": 5, + "y": 8 + }, + { + "x": 2, + "y": 8 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 8, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/clown face happy.json b/data/tiles/clown face happy.json new file mode 100644 index 00000000..a46c4120 --- /dev/null +++ b/data/tiles/clown face happy.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 2 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 6, + "y": 2 + } + ] +} \ No newline at end of file diff --git a/data/tiles/cowboy smile.json b/data/tiles/cowboy smile.json new file mode 100644 index 00000000..8ad28dbe --- /dev/null +++ b/data/tiles/cowboy smile.json @@ -0,0 +1,85 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 2 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 5, + "y": 8 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 6, + "y": 3 + } + ] +} \ No newline at end of file diff --git a/data/tiles/creeper face.json b/data/tiles/creeper face.json new file mode 100644 index 00000000..15019a26 --- /dev/null +++ b/data/tiles/creeper face.json @@ -0,0 +1,77 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 5, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/dollar sign.json b/data/tiles/dollar sign.json new file mode 100644 index 00000000..6df2ca40 --- /dev/null +++ b/data/tiles/dollar sign.json @@ -0,0 +1,89 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 6, + "y": 2 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 4, + "y": 0 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 4, + "y": 8 + } + ] +} \ No newline at end of file diff --git a/data/tiles/generic happy face.json b/data/tiles/generic happy face.json new file mode 100644 index 00000000..d0f2415a --- /dev/null +++ b/data/tiles/generic happy face.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 1 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 1, + "y": 2 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 8, + "y": 2 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 7, + "y": 2 + } + ] +} \ No newline at end of file diff --git a/data/tiles/heart soft.json b/data/tiles/heart soft.json new file mode 100644 index 00000000..416301af --- /dev/null +++ b/data/tiles/heart soft.json @@ -0,0 +1,77 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 1 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 1, + "y": 2 + }, + { + "x": 2, + "y": 1 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 8, + "y": 3 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 8, + "y": 2 + } + ] +} \ No newline at end of file diff --git a/data/tiles/heart.json b/data/tiles/heart.json new file mode 100644 index 00000000..e4621301 --- /dev/null +++ b/data/tiles/heart.json @@ -0,0 +1,69 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 8, + "y": 3 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 8, + "y": 2 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 4, + "y": 1 + } + ] +} \ No newline at end of file diff --git a/data/tiles/javalogo.json b/data/tiles/javalogo.json new file mode 100644 index 00000000..02554fc4 --- /dev/null +++ b/data/tiles/javalogo.json @@ -0,0 +1,85 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 5, + "y": 8 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 2, + "y": 8 + }, + { + "x": 1, + "y": 7 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 1, + "y": 5 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 8, + "y": 6 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 6, + "y": 2 + } + ] +} \ No newline at end of file diff --git a/data/tiles/kitty.json b/data/tiles/kitty.json new file mode 100644 index 00000000..7efca7ba --- /dev/null +++ b/data/tiles/kitty.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 1 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 8, + "y": 3 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 8, + "y": 5 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 1, + "y": 5 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 5, + "y": 6 + } + ] +} \ No newline at end of file diff --git a/data/tiles/mario mushroom.json b/data/tiles/mario mushroom.json new file mode 100644 index 00000000..799014d0 --- /dev/null +++ b/data/tiles/mario mushroom.json @@ -0,0 +1,89 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 1, + "y": 5 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 2, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/metroid.json b/data/tiles/metroid.json new file mode 100644 index 00000000..0497c521 --- /dev/null +++ b/data/tiles/metroid.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 7 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 8, + "y": 3 + }, + { + "x": 1, + "y": 6 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 8, + "y": 6 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 8, + "y": 4 + } + ] +} \ No newline at end of file diff --git a/data/tiles/moldorm vertical.json b/data/tiles/moldorm vertical.json new file mode 100644 index 00000000..8da7e4be --- /dev/null +++ b/data/tiles/moldorm vertical.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 5, + "y": 1 + }, + { + "x": 6, + "y": 0 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 4, + "y": 0 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 4, + "y": 6 + } + ] +} \ No newline at end of file diff --git a/data/tiles/moldorm.json b/data/tiles/moldorm.json new file mode 100644 index 00000000..5fe664b8 --- /dev/null +++ b/data/tiles/moldorm.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 3 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 1, + "y": 5 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 8, + "y": 1 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 9, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 9, + "y": 3 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 6, + "y": 6 + } + ] +} \ No newline at end of file diff --git a/data/tiles/panda shocked emoji.json b/data/tiles/panda shocked emoji.json new file mode 100644 index 00000000..9c2e83e5 --- /dev/null +++ b/data/tiles/panda shocked emoji.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 7, + "y": 3 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 8, + "y": 6 + }, + { + "x": 8, + "y": 7 + }, + { + "x": 8, + "y": 8 + }, + { + "x": 2, + "y": 8 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 2, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 8, + "y": 1 + }, + { + "x": 5, + "y": 8 + } + ] +} \ No newline at end of file diff --git a/data/tiles/panda thinking emoji.json b/data/tiles/panda thinking emoji.json new file mode 100644 index 00000000..3ce61bfb --- /dev/null +++ b/data/tiles/panda thinking emoji.json @@ -0,0 +1,77 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 1, + "y": 7 + }, + { + "x": 2, + "y": 8 + }, + { + "x": 1, + "y": 8 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 3, + "y": 8 + } + ] +} \ No newline at end of file diff --git a/data/tiles/pokata and ender key.json b/data/tiles/pokata and ender key.json new file mode 100644 index 00000000..4be09996 --- /dev/null +++ b/data/tiles/pokata and ender key.json @@ -0,0 +1,81 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 1 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 8 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 5, + "y": 8 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 5, + "y": 4 + } + ] +} \ No newline at end of file diff --git a/data/tiles/pokata key.json b/data/tiles/pokata key.json new file mode 100644 index 00000000..c9dd90a2 --- /dev/null +++ b/data/tiles/pokata key.json @@ -0,0 +1,89 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 3, + "y": 1 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 8 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 5, + "y": 8 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 4, + "y": 1 + } + ] +} \ No newline at end of file diff --git a/data/tiles/rupee diagonal.json b/data/tiles/rupee diagonal.json new file mode 100644 index 00000000..42ccf793 --- /dev/null +++ b/data/tiles/rupee diagonal.json @@ -0,0 +1,89 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 4 + }, + { + "x": 1, + "y": 5 + }, + { + "x": 1, + "y": 6 + }, + { + "x": 1, + "y": 7 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 5, + "y": 3 + } + ] +} \ No newline at end of file diff --git a/data/tiles/scream emoji.json b/data/tiles/scream emoji.json new file mode 100644 index 00000000..27888c8e --- /dev/null +++ b/data/tiles/scream emoji.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 2 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 1, + "y": 7 + }, + { + "x": 8, + "y": 7 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 7, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/screw attack.json b/data/tiles/screw attack.json new file mode 100644 index 00000000..cecc0f7c --- /dev/null +++ b/data/tiles/screw attack.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 7 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 5, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/space invader metroid.json b/data/tiles/space invader metroid.json new file mode 100644 index 00000000..7b463325 --- /dev/null +++ b/data/tiles/space invader metroid.json @@ -0,0 +1,85 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 4, + "y": 1 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 2, + "y": 8 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 1, + "y": 7 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 8, + "y": 5 + }, + { + "x": 7, + "y": 8 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 8, + "y": 7 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 1, + "y": 5 + }, + { + "x": 6, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/sword.json b/data/tiles/sword.json new file mode 100644 index 00000000..94626172 --- /dev/null +++ b/data/tiles/sword.json @@ -0,0 +1,85 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 8 + }, + { + "x": 8, + "y": 1 + }, + { + "x": 8, + "y": 2 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 5, + "y": 8 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 3, + "y": 5 + } + ] +} \ No newline at end of file diff --git a/data/tiles/thinking emoji.json b/data/tiles/thinking emoji.json new file mode 100644 index 00000000..5cedac30 --- /dev/null +++ b/data/tiles/thinking emoji.json @@ -0,0 +1,65 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 5, + "y": 6 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 3, + "y": 0 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 6, + "y": 0 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 3, + "y": 3 + } + ] +} \ No newline at end of file diff --git a/data/tiles/tile shaped tiles randomish.json b/data/tiles/tile shaped tiles randomish.json new file mode 100644 index 00000000..3adc1403 --- /dev/null +++ b/data/tiles/tile shaped tiles randomish.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 4, + "y": 2 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 2, + "y": 6 + } + ] +} \ No newline at end of file diff --git a/data/tiles/tile shaped tiles.json b/data/tiles/tile shaped tiles.json new file mode 100644 index 00000000..ec9e9e02 --- /dev/null +++ b/data/tiles/tile shaped tiles.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 2, + "y": 7 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 4 + } + ] +} \ No newline at end of file diff --git a/data/tiles/triangle.json b/data/tiles/triangle.json new file mode 100644 index 00000000..215d764c --- /dev/null +++ b/data/tiles/triangle.json @@ -0,0 +1,69 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 1, + "y": 5 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 4, + "y": 4 + } + ] +} \ No newline at end of file diff --git a/data/tiles/triple triforce.json b/data/tiles/triple triforce.json new file mode 100644 index 00000000..0906de0b --- /dev/null +++ b/data/tiles/triple triforce.json @@ -0,0 +1,53 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 4, + "y": 2 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 1, + "y": 6 + } + ] +} \ No newline at end of file diff --git a/data/tiles/vanilla wrong order.json b/data/tiles/vanilla wrong order.json new file mode 100644 index 00000000..3eef4fc0 --- /dev/null +++ b/data/tiles/vanilla wrong order.json @@ -0,0 +1,93 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 7, + "y": 2 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 2, + "y": 7 + } + ] +} \ No newline at end of file diff --git a/data/tiles/vanilla.json b/data/tiles/vanilla.json new file mode 100644 index 00000000..ab96a41d --- /dev/null +++ b/data/tiles/vanilla.json @@ -0,0 +1,27 @@ +{ + "Speed":224, + "Items":[ + {"x":4,"y":4}, + {"x":5,"y":4}, + {"x":3,"y":3}, + {"x":6,"y":5}, + {"x":6,"y":3}, + {"x":3,"y":5}, + {"x":4,"y":2}, + {"x":5,"y":6}, + {"x":5,"y":2}, + {"x":4,"y":6}, + {"x":2,"y":2}, + {"x":7,"y":7}, + {"x":7,"y":2}, + {"x":2,"y":7}, + {"x":2,"y":4}, + {"x":7,"y":5}, + {"x":7,"y":4}, + {"x":2,"y":5}, + {"x":4,"y":3}, + {"x":5,"y":5}, + {"x":5,"y":3}, + {"x":4,"y":5} + ] +} \ No newline at end of file diff --git a/data/tiles/z1 dungeon 4.json b/data/tiles/z1 dungeon 4.json new file mode 100644 index 00000000..73e649eb --- /dev/null +++ b/data/tiles/z1 dungeon 4.json @@ -0,0 +1,81 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 4, + "y": 8 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 5, + "y": 1 + } + ] +} diff --git a/data/tiles/z1 dungeon1.json b/data/tiles/z1 dungeon1.json new file mode 100644 index 00000000..a515c846 --- /dev/null +++ b/data/tiles/z1 dungeon1.json @@ -0,0 +1,73 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 4, + "y": 6 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 4, + "y": 4 + } + ] +} \ No newline at end of file diff --git a/data/tiles/ze.json b/data/tiles/ze.json new file mode 100644 index 00000000..41a5338e --- /dev/null +++ b/data/tiles/ze.json @@ -0,0 +1,85 @@ +{ + "Speed": 224, + "Items": [ + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 1, + "y": 7 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 1, + "y": 6 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 5, + "y": 6 + } + ] +} \ No newline at end of file diff --git a/data/web/package-lock.json b/data/web/package-lock.json index fda4a04e..b8935516 100644 --- a/data/web/package-lock.json +++ b/data/web/package-lock.json @@ -5694,9 +5694,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "7.1.0", diff --git a/dumpSprites.py b/dumpSprites.py index 9c91d9ce..f29ad202 100644 --- a/dumpSprites.py +++ b/dumpSprites.py @@ -1,39 +1,35 @@ -import argparse -import json -from os import listdir -from os.path import isfile, join -from worlds.alttp.Rom import Sprite -from Gui import get_image_for_sprite - -parser = argparse.ArgumentParser(description='Dump sprite data and .png files to a directory.') -parser.add_argument('-i') -parser.add_argument('-o') -args = parser.parse_args() - -if not args.i or not args.o: - print('Invalid arguments provided. -i and -o are required.') - exit() +from Gui import * +import threading # Target directories -input_dir = args.i -output_dir = args.o +input_dir = local_path("data", "sprites", "alttpr") +output_dir = local_path("WebHostLib", "static", "static") -# Get a list of all files in the input directory -targetFiles = [file for file in listdir(input_dir) if isfile(join(input_dir, file))] +#update sprites through gui.py's functions +done = threading.Event() +top = Tk() +top.withdraw() +BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set()) +while not done.isSet(): + top.update() -spriteData = {} +print("Done updating sprites") -for file in targetFiles: - if file[-5:] != '.zspr': - continue +spriteData = [] - sprite = Sprite(join(input_dir, file)) - spriteData[sprite.name] = file +for file in os.listdir(input_dir): + sprite = Sprite(os.path.join(input_dir, file)) - image = open(f'{output_dir}/{sprite.name}.gif', 'wb') - image.write(get_image_for_sprite(sprite, True)) - image.close() + if not sprite.name: + print("Warning:",file,"has no name.") + sprite.name = file.split(".", 1)[0] -jsonFile = open(f'{output_dir}/spriteData.json', 'w') -jsonFile.write(json.dumps(spriteData)) -jsonFile.close() + if sprite.valid: + with open(os.path.join(output_dir, "sprites", f"{sprite.name}.gif"), 'wb') as image: + image.write(get_image_for_sprite(sprite, True)) + spriteData.append({"file": file, "author": sprite.author_name, "name": sprite.name}) + else: + print(file, "dropped, as it has no valid sprite data.") +spriteData.sort(key=lambda entry: entry["name"]) +with open(f'{output_dir}/spriteData.json', 'w') as file: + json.dump({"sprites": spriteData}, file, indent=1) diff --git a/host.yaml b/host.yaml index 4cdc5174..bccfc9e5 100644 --- a/host.yaml +++ b/host.yaml @@ -22,8 +22,6 @@ server_options: loglevel: "info" # Allows for clients to log on and manage the server. If this is null, no remote administration is possible. server_password: null - # Automatically forward the port that is used, then close that port after 24 hours - port_forward: false # Disallow !getitem. Old /getitem cannot be blocked this way disable_item_cheat: false # Client hint system @@ -57,10 +55,15 @@ multi_mystery_options: # Teams # Note that there is currently no way to supply names for teams 2+ through MultiMystery teams: 1 - # Location of your Enemizer CLI, available here: https://github.com/Bonta0/Enemizer/releases + # Location of your Enemizer CLI, available here: https://github.com/Ijwu/Enemizer/releases enemizer_path: "EnemizerCLI/EnemizerCLI.Core.exe" # Folder from which the player yaml files are pulled from player_files_path: "Players" + #amount of players, 0 to infer from player files + players: 0 + # general weights file, within the stated player_files_path location + # gets used if players is higher than the amount of per-player files found to fill remaining slots + weights_file_path: "weights.yaml" # Meta file name, within the stated player_files_path location meta_file_path: "meta.yaml" # Automatically launches {player_name}.yaml's ROM file using the OS's default program once generation completes. (likely your emulator) @@ -82,7 +85,7 @@ multi_mystery_options: # 2 -> Delete the non-zipped one. zip_diffs: 2 # Zip spoiler log - # 0 -> Include the spoiler log in the zip + # 1 -> Include the spoiler log in the zip # 2 -> Delete the non-zipped one zip_spoiler: 0 # Zip multidata @@ -94,5 +97,8 @@ multi_mystery_options: # 2 -> 7z is recommended for roms. All of them get the job done. # 3 -> bz2 zip_format: 1 - # Create roms flagged as race roms + # Create encrypted race roms race: 0 + # List of options that can be plando'd. Can be combined, for example "bosses, items" + # Available options: bosses, items, texts, connections + plando_options: "bosses" diff --git a/icon.ico b/icon.ico index 797410ab..a142b56b 100644 Binary files a/icon.ico and b/icon.ico differ diff --git a/inno_setup.iss b/inno_setup_38.iss similarity index 93% rename from inno_setup.iss rename to inno_setup_38.iss index 9f1e75f1..ff917b97 100644 --- a/inno_setup.iss +++ b/inno_setup_38.iss @@ -11,6 +11,7 @@ AppName={#MyAppName} AppVerName={#MyAppName} DefaultDirName={commonappdata}\{#MyAppName} DisableProgramGroupPage=yes +DefaultGroupName=Berserker's Multiworld OutputDir=setups OutputBaseFilename=Setup {#MyAppName} Compression=lzma2 @@ -39,7 +40,7 @@ NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-mod [Files] Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external -Source: "{#sourcepath}*"; Excludes: "*.key, *.log, *.hpkey"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{#sourcepath}*"; Excludes: "*.sfc, *.log, data\sprites\alttpr"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall ; NOTE: Don't use "Flags: ignoreversion" on any shared system files @@ -51,6 +52,7 @@ Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: [Run] Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." +Filename: "{app}\BerserkerMultiCreator"; Parameters: "update_sprites"; StatusMsg: "Updating Sprite Library..." [UninstallDelete] Type: dirifempty; Name: "{app}" @@ -80,7 +82,7 @@ begin begin // Is the installed version at least the packaged one ? Log('VC Redist x64 Version : found ' + strVersion); - Result := (CompareStr(strVersion, 'v14.26.28720') < 0); + Result := (CompareStr(strVersion, 'v14.28.29325') < 0); end else begin diff --git a/inno_setup_39.iss b/inno_setup_39.iss new file mode 100644 index 00000000..7f8d6b7d --- /dev/null +++ b/inno_setup_39.iss @@ -0,0 +1,141 @@ +#define sourcepath "build\exe.win-amd64-3.9\" +#define MyAppName "BerserkerMultiWorld" +#define MyAppExeName "BerserkerMultiClient.exe" +#define MyAppIcon "icon.ico" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +AppId={{6D826EE0-49BE-4B36-BACE-09C6971CD85C}} +AppName={#MyAppName} +AppVerName={#MyAppName} +DefaultDirName={commonappdata}\{#MyAppName} +DisableProgramGroupPage=yes +DefaultGroupName=Berserker's Multiworld +OutputDir=setups +OutputBaseFilename=Setup {#MyAppName} +Compression=lzma2 +SolidCompression=yes +LZMANumBlockThreads=8 +ArchitecturesInstallIn64BitMode=x64 +ChangesAssociations=yes +ArchitecturesAllowed=x64 +AllowNoIcons=yes +SetupIconFile={#MyAppIcon} +UninstallDisplayIcon={app}\{#MyAppExeName} +SignTool= signtool +LicenseFile= LICENSE +WizardStyle= modern +SetupLogging=yes + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; + + +[Dirs] +NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; + +[Files] +Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external +Source: "{#sourcepath}*"; Excludes: "*.sfc, *.log, data\sprites\alttpr"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; +Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." +Filename: "{app}\BerserkerMultiCreator"; Parameters: "update_sprites"; StatusMsg: "Updating Sprite Library..." + +[UninstallDelete] +Type: dirifempty; Name: "{app}" + +[Registry] + +Root: HKCR; Subkey: ".bmbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Berserker's Multiworld Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: "" + +Root: HKCR; Subkey: ".multidata"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Berserker's Multiworld Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\BerserkerMultiServer.exe,0"; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\BerserkerMultiServer.exe"" --multidata ""%1"""; ValueType: string; ValueName: "" + + + +[Code] +// See: https://stackoverflow.com/a/51614652/2287576 +function IsVCRedist64BitNeeded(): boolean; +var + strVersion: string; +begin + if (RegQueryStringValue(HKEY_LOCAL_MACHINE, + 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', strVersion)) then + begin + // Is the installed version at least the packaged one ? + Log('VC Redist x64 Version : found ' + strVersion); + Result := (CompareStr(strVersion, 'v14.28.29325') < 0); + end + else + begin + // Not even an old version installed + Log('VC Redist x64 is not already installed'); + Result := True; + end; +end; + +var ROMFilePage: TInputFileWizardPage; +var R : longint; +var rom: string; + +procedure InitializeWizard(); +begin + rom := FileSearch('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', WizardDirValue()); + if Length(rom) > 0 then + begin + log('existing ROM found'); + log(IntToStr(CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173'))); + if CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173') = 0 then + begin + log('existing ROM verified'); + exit; + end; + log('existing ROM failed verification'); + end; + rom := '' + ROMFilePage := + CreateInputFilePage( + wpLicense, + 'Select ROM File', + 'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?', + 'Select the file, then click Next.'); + + ROMFilePage.Add( + 'Location of ROM file:', + 'SNES ROM files|*.sfc|All files|*.*', + '.sfc'); +end; + +function GetROMPath(Param: string): string; +begin + if Length(rom) > 0 then + Result := rom + else if Assigned(RomFilePage) then + begin + R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') + if R <> 0 then + MsgBox('ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := ROMFilePage.Values[0] + end + else + Result := ''; + end; diff --git a/meta.yaml b/meta.yaml index 3f1afb56..6642402b 100644 --- a/meta.yaml +++ b/meta.yaml @@ -21,9 +21,10 @@ goals: dungeons: 50 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2) pedestal: 100 # Pull the Triforce from the Master Sword pedestal triforce-hunt: 5 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle - local_triforce_hunt: 10 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle - ganon_triforce_hunt: 5 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon - local_ganon_triforce_hunt: 20 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon + local_triforce_hunt: 5 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle + ganon_triforce_hunt: 10 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon + local_ganon_triforce_hunt: 10 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon + ganon_pedestal: 10 # Pull the Master Sword pedestal, then kill Ganon null: 0 # Maintain individual goals mode: standard: 10 @@ -50,4 +51,14 @@ ganon_open: '6': 9 '7': 10 random: 5 # This will mean differing completion times. But leaving it for that surprise effect +triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces. + extra: 0 # available = triforce_pieces_extra + triforce_pieces_required + percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required + available: 50 # available = triforce_pieces_available +triforce_pieces_available: # Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1 + # Format "pieces: chance" + 30: 50 +triforce_pieces_required: # Set to how many out of X triforce pieces you need to win the game in a triforce hunt. Default is 20. Max is 90, Min is 1 + # Format "pieces: chance" + 25: 50 # Do not use meta rom options at this time \ No newline at end of file diff --git a/playerSettings.yaml b/playerSettings.yaml index b9396adb..22b40aa9 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -107,7 +107,7 @@ triforce_pieces_extra: # Set to how many extra triforces pieces are available to 10: 50 15: 0 20: 0 -triforce_pieces_percentage: # Set to how many extra triforces pieces according to a percentage of the required ones, are available to collect in the world. +triforce_pieces_percentage: # Set to how many triforce pieces according to a percentage of the required ones, are available to collect in the world. # Format "pieces: chance" 100: 0 #No extra 150: 50 #Half the required will be added as extra @@ -165,7 +165,6 @@ item_pool: normal: 50 # Item availability remains unchanged from vanilla game hard: 0 # Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless) expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless) - crowd_control: 0 # Sets up the item pool for the crowd control extension. Do not use it without crowd control item_functionality: easy: 0 # Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere. normal: 50 # Vanilla item functionality @@ -174,6 +173,9 @@ item_functionality: progression_balancing: on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch around missing items. +tile_shuffle: # Randomize the tile layouts in flying tile rooms + on: 0 + off: 50 ### Enemizer Section ### boss_shuffle: none: 50 # Vanilla bosses @@ -187,9 +189,6 @@ enemy_shuffle: # Randomize enemy placement killable_thieves: # Make thieves killable on: 0 # Usually turned on together with enemy_shuffle to make annoying thief placement more manageable off: 50 -tile_shuffle: # Randomize the tile layouts in flying tile rooms - on: 0 - off: 50 bush_shuffle: # Randomize the chance that bushes have enemies and the enemies under said bush on: 0 off: 50 @@ -232,12 +231,33 @@ timer: ohko: 0 # Timer always at zero. Permanent OHKO. timed_countdown: 0 # Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though. display: 0 # Displays a timer, but otherwise does not affect gameplay or the item pool. +countdown_start_time: # For timed_ohko and timed_countdown timer modes, the amount of time in minutes to start with + 0: 0 # For timed_ohko, starts in OHKO mode when starting the game + 10: 50 + 20: 0 + 30: 0 + 60: 0 +red_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock + -2: 50 + 1: 0 +blue_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock + 1: 0 + 2: 50 +green_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock + 4: 50 + 10: 0 + 15: 0 # Can be uncommented to use it # local_items: # Force certain items to appear in your world only, not across the multiworld. Recognizes some group names, like "Swords" # - "Moon Pearl" # - "Small Keys" # - "Big Keys" # Can be uncommented to use it +# non_local_items: # Force certain items to appear outside your world only, always across the multiworld. Recognizes some group names, like "Swords" +# - "Moon Pearl" +# - "Small Keys" +# - "Big Keys" +# Can be uncommented to use it # startinventory: # Begin the file with the listed items/upgrades # Pegasus Boots: on # Bomb Upgrade (+10): 4 @@ -280,6 +300,7 @@ linked_options: hard: 1 expert: 1 percentage: 0 # Set this to the percentage chance you want enemizer +### door rando only options ### door_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise vanilla: 50 # Everything should be like in vanilla basic: 0 # Dungeons are shuffled within themselves @@ -289,12 +310,16 @@ intensity: # Only available if the host uses the doors branch, it is ignored oth 2: 0 # And shuffles open edges and straight staircases 3: 0 # And shuffles dungeon lobbies random: 0 # Picks one of those at random +key_drop_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise + on: 0 # Enables the small keys dropped by enemies or under pots, and the big key dropped by the Ball & Chain guard to be shuffled into the pool. This extends the number of checks to 249. + off: 50 experimental: # Only available if the host uses the doors branch, it is ignored otherwise - on: 0 # Enables experimental features. Currently, this is just the dungeon keys in chest counter. + on: 0 # Enables experimental features. off: 50 debug: # Only available if the host uses the doors branch, it is ignored otherwise on: 0 # Enables debugging features. Currently, these are the Item collection counter. (overwrites total triforce pieces) and Castle Gate closed indicator. off: 50 +### end of door rando only options ### rom: random_sprite_on_event: # An alternative to specifying randomonhit / randomonexit / etc... in sprite down below. enabled: # If enabled, sprite down below is ignored completely, (although it may become the sprite pool) @@ -367,9 +392,51 @@ rom: off: 0 ow_palettes: # Change the colors of the overworld default: 50 # No changes - random: 0 # Shuffle the colors - blackout: 0 # Never use this + random: 0 # Shuffle the colors, with harmony in mind + blackout: 0 # everything black / blind mode + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 uw_palettes: # Change the colors of caves and dungeons default: 50 # No changes - random: 0 # Shuffle the colors - blackout: 0 # Never use this + random: 0 # Shuffle the colors, with harmony in mind + blackout: 0 # everything black / blind mode + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 + hud_palettes: # Change the colors of the hud + default: 50 # No changes + random: 0 # Shuffle the colors, with harmony in mind + blackout: 0 # everything black / blind mode + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 + sword_palettes: # Change the colors of swords + default: 50 # No changes + random: 0 # Shuffle the colors, with harmony in mind + blackout: 0 # everything black / blind mode + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 + shield_palettes: # Change the colors of shields + default: 50 # No changes + random: 0 # Shuffle the colors, with harmony in mind + blackout: 0 # everything black / blind mode + grayscale: 0 + negative: 0 + classic: 0 + dizzy: 0 + sick: 0 + puke: 0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 24e7fc59..0588e3ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ websockets>=8.1 PyYAML>=5.3.1 fuzzywuzzy>=0.18.0 bsdiff4>=1.2.0 -prompt_toolkit>=3.0.6 +prompt_toolkit>=3.0.8 appdirs>=1.4.4 -maseya-z3pr>=1.0.0rc1 \ No newline at end of file +maseya-z3pr>=1.0.0rc1 +xxtea>=2.0.0.post0 diff --git a/setup.py b/setup.py index 03257fa8..48872d7b 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,9 @@ buildfolder = Path("build", folder) sbuildfolder = str(buildfolder) libfolder = Path(buildfolder, "lib") library = Path(libfolder, "library.zip") -print("Outputting to: " + str(buildfolder)) -compress = False -icon="icon.ico" +print("Outputting to: " + sbuildfolder) + +icon = "icon.ico" if os.path.exists("X:/pw.txt"): print("Using signtool") @@ -28,13 +28,16 @@ else: from hashlib import sha3_512 import base64 + def _threaded_hash(filepath): hasher = sha3_512() hasher.update(open(filepath, "rb").read()) return base64.b85encode(hasher.digest()).decode() + os.makedirs(buildfolder, exist_ok=True) + def manifest_creation(): hashes = {} manifestpath = os.path.join(buildfolder, "manifest.json") @@ -51,11 +54,11 @@ def manifest_creation(): print("Created Manifest") -scripts = {"MultiClient.py" : "BerserkerMultiClient", - "MultiMystery.py" : "BerserkerMultiMystery", - "MultiServer.py" : "BerserkerMultiServer", - "gui.py" : "BerserkerMultiCreator", - "Mystery.py" : "BerserkerMystery"} +scripts = {"MultiClient.py": "BerserkerMultiClient", + "MultiMystery.py": "BerserkerMultiMystery", + "MultiServer.py": "BerserkerMultiServer", + "gui.py": "BerserkerMultiCreator", + "Mystery.py": "BerserkerMystery"} exes = [] @@ -66,7 +69,6 @@ for script, scriptname in scripts.items(): icon=icon, )) - import datetime buildtime = datetime.datetime.utcnow() @@ -113,7 +115,7 @@ for data in extra_data: installfile(Path(data)) os.makedirs(buildfolder / "Players", exist_ok=True) -shutil.copyfile("playerSettings.yaml", buildfolder / "Players" / "playerSettings.yaml") +shutil.copyfile("playerSettings.yaml", buildfolder / "Players" / "weightedSettings.yaml") try: from maseya import z3pr @@ -128,11 +130,13 @@ else: qusb2sneslog = buildfolder / "QUsb2Snes" / "log.txt" if os.path.exists(qusb2sneslog): os.remove(qusb2sneslog) +qusb2snesconfig = buildfolder / "QUsb2Snes" / "config.ini" +if os.path.exists(qusb2snesconfig): + os.remove(qusb2snesconfig) if signtool: for exe in exes: print(f"Signing {exe.targetName}") os.system(signtool + exe.targetName) - manifest_creation() diff --git a/test/TestBase.py b/test/TestBase.py index b8f3df5b..24fc7d89 100644 --- a/test/TestBase.py +++ b/test/TestBase.py @@ -1,11 +1,11 @@ import unittest -from BaseClasses import CollectionState +from BaseClasses import CollectionState, World from worlds.alttp.Items import ItemFactory class TestBase(unittest.TestCase): - + world: World _state_cache = {} def get_state(self, items): @@ -23,7 +23,7 @@ class TestBase(unittest.TestCase): for location, access, *item_pool in access_pool: items = item_pool[0] all_except = item_pool[1] if len(item_pool) > 1 else None - with self.subTest(location=location, access=access, items=items, all_except=all_except): + with self.subTest(msg="Reach Location", location=location, access=access, items=items, all_except=all_except): if all_except and len(all_except) > 0: items = self.world.itempool[:] items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] @@ -34,11 +34,23 @@ class TestBase(unittest.TestCase): self.assertEqual(self.world.get_location(location, 1).can_reach(state), access) + + #check for partial solution + if not all_except and access:# we are not supposed to be able to reach location with partial inventory + for missing_item in item_pool[0]: + with self.subTest(msg="Location reachable without required item", location=location, + items=item_pool[0], missing_item=missing_item): + new_items = item_pool[0].copy() + new_items.remove(missing_item) + items = ItemFactory(new_items, 1) + state = self.get_state(items) + self.assertEqual(self.world.get_location(location, 1).can_reach(state), False) + def run_entrance_tests(self, access_pool): for entrance, access, *item_pool in access_pool: items = item_pool[0] all_except = item_pool[1] if len(item_pool) > 1 else None - with self.subTest(entrance=entrance, access=access, items=items, all_except=all_except): + with self.subTest(msg="Reach Entrance", entrance=entrance, access=access, items=items, all_except=all_except): if all_except and len(all_except) > 0: items = self.world.itempool[:] items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] @@ -47,4 +59,15 @@ class TestBase(unittest.TestCase): items = ItemFactory(items, 1) state = self.get_state(items) - self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), access) \ No newline at end of file + self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), access) + + #check for partial solution + if not all_except and access:# we are not supposed to be able to reach location with partial inventory + for missing_item in item_pool[0]: + with self.subTest(msg="Entrance reachable without required item", entrance=entrance, + items=item_pool[0], missing_item=missing_item): + new_items = item_pool[0].copy() + new_items.remove(missing_item) + items = ItemFactory(new_items, 1) + state = self.get_state(items) + self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), False) \ No newline at end of file diff --git a/test/inverted/TestInvertedDarkWorld.py b/test/inverted/TestInvertedDarkWorld.py index 82ba2065..047d73c9 100644 --- a/test/inverted/TestInvertedDarkWorld.py +++ b/test/inverted/TestInvertedDarkWorld.py @@ -55,7 +55,7 @@ class TestInvertedDarkWorld(TestInverted): ["Pyramid", False, []], ["Pyramid", True, ['Beat Agahnim 1', 'Magic Mirror']], ["Pyramid", True, ['Hammer']], - ["Pyramid", True, ['Flippers', 'Progressive Glove']], + ["Pyramid", True, ['Flippers']], ["Pyramid", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Moon Pearl']], ["Pyramid Fairy - Left", False, []], diff --git a/test/inverted/TestInvertedEntrances.py b/test/inverted/TestInvertedEntrances.py index 9ae260d6..cb9c3f7e 100644 --- a/test/inverted/TestInvertedEntrances.py +++ b/test/inverted/TestInvertedEntrances.py @@ -66,7 +66,8 @@ class TestEntrances(TestInverted): ["Palace of Darkness", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl", "Flute"]], ["Palace of Darkness", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl", "Magic Mirror"]], ["Palace of Darkness", True, ["Beat Agahnim 1", "Moon Pearl", "Flute"]], - ["Palace of Darkness", True, ["Beat Agahnim 1", "Moon Pearl", "Magic Mirror"]], + # Moon Pearl not needed, you can mirror as Bunny + ["Palace of Darkness", True, ["Beat Agahnim 1", "Magic Mirror"]], ["Swamp Palace", True, []], @@ -114,5 +115,4 @@ class TestEntrances(TestInverted): ["Inverted Ganons Tower", False, [], ["Crystal 7"]], ["Inverted Ganons Tower", True, ["Beat Agahnim 1", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], ["Inverted Ganons Tower", True, ["Moon Pearl", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], - ["Inverted Ganons Tower", True, ["Moon Pearl", "Hammer", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], ]) \ No newline at end of file diff --git a/test/inverted/TestInvertedLightWorld.py b/test/inverted/TestInvertedLightWorld.py index 64ee267c..07ab39e8 100644 --- a/test/inverted/TestInvertedLightWorld.py +++ b/test/inverted/TestInvertedLightWorld.py @@ -195,7 +195,7 @@ class TestInvertedLightWorld(TestInverted): ["Bombos Tablet", True, ['Beat Agahnim 1', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword', 'Progressive Sword']], ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Hammer', 'Progressive Sword', 'Progressive Sword']], - ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Beat Agahnim 1', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Book of Mudora', 'Beat Agahnim 1', 'Progressive Sword', 'Progressive Sword']], ["Floodgate Chest", False, []], ["Floodgate Chest", False, [], ['Moon Pearl']], diff --git a/test/inverted/TestInvertedTurtleRock.py b/test/inverted/TestInvertedTurtleRock.py index 03eebc63..1054c7ed 100644 --- a/test/inverted/TestInvertedTurtleRock.py +++ b/test/inverted/TestInvertedTurtleRock.py @@ -68,10 +68,11 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Big Key Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + # Mirror in from ledge, use left side entrance, have enough keys to get to the chest + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], @@ -88,9 +89,7 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria']], ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria']], - ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria']], - #@todo: Advanced? ["Turtle Rock - Eye Bridge - Bottom Left", False, []], ["Turtle Rock - Eye Bridge - Bottom Left", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], ["Turtle Rock - Eye Bridge - Bottom Left", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], @@ -103,18 +102,20 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + # Mirroring into Eye Bridge does not require Cane of Somaria + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Eye Bridge - Bottom Right", False, []], ["Turtle Rock - Eye Bridge - Bottom Right", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], @@ -128,18 +129,18 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Eye Bridge - Top Left", False, []], ["Turtle Rock - Eye Bridge - Top Left", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], @@ -153,18 +154,18 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Eye Bridge - Top Right", False, []], ["Turtle Rock - Eye Bridge - Top Right", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], @@ -178,18 +179,18 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], ["Turtle Rock - Boss", False, []], ["Turtle Rock - Boss", False, [], ['Cane of Somaria']], @@ -200,7 +201,7 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']], ["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] diff --git a/test/inverted_minor_glitches/TestInvertedDarkWorld.py b/test/inverted_minor_glitches/TestInvertedDarkWorld.py new file mode 100644 index 00000000..462d9701 --- /dev/null +++ b/test/inverted_minor_glitches/TestInvertedDarkWorld.py @@ -0,0 +1,102 @@ +from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor + + +class TestInvertedDarkWorld(TestInvertedMinor): + + def testNorthWest(self): + self.run_location_tests([ + ["Brewery", True, []], + + ["C-Shaped House", True, []], + + ["Chest Game", True, []], + + ["Peg Cave", False, []], + ["Peg Cave", False, [], ['Hammer']], + ["Peg Cave", False, [], ['Progressive Glove', 'Magic Mirror']], + ["Peg Cave", True, ['Hammer', 'Progressive Glove', 'Progressive Glove']], + ["Peg Cave", True, ['Hammer', 'Progressive Glove', 'Magic Mirror', 'Moon Pearl']], + ["Peg Cave", True, ['Hammer', 'Beat Agahnim 1', 'Magic Mirror']], + + ["Bumper Cave Ledge", False, []], + ["Bumper Cave Ledge", False, [], ['Moon Pearl']], + ["Bumper Cave Ledge", False, [], ['Cape']], + ["Bumper Cave Ledge", False, [], ['Progressive Glove']], + ["Bumper Cave Ledge", False, [], ['Magic Mirror']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Magic Mirror', 'Progressive Glove', 'Beat Agahnim 1']], + + ["Blacksmith", False, []], + ["Blacksmith", False, [], ['Progressive Glove', 'Magic Mirror']], + ["Blacksmith", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Blacksmith", True, ['Beat Agahnim 1', 'Magic Mirror']], + ["Blacksmith", True, ['Progressive Glove', 'Hammer', 'Magic Mirror', 'Moon Pearl']], + + ["Purple Chest", False, []], + ["Purple Chest", False, [], ['Progressive Glove', 'Magic Mirror']], + ["Purple Chest", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Purple Chest", True, ['Beat Agahnim 1', 'Magic Mirror']], + ["Purple Chest", True, ['Progressive Glove', 'Hammer', 'Magic Mirror', 'Moon Pearl']], + ]) + + def testNorthEast(self): + self.run_location_tests([ + ["Catfish", False, []], + ["Catfish", True, ['Beat Agahnim 1', 'Moon Pearl', 'Magic Mirror']], + ["Catfish", True, ['Progressive Glove']], + + ["Pyramid", True, []], + + ["Pyramid Fairy - Left", False, []], + ["Pyramid Fairy - Left", False, [], ['Magic Mirror']], + ["Pyramid Fairy - Left", False, [], ['Crystal 5']], + ["Pyramid Fairy - Left", False, [], ['Crystal 6']], + ["Pyramid Fairy - Left", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Hammer', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Left", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Left", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Beat Agahnim 1']], + + ["Pyramid Fairy - Right", False, []], + ["Pyramid Fairy - Right", False, [], ['Magic Mirror']], + ["Pyramid Fairy - Right", False, [], ['Crystal 5']], + ["Pyramid Fairy - Right", False, [], ['Crystal 6']], + ["Pyramid Fairy - Right", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Hammer', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Right", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Right", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Beat Agahnim 1']], + ]) + + def testSouth(self): + self.run_location_tests([ + ["Hype Cave - Top", True, []], + + ["Hype Cave - Middle Right", True, []], + + ["Hype Cave - Middle Left", True, []], + + ["Hype Cave - Bottom", True, []], + + ["Hype Cave - Generous Guy", True, []], + + ["Stumpy", True, []], + + ["Digging Game", True, []], + + ["Link's House", True, []], + ]) + + def testMireArea(self): + self.run_location_tests([ + ["Mire Shed - Left", False, []], + ["Mire Shed - Left", False, [], ['Flute', 'Magic Mirror']], + ["Mire Shed - Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove']], + ["Mire Shed - Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Hammer']], + ["Mire Shed - Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1']], + ["Mire Shed - Left", True, ['Magic Mirror', 'Beat Agahnim 1']], + + ["Mire Shed - Right", False, []], + ["Mire Shed - Right", False, [], ['Flute', 'Magic Mirror']], + ["Mire Shed - Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove']], + ["Mire Shed - Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Hammer']], + ["Mire Shed - Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1']], + ["Mire Shed - Right", True, ['Magic Mirror', 'Beat Agahnim 1']], + ]) \ No newline at end of file diff --git a/test/inverted_minor_glitches/TestInvertedDeathMountain.py b/test/inverted_minor_glitches/TestInvertedDeathMountain.py new file mode 100644 index 00000000..a5adf13f --- /dev/null +++ b/test/inverted_minor_glitches/TestInvertedDeathMountain.py @@ -0,0 +1,229 @@ +from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor + + +class TestInvertedDeathMountain(TestInvertedMinor): + + def testWestDeathMountain(self): + self.run_location_tests([ + ["Old Man", False, []], + ["Old Man", False, [], ['Progressive Glove', 'Flute']], + ["Old Man", False, [], ['Lamp']], + ["Old Man", True, ['Progressive Glove', 'Lamp']], + ["Old Man", False, ['Flute', 'Lamp']], + + ["Spectacle Rock Cave", False, []], + ["Spectacle Rock Cave", False, [], ['Progressive Glove', 'Flute']], + ["Spectacle Rock Cave", False, [], ['Lamp', 'Flute']], + ["Spectacle Rock Cave", False, ['Flute', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock Cave", False, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock Cave", False, ['Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Spectacle Rock Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Spectacle Rock Cave", True, ['Progressive Glove', 'Lamp']], + ]) + + def testEastDeathMountain(self): + self.run_location_tests([ + ["Spiral Cave", False, []], + ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], + ["Spiral Cave", False, [], ['Lamp', 'Flute']], + ["Spiral Cave", False, ['Progressive Glove'], ['Hookshot', 'Progressive Glove']], + ["Spiral Cave", False, ['Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Spiral Cave", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Spiral Cave", False, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Spiral Cave", False, ['Flute', 'Hookshot', 'Moon Pearl']], + ["Spiral Cave", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Spiral Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hookshot']], + ["Spiral Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Spiral Cave", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Far Left", False, []], + ["Paradox Cave Lower - Far Left", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Far Left", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Far Left", False, [], ['Lamp', 'Flute']], + ["Paradox Cave Lower - Far Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Left", False, []], + ["Paradox Cave Lower - Left", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Left", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Left", False, [], ['Lamp', 'Flute']], + ["Paradox Cave Lower - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Middle", False, []], + ["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Middle", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Middle", False, [], ['Lamp', 'Flute']], + ["Paradox Cave Lower - Middle", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Right", False, []], + ["Paradox Cave Lower - Right", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Right", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Right", False, [], ['Lamp', 'Flute']], + ["Paradox Cave Lower - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Far Right", False, []], + ["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Far Right", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Far Right", False, [], ['Lamp', 'Flute']], + ["Paradox Cave Lower - Far Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Upper - Left", False, []], + ["Paradox Cave Upper - Left", False, [], ['Moon Pearl']], + ["Paradox Cave Upper - Left", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Upper - Left", False, [], ['Lamp', 'Flute']], + ["Paradox Cave Upper - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Flute', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Upper - Right", False, []], + ["Paradox Cave Upper - Right", False, [], ['Moon Pearl']], + ["Paradox Cave Upper - Right", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Upper - Right", False, [], ['Lamp', 'Flute']], + ["Paradox Cave Upper - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Flute', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Mimic Cave", False, []], + ["Mimic Cave", False, [], ['Moon Pearl']], + ["Mimic Cave", False, [], ['Hammer']], + ["Mimic Cave", False, [], ['Progressive Glove', 'Flute']], + ["Mimic Cave", False, [], ['Lamp', 'Flute']], + ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Mimic Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], + + ["Ether Tablet", False, []], + ["Ether Tablet", False, [], ['Moon Pearl']], + ["Ether Tablet", False, [], ['Progressive Glove', 'Flute']], + ["Ether Tablet", False, [], ['Lamp', 'Flute']], + ["Ether Tablet", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Ether Tablet", False, [], ['Hammer']], + ["Ether Tablet", False, ['Progressive Sword'], ['Progressive Sword']], + ["Ether Tablet", False, [], ['Book of Mudora']], + ["Ether Tablet", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + + ["Spectacle Rock", False, []], + ["Spectacle Rock", False, [], ['Moon Pearl']], + ["Spectacle Rock", False, [], ['Progressive Glove', 'Flute']], + ["Spectacle Rock", False, [], ['Lamp', 'Flute']], + ["Spectacle Rock", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Spectacle Rock", False, [], ['Hammer']], + ["Spectacle Rock", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Hammer', 'Hookshot']], + ["Spectacle Rock", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Spectacle Rock", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], + ]) + + def testEastDarkWorldDeathMountain(self): + self.run_location_tests([ + ["Superbunny Cave - Top", False, []], + ["Superbunny Cave - Top", False, [], ['Progressive Glove', 'Flute']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Lamp']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Flute']], + ["Superbunny Cave - Top", True, ['Hammer', 'Progressive Glove', 'Moon Pearl', 'Flute']], + + ["Superbunny Cave - Bottom", False, []], + ["Superbunny Cave - Bottom", False, [], ['Progressive Glove', 'Flute']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Lamp']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Flute']], + ["Superbunny Cave - Bottom", True, ['Hammer', 'Progressive Glove', 'Moon Pearl', 'Flute']], + + ["Hookshot Cave - Bottom Right", False, []], + ["Hookshot Cave - Bottom Right", False, [], ['Progressive Glove', 'Flute']], + ["Hookshot Cave - Bottom Right", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Lamp', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Flute', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Flute', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Flute', 'Hookshot']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Flute', 'Hookshot']], + + ["Hookshot Cave - Bottom Left", False, []], + ["Hookshot Cave - Bottom Left", False, [], ['Progressive Glove', 'Flute']], + ["Hookshot Cave - Bottom Left", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Flute', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Flute', 'Hookshot']], + + ["Hookshot Cave - Top Left", False, []], + ["Hookshot Cave - Top Left", False, [], ['Progressive Glove', 'Flute']], + ["Hookshot Cave - Top Left", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Flute', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Flute', 'Hookshot']], + + ["Hookshot Cave - Top Right", False, []], + ["Hookshot Cave - Top Right", False, [], ['Progressive Glove', 'Flute']], + ["Hookshot Cave - Top Right", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Flute', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Flute', 'Hookshot']], + ]) + + def testWestDarkWorldDeathMountain(self): + self.run_location_tests([ + ["Spike Cave", False, []], + ["Spike Cave", False, [], ['Progressive Glove']], + ["Spike Cave", False, [], ['Hammer']], + ["Spike Cave", False, [], ['Cape', 'Cane of Byrna']], + ["Spike Cave", False, [], ['Cane of Byrna', 'AnyBottle', 'Magic Upgrade (1/2)']], + ["Spike Cave", False, [], ['AnyBottle', 'Magic Upgrade (1/2)', 'Pegasus Boots', 'Boss Heart Container', 'Piece of Heart', 'Sanctuary Heart Container']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + # Change from base ER - this fork places a blue potion in dark world + #["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Cape']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cape']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + #["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Pegasus Boots', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Pegasus Boots', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Boss Heart Container', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Boss Heart Container', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cane of Byrna']], + ]) + diff --git a/test/inverted_minor_glitches/TestInvertedEntrances.py b/test/inverted_minor_glitches/TestInvertedEntrances.py new file mode 100644 index 00000000..29815e3b --- /dev/null +++ b/test/inverted_minor_glitches/TestInvertedEntrances.py @@ -0,0 +1,108 @@ +from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor + + +class TestEntrances(TestInvertedMinor): + + def testDungeonEntrances(self): + self.run_entrance_tests([ + ["Hyrule Castle Entrance (South)", False, []], + ["Hyrule Castle Entrance (South)", False, [], ["Beat Agahnim 1", "Moon Pearl"]], + ["Hyrule Castle Entrance (South)", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Hyrule Castle Entrance (South)", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Hyrule Castle Entrance (South)", True, ["Beat Agahnim 1"]], + ["Hyrule Castle Entrance (South)", True, ["Moon Pearl", "Hammer", "Progressive Glove"]], + ["Hyrule Castle Entrance (South)", True, ["Moon Pearl", "Progressive Glove", "Progressive Glove"]], + + ["Eastern Palace", False, []], + ["Eastern Palace", False, [], ["Beat Agahnim 1", "Moon Pearl"]], + ["Eastern Palace", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Eastern Palace", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Eastern Palace", True, ["Beat Agahnim 1"]], + ["Eastern Palace", True, ["Moon Pearl", "Hammer", "Progressive Glove"]], + ["Eastern Palace", True, ["Moon Pearl", "Progressive Glove", "Progressive Glove"]], + + ["Desert Palace Entrance (South)", False, []], + ["Desert Palace Entrance (South)", False, [], ["Book of Mudora"]], + ["Desert Palace Entrance (South)", False, [], ["Beat Agahnim 1", "Moon Pearl"]], + ["Desert Palace Entrance (South)", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Desert Palace Entrance (South)", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Desert Palace Entrance (South)", True, ["Book of Mudora", "Beat Agahnim 1"]], + ["Desert Palace Entrance (South)", True, ["Book of Mudora", "Moon Pearl", "Hammer", "Progressive Glove"]], + ["Desert Palace Entrance (South)", True, ["Book of Mudora", "Moon Pearl", "Progressive Glove", "Progressive Glove"]], + ["Desert Palace Entrance (North)", False, []], + ["Desert Palace Entrance (North)", False, [], ["Book of Mudora"]], + ["Desert Palace Entrance (North)", False, [], ["Progressive Glove"]], + ["Desert Palace Entrance (North)", False, [], ["Moon Pearl"]], + ["Desert Palace Entrance (North)", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Desert Palace Entrance (North)", True, ["Moon Pearl", "Book of Mudora", "Progressive Glove", "Hammer"]], + ["Desert Palace Entrance (North)", True, ["Moon Pearl", "Book of Mudora", "Progressive Glove", "Progressive Glove"]], + ["Desert Palace Entrance (North)", True, ["Moon Pearl", "Book of Mudora", "Progressive Glove", "Beat Agahnim 1"]], + + ["Tower of Hera", False, []], + ["Tower of Hera", False, [], ["Moon Pearl"]], + ["Tower of Hera", False, [], ["Hammer"]], + ["Tower of Hera", False, ["Progressive Glove"], ["Hookshot", "Progressive Glove"]], + ["Tower of Hera", False, [], ["Flute", "Lamp"]], + ["Tower of Hera", False, [], ["Flute", "Progressive Glove"]], + ["Tower of Hera", True, ["Moon Pearl", "Hammer", "Progressive Glove", "Progressive Glove", "Lamp"]], + ["Tower of Hera", True, ["Moon Pearl", "Hammer", "Hookshot", "Progressive Glove", "Lamp"]], + ["Tower of Hera", True, ["Moon Pearl", "Hammer", "Hookshot", "Progressive Glove", "Flute"]], + ["Tower of Hera", True, ["Moon Pearl", "Hammer", "Beat Agahnim 1", "Flute", "Hookshot"]], + + ["Inverted Agahnims Tower", False, []], + ["Inverted Agahnims Tower", False, [], ["Flute", "Lamp"]], + ["Inverted Agahnims Tower", False, [], ["Flute", "Progressive Glove"]], + ["Inverted Agahnims Tower", False, [], ["Moon Pearl", "Lamp"]], + ["Inverted Agahnims Tower", False, [], ["Moon Pearl", "Progressive Glove"]], + ["Inverted Agahnims Tower", True, ["Lamp", "Progressive Glove"]], + ["Inverted Agahnims Tower", True, ["Flute", "Beat Agahnim 1", "Moon Pearl"]], + ["Inverted Agahnims Tower", True, ["Flute", "Progressive Glove", "Progressive Glove", "Moon Pearl"]], + ["Inverted Agahnims Tower", True, ["Flute", "Progressive Glove", "Hammer", "Moon Pearl"]], + + ["Palace of Darkness", True, []], + + ["Swamp Palace", True, []], + + ["Thieves Town", True, []], + + ["Skull Woods First Section Door", True, []], + + ["Skull Woods Final Section", False, []], + ["Skull Woods Final Section", False, [], ["Fire Rod"]], + ["Skull Woods Final Section", True, ["Fire Rod"]], + + ["Ice Palace", True, []], + + ["Misery Mire", False, []], + ["Misery Mire", False, [], ["Flute", "Magic Mirror"]], + ["Misery Mire", False, [], ["Moon Pearl", "Magic Mirror"]], + ["Misery Mire", False, [], ["Ether"]], + ["Misery Mire", False, [], ["Progressive Sword"]], + ["Misery Mire", True, ["Progressive Sword", "Ether", "Beat Agahnim 1", "Magic Mirror"]], + ["Misery Mire", True, ["Progressive Sword", "Ether", "Beat Agahnim 1", "Moon Pearl", "Flute"]], + ["Misery Mire", True, ["Progressive Sword", "Ether", "Moon Pearl", "Hammer", "Progressive Glove", "Magic Mirror"]], + ["Misery Mire", True, ["Progressive Sword", "Ether", "Moon Pearl", "Hammer", "Progressive Glove", "Flute"]], + ["Misery Mire", True, ["Progressive Sword", "Ether", "Moon Pearl", "Progressive Glove", "Progressive Glove", "Magic Mirror"]], + ["Misery Mire", True, ["Progressive Sword", "Ether", "Moon Pearl", "Progressive Glove", "Progressive Glove", "Flute"]], + + ["Turtle Rock", False, []], + ["Turtle Rock", False, [], ["Quake"]], + ["Turtle Rock", False, [], ["Progressive Sword"]], + ["Turtle Rock", False, [], ["Lamp", "Flute"]], + ["Turtle Rock", False, [], ["Progressive Glove", "Flute"]], + ["Turtle Rock", True, ["Quake", "Progressive Sword", "Progressive Glove", "Lamp"]], + ["Turtle Rock", True, ["Quake", "Progressive Sword", "Progressive Glove", "Progressive Glove", "Moon Pearl", "Flute"]], + ["Turtle Rock", True, ["Quake", "Progressive Sword", "Progressive Glove", "Hammer", "Moon Pearl", "Flute"]], + ["Turtle Rock", True, ["Quake", "Progressive Sword", "Beat Agahnim 1", "Moon Pearl", "Flute"]], + + ["Inverted Ganons Tower", False, []], + ["Inverted Ganons Tower", False, [], ["Crystal 1"]], + ["Inverted Ganons Tower", False, [], ["Crystal 2"]], + ["Inverted Ganons Tower", False, [], ["Crystal 3"]], + ["Inverted Ganons Tower", False, [], ["Crystal 4"]], + ["Inverted Ganons Tower", False, [], ["Crystal 5"]], + ["Inverted Ganons Tower", False, [], ["Crystal 6"]], + ["Inverted Ganons Tower", False, [], ["Crystal 7"]], + ["Inverted Ganons Tower", True, ["Beat Agahnim 1", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ["Inverted Ganons Tower", True, ["Moon Pearl", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ]) \ No newline at end of file diff --git a/test/inverted_minor_glitches/TestInvertedLightWorld.py b/test/inverted_minor_glitches/TestInvertedLightWorld.py new file mode 100644 index 00000000..2366b491 --- /dev/null +++ b/test/inverted_minor_glitches/TestInvertedLightWorld.py @@ -0,0 +1,376 @@ +from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor + + +class TestInvertedLightWorld(TestInvertedMinor): + def setUp(self): + super().setUp() + + def testLostWoods(self): + self.run_location_tests([ + ["Master Sword Pedestal", False, []], + ["Master Sword Pedestal", False, [], ['Green Pendant']], + ["Master Sword Pedestal", False, [], ['Red Pendant']], + ["Master Sword Pedestal", False, [], ['Blue Pendant']], + ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1']], + ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mushroom", False, []], + ["Mushroom", False, [], ['Moon Pearl']], + ["Mushroom", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mushroom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mushroom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Lost Woods Hideout", False, []], + ["Lost Woods Hideout", False, [], ['Moon Pearl']], + ["Lost Woods Hideout", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Lost Woods Hideout", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Lost Woods Hideout", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Lumberjack Tree", False, []], + ["Lumberjack Tree", False, [], ['Pegasus Boots']], + ["Lumberjack Tree", False, [], ['Beat Agahnim 1']], + ["Lumberjack Tree", False, [], ['Moon Pearl']], + ["Lumberjack Tree", True, ['Pegasus Boots', 'Beat Agahnim 1', 'Moon Pearl']], + ]) + + def testKakariko(self): + self.run_location_tests([ + ["Kakariko Tavern", False, []], + ["Kakariko Tavern", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Tavern", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Tavern", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Chicken House", False, []], + ["Chicken House", False, [], ['Moon Pearl']], + ["Chicken House", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + # top can't be bombed as super bunny and needs Moon Pearl + ["Kakariko Well - Top", False, []], + ["Kakariko Well - Top", False, [], ['Moon Pearl']], + ["Kakariko Well - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Left", False, []], + ["Kakariko Well - Left", True, ['Beat Agahnim 1']], + ["Kakariko Well - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Middle", False, []], + ["Kakariko Well - Middle", True, ['Beat Agahnim 1']], + ["Kakariko Well - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Right", False, []], + ["Kakariko Well - Right", True, ['Beat Agahnim 1']], + ["Kakariko Well - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Bottom", False, []], + ["Kakariko Well - Bottom", True, ['Beat Agahnim 1']], + ["Kakariko Well - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Top", False, []], + ["Blind's Hideout - Top", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Blind's Hideout - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Left", False, []], + ["Blind's Hideout - Left", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Blind's Hideout - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Right", False, []], + ["Blind's Hideout - Right", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Blind's Hideout - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Far Left", False, []], + ["Blind's Hideout - Far Left", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Blind's Hideout - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Far Right", False, []], + ["Blind's Hideout - Far Right", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Blind's Hideout - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Far Right", True, ['Magic Mirror', 'Beat Agahnim 1']], + ["Blind's Hideout - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Bottle Merchant", False, []], + ["Bottle Merchant", True, ['Beat Agahnim 1']], + ["Bottle Merchant", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Bottle Merchant", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Sick Kid", False, []], + ["Sick Kid", False, [], ['AnyBottle']], + ["Sick Kid", False, ['Bottle (Bee)']], + ["Sick Kid", False, ['Bottle (Fairy)']], + ["Sick Kid", False, ['Bottle (Red Potion)']], + ["Sick Kid", False, ['Bottle (Green Potion)']], + ["Sick Kid", False, ['Bottle (Blue Potion)']], + ["Sick Kid", False, ['Bottle']], + ["Sick Kid", False, ['Bottle (Good Bee)']], + ["Sick Kid", True, ['Bottle (Bee)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Bee)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Bee)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Fairy)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Fairy)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Fairy)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Red Potion)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Red Potion)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Red Potion)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Green Potion)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Green Potion)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Green Potion)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Blue Potion)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Blue Potion)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Blue Potion)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Good Bee)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Good Bee)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Good Bee)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Magic Bat", False, []], + ["Magic Bat", False, [], ['Magic Powder']], + ["Magic Bat", False, [], ['Hammer']], + ["Magic Bat", False, [], ['Moon Pearl']], + ["Magic Bat", False, ['Magic Powder', 'Hammer', 'Moon Pearl']], + ["Magic Bat", True, ['Magic Powder', 'Hammer', 'Moon Pearl', 'Beat Agahnim 1']], + ["Magic Bat", True, ['Magic Powder', 'Hammer', 'Moon Pearl', 'Progressive Glove']], + + ["Library", False, []], + ["Library", False, [], ['Pegasus Boots']], + ["Library", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], + ["Library", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Library", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Maze Race", False, []], + ["Maze Race", False, [], ['Moon Pearl']], + ["Maze Race", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ]) + + def testSouthLightWorld(self): + self.run_location_tests([ + ["Desert Ledge", False, []], + ["Desert Ledge", False, [], ['Book of Mudora']], + ["Desert Ledge", True, ['Book of Mudora', 'Beat Agahnim 1']], + ["Desert Ledge", True, ['Book of Mudora', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Desert Ledge", True, ['Book of Mudora', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Checkerboard Cave", False, []], + ["Checkerboard Cave", False, [], ['Progressive Glove']], + ["Checkerboard Cave", False, [], ['Moon Pearl']], + ["Checkerboard Cave", True, ['Progressive Glove', 'Beat Agahnim 1', 'Moon Pearl']], + ["Checkerboard Cave", True, ['Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Checkerboard Cave", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Aginah's Cave", False, []], + ["Aginah's Cave", False, [], ['Moon Pearl']], + ["Aginah's Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Bombos Tablet", False, []], + ["Bombos Tablet", False, ['Progressive Sword'], ['Progressive Sword']], + ["Bombos Tablet", False, [], ['Book of Mudora']], + ["Bombos Tablet", False, [], ['Moon Pearl', 'Beat Agahnim 1']], + ["Bombos Tablet", True, ['Beat Agahnim 1', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Hammer', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Book of Mudora', 'Beat Agahnim 1', 'Progressive Sword', 'Progressive Sword']], + + ["Floodgate Chest", False, []], + ["Floodgate Chest", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Floodgate Chest", True, ['Magic Mirror', 'Beat Agahnim 1']], + ["Floodgate Chest", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Floodgate Chest", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Floodgate Chest", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Sunken Treasure", False, []], + ["Sunken Treasure", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Sunken Treasure", True, ['Magic Mirror', 'Beat Agahnim 1']], + ["Sunken Treasure", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sunken Treasure", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sunken Treasure", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Far Left", False, []], + ["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Left", False, []], + ["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Generous Guy", False, []], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Right", False, []], + ["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Far Right", False, []], + ["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Ice Rod Cave", False, []], + ["Ice Rod Cave", False, [], ['Moon Pearl']], + ["Ice Rod Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ]) + + def testZoraArea(self): + self.run_location_tests([ + ["King Zora", False, []], + ["King Zora", False, [], ['Moon Pearl']], + ["King Zora", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["King Zora", True, ['Progressive Glove', 'Moon Pearl', 'Hammer']], + ["King Zora", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Zora's Ledge", False, []], + ["Zora's Ledge", False, [], ['Flippers']], + ["Zora's Ledge", False, [], ['Moon Pearl']], + ["Zora's Ledge", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["Zora's Ledge", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Zora's Ledge", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Waterfall Fairy - Left", False, []], + ["Waterfall Fairy - Left", False, [], ['Flippers']], + ["Waterfall Fairy - Left", False, [], ['Moon Pearl']], + ["Waterfall Fairy - Left", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["Waterfall Fairy - Left", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Waterfall Fairy - Left", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Waterfall Fairy - Right", False, []], + ["Waterfall Fairy - Right", False, [], ['Flippers']], + ["Waterfall Fairy - Right", False, [], ['Moon Pearl']], + ["Waterfall Fairy - Right", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["Waterfall Fairy - Right", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Waterfall Fairy - Right", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ]) + + def testLightWorld(self): + self.run_location_tests([ + ["Link's Uncle", False, []], + ["Link's Uncle", False, [], ['Moon Pearl']], + ["Link's Uncle", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Link's Uncle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Link's Uncle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Secret Passage", False, []], + ["Secret Passage", False, [], ['Moon Pearl']], + ["Secret Passage", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Secret Passage", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Secret Passage", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["King's Tomb", False, []], + ["King's Tomb", False, [], ['Pegasus Boots']], + ["King's Tomb", False, ['Progressive Glove'], ['Progressive Glove']], + ["King's Tomb", False, [], ['Moon Pearl']], + ["King's Tomb", True, ['Pegasus Boots', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Sahasrahla's Hut - Left", False, []], + ["Sahasrahla's Hut - Left", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + # super bunny bonk + ["Sahasrahla's Hut - Left", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']], + + ["Sahasrahla's Hut - Middle", False, []], + ["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + # super bunny bonk + ["Sahasrahla's Hut - Middle", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']], + + ["Sahasrahla's Hut - Right", False, []], + ["Sahasrahla's Hut - Right", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + # super bunny bonk + ["Sahasrahla's Hut - Right", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']], + + ["Sahasrahla", False, []], + ["Sahasrahla", False, [], ['Green Pendant']], + ["Sahasrahla", True, ['Green Pendant', 'Beat Agahnim 1']], + ["Sahasrahla", True, ['Green Pendant', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla", True, ['Green Pendant', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Bonk Rock Cave", False, []], + ["Bonk Rock Cave", False, [], ['Pegasus Boots']], + ["Bonk Rock Cave", False, [], ['Moon Pearl']], + ["Bonk Rock Cave", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], + ["Bonk Rock Cave", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Bonk Rock Cave", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Hobo", False, []], + ["Hobo", False, [], ['Moon Pearl']], + ["Hobo", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Hobo", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hobo", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Cave 45", False, []], + ["Cave 45", False, [], ['Moon Pearl', 'Magic Mirror']], + ["Cave 45", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Cave 45", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Cave 45", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Cave 45", True, ['Magic Mirror', 'Beat Agahnim 1']], + + ["Graveyard Cave", False, []], + ["Graveyard Cave", False, [], ['Moon Pearl']], + ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + + ["Potion Shop", False, []], + ["Potion Shop", False, [], ['Mushroom']], + ["Potion Shop", False, [], ['Moon Pearl']], + ["Potion Shop", True, ['Mushroom', 'Moon Pearl', 'Beat Agahnim 1']], + ["Potion Shop", True, ['Mushroom', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Potion Shop", True, ['Mushroom', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Lake Hylia Island", False, []], + ["Lake Hylia Island", False, [], ['Moon Pearl']], + ["Lake Hylia Island", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Lake Hylia Island", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Lake Hylia Island", True, ['Moon Pearl', 'Beat Agahnim 1']], + + ["Flute Spot", False, []], + ["Flute Spot", False, [], ['Shovel']], + ["Flute Spot", False, [], ['Moon Pearl']], + ["Flute Spot", True, ['Shovel', 'Moon Pearl', 'Beat Agahnim 1']], + ["Flute Spot", True, ['Shovel', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Flute Spot", True, ['Shovel', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Ganon", False, []], + ["Ganon", False, [], ['Moon Pearl']], + ["Ganon", False, [], ['Beat Agahnim 2']], + ]) \ No newline at end of file diff --git a/test/inverted_minor_glitches/TestInvertedMinor.py b/test/inverted_minor_glitches/TestInvertedMinor.py new file mode 100644 index 00000000..30f27dce --- /dev/null +++ b/test/inverted_minor_glitches/TestInvertedMinor.py @@ -0,0 +1,28 @@ +from BaseClasses import World +from Dungeons import create_dungeons, get_dungeon_item_pool +from EntranceShuffle import link_inverted_entrances +from InvertedRegions import create_inverted_regions +from ItemPool import generate_itempool, difficulties +from Items import ItemFactory +from Regions import mark_light_world_regions, create_shops +from Rules import set_rules +from test.TestBase import TestBase + + +class TestInvertedMinor(TestBase): + def setUp(self): + self.world = World(1, {1:'vanilla'}, {1:'minorglitches'}, {1:'inverted'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, + True, {1:False}, False, None, {1:False}) + self.world.difficulty_requirements[1] = difficulties['normal'] + create_inverted_regions(self.world, 1) + create_dungeons(self.world, 1) + create_shops(self.world, 1) + link_inverted_entrances(self.world, 1) + generate_itempool(self.world, 1) + self.world.required_medallions[1] = ['Ether', 'Quake'] + self.world.itempool.extend(get_dungeon_item_pool(self.world)) + self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.world.get_location('Agahnim 1', 1).item = None + self.world.get_location('Agahnim 2', 1).item = None + mark_light_world_regions(self.world, 1) + set_rules(self.world, 1) diff --git a/test/inverted_minor_glitches/TestInvertedTurtleRock.py b/test/inverted_minor_glitches/TestInvertedTurtleRock.py new file mode 100644 index 00000000..74a2b06f --- /dev/null +++ b/test/inverted_minor_glitches/TestInvertedTurtleRock.py @@ -0,0 +1,208 @@ +from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor + + +class TestInvertedTurtleRock(TestInvertedMinor): + + def testTurtleRock(self): + self.run_location_tests([ + ["Turtle Rock - Compass Chest", False, []], + ["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']], + ["Turtle Rock - Compass Chest", False, [], ['Quake', 'Magic Mirror']], + ["Turtle Rock - Compass Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Chain Chomps", False, []], + ["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']], + # Item rando only needs 1 key. ER needs to consider the case when the back is accessible, but not the middle (key wasted on Trinexx door) + ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], + ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot']], + ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + + ["Turtle Rock - Roller Room - Left", False, []], + ["Turtle Rock - Roller Room - Left", False, [], ['Cane of Somaria']], + ["Turtle Rock - Roller Room - Left", False, [], ['Fire Rod']], + ["Turtle Rock - Roller Room - Left", False, [], ['Quake', 'Magic Mirror']], + ["Turtle Rock - Roller Room - Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Roller Room - Right", False, []], + ["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']], + ["Turtle Rock - Roller Room - Right", False, [], ['Fire Rod']], + ["Turtle Rock - Roller Room - Right", False, [], ['Quake', 'Magic Mirror']], + ["Turtle Rock - Roller Room - Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Big Chest", False, []], + ["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hookshot']], + + ["Turtle Rock - Big Key Chest", False, []], + ["Turtle Rock - Big Key Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + # Mirror in from ledge, use left side entrance, have enough keys to get to the chest + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Crystaroller Room", False, []], + ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']], + ["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria']], + + ["Turtle Rock - Eye Bridge - Bottom Left", False, []], + ["Turtle Rock - Eye Bridge - Bottom Left", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + # Mirroring into Eye Bridge does not require Cane of Somaria + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Eye Bridge - Bottom Right", False, []], + ["Turtle Rock - Eye Bridge - Bottom Right", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Eye Bridge - Top Left", False, []], + ["Turtle Rock - Eye Bridge - Top Left", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Top Left", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Top Left", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Top Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Eye Bridge - Top Right", False, []], + ["Turtle Rock - Eye Bridge - Top Right", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Top Right", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Top Right", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Top Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Boss", False, []], + ["Turtle Rock - Boss", False, [], ['Cane of Somaria']], + ["Turtle Rock - Boss", False, [], ['Ice Rod']], + ["Turtle Rock - Boss", False, [], ['Fire Rod']], + ["Turtle Rock - Boss", False, [], ['Progressive Sword', 'Hammer']], + ["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Moon Pearl', 'Flute', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Beat Agahnim 1', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] + ]) \ No newline at end of file diff --git a/test/inverted_minor_glitches/__init__.py b/test/inverted_minor_glitches/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/inverted_owg/TestDarkWorld.py b/test/inverted_owg/TestDarkWorld.py index 89dff120..753f7d65 100644 --- a/test/inverted_owg/TestDarkWorld.py +++ b/test/inverted_owg/TestDarkWorld.py @@ -37,11 +37,11 @@ class TestDarkWorld(TestInvertedOWG): ["Blacksmith", False, []], ["Blacksmith", True, ['Magic Mirror', 'Pegasus Boots']], - ["Blacksmith", True, ['Progressive Glove', 'Progressive Glove', 'Pegasus Boots', 'Moon Pearl']], + ["Blacksmith", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Purple Chest", False, []], ["Purple Chest", True, ['Magic Mirror', 'Pegasus Boots']], - ["Purple Chest", True, ['Progressive Glove', 'Progressive Glove', 'Pegasus Boots', 'Moon Pearl']], + ["Purple Chest", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ]) def testEastDarkWorld(self): @@ -49,11 +49,7 @@ class TestDarkWorld(TestInvertedOWG): ["Catfish", False, []], ["Catfish", True, ['Pegasus Boots']], - #todo: Qirn Jump - #["Pyramid", True, []], - ["Pyramid", False, []], - ["Pyramid", True, ['Pegasus Boots']], - ["Pyramid", True, ['Flippers']], + ["Pyramid", True, []], ["Pyramid Fairy - Left", False, []], ["Pyramid Fairy - Left", False, [], ['Magic Mirror']], diff --git a/test/inverted_owg/TestDeathMountain.py b/test/inverted_owg/TestDeathMountain.py index 093331c2..a0787968 100644 --- a/test/inverted_owg/TestDeathMountain.py +++ b/test/inverted_owg/TestDeathMountain.py @@ -73,14 +73,19 @@ class TestDeathMountain(TestInvertedOWG): ["Spike Cave", False, [], ['Progressive Glove']], ["Spike Cave", False, [], ['Hammer']], ["Spike Cave", False, [], ['Cape', 'Cane of Byrna']], - # ER doesn't put in an extra potion - #["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Pegasus Boots', 'Cape']], - ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Pegasus Boots', 'Cape', 'Moon Pearl']], - ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Pegasus Boots', 'Cane of Byrna']], - ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Pegasus Boots', 'Cape']], - ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Pegasus Boots', 'Cane of Byrna']], - ["Spike Cave", True, ['Magic Upgrade (1/4)', 'Hammer', 'Progressive Glove', 'Pegasus Boots', 'Cape']], - ["Spike Cave", True, ['Magic Upgrade (1/4)', 'Hammer', 'Progressive Glove', 'Pegasus Boots', 'Cane of Byrna']], + ["Spike Cave", False, [], ['Cane of Byrna', 'AnyBottle', 'Magic Upgrade (1/2)']], + ["Spike Cave", False, [], ['AnyBottle', 'Magic Upgrade (1/2)', 'Pegasus Boots', 'Boss Heart Container', 'Piece of Heart', 'Sanctuary Heart Container']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + # Change from base ER - this fork places a blue potion in dark world + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Pegasus Boots', 'Hammer', 'Progressive Glove', 'Cane of Byrna']], + ["Spike Cave", True, ['Boss Heart Container', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Boss Heart Container', 'Hammer', 'Progressive Glove', 'Flute', 'Moon Pearl', 'Cane of Byrna']], ]) def testEastDarkWorldDeathMountain(self): diff --git a/test/inverted_owg/TestDungeons.py b/test/inverted_owg/TestDungeons.py index 3cc7bb37..abd9d193 100644 --- a/test/inverted_owg/TestDungeons.py +++ b/test/inverted_owg/TestDungeons.py @@ -37,7 +37,7 @@ class TestDungeons(TestInvertedOWG): ["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']], ["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']], ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Lamp']], - ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']], ["Tower of Hera - Basement Cage", False, []], ["Tower of Hera - Basement Cage", False, [], ['Moon Pearl']], @@ -48,13 +48,8 @@ class TestDungeons(TestInvertedOWG): ["Castle Tower - Room 03", True, ['Pegasus Boots', 'Progressive Sword']], ["Castle Tower - Room 03", True, ['Pegasus Boots', 'Progressive Bow']], - #todo: Qirn Jump - #["Palace of Darkness - Shooter Room", True, []], - ["Palace of Darkness - Shooter Room", True, ['Pegasus Boots']], - ["Palace of Darkness - Shooter Room", True, ['Hammer']], - ["Palace of Darkness - Shooter Room", True, ['Flippers']], - ["Palace of Darkness - Shooter Room", True, ['Pegasus Boots', 'Progressive Glove']], - ["Palace of Darkness - Shooter Room", True, ['Pegasus Boots', 'Magic Mirror']], + # Qirn Jump + ["Palace of Darkness - Shooter Room", True, []], ["Swamp Palace - Entrance", False, []], ["Swamp Palace - Entrance", False, [], ['Magic Mirror']], @@ -78,11 +73,9 @@ class TestDungeons(TestInvertedOWG): ["Ice Palace - Compass Chest", False, []], ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos', 'Progressive Sword']], - #todo: Qirn Jump - #["Ice Palace - Compass Chest", True, ['Fire Rod']], - #["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']], - ["Ice Palace - Compass Chest", True, ['Pegasus Boots', 'Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Pegasus Boots', 'Bombos', 'Progressive Sword']], + # Qirn Jump + ["Ice Palace - Compass Chest", True, ['Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']], ["Misery Mire - Bridge Chest", False, []], ["Misery Mire - Bridge Chest", False, [], ['Ether']], @@ -109,7 +102,7 @@ class TestDungeons(TestInvertedOWG): ["Ganons Tower - Hope Room - Left", False, [], ['Crystal 5']], ["Ganons Tower - Hope Room - Left", False, [], ['Crystal 6']], ["Ganons Tower - Hope Room - Left", False, [], ['Crystal 7']], - ["Ganons Tower - Hope Room - Left", True, ['Beat Agahnim 1', 'Hookshot', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']], - ["Ganons Tower - Hope Room - Left", True, ['Pegasus Boots', 'Magic Mirror', 'Hookshot', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']], - ["Ganons Tower - Hope Room - Left", True, ['Pegasus Boots', 'Moon Pearl', 'Hookshot', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']], + ["Ganons Tower - Hope Room - Left", True, ['Beat Agahnim 1', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']], + ["Ganons Tower - Hope Room - Left", True, ['Pegasus Boots', 'Magic Mirror', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']], + ["Ganons Tower - Hope Room - Left", True, ['Pegasus Boots', 'Moon Pearl', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']], ]) \ No newline at end of file diff --git a/test/inverted_owg/TestLightWorld.py b/test/inverted_owg/TestLightWorld.py index ad833017..848122b3 100644 --- a/test/inverted_owg/TestLightWorld.py +++ b/test/inverted_owg/TestLightWorld.py @@ -23,7 +23,8 @@ class TestLightWorld(TestInvertedOWG): ["King's Tomb", False, []], ["King's Tomb", False, [], ['Pegasus Boots']], ["King's Tomb", False, [], ['Moon Pearl']], - ["King's Tomb", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["King's Tomb", False, ['Magic Mirror']], + ["King's Tomb", True, ['Pegasus Boots', 'Moon Pearl']], ["Floodgate Chest", False, []], ["Floodgate Chest", False, [], ['Moon Pearl', 'Magic Mirror']], diff --git a/test/minor_glitches/TestDarkWorld.py b/test/minor_glitches/TestDarkWorld.py new file mode 100644 index 00000000..10c8f89e --- /dev/null +++ b/test/minor_glitches/TestDarkWorld.py @@ -0,0 +1,164 @@ +from test.minor_glitches.TestMinor import TestMinor + + +class TestDarkWorld(TestMinor): + + def testSouthDarkWorld(self): + self.run_location_tests([ + ["Hype Cave - Top", False, []], + ["Hype Cave - Top", False, [], ['Moon Pearl']], + ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Hype Cave - Middle Right", False, []], + ["Hype Cave - Middle Right", False, [], ['Moon Pearl']], + ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Hype Cave - Middle Left", False, []], + ["Hype Cave - Middle Left", False, [], ['Moon Pearl']], + ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Hype Cave - Bottom", False, []], + ["Hype Cave - Bottom", False, [], ['Moon Pearl']], + ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Hype Cave - Generous Guy", False, []], + ["Hype Cave - Generous Guy", False, [], ['Moon Pearl']], + ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Stumpy", False, []], + ["Stumpy", False, [], ['Moon Pearl']], + ["Stumpy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Stumpy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Stumpy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Stumpy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Stumpy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Digging Game", False, []], + ["Digging Game", False, [], ['Moon Pearl']], + ["Digging Game", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Digging Game", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Digging Game", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Digging Game", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Digging Game", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']] + ]) + + def testWestDarkWorld(self): + self.run_location_tests([ + ["Brewery", False, []], + ["Brewery", False, [], ['Moon Pearl']], + ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["C-Shaped House", False, []], + ["C-Shaped House", False, [], ['Moon Pearl']], + ["C-Shaped House", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["C-Shaped House", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["C-Shaped House", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["C-Shaped House", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Chest Game", False, []], + ["Chest Game", False, [], ['Moon Pearl']], + ["Chest Game", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Chest Game", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Chest Game", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Chest Game", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Peg Cave", False, []], + ["Peg Cave", False, [], ['Moon Pearl']], + ["Peg Cave", False, [], ['Hammer']], + ["Peg Cave", False, [], ['Progressive Glove']], + ["Peg Cave", True, ['Moon Pearl', 'Hammer', 'Progressive Glove', 'Progressive Glove']], + + ["Bumper Cave Ledge", False, []], + ["Bumper Cave Ledge", False, [], ['Moon Pearl']], + ["Bumper Cave Ledge", False, [], ['Cape']], + ["Bumper Cave Ledge", False, [], ['Progressive Glove']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Progressive Glove', 'Progressive Glove']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Progressive Glove', 'Hammer']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + + ["Blacksmith", False, []], + ["Blacksmith", False, [], ['Progressive Glove']], + ["Blacksmith", False, [], ['Moon Pearl']], + ["Blacksmith", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Purple Chest", False, []], + ["Purple Chest", False, [], ['Progressive Glove']], + ["Purple Chest", False, [], ['Moon Pearl']], + ["Purple Chest", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']] + ]) + + def testEastDarkWorld(self): + self.run_location_tests([ + ["Catfish", False, []], + ["Catfish", False, [], ['Progressive Glove']], + ["Catfish", False, [], ['Moon Pearl']], + ["Catfish", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove']], + ["Catfish", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Catfish", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Pyramid", False, []], + ["Pyramid", False, [], ['Beat Agahnim 1', 'Moon Pearl']], + ["Pyramid", True, ['Beat Agahnim 1']], + ["Pyramid", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Pyramid", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Pyramid Fairy - Left", False, []], + ["Pyramid Fairy - Left", False, [], ['Moon Pearl']], + ["Pyramid Fairy - Left", False, [], ['Crystal 5']], + ["Pyramid Fairy - Left", False, [], ['Crystal 6']], + ["Pyramid Fairy - Left", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Hammer']], + ["Pyramid Fairy - Left", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Progressive Glove', 'Hammer']], + ["Pyramid Fairy - Left", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + ["Pyramid Fairy - Left", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot', 'Magic Mirror']], + ["Pyramid Fairy - Left", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Flippers', 'Hookshot', 'Magic Mirror']], + + ["Pyramid Fairy - Right", False, []], + ["Pyramid Fairy - Right", False, [], ['Moon Pearl']], + ["Pyramid Fairy - Right", False, [], ['Crystal 5']], + ["Pyramid Fairy - Right", False, [], ['Crystal 6']], + ["Pyramid Fairy - Right", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Hammer']], + ["Pyramid Fairy - Right", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Progressive Glove', 'Hammer']], + ["Pyramid Fairy - Right", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + ["Pyramid Fairy - Right", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot', 'Magic Mirror']], + ["Pyramid Fairy - Right", True, ['Moon Pearl', 'Crystal 5', 'Crystal 6', 'Beat Agahnim 1', 'Flippers', 'Hookshot', 'Magic Mirror']], + + ["Ganon", False, []], + ["Ganon", False, [], ['Moon Pearl']], + ["Ganon", False, [], ['Beat Agahnim 2']], + ]) + + def testMireArea(self): + self.run_location_tests([ + ["Mire Shed - Left", False, []], + ["Mire Shed - Left", False, [], ['Progressive Glove']], + ["Mire Shed - Left", False, [], ['Flute']], + ["Mire Shed - Left", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove']], + + ["Mire Shed - Right", False, []], + ["Mire Shed - Right", False, [], ['Progressive Glove']], + ["Mire Shed - Right", False, [], ['Flute']], + ["Mire Shed - Right", True, ['Moon Pearl', 'Flute', 'Progressive Glove', 'Progressive Glove']], + ]) diff --git a/test/minor_glitches/TestDeathMountain.py b/test/minor_glitches/TestDeathMountain.py new file mode 100644 index 00000000..14f5dae4 --- /dev/null +++ b/test/minor_glitches/TestDeathMountain.py @@ -0,0 +1,228 @@ +from test.minor_glitches.TestMinor import TestMinor + + +class TestDeathMountain(TestMinor): + + def testWestDeathMountain(self): + self.run_location_tests([ + ["Ether Tablet", False, []], + ["Ether Tablet", False, [], ['Progressive Glove', 'Flute']], + ["Ether Tablet", False, [], ['Lamp', 'Flute']], + ["Ether Tablet", False, [], ['Magic Mirror', 'Hookshot']], + ["Ether Tablet", False, [], ['Magic Mirror', 'Hammer']], + ["Ether Tablet", False, ['Progressive Sword'], ['Progressive Sword']], + ["Ether Tablet", False, [], ['Book of Mudora']], + ["Ether Tablet", True, ['Flute', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Flute', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + + ["Old Man", False, []], + ["Old Man", False, [], ['Progressive Glove', 'Flute']], + ["Old Man", False, [], ['Lamp']], + ["Old Man", True, ['Flute', 'Lamp']], + ["Old Man", True, ['Progressive Glove', 'Lamp']], + + ["Spectacle Rock Cave", False, []], + ["Spectacle Rock Cave", False, [], ['Progressive Glove', 'Flute']], + ["Spectacle Rock Cave", False, [], ['Lamp', 'Flute']], + ["Spectacle Rock Cave", True, ['Flute']], + ["Spectacle Rock Cave", True, ['Progressive Glove', 'Lamp']], + + ["Spectacle Rock", False, []], + ["Spectacle Rock", False, [], ['Progressive Glove', 'Flute']], + ["Spectacle Rock", False, [], ['Lamp', 'Flute']], + ["Spectacle Rock", False, [], ['Magic Mirror']], + ["Spectacle Rock", True, ['Flute', 'Magic Mirror']], + ["Spectacle Rock", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ]) + + def testEastDeathMountain(self): + self.run_location_tests([ + ["Mimic Cave", False, []], + ["Mimic Cave", False, [], ['Quake']], + ["Mimic Cave", False, [], ['Progressive Sword']], + ["Mimic Cave", False, ['Progressive Glove'], ['Progressive Glove']], + ["Mimic Cave", False, [], ['Hammer']], + ["Mimic Cave", False, [], ['Magic Mirror']], + ["Mimic Cave", False, [], ['Moon Pearl']], + ["Mimic Cave", False, [], ['Cane of Somaria']], + ["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], + ["Mimic Cave", True, ['Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Spiral Cave", False, []], + ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], + ["Spiral Cave", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Spiral Cave", False, [], ['Magic Mirror', 'Hookshot']], + ["Spiral Cave", False, [], ['Hammer', 'Hookshot']], + ["Spiral Cave", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Spiral Cave", False, ['Progressive Glove', 'Hookshot']], + ["Spiral Cave", False, ['Flute', 'Magic Mirror']], + ["Spiral Cave", False, ['Flute', 'Hammer']], + ["Spiral Cave", True, ['Flute', 'Hookshot']], + ["Spiral Cave", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Spiral Cave", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Spiral Cave", True, ['Flute', 'Magic Mirror', 'Hammer']], + + ["Paradox Cave Lower - Far Left", False, []], + ["Paradox Cave Lower - Far Left", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Far Left", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Paradox Cave Lower - Far Left", False, [], ['Magic Mirror', 'Hookshot']], + ["Paradox Cave Lower - Far Left", False, [], ['Hammer', 'Hookshot']], + ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Far Left", False, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Far Left", False, ['Flute', 'Hammer']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + + ["Paradox Cave Lower - Left", False, []], + ["Paradox Cave Lower - Left", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Left", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Paradox Cave Lower - Left", False, [], ['Magic Mirror', 'Hookshot']], + ["Paradox Cave Lower - Left", False, [], ['Hammer', 'Hookshot']], + ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Left", False, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Left", False, ['Flute', 'Hammer']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + + ["Paradox Cave Lower - Middle", False, []], + ["Paradox Cave Lower - Middle", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Middle", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Paradox Cave Lower - Middle", False, [], ['Magic Mirror', 'Hookshot']], + ["Paradox Cave Lower - Middle", False, [], ['Hammer', 'Hookshot']], + ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Middle", False, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Middle", False, ['Flute', 'Hammer']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror', 'Hammer']], + + ["Paradox Cave Lower - Right", False, []], + ["Paradox Cave Lower - Right", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Right", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Paradox Cave Lower - Right", False, [], ['Magic Mirror', 'Hookshot']], + ["Paradox Cave Lower - Right", False, [], ['Hammer', 'Hookshot']], + ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Right", False, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Right", False, ['Flute', 'Hammer']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + + ["Paradox Cave Lower - Far Right", False, []], + ["Paradox Cave Lower - Far Right", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Lower - Far Right", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Paradox Cave Lower - Far Right", False, [], ['Magic Mirror', 'Hookshot']], + ["Paradox Cave Lower - Far Right", False, [], ['Hammer', 'Hookshot']], + ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Far Right", False, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Far Right", False, ['Flute', 'Hammer']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + + ["Paradox Cave Upper - Left", False, []], + ["Paradox Cave Upper - Left", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Upper - Left", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Paradox Cave Upper - Left", False, [], ['Magic Mirror', 'Hookshot']], + ["Paradox Cave Upper - Left", False, [], ['Hammer', 'Hookshot']], + ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Upper - Left", False, ['Flute', 'Magic Mirror']], + ["Paradox Cave Upper - Left", False, ['Flute', 'Hammer']], + ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + + ["Paradox Cave Upper - Right", False, []], + ["Paradox Cave Upper - Right", False, [], ['Progressive Glove', 'Flute']], + ["Paradox Cave Upper - Right", False, [], ['Magic Mirror', 'Hammer', 'Hookshot']], + ["Paradox Cave Upper - Right", False, [], ['Magic Mirror', 'Hookshot']], + ["Paradox Cave Upper - Right", False, [], ['Hammer', 'Hookshot']], + ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Upper - Right", False, ['Flute', 'Magic Mirror']], + ["Paradox Cave Upper - Right", False, ['Flute', 'Hammer']], + ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + ]) + + def testWestDarkWorldDeathMountain(self): + self.run_location_tests([ + ["Spike Cave", False, []], + ["Spike Cave", False, [], ['Progressive Glove']], + ["Spike Cave", False, [], ['Moon Pearl']], + ["Spike Cave", False, [], ['Hammer']], + ["Spike Cave", False, [], ['Cape', 'Cane of Byrna']], + ["Spike Cave", True, ['Bottle', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + ["Spike Cave", True, ['Bottle', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Flute', 'Cape']], + ["Spike Cave", True, ['Bottle', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Bottle', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Flute', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Flute', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Flute', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/4)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/4)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Flute', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/4)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/4)', 'Moon Pearl', 'Hammer', 'Progressive Glove', 'Flute', 'Cane of Byrna']], + ]) + + def testEastDarkWorldDeathMountain(self): + self.run_location_tests([ + ["Superbunny Cave - Top", False, []], + ["Superbunny Cave - Top", False, [], ['Progressive Glove']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Flute']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Lamp']], + + ["Superbunny Cave - Bottom", False, []], + ["Superbunny Cave - Bottom", False, [], ['Progressive Glove']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Flute']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Lamp']], + + ["Hookshot Cave - Bottom Right", False, []], + ["Hookshot Cave - Bottom Right", False, [], ['Progressive Glove']], + ["Hookshot Cave - Bottom Right", False, [], ['Moon Pearl']], + ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], + ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Flute', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], + ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Lamp', 'Pegasus Boots']], + + ["Hookshot Cave - Bottom Left", False, []], + ["Hookshot Cave - Bottom Left", False, [], ['Progressive Glove']], + ["Hookshot Cave - Bottom Left", False, [], ['Moon Pearl']], + ["Hookshot Cave - Bottom Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], + ["Hookshot Cave - Bottom Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], + + ["Hookshot Cave - Top Left", False, []], + ["Hookshot Cave - Top Left", False, [], ['Progressive Glove']], + ["Hookshot Cave - Top Left", False, [], ['Moon Pearl']], + ["Hookshot Cave - Top Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], + ["Hookshot Cave - Top Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], + + ["Hookshot Cave - Top Right", False, []], + ["Hookshot Cave - Top Right", False, [], ['Progressive Glove']], + ["Hookshot Cave - Top Right", False, [], ['Moon Pearl']], + ["Hookshot Cave - Top Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], + ["Hookshot Cave - Top Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], + ]) diff --git a/test/minor_glitches/TestEntrances.py b/test/minor_glitches/TestEntrances.py new file mode 100644 index 00000000..828b05b7 --- /dev/null +++ b/test/minor_glitches/TestEntrances.py @@ -0,0 +1,132 @@ +from test.minor_glitches.TestMinor import TestMinor + + +class TestEntrances(TestMinor): + + def testDungeonEntrances(self): + self.run_entrance_tests([ + ["Hyrule Castle Entrance (South)", True, []], + + ["Eastern Palace", True, []], + + ["Desert Palace Entrance (South)", False, []], + ["Desert Palace Entrance (South)", False, [], ["Book of Mudora", "Flute"]], + ["Desert Palace Entrance (South)", False, [], ["Book of Mudora", "Magic Mirror"]], + ["Desert Palace Entrance (South)", False, ["Progressive Glove"], ["Book of Mudora", "Progressive Glove"]], + ["Desert Palace Entrance (South)", True, ["Book of Mudora"]], + ["Desert Palace Entrance (South)", True, ["Flute", "Progressive Glove", "Progressive Glove", "Magic Mirror"]], + ["Desert Palace Entrance (North)", False, []], + ["Desert Palace Entrance (North)", False, [], ["Progressive Glove"]], + ["Desert Palace Entrance (North)", False, [], ["Book of Mudora", "Flute"]], + ["Desert Palace Entrance (North)", False, [], ["Book of Mudora", "Magic Mirror"]], + ["Desert Palace Entrance (North)", True, ["Book of Mudora", "Progressive Glove"]], + ["Desert Palace Entrance (North)", True, ["Flute", "Progressive Glove", "Progressive Glove", "Magic Mirror"]], + + ["Tower of Hera", False, []], + ["Tower of Hera", False, [], ["Flute", "Progressive Glove"]], + ["Tower of Hera", False, [], ["Flute", "Lamp"]], + ["Tower of Hera", False, [], ["Magic Mirror", "Hammer"]], + ["Tower of Hera", False, [], ["Magic Mirror", "Hookshot"]], + ["Tower of Hera", True, ["Flute", "Magic Mirror"]], + ["Tower of Hera", True, ["Progressive Glove", "Lamp", "Magic Mirror"]], + ["Tower of Hera", True, ["Flute", "Hookshot", "Hammer"]], + ["Tower of Hera", True, ["Progressive Glove", "Lamp", "Magic Mirror"]], + + ["Agahnims Tower", False, []], + ["Agahnims Tower", False, ["Progressive Sword"], ["Cape", "Progressive Sword", "Beat Agahnim 1"]], + ["Agahnims Tower", True, ["Cape"]], + ["Agahnims Tower", True, ["Progressive Sword", "Progressive Sword"]], + ["Agahnims Tower", True, ["Beat Agahnim 1"]], + + ["Palace of Darkness", False, []], + ["Palace of Darkness", False, [], ["Moon Pearl"]], + ["Palace of Darkness", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Palace of Darkness", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Palace of Darkness", True, ["Beat Agahnim 1", "Moon Pearl"]], + ["Palace of Darkness", True, ["Hammer", "Progressive Glove", "Moon Pearl"]], + # village of outcasts portal -> qirn jump + ["Palace of Darkness", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl"]], + + ["Swamp Palace", False, []], + ["Swamp Palace", False, [], ["Moon Pearl"]], + ["Swamp Palace", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Swamp Palace", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Swamp Palace", True, ["Beat Agahnim 1", "Moon Pearl", "Hammer"]], + ["Swamp Palace", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Flippers"]], + ["Swamp Palace", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Progressive Glove"]], + ["Swamp Palace", True, ["Hammer", "Progressive Glove", "Moon Pearl"]], + ["Swamp Palace", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl"]], + + ["Thieves Town", False, []], + ["Thieves Town", False, [], ["Moon Pearl"]], + ["Thieves Town", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Thieves Town", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Thieves Town", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Flippers"]], + ["Thieves Town", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Hammer"]], + ["Thieves Town", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Progressive Glove"]], + ["Thieves Town", True, ["Hammer", "Progressive Glove", "Moon Pearl"]], + ["Thieves Town", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl"]], + + ["Skull Woods First Section Door", False, []], + ["Skull Woods First Section Door", False, [], ["Moon Pearl"]], + ["Skull Woods First Section Door", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Skull Woods First Section Door", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Skull Woods First Section Door", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Flippers"]], + ["Skull Woods First Section Door", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Hammer"]], + ["Skull Woods First Section Door", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Progressive Glove"]], + ["Skull Woods First Section Door", True, ["Hammer", "Progressive Glove", "Moon Pearl"]], + ["Skull Woods First Section Door", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl"]], + + ["Skull Woods Final Section", False, []], + ["Skull Woods Final Section", False, [], ["Moon Pearl"]], + ["Skull Woods Final Section", False, [], ["Fire Rod"]], + ["Skull Woods Final Section", False, [], ["Beat Agahnim 1", "Progressive Glove"]], + ["Skull Woods Final Section", False, ["Progressive Glove"], ["Beat Agahnim 1", "Hammer", "Progressive Glove"]], + ["Skull Woods Final Section", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Flippers", "Fire Rod"]], + ["Skull Woods Final Section", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Hammer", "Fire Rod"]], + ["Skull Woods Final Section", True, ["Beat Agahnim 1", "Moon Pearl", "Hookshot", "Progressive Glove", "Fire Rod"]], + ["Skull Woods Final Section", True, ["Hammer", "Progressive Glove", "Moon Pearl", "Fire Rod"]], + ["Skull Woods Final Section", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl", "Fire Rod"]], + + ["Ice Palace", False, []], + ["Ice Palace", False, ["Progressive Glove"], ["Progressive Glove"]], + ["Ice Palace", True, ["Progressive Glove", "Progressive Glove"]], + + ["Misery Mire", False, []], + ["Misery Mire", False, [], ["Moon Pearl"]], + ["Misery Mire", False, [], ["Flute"]], + ["Misery Mire", False, [], ["Ether"]], + ["Misery Mire", False, [], ["Progressive Sword"]], + ["Misery Mire", False, ["Progressive Glove"], ["Progressive Glove"]], + ["Misery Mire", True, ["Progressive Glove", "Progressive Glove", "Flute", "Moon Pearl", "Ether", "Progressive Sword"]], + + ["Turtle Rock", False, []], + ["Turtle Rock", False, [], ["Moon Pearl"]], + ["Turtle Rock", False, [], ["Hammer"]], + ["Turtle Rock", False, ["Progressive Glove"], ["Progressive Glove"]], + ["Turtle Rock", False, [], ["Quake"]], + ["Turtle Rock", False, [], ["Progressive Sword"]], + ["Turtle Rock", False, [], ["Lamp", "Flute"]], + ["Turtle Rock", False, [], ["Hookshot", "Magic Mirror"]], + ["Turtle Rock", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl", "Hammer", "Quake", "Progressive Sword", "Lamp", "Hookshot"]], + ["Turtle Rock", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl", "Hammer", "Quake", "Progressive Sword", "Lamp", "Magic Mirror"]], + ["Turtle Rock", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl", "Hammer", "Quake", "Progressive Sword", "Flute", "Hookshot"]], + ["Turtle Rock", True, ["Progressive Glove", "Progressive Glove", "Moon Pearl", "Hammer", "Quake", "Progressive Sword", "Flute", "Magic Mirror"]], + + ["Ganons Tower", False, []], + ["Ganons Tower", False, ["Progressive Glove"], ["Progressive Glove"]], + ["Ganons Tower", False, [], ["Lamp", "Flute"]], + ["Ganons Tower", False, [], ["Hookshot", "Hammer"]], + ["Ganons Tower", False, [], ["Hookshot", "Magic Mirror"]], + ["Ganons Tower", False, [], ["Crystal 1"]], + ["Ganons Tower", False, [], ["Crystal 2"]], + ["Ganons Tower", False, [], ["Crystal 3"]], + ["Ganons Tower", False, [], ["Crystal 4"]], + ["Ganons Tower", False, [], ["Crystal 5"]], + ["Ganons Tower", False, [], ["Crystal 6"]], + ["Ganons Tower", False, [], ["Crystal 7"]], + ["Ganons Tower", True, ["Lamp", "Magic Mirror", "Hammer", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ["Ganons Tower", True, ["Lamp", "Hookshot", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ["Ganons Tower", True, ["Flute", "Magic Mirror", "Hammer", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ["Ganons Tower", True, ["Flute", "Hookshot", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ]) diff --git a/test/minor_glitches/TestLightWorld.py b/test/minor_glitches/TestLightWorld.py new file mode 100644 index 00000000..bcabf562 --- /dev/null +++ b/test/minor_glitches/TestLightWorld.py @@ -0,0 +1,192 @@ +from test.minor_glitches.TestMinor import TestMinor + + +class TestLightWorld(TestMinor): + + def testLightWorld(self): + self.run_location_tests([ + ["Master Sword Pedestal", False, []], + ["Master Sword Pedestal", False, [], ['Green Pendant']], + ["Master Sword Pedestal", False, [], ['Red Pendant']], + ["Master Sword Pedestal", False, [], ['Blue Pendant']], + ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant']], + + ["Link's Uncle", True, []], + + ["Secret Passage", True, []], + + ["King's Tomb", False, []], + ["King's Tomb", False, [], ['Pegasus Boots']], + ["King's Tomb", True, ['Pegasus Boots', 'Progressive Glove', 'Progressive Glove']], + ["King's Tomb", True, ['Pegasus Boots', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Magic Mirror']], + ["King's Tomb", True, ['Pegasus Boots', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot', 'Moon Pearl', 'Magic Mirror']], + ["King's Tomb", True, ['Pegasus Boots', 'Beat Agahnim 1', 'Hammer', 'Hookshot', 'Moon Pearl', 'Magic Mirror']], + ["King's Tomb", True, ['Pegasus Boots', 'Beat Agahnim 1', 'Flippers', 'Hookshot', 'Moon Pearl', 'Magic Mirror']], + + ["Floodgate Chest", True, []], + + ["Link's House", True, []], + + ["Kakariko Tavern", True, []], + + ["Chicken House", True, []], + + ["Aginah's Cave", True, []], + + ["Sahasrahla's Hut - Left", True, []], + + ["Sahasrahla's Hut - Middle", True, []], + + ["Sahasrahla's Hut - Right", True, []], + + ["Kakariko Well - Top", True, []], + + ["Kakariko Well - Left", True, []], + + ["Kakariko Well - Middle", True, []], + + ["Kakariko Well - Right", True, []], + + ["Kakariko Well - Bottom", True, []], + + ["Blind's Hideout - Top", True, []], + + ["Blind's Hideout - Left", True, []], + + ["Blind's Hideout - Right", True, []], + + ["Blind's Hideout - Far Left", True, []], + + ["Blind's Hideout - Far Right", True, []], + + ["Bonk Rock Cave", False, []], + ["Bonk Rock Cave", False, [], ['Pegasus Boots']], + ["Bonk Rock Cave", True, ['Pegasus Boots']], + + ["Mini Moldorm Cave - Far Left", True, []], + + ["Mini Moldorm Cave - Left", True, []], + + ["Mini Moldorm Cave - Right", True, []], + + ["Mini Moldorm Cave - Far Right", True, []], + + ["Ice Rod Cave", True, []], + + ["Bottle Merchant", True, []], + + ["Sahasrahla", False, []], + ["Sahasrahla", False, [], ['Green Pendant']], + ["Sahasrahla", True, ['Green Pendant']], + + ["Magic Bat", False, []], + ["Magic Bat", False, [], ['Magic Powder']], + ["Magic Bat", False, [], ['Hammer', 'Magic Mirror']], + ["Magic Bat", False, [], ['Hammer', 'Moon Pearl']], + ["Magic Bat", False, ['Progressive Glove'], ['Hammer', 'Progressive Glove']], + ["Magic Bat", True, ['Magic Powder', 'Hammer']], + ["Magic Bat", True, ['Magic Powder', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Magic Mirror']], + + ["Sick Kid", False, []], + ["Sick Kid", False, [], ['AnyBottle']], + ["Sick Kid", True, ['Bottle (Bee)']], + ["Sick Kid", True, ['Bottle (Fairy)']], + ["Sick Kid", True, ['Bottle (Red Potion)']], + ["Sick Kid", True, ['Bottle (Green Potion)']], + ["Sick Kid", True, ['Bottle (Blue Potion)']], + ["Sick Kid", True, ['Bottle']], + ["Sick Kid", True, ['Bottle (Good Bee)']], + + ["Hobo", True, []], + + ["Bombos Tablet", False, []], + ["Bombos Tablet", False, [], ['Magic Mirror']], + ["Bombos Tablet", False, ['Progressive Sword'], ['Progressive Sword']], + ["Bombos Tablet", False, [], ['Book of Mudora']], + ["Bombos Tablet", False, [], ['Moon Pearl']], + ["Bombos Tablet", True, ['Moon Pearl', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Progressive Glove']], + ["Bombos Tablet", True, ['Moon Pearl', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Hammer']], + ["Bombos Tablet", True, ['Moon Pearl', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword', 'Beat Agahnim 1', 'Hammer']], + ["Bombos Tablet", True, ['Moon Pearl', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Bombos Tablet", True, ['Moon Pearl', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["King Zora", True, []], + + ["Lost Woods Hideout", True, []], + + ["Lumberjack Tree", False, []], + ["Lumberjack Tree", False, [], ['Pegasus Boots']], + ["Lumberjack Tree", False, [], ['Beat Agahnim 1']], + ["Lumberjack Tree", True, ['Pegasus Boots', 'Beat Agahnim 1']], + + ["Cave 45", False, []], + ["Cave 45", False, [], ['Magic Mirror']], + ["Cave 45", False, [], ['Moon Pearl']], + ["Cave 45", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Cave 45", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Cave 45", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer']], + ["Cave 45", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Cave 45", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Graveyard Cave", False, []], + ["Graveyard Cave", False, [], ['Magic Mirror']], + ["Graveyard Cave", False, [], ['Moon Pearl']], + ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']], + ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + + ["Checkerboard Cave", False, []], + ["Checkerboard Cave", False, [], ['Progressive Glove']], + ["Checkerboard Cave", False, [], ['Flute']], + ["Checkerboard Cave", False, [], ['Magic Mirror']], + ["Checkerboard Cave", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Generous Guy", True, []], + + ["Library", False, []], + ["Library", False, [], ['Pegasus Boots']], + ["Library", True, ['Pegasus Boots']], + + ["Mushroom", True, []], + + ["Potion Shop", False, []], + ["Potion Shop", False, [], ['Mushroom']], + ["Potion Shop", True, ['Mushroom']], + + ["Maze Race", True, []], + + ["Desert Ledge", False, []], + ["Desert Ledge", False, [], ['Book of Mudora', 'Flute']], + ["Desert Ledge", False, [], ['Book of Mudora', 'Magic Mirror']], + ["Desert Ledge", False, ['Progressive Glove'], ['Book of Mudora', 'Progressive Glove']], + ["Desert Ledge", True, ['Book of Mudora']], + ["Desert Ledge", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + + ["Lake Hylia Island", False, []], + ["Lake Hylia Island", False, [], ['Magic Mirror']], + ["Lake Hylia Island", False, [], ['Moon Pearl']], + ["Lake Hylia Island", False, [], ['Flippers']], + ["Lake Hylia Island", True, ['Flippers', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Lake Hylia Island", True, ['Flippers', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Lake Hylia Island", True, ['Flippers', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1']], + + ["Sunken Treasure", True, []], + + ["Zora's Ledge", False, []], + ["Zora's Ledge", False, [], ['Flippers']], + ["Zora's Ledge", True, ['Flippers']], + + ["Flute Spot", False, []], + ["Flute Spot", False, [], ['Shovel']], + ["Flute Spot", True, ['Shovel']], + + ["Waterfall Fairy - Left", False, []], + ["Waterfall Fairy - Left", False, [], ['Flippers']], + ["Waterfall Fairy - Left", True, ['Flippers']], + + ["Waterfall Fairy - Right", False, []], + ["Waterfall Fairy - Right", False, [], ['Flippers']], + ["Waterfall Fairy - Right", True, ['Flippers']], + ]) \ No newline at end of file diff --git a/test/minor_glitches/TestMinor.py b/test/minor_glitches/TestMinor.py new file mode 100644 index 00000000..23dd387c --- /dev/null +++ b/test/minor_glitches/TestMinor.py @@ -0,0 +1,28 @@ +from BaseClasses import World +from Dungeons import create_dungeons, get_dungeon_item_pool +from EntranceShuffle import link_entrances +from InvertedRegions import mark_dark_world_regions +from ItemPool import difficulties, generate_itempool +from Items import ItemFactory +from Regions import create_regions, create_shops +from Rules import set_rules +from test.TestBase import TestBase + + +class TestMinor(TestBase): + def setUp(self): + self.world = World(1, {1:'vanilla'}, {1:'minorglitches'}, {1:'open'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, + True, {1:False}, False, None, {1:False}) + self.world.difficulty_requirements[1] = difficulties['normal'] + create_regions(self.world, 1) + create_dungeons(self.world, 1) + create_shops(self.world, 1) + link_entrances(self.world, 1) + generate_itempool(self.world, 1) + self.world.required_medallions[1] = ['Ether', 'Quake'] + self.world.itempool.extend(get_dungeon_item_pool(self.world)) + self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.world.get_location('Agahnim 1', 1).item = None + self.world.get_location('Agahnim 2', 1).item = None + mark_dark_world_regions(self.world, 1) + set_rules(self.world, 1) \ No newline at end of file diff --git a/test/minor_glitches/__init__.py b/test/minor_glitches/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/owg/TestDarkWorld.py b/test/owg/TestDarkWorld.py index 9402f4db..80104379 100644 --- a/test/owg/TestDarkWorld.py +++ b/test/owg/TestDarkWorld.py @@ -77,7 +77,7 @@ class TestDarkWorld(TestVanillaOWG): ["Catfish", True, ['Moon Pearl', 'Pegasus Boots']], ["Catfish", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove']], ["Catfish", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Catfish", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Flippers']], + ["Catfish", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Pyramid", False, []], ["Pyramid", False, [], ['Beat Agahnim 1', 'Moon Pearl', 'Magic Mirror']], @@ -86,7 +86,7 @@ class TestDarkWorld(TestVanillaOWG): ["Pyramid", True, ['Magic Mirror', 'Pegasus Boots']], ["Pyramid", True, ['Beat Agahnim 1']], ["Pyramid", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Pyramid", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Flippers']], + ["Pyramid", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Pyramid Fairy - Left", False, []], ["Pyramid Fairy - Left", False, [], ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], diff --git a/test/owg/TestDeathMountain.py b/test/owg/TestDeathMountain.py index 30c58f85..83dd2c54 100644 --- a/test/owg/TestDeathMountain.py +++ b/test/owg/TestDeathMountain.py @@ -153,27 +153,25 @@ class TestDeathMountain(TestVanillaOWG): ["Superbunny Cave - Top", True, ['Hammer', 'Pegasus Boots']], ["Superbunny Cave - Top", True, ['Moon Pearl', 'Pegasus Boots']], ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], - ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Flute']], + ["Superbunny Cave - Top", True, ['Magic Mirror', 'Flute']], ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], - ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Lamp']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Magic Mirror', 'Lamp']], ["Superbunny Cave - Bottom", False, []], ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Pegasus Boots']], ["Superbunny Cave - Bottom", True, ['Hammer', 'Pegasus Boots']], ["Superbunny Cave - Bottom", True, ['Moon Pearl', 'Pegasus Boots']], ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], - ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Flute']], + ["Superbunny Cave - Bottom", True, ['Magic Mirror', 'Flute']], ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], - ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Lamp']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Magic Mirror', 'Lamp']], ["Hookshot Cave - Bottom Right", False, []], ["Hookshot Cave - Bottom Right", False, [], ['Progressive Glove', 'Pegasus Boots']], ["Hookshot Cave - Bottom Right", False, [], ['Moon Pearl']], ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Pegasus Boots']], ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Flute']], - ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Flute', 'Pegasus Boots']], ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hookshot', 'Lamp']], - ["Hookshot Cave - Bottom Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hammer', 'Lamp', 'Pegasus Boots']], ["Hookshot Cave - Bottom Left", False, []], ["Hookshot Cave - Bottom Left", False, [], ['Progressive Glove', 'Pegasus Boots']], diff --git a/test/owg/TestDungeons.py b/test/owg/TestDungeons.py index de257d91..cb4dc075 100644 --- a/test/owg/TestDungeons.py +++ b/test/owg/TestDungeons.py @@ -50,9 +50,8 @@ class TestDungeons(TestVanillaOWG): ["Palace of Darkness - Shooter Room", True, ['Moon Pearl', 'Pegasus Boots']], ["Palace of Darkness - Shooter Room", True, ['Moon Pearl', 'Beat Agahnim 1']], ["Palace of Darkness - Shooter Room", True, ['Moon Pearl', 'Hammer', 'Progressive Glove']], - #todo: Qirn jump in logic? - #["Palace of Darkness - Shooter Room", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Palace of Darkness - Shooter Room", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Flippers']], + + ["Palace of Darkness - Shooter Room", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Swamp Palace - Entrance", False, []], ["Swamp Palace - Entrance", False, [], ['Magic Mirror']], @@ -105,15 +104,14 @@ class TestDungeons(TestVanillaOWG): #todo: does clip require sword? #["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Sword']], - ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Progressive Sword', 'Quake', 'Hammer']], - ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Progressive Sword', 'Quake']], + ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", False, []], #todo: does clip require sword? #["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots']], ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots', 'Progressive Sword']], - ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror', 'Hammer']], - ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)']], @@ -121,7 +119,7 @@ class TestDungeons(TestVanillaOWG): #["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Progressive Sword']], ["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Hookshot']], - ["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Hammer', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Big Key (Turtle Rock)']], ["Ganons Tower - Hope Room - Left", False, []], ["Ganons Tower - Hope Room - Left", False, ['Moon Pearl', 'Crystal 1']], diff --git a/worlds/alttp/Bosses.py b/worlds/alttp/Bosses.py index f4e7156a..8d3120cb 100644 --- a/worlds/alttp/Bosses.py +++ b/worlds/alttp/Bosses.py @@ -6,14 +6,10 @@ from Fill import FillError def BossFactory(boss: str, player: int) -> Optional[Boss]: - if boss is None: - return None if boss in boss_table: enemizer_name, defeat_rule = boss_table[boss] return Boss(boss, enemizer_name, defeat_rule, player) - - logging.error('Unknown Boss: %s', boss) - return None + raise Exception('Unknown Boss: %s', boss) def ArmosKnightsDefeatRule(state, player: int): @@ -122,16 +118,15 @@ def GanonDefeatRule(state, player: int): state.has_fire_source(player) and \ state.has('Silver Bow', player) and \ state.can_shoot_arrows(player) - easy_hammer = state.world.difficulty_adjustments[player] == "easy" and state.has("Hammer", player) and \ - state.has('Silver Bow', player) and state.can_shoot_arrows(player) - can_hurt = state.has_beam_sword(player) or easy_hammer + + can_hurt = state.has_beam_sword(player) common = can_hurt and state.has_fire_source(player) # silverless ganon may be needed in minor glitches if state.world.logic[player] in {"owglitches", "minorglitches", "none"}: # need to light torch a sufficient amount of times return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or ( state.has('Silver Bow', player) and state.can_shoot_arrows(player)) or - state.has('Lamp', player) or state.can_extend_magic(player, 12)) + state.has('Lamp', player) or state.can_extend_magic(player, 12)) else: return common and state.has('Silver Bow', player) and state.can_shoot_arrows(player) @@ -153,27 +148,33 @@ boss_table = { } -def can_place_boss(world, player: int, boss: str, dungeon_name: str, level: Optional[str] = None) -> bool: - if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'top': - if boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: +def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) -> bool: + # blacklist approach + if boss in {"Agahnim", "Agahnim2", "Ganon"}: + return False + + if dungeon_name == 'Ganons Tower': + if level == 'top': + if boss in {"Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"}: + return False + elif level == 'middle': + if boss == "Blind": + return False + + elif dungeon_name == 'Tower of Hera': + if boss in {"Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"}: return False - if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'middle': - if boss in ["Blind"]: + elif dungeon_name == 'Skull Woods': + if boss == "Trinexx": return False - if dungeon_name == 'Tower of Hera' and boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: - return False - - if dungeon_name == 'Skull Woods' and boss in ["Trinexx"]: - return False - - if boss in ["Agahnim", "Agahnim2", "Ganon"]: - return False return True def place_boss(world, player: int, boss: str, location: str, level: Optional[str]): + if location == 'Ganons Tower' and world.mode[player] == 'inverted': + location = 'Inverted Ganons Tower' logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else '')) world.get_dungeon(location, player).bosses[level] = BossFactory(boss, player) @@ -182,85 +183,112 @@ def place_bosses(world, player: int): if world.boss_shuffle[player] == 'none': return # Most to least restrictive order - if world.mode[player] != 'inverted': - boss_locations = [ - ['Ganons Tower', 'top'], - ['Tower of Hera', None], - ['Skull Woods', None], - ['Ganons Tower', 'middle'], - ['Eastern Palace', None], - ['Desert Palace', None], - ['Palace of Darkness', None], - ['Swamp Palace', None], - ['Thieves Town', None], - ['Ice Palace', None], - ['Misery Mire', None], - ['Turtle Rock', None], - ['Ganons Tower', 'bottom'], - ] - else: - boss_locations = [ - ['Inverted Ganons Tower', 'top'], - ['Tower of Hera', None], - ['Skull Woods', None], - ['Inverted Ganons Tower', 'middle'], - ['Eastern Palace', None], - ['Desert Palace', None], - ['Palace of Darkness', None], - ['Swamp Palace', None], - ['Thieves Town', None], - ['Ice Palace', None], - ['Misery Mire', None], - ['Turtle Rock', None], - ['Inverted Ganons Tower', 'bottom'], - ] + boss_locations = [ + ['Ganons Tower', 'top'], + ['Tower of Hera', None], + ['Skull Woods', None], + ['Ganons Tower', 'middle'], + ['Eastern Palace', None], + ['Desert Palace', None], + ['Palace of Darkness', None], + ['Swamp Palace', None], + ['Thieves Town', None], + ['Ice Palace', None], + ['Misery Mire', None], + ['Turtle Rock', None], + ['Ganons Tower', 'bottom'], + ] all_bosses = sorted(boss_table.keys()) # sorted to be deterministic on older pythons placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']] - anywhere_bosses = [boss for boss in placeable_bosses if all( - can_place_boss(world, player, boss, loc, level) for loc, level in boss_locations)] - if world.boss_shuffle[player] in ["basic", "normal"]: + + shuffle_mode = world.boss_shuffle[player] + already_placed_bosses = [] + if ";" in shuffle_mode: + bosses = shuffle_mode.split(";") + shuffle_mode = bosses.pop() + for boss in bosses: + if "-" in boss: + loc, boss = boss.split("-") + boss = boss.title() + level = None + if loc.split(" ")[-1] in {"top", "middle", "bottom"}: + # split off level + loc = loc.split(" ") + level = loc[-1] + loc = " ".join(loc[:-1]) + loc = loc.title() + if can_place_boss(boss, loc, level) and [loc, level] in boss_locations: + place_boss(world, player, boss, loc, level) + already_placed_bosses.append(boss) + boss_locations.remove([loc, level]) + else: + Exception("Cannot place", boss, "at", loc, level, "for player", player) + else: + boss = boss.title() + boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations) + + if shuffle_mode == "none": + return # vanilla bosses come pre-placed + + if shuffle_mode in ["basic", "normal"]: if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm'] else: # all bosses present, the three duplicates chosen at random bosses = all_bosses + [world.random.choice(placeable_bosses) for _ in range(3)] + # there is probably a better way to do this + while already_placed_bosses: + # remove already manually placed bosses, to prevent for example triple Lanmolas + boss = already_placed_bosses.pop() + if boss in bosses: + bosses.remove(boss) + # there may be more bosses than locations at this point, depending on manual placement + logging.debug('Bosses chosen %s', bosses) world.random.shuffle(bosses) - for [loc, level] in boss_locations: - boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None) + for loc, level in boss_locations: + boss = next((b for b in bosses if can_place_boss(b, loc, level)), None) if not boss: loc_text = loc + (' (' + level + ')' if level else '') raise FillError('Could not place boss for location %s' % loc_text) bosses.remove(boss) place_boss(world, player, boss, loc, level) - elif world.boss_shuffle[player] == "chaos": # all bosses chosen at random - for [loc, level] in boss_locations: + elif shuffle_mode == "chaos": # all bosses chosen at random + for loc, level in boss_locations: try: boss = world.random.choice( - [b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)]) + [b for b in placeable_bosses if can_place_boss(b, loc, level)]) except IndexError: loc_text = loc + (' (' + level + ')' if level else '') raise FillError('Could not place boss for location %s' % loc_text) else: place_boss(world, player, boss, loc, level) - elif world.boss_shuffle[player] == "singularity": + elif shuffle_mode == "singularity": primary_boss = world.random.choice(placeable_bosses) - remaining_boss_locations = [] - for loc, level in boss_locations: - # place that boss where it can go - if can_place_boss(world, player, primary_boss, loc, level): - place_boss(world, player, primary_boss, loc, level) - else: - remaining_boss_locations.append((loc, level)) + remaining_boss_locations, _ = place_where_possible(world, player, primary_boss, boss_locations) if remaining_boss_locations: # pick a boss to go into the remaining locations remaining_boss = world.random.choice([boss for boss in placeable_bosses if all( - can_place_boss(world, player, boss, loc, level) for loc, level in remaining_boss_locations)]) - for loc, level in remaining_boss_locations: - place_boss(world, player, remaining_boss, loc, level) + can_place_boss(boss, loc, level) for loc, level in remaining_boss_locations)]) + remaining_boss_locations, _ = place_where_possible(world, player, remaining_boss, remaining_boss_locations) + if remaining_boss_locations: + raise Exception("Unfilled boss locations!") else: - raise FillError(f"Could not find boss shuffle mode {world.boss_shuffle[player]}") + raise FillError(f"Could not find boss shuffle mode {shuffle_mode}") + + +def place_where_possible(world, player: int, boss: str, boss_locations): + remainder = [] + placed_bosses = [] + for loc, level in boss_locations: + # place that boss where it can go + if can_place_boss(boss, loc, level): + place_boss(world, player, boss, loc, level) + placed_bosses.append(boss) + else: + remainder.append((loc, level)) + return remainder, placed_bosses diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index 758ce0f7..cd85dd36 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -9,7 +9,7 @@ def create_dungeons(world, player): def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items): dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.keyshuffle[player] == "universal" else small_keys, dungeon_items, player) - dungeon.boss = BossFactory(default_boss, player) + dungeon.boss = BossFactory(default_boss, player) if default_boss else None for region in dungeon.regions: world.get_region(region, player).dungeon = dungeon dungeon.world = world diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index 98d6a2ab..ac24c891 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -8,7 +8,7 @@ import shlex import sys from worlds.alttp.Main import main, get_seed -from worlds.alttp.Rom import get_sprite_from_name +from worlds.alttp.Rom import Sprite from Utils import is_bundled, close_console @@ -129,6 +129,14 @@ def parse_arguments(argv, no_defaults=False): Timed mode. If time runs out, you lose (but can still keep playing). ''') + parser.add_argument('--countdown_start_time', default=defval(10), type=int, + help='''Set amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes''') + parser.add_argument('--red_clock_time', default=defval(-2), type=int, + help='''Set amount of time, in minutes, to add from picking up red clocks; negative removes time instead''') + parser.add_argument('--blue_clock_time', default=defval(2), type=int, + help='''Set amount of time, in minutes, to add from picking up blue clocks; negative removes time instead''') + parser.add_argument('--green_clock_time', default=defval(4), type=int, + help='''Set amount of time, in minutes, to add from picking up green clocks; negative removes time instead''') parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'], help='''\ Select dungeon counter display settings. (default: %(default)s) @@ -173,7 +181,7 @@ def parse_arguments(argv, no_defaults=False): slightly biased to placing progression items with less restrictions. ''') - parser.add_argument('--shuffle', default=defval('full'), const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'], + parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'], help='''\ Select Entrance Shuffling Algorithm. (default: %(default)s) Full: Mix cave and dungeon entrances freely while limiting @@ -258,6 +266,8 @@ def parse_arguments(argv, no_defaults=False): help='Specifies a list of items that will be in your starting inventory (separated by commas)') parser.add_argument('--local_items', default=defval(''), help='Specifies a list of items that will not spread across the multiworld (separated by commas)') + parser.add_argument('--non_local_items', default=defval(''), + help='Specifies a list of items that will spread across the multiworld (separated by commas)') parser.add_argument('--custom', default=defval(False), help='Not supported.') parser.add_argument('--customitemarray', default=defval(False), help='Not supported.') parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\ @@ -283,8 +293,13 @@ def parse_arguments(argv, no_defaults=False): ''') parser.add_argument('--heartcolor', default=defval('red'), const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'], help='Select the color of Link\'s heart meter. (default: %(default)s)') - parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout']) - parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout']) + parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick']) + parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick']) + parser.add_argument('--hud_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick']) + parser.add_argument('--shield_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick']) + parser.add_argument('--sword_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick']) + parser.add_argument('--link_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick']) + 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, @@ -334,12 +349,20 @@ def parse_arguments(argv, no_defaults=False): create a binary patch file from which the randomized rom can be recreated using MultiClient.''') parser.add_argument('--disable_glitch_boots', default=defval(False), action='store_true', help='''\ turns off starting with Pegasus Boots in glitched modes.''') - if multiargs.multi: for player in range(1, multiargs.multi + 1): parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS) ret = parser.parse_args(argv) + + # shuffle medallions + + ret.required_medallions = ("random", "random") + # cannot be set through CLI currently + ret.plando_items = [] + ret.plando_texts = {} + ret.plando_connections = [] + ret.glitch_boots = not ret.disable_glitch_boots if ret.timer == "none": ret.timer = False @@ -361,14 +384,18 @@ def parse_arguments(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'crystals_ganon', 'crystals_gt', 'open_pyramid', 'timer', + 'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', - 'local_items', 'retro', 'accessibility', 'hints', 'beemizer', + 'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer', 'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', "skip_progression_balancing", "triforce_pieces_available", - "triforce_pieces_required", "shop_shuffle", + "triforce_pieces_required", "shop_shuffle", "required_medallions", + "plando_items", "plando_texts", "plando_connections", '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']: + 'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', + 'restrict_dungeon_item_on_boss', + '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: setattr(ret, name, {1: value}) diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index 49e6791e..2c85d4bb 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -139,7 +139,7 @@ def link_entrances(world, player): dw_must_exits = list(DW_Entrances_Must_Exit) old_man_entrances = list(Old_Man_Entrances) caves = list(Cave_Exits + Cave_Three_Exits) - single_doors = list(Single_Cave_Doors) + bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors + Bomb_Shop_Multi_Cave_Doors) blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) door_targets = list(Single_Cave_Targets) @@ -2240,6 +2240,20 @@ def unbias_some_entrances(world, Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_ tuplize_lists_in_list(Cave_Three_Exits) +lookup = { + "both": connect_two_way, + "entrance": connect_entrance, + "exit": lambda x, y, z, w: connect_exit(x, z, y, w) +} + + +def plando_connect(world, player: int): + if world.plando_connections[player]: + for connection in world.plando_connections[player]: + func = lookup[connection.direction] + func(world, connection.entrance, connection.exit, player) + + LW_Dungeon_Entrances = ['Desert Palace Entrance (South)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (North)', @@ -2305,10 +2319,10 @@ Cave_Exits_Base = [['Elder House Exit (East)', 'Elder House Exit (West)'], Cave_Exits_Base += [('Superbunny Cave Exit (Bottom)', 'Superbunny Cave Exit (Top)'), ('Spiral Cave Exit (Top)', 'Spiral Cave Exit')] - -Cave_Three_Exits_Base = [('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cave Exit (Top)', - 'Spectacle Rock Cave Exit'), - ['Paradox Cave Exit (Top)', 'Paradox Cave Exit (Middle)','Paradox Cave Exit (Bottom)']] +Cave_Three_Exits_Base = [ + ('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cave Exit (Top)', 'Spectacle Rock Cave Exit'), + ('Paradox Cave Exit (Top)', 'Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Bottom)') +] LW_Entrances = ['Elder House (East)', diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 30894858..da00156f 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -320,8 +320,14 @@ def generate_itempool(world, player: int): world.random.shuffle(nonprogressionitems) # shuffle medallions - mm_medallion = world.random.choice(['Ether', 'Quake', 'Bombos']) - tr_medallion = world.random.choice(['Ether', 'Quake', 'Bombos']) + if world.required_medallions[player][0] == "random": + mm_medallion = world.random.choice(['Ether', 'Quake', 'Bombos']) + else: + mm_medallion = world.required_medallions[player][0] + if world.required_medallions[player][0] == "random": + tr_medallion = world.random.choice(['Ether', 'Quake', 'Bombos']) + else: + tr_medallion = world.required_medallions[player][0] world.required_medallions[player] = (mm_medallion, tr_medallion) place_bosses(world, player) diff --git a/worlds/alttp/Items.py b/worlds/alttp/Items.py index 7cc2826c..c896e483 100644 --- a/worlds/alttp/Items.py +++ b/worlds/alttp/Items.py @@ -225,3 +225,6 @@ for basename, substring in _simple_groups: del (_simple_groups) progression_items = {name for name, data in item_table.items() if type(data[3]) == int and data[0]} +item_name_groups['Everything'] = {name for name, data in item_table.items() if type(data[3]) == int} +item_name_groups['Progression Items'] = progression_items +item_name_groups['Non Progression Items'] = item_name_groups['Everything'] - progression_items diff --git a/worlds/alttp/Main.py b/worlds/alttp/Main.py index ab450064..2e5b6b8e 100644 --- a/worlds/alttp/Main.py +++ b/worlds/alttp/Main.py @@ -71,6 +71,10 @@ def main(args, seed=None): world.tile_shuffle = args.tile_shuffle.copy() world.beemizer = args.beemizer.copy() world.timer = args.timer.copy() + world.countdown_start_time = args.countdown_start_time.copy() + world.red_clock_time = args.red_clock_time.copy() + world.blue_clock_time = args.blue_clock_time.copy() + world.green_clock_time = args.green_clock_time.copy() world.shufflepots = args.shufflepots.copy() world.progressive = args.progressive.copy() world.dungeon_counters = args.dungeon_counters.copy() @@ -82,7 +86,11 @@ def main(args, seed=None): world.shuffle_prizes = args.shuffle_prizes.copy() world.sprite_pool = args.sprite_pool.copy() world.dark_room_logic = args.dark_room_logic.copy() + world.plando_items = args.plando_items.copy() + world.plando_texts = args.plando_texts.copy() + world.plando_connections = args.plando_connections.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)} @@ -105,7 +113,30 @@ def main(args, seed=None): item = ItemFactory(tok.strip(), player) if item: world.push_precollected(item) - world.local_items[player] = {item.strip() for item in args.local_items[player].split(',')} + # item in item_table gets checked in mystery, but not CLI - so we double-check here + world.local_items[player] = {item.strip() for item in args.local_items[player].split(',') if + item.strip() in item_table} + world.non_local_items[player] = {item.strip() for item in args.non_local_items[player].split(',') if + item.strip() in item_table} + # items can't be both local and non-local, prefer local + world.non_local_items[player] -= world.local_items[player] + + # dungeon items can't be in non-local if the appropriate dungeon item shuffle setting is not set. + if not world.mapshuffle[player]: + world.non_local_items[player] -= item_name_groups['Maps'] + + if not world.compassshuffle[player]: + world.non_local_items[player] -= item_name_groups['Compasses'] + + if not world.keyshuffle[player]: + world.non_local_items[player] -= item_name_groups['Small Keys'] + + if not world.bigkeyshuffle[player]: + world.non_local_items[player] -= item_name_groups['Big Keys'] + + # Not possible to place pendants/crystals out side of boss prizes yet. + world.non_local_items[player] -= item_name_groups['Pendants'] + world.non_local_items[player] -= item_name_groups['Crystals'] world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player]) @@ -129,6 +160,7 @@ def main(args, seed=None): else: link_inverted_entrances(world, player) mark_dark_world_regions(world, player) + plando_connect(world, player) logger.info('Generating Item Pool.') @@ -144,11 +176,50 @@ def main(args, seed=None): fill_prizes(world) + logger.info("Running Item Plando") + + world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids} + + for player in world.player_ids: + placement: PlandoItem + for placement in world.plando_items[player]: + target_world: int = placement.world + if target_world is False or world.players == 1: + target_world = player # in own world + elif target_world is True: # in any other world + target_world = player + while target_world == player: + target_world = world.random.randint(1, world.players + 1) + elif target_world is None: # any random world + target_world = world.random.randint(1, world.players + 1) + elif type(target_world) == int: # target world by player id + pass + else: # find world by name + target_world = world_name_lookup[target_world] + + location = world.get_location(placement.location, target_world) + if location.item: + raise Exception(f"Cannot place item into already filled location {location}.") + item = ItemFactory(placement.item, player) + if placement.from_pool: + try: + world.itempool.remove(item) + except ValueError: + logger.warning(f"Could not remove {item} from pool as it's already missing from it.") + + if location.can_fill(world.state, item, False): + world.push_item(location, item, collect=False) + location.event = True # flag location to be checked during fill + location.locked = True + logger.debug(f"Plando placed {item} at {location}") + else: + raise Exception(f"Can't place {item} at {location} due to fill condition not met.") + logger.info('Placing Dungeon Items.') - shuffled_locations = None - if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) + - list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())): + if args.algorithm in ['balanced', 'vt26'] or any( + list(args.mapshuffle.values()) + list(args.compassshuffle.values()) + + list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())): fill_dungeons_restrictive(world) else: fill_dungeons(world) @@ -160,7 +231,7 @@ def main(args, seed=None): elif args.algorithm == 'vt25': distribute_items_restrictive(world, False) elif args.algorithm == 'vt26': - distribute_items_restrictive(world, True, shuffled_locations) + distribute_items_restrictive(world, True) elif args.algorithm == 'balanced': distribute_items_restrictive(world, True) @@ -177,7 +248,7 @@ def main(args, seed=None): use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' or world.shufflepots[player] or world.bush_shuffle[player] - or world.killable_thieves[player] or world.tile_shuffle[player]) + or world.killable_thieves[player]) rom = LocalRom(args.rom) @@ -187,13 +258,22 @@ def main(args, seed=None): patch_enemizer(world, player, rom, args.enemizercli) if args.race: - patch_race_rom(rom) + patch_race_rom(rom, world, player) world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash) + palettes_options={} + palettes_options['dungeon']=args.uw_palettes[player] + palettes_options['overworld']=args.ow_palettes[player] + palettes_options['hud']=args.hud_palettes[player] + palettes_options['sword']=args.sword_palettes[player] + palettes_options['shield']=args.shield_palettes[player] + palettes_options['link']=args.link_palettes[player] + apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], - args.ow_palettes[player], args.uw_palettes[player], world, player, True) + palettes_options, world, player, True) + mcsb_name = '' if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], @@ -287,6 +367,30 @@ def main(args, seed=None): if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name: er_hint_data[region.player][location.address] = main_entrance.name + ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace', + 'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace', + 'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total") + + checks_in_area = {player: {area: list() for area in ordered_areas} + for player in range(1, world.players + 1)} + + for player in range(1, world.players + 1): + checks_in_area[player]["Total"] = 0 + + for location in [loc for loc in world.get_filled_locations() if type(loc.address) is int]: + main_entrance = get_entrance_to_region(location.parent_region) + if location.parent_region.dungeon: + dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', + 'Inverted Ganons Tower': 'Ganons Tower'}\ + .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) + checks_in_area[location.player][dungeonname].append(location.address) + elif main_entrance.parent_region.type == RegionType.LightWorld: + checks_in_area[location.player]["Light World"].append(location.address) + elif main_entrance.parent_region.type == RegionType.DarkWorld: + checks_in_area[location.player]["Dark World"].append(location.address) + checks_in_area[location.player]["Total"] += 1 + + precollected_items = [[] for player in range(world.players)] for item in world.precollected_items: precollected_items[item.player - 1].append(item.code) @@ -297,6 +401,7 @@ def main(args, seed=None): for future in roms: rom_name = future.result() rom_names.append(rom_name) + minimum_versions = {"server": (1, 0, 0)} multidata = zlib.compress(pickle.dumps({"names": parsed_names, "roms": {base64.b64encode(rom_name).decode(): (team, slot) for slot, team, rom_name in rom_names}, "remote_items": {player for player in range(1, world.players + 1) if @@ -306,11 +411,13 @@ def main(args, seed=None): (location.item.code, location.item.player) for location in world.get_filled_locations() if type(location.address) is int}, + "checks_in_area": checks_in_area, "server_options": get_options()["server_options"], "er_hint_data": er_hint_data, "precollected_items": precollected_items, "version": _version_tuple, - "tags": ["AP"] + "tags": ["AP"], + "minimum_versions": minimum_versions, }), 9) with open(output_path('%s.multidata' % outfilebase), 'wb') as f: diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index 58049039..4326e7d1 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -1,4 +1,5 @@ import collections +import typing from BaseClasses import Region, Location, Entrance, RegionType, Shop, TakeAny, UpgradeShop, ShopType @@ -397,250 +398,315 @@ shop_table = { 'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)]) } -location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'), - 'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'), - 'Flute Spot': (0x18014a, 0x18633d, False, 'underground'), - 'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'), - 'Purple Chest': (0x33d68, 0x186359, False, 'from a box'), - "Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'), - "Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'), - "Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'), - "Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'), - "Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'), - "Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'), - 'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'), - 'King Zora': (0xee1c3, 0x186360, False, 'at a high price'), - "Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'), - 'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'), - 'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'), - "King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'), - 'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'), - "Link's House": (0xe9bc, 0x186190, False, 'in your home'), - 'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'), - 'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'), - "Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'), - "Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'), - "Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'), - "Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'), - 'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'), - 'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'), - 'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'), - 'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'), - 'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'), - 'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'), - 'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'), - 'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'), - 'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'), - 'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'), - 'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'), - 'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'), - 'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'), - 'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'), - 'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'), - 'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'), - 'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'), - 'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'), - 'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'), - 'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'), - 'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'), - 'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'), - 'Library': (0x180012, 0x18635c, False, 'near books'), - 'Potion Shop': (0x180014, 0x18635d, False, 'near potions'), - 'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'), - 'Maze Race': (0x180142, 0x186351, False, 'at the race'), - 'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'), - 'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'), - 'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'), - 'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'), - 'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'), - 'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'), - 'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'), - 'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'), - 'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'), - 'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'), - 'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'), - 'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'), - 'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'), - 'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'), - 'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'), - 'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'), - "Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'), - 'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'), - 'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'), - 'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'), - 'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'), - 'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'), - 'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'), - 'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'), - 'Old Man': (0xf69fa, 0x186364, False, 'with the old man'), - 'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'), - 'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'), - 'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'), - 'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'), - 'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'), - 'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'), - 'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'), - 'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'), - 'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'), - 'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'), - 'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'), - 'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'), - 'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'), - 'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'), - 'Catfish': (0xee185, 0x186361, False, 'with a catfish'), - 'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'), - 'Digging Game': (0x180148, 0x186357, False, 'underground'), - 'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'), - 'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'), - 'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'), - 'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'), - 'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'), - 'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'), - 'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'), - 'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'), - 'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'), - 'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'), - 'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'), - 'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'), - 'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'), - 'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'), - 'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'), - 'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'), - 'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'), - 'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'), - 'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'), - 'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'), - 'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'), - 'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'), - 'Floating Island': (0x180141, 0x186350, False, 'on an island'), - 'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'), - 'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'), - 'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'), - 'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'), - 'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'), - 'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'), - 'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'), - 'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'), - 'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'), - 'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'), - 'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'), - "Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"), - "Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"), - "Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"), - "Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"), - "Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"), - "Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"), - "Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"), - "Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'), - 'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'), - 'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'), - 'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'), - 'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'), - 'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'), - 'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'), - 'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'), - 'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'), - 'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'), - 'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'), - 'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'), - 'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'), - 'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'), - 'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'), - 'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'), - 'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'), - 'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'), - 'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'), - 'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'), - 'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'), - 'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'), - 'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'), - 'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'), - 'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'), - 'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'), - 'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'), - 'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'), - 'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'), - 'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'), - 'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'), - 'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'), - 'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'), - 'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'), - 'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'), - 'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'), - 'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'), - 'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'), - 'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'), - 'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'), - 'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'), - 'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'), - 'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'), - "Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"), - 'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"), - 'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"), - 'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"), - 'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"), - 'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"), - 'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"), - 'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"), - 'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"), - 'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"), - 'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"), - 'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"), - 'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"), - 'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"), - 'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"), - 'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"), - 'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"), - 'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"), - "Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"), - 'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"), - 'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"), - 'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"), - 'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"), - 'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"), - 'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"), - 'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"), - 'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"), - 'Ganon': (None, None, False, 'from me'), - 'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'), - 'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'), - 'Floodgate': (None, None, False, None), - 'Frog': (None, None, False, None), - 'Missing Smith': (None, None, False, None), - 'Dark Blacksmith Ruins': (None, None, False, None), - 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'), - 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'), - 'Tower of Hera - Prize': ( - [0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'), - 'Palace of Darkness - Prize': ( - [0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'), - 'Swamp Palace - Prize': ( - [0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'), - 'Thieves\' Town - Prize': ( - [0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'), - 'Skull Woods - Prize': ( - [0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'), - 'Ice Palace - Prize': ( - [0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'), - 'Misery Mire - Prize': ( - [0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'), - 'Turtle Rock - Prize': ( - [0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')} +old_location_address_to_new_location_address = { + 0x2eb18: 0x18001b, # Bottle Merchant + 0x33d68: 0x18001a, # Purple Chest + 0x2df45: 0x18001d, # Link's Uncle + 0x2f1fc: 0x180008, # Sahasrahla + 0x18002a: 0x18001c, # Black Smith + 0x339cf: 0x180009, # Sick Kid + 0x33e7d: 0x180019, # Hobo + 0x180160: 0x18000b, # Desert Palace - Desert Torch + 0x289b0: 0x180018, # Master Sword Pedestal + 0xf69fa: 0x180007, # Old Man + 0x180162: 0x18000d, # Tower of Hera - Basement Cage + 0x330c7: 0x18000a, # Stumpy + 0x180161: 0x18000c # Ganons Tower - Bob's Torch +} + + +key_drop_data = { + 'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037], + 'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034], + 'Hyrule Castle - Key Rat Key Drop': [0x14000c, 0x14000d], + 'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d], + 'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b], + 'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049], + 'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031], + 'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b], + 'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028], + 'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061], + 'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052], + 'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019], + 'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016], + 'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013], + 'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010], + 'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a], + 'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e], + 'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c], + 'Thieves\' Town - Hallway Pot Key': [0x14005d, 0x14005e], + 'Thieves\' Town - Spike Switch Pot Key': [0x14004e, 0x14004f], + 'Ice Palace - Jelly Key Drop': [0x140003, 0x140004], + 'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022], + 'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025], + 'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046], + 'Misery Mire - Spikes Pot Key': [0x140054, 0x140055], + 'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c], + 'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064], + 'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058], + 'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007], + 'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040], + 'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043], + 'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a], + 'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f] +} + +# tuple contents: +# address to write to for item +# address to write to for player getting the item +# can this location drop a crystal +# hint tile/npc text for this location +location_table: typing.Dict[str, + typing.Tuple[typing.Optional[typing.Union[int, typing.List[int]]], + typing.Optional[int], + bool, + typing.Optional[str]]] = \ + {'Mushroom': (0x180013, 0x186338, False, 'in the woods'), + 'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'), + 'Flute Spot': (0x18014a, 0x18633d, False, 'underground'), + 'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'), + 'Purple Chest': (0x33d68, 0x186359, False, 'from a box'), + "Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'), + "Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'), + "Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'), + "Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'), + "Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'), + "Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'), + 'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'), + 'King Zora': (0xee1c3, 0x186360, False, 'at a high price'), + "Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'), + 'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'), + 'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'), + "King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'), + 'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'), + "Link's House": (0xe9bc, 0x186190, False, 'in your home'), + 'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'), + 'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'), + "Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'), + "Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'), + "Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'), + "Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'), + 'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'), + 'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'), + 'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'), + 'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'), + 'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'), + 'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'), + 'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'), + 'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'), + 'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'), + 'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'), + 'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'), + 'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'), + 'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'), + 'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'), + 'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'), + 'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'), + 'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'), + 'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'), + 'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'), + 'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'), + 'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'), + 'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'), + 'Library': (0x180012, 0x18635c, False, 'near books'), + 'Potion Shop': (0x180014, 0x18635d, False, 'near potions'), + 'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'), + 'Maze Race': (0x180142, 0x186351, False, 'at the race'), + 'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'), + 'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'), + 'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'), + 'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'), + 'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'), + 'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'), + 'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'), + 'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'), + 'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'), + 'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'), + 'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'), + 'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'), + 'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'), + 'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'), + 'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'), + 'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'), + "Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'), + 'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'), + 'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'), + 'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'), + 'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'), + 'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'), + 'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'), + 'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'), + 'Old Man': (0xf69fa, 0x186364, False, 'with the old man'), + 'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'), + 'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'), + 'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'), + 'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'), + 'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'), + 'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'), + 'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'), + 'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'), + 'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'), + 'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'), + 'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'), + 'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'), + 'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'), + 'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'), + 'Catfish': (0xee185, 0x186361, False, 'with a catfish'), + 'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'), + 'Digging Game': (0x180148, 0x186357, False, 'underground'), + 'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'), + 'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'), + 'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'), + 'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'), + 'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'), + 'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'), + 'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'), + 'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'), + 'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'), + 'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'), + 'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'), + 'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'), + 'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'), + 'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'), + 'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'), + 'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'), + 'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'), + 'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'), + 'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'), + 'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'), + 'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'), + 'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'), + 'Floating Island': (0x180141, 0x186350, False, 'on an island'), + 'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'), + 'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'), + 'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'), + 'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'), + 'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'), + 'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'), + 'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'), + 'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'), + 'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'), + 'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'), + 'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'), + "Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"), + "Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"), + "Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"), + "Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"), + "Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"), + "Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"), + "Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"), + "Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'), + 'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'), + 'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'), + 'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'), + 'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'), + 'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'), + 'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'), + 'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'), + 'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'), + 'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'), + 'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'), + 'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'), + 'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'), + 'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'), + 'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'), + 'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'), + 'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'), + 'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'), + 'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'), + 'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'), + 'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'), + 'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'), + 'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'), + 'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'), + 'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'), + 'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'), + 'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'), + 'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'), + 'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'), + 'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'), + 'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'), + 'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'), + 'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'), + 'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'), + 'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'), + 'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'), + 'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'), + 'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'), + 'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'), + 'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'), + 'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'), + "Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"), + 'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"), + 'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"), + 'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"), + 'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"), + 'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"), + "Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"), + 'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"), + 'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"), + 'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"), + 'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"), + 'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"), + 'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"), + 'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"), + 'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"), + 'Ganon': (None, None, False, 'from me'), + 'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'), + 'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'), + 'Floodgate': (None, None, False, None), + 'Frog': (None, None, False, None), + 'Missing Smith': (None, None, False, None), + 'Dark Blacksmith Ruins': (None, None, False, None), + 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'), + 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'), + 'Tower of Hera - Prize': ( + [0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'), + 'Palace of Darkness - Prize': ( + [0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'), + 'Swamp Palace - Prize': ( + [0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'), + 'Thieves\' Town - Prize': ( + [0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'), + 'Skull Woods - Prize': ( + [0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'), + 'Ice Palace - Prize': ( + [0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'), + 'Misery Mire - Prize': ( + [0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'), + 'Turtle Rock - Prize': ( + [0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')} lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int} -lookup_id_to_name[-1] = "cheat console" +lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"} +lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int} +lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1} lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks', 1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks', @@ -745,7 +811,28 @@ lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 19125 60103: 'Ganons Tower', 60106: 'Ganons Tower', 60109: 'Ganons Tower', 60127: 'Ganons Tower', 60118: 'Ganons Tower', 60148: 'Ganons Tower', 60151: 'Ganons Tower', 60145: 'Ganons Tower', 60157: 'Ganons Tower', - 60160: 'Ganons Tower', 60163: 'Ganons Tower', 60166: 'Ganons Tower'} + 60160: 'Ganons Tower', 60163: 'Ganons Tower', 60166: 'Ganons Tower', + 0x140037: 'Hyrule Castle Entrance (South)', + 0x140034: 'Hyrule Castle Entrance (South)', + 0x14000d: 'Hyrule Castle Entrance (South)', + 0x14003d: 'Hyrule Castle Entrance (South)', + 0x14005b: 'Eastern Palace', 0x140049: 'Eastern Palace', + 0x140031: 'Desert Palace Entrance (North)', + 0x14002b: 'Desert Palace Entrance (North)', + 0x140028: 'Desert Palace Entrance (North)', + 0x140061: 'Agahnims Tower', 0x140052: 'Agahnims Tower', + 0x140019: 'Swamp Palace', 0x140016: 'Swamp Palace', 0x140013: 'Swamp Palace', + 0x140010: 'Swamp Palace', 0x14000a: 'Swamp Palace', + 0x14002e: 'Skull Woods Second Section Door (East)', + 0x14001c: 'Skull Woods Final Section', + 0x14005e: 'Thieves Town', 0x14004f: 'Thieves Town', + 0x140004: 'Ice Palace', 0x140022: 'Ice Palace', + 0x140025: 'Ice Palace', 0x140046: 'Ice Palace', + 0x140055: 'Misery Mire', 0x14004c: 'Misery Mire', + 0x140064: 'Misery Mire', + 0x140058: 'Turtle Rock', 0x140007: 'Dark Death Mountain Ledge (West)', + 0x140040: 'Ganons Tower', 0x140043: 'Ganons Tower', + 0x14003a: 'Ganons Tower', 0x14001f: 'Ganons Tower'} lookup_prizes = {location for location in location_table if location.endswith(" - Prize")} lookup_boss_drops = {location for location in location_table if location.endswith(" - Boss")} \ No newline at end of file diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 4877118d..f0c497fe 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -1,5 +1,7 @@ +from __future__ import annotations + JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '31d50ce7f1dd3bc33bdc3b2e90f0104e' +RANDOMIZERBASEHASH = '5a607e36a82bbd14180536c8ec3ae49b' import io import json @@ -11,14 +13,16 @@ import struct import sys import subprocess import threading +import xxtea import concurrent.futures from typing import Optional from BaseClasses import CollectionState, ShopType, Region, Location from worlds.alttp.Dungeons import dungeon_music_addresses -from worlds.alttp.Regions import location_table +from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable -from worlds.alttp.Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts +from worlds.alttp.Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, \ + BombShop2_texts, junk_texts from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, \ LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \ @@ -34,17 +38,22 @@ try: except: z3pr = None + class LocalRom(object): - def __init__(self, file, patch=True, name=None, hash=None): + def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): self.name = name self.hash = hash self.orig_buffer = None + with open(file, 'rb') as stream: self.buffer = read_rom(stream) if patch: self.patch_base_rom() self.orig_buffer = self.buffer.copy() + if vanillaRom: + with open(vanillaRom, 'rb') as vanillaStream: + self.orig_buffer = read_rom(vanillaStream) def read_byte(self, address: int) -> int: return self.buffer[address] @@ -58,6 +67,37 @@ class LocalRom(object): def write_bytes(self, startaddress: int, values): self.buffer[startaddress:startaddress + len(values)] = values + def encrypt_range(self, startaddress: int, length: int, key: bytes): + for i in range(0, length, 8): + data = bytes(self.read_bytes(startaddress + i, 8)) + data = xxtea.encrypt(data, key, padding=False) + self.write_bytes(startaddress + i, bytearray(data)) + + def encrypt(self, world, player): + local_random = world.rom_seeds[player] + key = bytes(local_random.getrandbits(8 * 16).to_bytes(16, 'big')) + self.write_bytes(0x1800B0, bytearray(key)) + self.write_int16(0x180087, 1) + + itemtable = [] + locationtable = [] + itemplayertable = [] + for i in range(168): + itemtable.append(self.read_byte(0xE96E + (i * 3))) + itemplayertable.append(self.read_byte(0x186142 + (i * 3))) + locationtable.append(self.read_byte(0xe96C + (i * 3))) + locationtable.append(self.read_byte(0xe96D + (i * 3))) + self.write_bytes(0xE96C, locationtable) + self.write_bytes(0xE96C + 0x150, itemtable) + self.encrypt_range(0xE96C + 0x150, 168, key) + self.write_bytes(0x186140, [0] * 0x150) + self.write_bytes(0x186140 + 0x150, itemplayertable) + self.encrypt_range(0x186140 + 0x150, 168, key) + self.encrypt_range(0x186338, 56, key) + self.encrypt_range(0x180000, 32, key) + self.encrypt_range(0x180140, 32, key) + self.encrypt_range(0xEDA1, 8, key) + def write_to_file(self, file, hide_enemizer=False): with open(file, 'wb') as outfile: outfile.write(self.buffer) @@ -151,7 +191,7 @@ def check_enemizer(enemizercli): version = lib.split("/")[-1] version = tuple(int(element) for element in version.split(".")) logging.debug(f"Found Enemizer version {version}") - if version < (6, 3, 0): + if version < (6, 4, 0): raise Exception( f"Enemizer found at {enemizercli} is outdated ({info}), please update your Enemizer. " f"Such as https://github.com/Ijwu/Enemizer/releases") @@ -190,7 +230,7 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand rom.write_int16(0x18637F, onevent) - sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite, local_random) + sprite = Sprite(sprite) if os.path.isfile(sprite) else Sprite.get_sprite_from_name(sprite, local_random) # write link sprite if required if sprite: @@ -203,7 +243,8 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand if isinstance(sprite_pool, str): sprite_pool = sprite_pool.split(':') for spritename in sprite_pool: - sprite = Sprite(spritename) if os.path.isfile(spritename) else get_sprite_from_name(spritename, local_random) + sprite = Sprite(spritename) if os.path.isfile(spritename) else Sprite.get_sprite_from_name( + spritename, local_random) if sprite: sprites.append(sprite) else: @@ -284,7 +325,7 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli): 'SwordGraphics': "sword_gfx/normal.gfx", 'BeeMizer': False, 'BeesLevel': 0, - 'RandomizeTileTrapPattern': world.tile_shuffle[player], + 'RandomizeTileTrapPattern': False, 'RandomizeTileTrapFloorTile': False, 'AllowKillableThief': world.killable_thieves[player], 'RandomizeSpriteOnHit': False, @@ -309,9 +350,15 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli): 'IcePalace': world.get_dungeon("Ice Palace", player).boss.enemizer_name, 'MiseryMire': world.get_dungeon("Misery Mire", player).boss.enemizer_name, 'TurtleRock': world.get_dungeon("Turtle Rock", player).boss.enemizer_name, - 'GanonsTower1': world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', player).bosses['bottom'].enemizer_name, - 'GanonsTower2': world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', player).bosses['middle'].enemizer_name, - 'GanonsTower3': world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', player).bosses['top'].enemizer_name, + 'GanonsTower1': + world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', + player).bosses['bottom'].enemizer_name, + 'GanonsTower2': + world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', + player).bosses['middle'].enemizer_name, + 'GanonsTower3': + world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', + player).bosses['top'].enemizer_name, 'GanonsTower4': 'Agahnim2', 'Ganon': 'Ganon', } @@ -338,18 +385,19 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli): stderr=subprocess.STDOUT, universal_newlines=True) - logging.debug(f"Enemizer attempt {i + 1} of {max_enemizer_tries} for player {player} using enemizer seed {enemizer_seed}") + logging.debug( + f"Enemizer attempt {i + 1} of {max_enemizer_tries} for player {player} using enemizer seed {enemizer_seed}") for stdout_line in iter(p_open.stdout.readline, ""): logging.debug(stdout_line.rstrip()) p_open.stdout.close() return_code = p_open.wait() if return_code: - if i == max_enemizer_tries-1: + if i == max_enemizer_tries - 1: raise subprocess.CalledProcessError(return_code, enemizer_command) continue - for j in range(i+1, max_enemizer_tries): + for j in range(i + 1, max_enemizer_tries): world.rom_seeds[player].randint(0, 999999999) # Sacrifice all remaining random numbers that would have been used for unused enemizer tries. # This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness @@ -368,6 +416,55 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli): except OSError: pass +tile_list_lock = threading.Lock() +_tile_collection_table = [] +def _populate_tile_sets(): + with tile_list_lock: + if not _tile_collection_table: + def load_tileset_from_file(file): + tileset = TileSet(file) + _tile_collection_table.append(tileset) + + with concurrent.futures.ThreadPoolExecutor() as pool: + for dir in [local_path('data', 'tiles')]: + for file in os.listdir(dir): + pool.submit(load_tileset_from_file, os.path.join(dir, file)) + +class TileSet: + def __init__(self, filename): + with open(filename, 'rt', encoding='utf-8-sig') as file: + jsondata = json.load(file) + self.speed = jsondata['Speed'] + self.tiles = jsondata['Items'] + self.name = os.path.basename(os.path.splitext(filename)[0]) + + def __hash__(self): + return hash(self.name) + + def get_bytes(self): + data = [] + for tile in self.tiles: + data.append((tile['x'] + 3) * 16) + while len(data) < 22: + data.append(0) + for tile in self.tiles: + data.append((tile['y'] + 4) * 16) + return data + + def get_speed(self): + return self.speed + + def get_len(self): + return len(self.tiles) + + @staticmethod + def get_random_tile_set(localrandom=random): + _populate_tile_sets() + tile_sets = list(set(_tile_collection_table)) + tile_sets.sort(key=lambda x: x.name) + return localrandom.choice(tile_sets) + + sprite_list_lock = threading.Lock() _sprite_table = {} @@ -388,13 +485,6 @@ def _populate_sprite_table(): for file in os.listdir(dir): pool.submit(load_sprite_from_file, os.path.join(dir, file)) -def get_sprite_from_name(name, local_random=random): - _populate_sprite_table() - name = name.lower() - if name.startswith('random'): - return local_random.choice(list(_sprite_table.values())) - return _sprite_table.get(name, None) - class Sprite(object): palette = (255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157, 89, 71, 54, 104, 59, 74, 10, 239, 18, 92, 42, 113, 21, 24, 122, @@ -406,7 +496,7 @@ class Sprite(object): 61, 71, 54, 104, 59, 74, 10, 239, 18, 126, 86, 114, 24, 24, 122) glove_palette = (246, 82, 118, 3) - author_name:Optional[str] = None + author_name: Optional[str] = None def __init__(self, filename): with open(filename, 'rb') as file: @@ -453,6 +543,16 @@ class Sprite(object): else: self.valid = False + @staticmethod + def get_sprite_from_name(name: str, local_random=random) -> Optional[Sprite]: + _populate_sprite_table() + name = name.lower() + if name.startswith('random'): + sprites = list(set(_sprite_table.values())) + sprites.sort(key=lambda x: x.name) + return local_random.choice(sprites) + return _sprite_table.get(name, None) + @staticmethod def default_link_sprite(): return Sprite(local_path('../../data', 'default.zspr')) @@ -461,15 +561,15 @@ class Sprite(object): arr = [[0 for _ in range(8)] for _ in range(8)] for y in range(8): for x in range(8): - position = 1<<(7-x) + position = 1 << (7 - x) val = 0 - if self.sprite[pos+2*y] & position: + if self.sprite[pos + 2 * y] & position: val += 1 - if self.sprite[pos+2*y+1] & position: + if self.sprite[pos + 2 * y + 1] & position: val += 2 - if self.sprite[pos+2*y+16] & position: + if self.sprite[pos + 2 * y + 16] & position: val += 4 - if self.sprite[pos+2*y+17] & position: + if self.sprite[pos + 2 * y + 17] & position: val += 8 arr[y][x] = val return arr @@ -477,15 +577,15 @@ class Sprite(object): def decode16(self, pos): arr = [[0 for _ in range(16)] for _ in range(16)] top_left = self.decode8(pos) - top_right = self.decode8(pos+0x20) - bottom_left = self.decode8(pos+0x200) - bottom_right = self.decode8(pos+0x220) + top_right = self.decode8(pos + 0x20) + bottom_left = self.decode8(pos + 0x200) + bottom_right = self.decode8(pos + 0x220) for x in range(8): for y in range(8): arr[y][x] = top_left[y][x] - arr[y][x+8] = top_right[y][x] - arr[y+8][x] = bottom_left[y][x] - arr[y+8][x+8] = bottom_right[y][x] + arr[y][x + 8] = top_right[y][x] + arr[y + 8][x] = bottom_left[y][x] + arr[y + 8][x + 8] = bottom_right[y][x] return arr def parse_zspr(self, filedata, expected_kind): @@ -494,7 +594,8 @@ class Sprite(object): headersize = struct.calcsize(headerstr) if len(filedata) < headersize: return None - (version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind) = struct.unpack_from(headerstr, filedata) + (version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind) = struct.unpack_from( + headerstr, filedata) if version not in [1]: logger.error('Error parsing ZSPR file: Version %g not supported', version) return None @@ -534,19 +635,18 @@ class Sprite(object): def decode_palette(self): "Returns the palettes as an array of arrays of 15 colors" + def array_chunk(arr, size): return list(zip(*[iter(arr)] * size)) + def make_int16(pair): - return pair[1]<<8 | pair[0] + return pair[1] << 8 | pair[0] def expand_color(i): return ((i & 0x1F) * 8, (i >> 5 & 0x1F) * 8, (i >> 10 & 0x1F) * 8) - raw_palette = self.palette - if raw_palette is None: - raw_palette = Sprite.default_palette # turn palette data into a list of RGB tuples with 8 bit values - palette_as_colors = [expand_color(make_int16(chnk)) for chnk in array_chunk(raw_palette, 2)] + palette_as_colors = [expand_color(make_int16(chnk)) for chnk in array_chunk(self.palette, 2)] # split into palettes of 15 colors return array_chunk(palette_as_colors, 15) @@ -554,7 +654,7 @@ class Sprite(object): def __hash__(self): return hash(self.name) - def write_to_rom(self, rom : LocalRom): + def write_to_rom(self, rom: LocalRom): if not self.valid: logging.warning("Tried writing invalid sprite to rom, skipping.") return @@ -565,6 +665,7 @@ class Sprite(object): rom.write_bytes(0x307000, self.palette) rom.write_bytes(0x307078, self.glove_palette) + def patch_rom(world, rom, player, team, enemized): local_random = world.rom_seeds[player] @@ -607,7 +708,8 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(location.player_address, location.item.player) else: itemid = 0x5A - rom.write_byte(location.address, itemid) + location_address = old_location_address_to_new_location_address.get(location.address, location.address) + rom.write_byte(location_address, itemid) else: # crystals for address, value in zip(location.address, itemid): @@ -643,12 +745,12 @@ def patch_rom(world, rom, player, team, enemized): # Thanks to Zarby89 for originally finding these values # todo fix screen scrolling - if world.shuffle[player] not in ['insanity', 'insanity_legacy', 'madness_legacy'] and \ - exit.name in ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', + if world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness_legacy'} and \ + exit.name in {'Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Palace of Darkness Exit', 'Swamp Palace Exit', 'Ganons Tower Exit', 'Desert Palace Exit (North)', 'Agahnims Tower Exit', 'Spiral Cave Exit (Top)', - 'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)']: + 'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)'}: # For exits that connot be reached from another, no need to apply offset fixes. rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else elif room_id == 0x0059 and world.fix_skullwoods_exit[player]: @@ -712,7 +814,9 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(0x180032, 0x00) # standard mode uncle_location = world.get_location('Link\'s Uncle', player) - if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', 'Fighter Sword', 'Golden Sword', 'Progressive Sword']: + if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', + 'Fighter Sword', 'Golden Sword', + 'Progressive Sword']: # disable sword sprite from uncle rom.write_bytes(0x6D263, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) rom.write_bytes(0x6D26B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) @@ -733,100 +837,104 @@ def patch_rom(world, rom, player, team, enemized): GREEN_TWENTY_RUPEES = 0x47 GREEN_CLOCK = ItemFactory('Green Clock', player).code - rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on + rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on # handle difficulty_adjustments if world.difficulty_adjustments[player] == 'hard': - rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon - rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup + rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon + rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup # Powdered Fairies Prize rom.write_byte(0x36DD0, 0xD8) # One Heart # potion heal amount rom.write_byte(0x180084, 0x38) # Seven Hearts # potion magic restore amount rom.write_byte(0x180085, 0x40) # Half Magic - #Cape magic cost + # Cape magic cost rom.write_bytes(0x3ADA7, [0x02, 0x04, 0x08]) # Byrna Invulnerability: off rom.write_byte(0x18004F, 0x00) - #Disable catching fairies + # Disable catching fairies rom.write_byte(0x34FD6, 0x80) overflow_replacement = GREEN_TWENTY_RUPEES # Rupoor negative value rom.write_int16(0x180036, world.rupoor_cost) # Set stun items - rom.write_byte(0x180180, 0x02) # Hookshot only + rom.write_byte(0x180180, 0x02) # Hookshot only elif world.difficulty_adjustments[player] == 'expert': - rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon - rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup + rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon + rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup # Powdered Fairies Prize rom.write_byte(0x36DD0, 0xD8) # One Heart # potion heal amount rom.write_byte(0x180084, 0x20) # 4 Hearts # potion magic restore amount rom.write_byte(0x180085, 0x20) # Quarter Magic - #Cape magic cost + # Cape magic cost rom.write_bytes(0x3ADA7, [0x02, 0x04, 0x08]) # Byrna Invulnerability: off rom.write_byte(0x18004F, 0x00) - #Disable catching fairies + # Disable catching fairies rom.write_byte(0x34FD6, 0x80) overflow_replacement = GREEN_TWENTY_RUPEES # Rupoor negative value rom.write_int16(0x180036, world.rupoor_cost) # Set stun items - rom.write_byte(0x180180, 0x00) # Nothing + rom.write_byte(0x180180, 0x00) # Nothing else: - rom.write_byte(0x180181, 0x00) # Make silver arrows freely usable - rom.write_byte(0x180182, 0x01) # auto equip silvers on pickup + rom.write_byte(0x180181, 0x00) # Make silver arrows freely usable + rom.write_byte(0x180182, 0x01) # auto equip silvers on pickup # Powdered Fairies Prize rom.write_byte(0x36DD0, 0xE3) # fairy # potion heal amount rom.write_byte(0x180084, 0xA0) # full # potion magic restore amount rom.write_byte(0x180085, 0x80) # full - #Cape magic cost + # Cape magic cost rom.write_bytes(0x3ADA7, [0x04, 0x08, 0x10]) # Byrna Invulnerability: on rom.write_byte(0x18004F, 0x01) - #Enable catching fairies + # Enable catching fairies rom.write_byte(0x34FD6, 0xF0) # Rupoor negative value rom.write_int16(0x180036, world.rupoor_cost) # Set stun items - rom.write_byte(0x180180, 0x03) # All standard items - #Set overflow items for progressive equipment + rom.write_byte(0x180180, 0x03) # All standard items + # Set overflow items for progressive equipment if world.timer[player] in ['timed', 'timed-countdown', 'timed-ohko']: overflow_replacement = GREEN_CLOCK else: overflow_replacement = GREEN_TWENTY_RUPEES - #Byrna residual magic cost + # Byrna residual magic cost rom.write_bytes(0x45C42, [0x04, 0x02, 0x01]) difficulty = world.difficulty_requirements[player] - #Set overflow items for progressive equipment + # Set overflow items for progressive equipment rom.write_bytes(0x180090, - [difficulty.progressive_sword_limit if world.swords[player] != 'swordless' else 0, overflow_replacement, + [difficulty.progressive_sword_limit if world.swords[player] != 'swordless' else 0, + overflow_replacement, difficulty.progressive_shield_limit, overflow_replacement, difficulty.progressive_armor_limit, overflow_replacement, difficulty.progressive_bottle_limit, overflow_replacement]) - #Work around for json patch ordering issues - write bow limit separately so that it is replaced in the patch + # Work around for json patch ordering issues - write bow limit separately so that it is replaced in the patch rom.write_bytes(0x180098, [difficulty.progressive_bow_limit, overflow_replacement]) - if difficulty.progressive_bow_limit < 2 and (world.swords[player] == 'swordless' or world.logic[player] == 'noglitches'): + if difficulty.progressive_bow_limit < 2 and ( + world.swords[player] == 'swordless' or world.logic[player] == 'noglitches'): rom.write_bytes(0x180098, [2, overflow_replacement]) - rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon - rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup + rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon + rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup # set up game internal RNG seed rom.write_bytes(0x178000, local_random.getrandbits(8 * 1024).to_bytes(1024, 'big')) if "g" in world.shuffle_prizes[player]: # shuffle prize packs - prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF, - 0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8, + prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, + 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF, + 0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, + 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8, 0xD8, 0xE3, 0xE0, 0xDB, 0xDE, 0xD8, 0xDB, 0xE2, 0xD9, 0xDA, 0xDB, 0xD9, 0xDB, 0xD9, 0xDB] dig_prizes = [0xB2, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, @@ -836,26 +944,26 @@ def patch_rom(world, rom, player, team, enemized): 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3] - def chunk(l,n): - return [l[i:i+n] for i in range(0, len(l), n)] + def chunk(l, n): + return [l[i:i + n] for i in range(0, len(l), n)] # randomize last 7 slots - prizes [-7:] = local_random.sample(prizes, 7) + prizes[-7:] = local_random.sample(prizes, 7) - #shuffle order of 7 main packs + # shuffle order of 7 main packs packs = chunk(prizes[:56], 8) local_random.shuffle(packs) prizes[:56] = [drop for pack in packs for drop in pack] if world.difficulty_adjustments[player] in ['hard', 'expert']: - prize_replacements = {0xE0: 0xDF, # Fairy -> heart - 0xE3: 0xD8} # Big magic -> small magic + prize_replacements = {0xE0: 0xDF, # Fairy -> heart + 0xE3: 0xD8} # Big magic -> small magic prizes = [prize_replacements.get(prize, prize) for prize in prizes] dig_prizes = [prize_replacements.get(prize, prize) for prize in dig_prizes] if world.retro[player]: - prize_replacements = {0xE1: 0xDA, #5 Arrows -> Blue Rupee - 0xE2: 0xDB} #10 Arrows -> Red Rupee + prize_replacements = {0xE1: 0xDA, # 5 Arrows -> Blue Rupee + 0xE2: 0xDB} # 10 Arrows -> Red Rupee prizes = [prize_replacements.get(prize, prize) for prize in prizes] dig_prizes = [prize_replacements.get(prize, prize) for prize in dig_prizes] rom.write_bytes(0x180100, dig_prizes) @@ -900,13 +1008,13 @@ def patch_rom(world, rom, player, team, enemized): # Fill in item substitutions table rom.write_bytes(0x184000, [ # original_item, limit, replacement_item, filler - 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees - 0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade - 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade - 0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) - 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 - 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 - 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel + 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees + 0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade + 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade + 0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) + 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 + 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 + 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel ]) # set Fountain bottle exchange items @@ -917,7 +1025,7 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(0x348FF, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][local_random.randint(0, 6)]) rom.write_byte(0x3493B, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][local_random.randint(0, 6)]) - #enable Fat Fairy Chests + # enable Fat Fairy Chests rom.write_bytes(0x1FC16, [0xB1, 0xC6, 0xF9, 0xC9, 0xC6, 0xF9]) # set Fat Fairy Bow/Sword prizes to be disappointing rom.write_byte(0x34914, 0x3A) # Bow and Arrow @@ -925,13 +1033,20 @@ def patch_rom(world, rom, player, team, enemized): # enable Waterfall fairy chests rom.write_bytes(0xE9AE, [0x14, 0x01]) rom.write_bytes(0xE9CF, [0x14, 0x01]) - rom.write_bytes(0x1F714, [225, 0, 16, 172, 13, 41, 154, 1, 88, 152, 15, 17, 177, 97, 252, 77, 129, 32, 218, 2, 44, 225, 97, 252, 190, 129, 97, 177, 98, 84, 218, 2, - 253, 141, 131, 68, 225, 98, 253, 30, 131, 49, 165, 201, 49, 164, 105, 49, 192, 34, 77, 164, 105, 49, 198, 249, 73, 198, 249, 16, 153, 160, 92, 153, - 162, 11, 152, 96, 13, 232, 192, 85, 232, 192, 11, 146, 0, 115, 152, 96, 254, 105, 0, 152, 163, 97, 254, 107, 129, 254, 171, 133, 169, 200, 97, 254, - 174, 129, 255, 105, 2, 216, 163, 98, 255, 107, 131, 255, 43, 135, 201, 200, 98, 255, 46, 131, 254, 161, 0, 170, 33, 97, 254, 166, 129, 255, 33, 2, - 202, 33, 98, 255, 38, 131, 187, 35, 250, 195, 35, 250, 187, 43, 250, 195, 43, 250, 187, 83, 250, 195, 83, 250, 176, 160, 61, 152, 19, 192, 152, 82, - 192, 136, 0, 96, 144, 0, 96, 232, 0, 96, 240, 0, 96, 152, 202, 192, 216, 202, 192, 216, 19, 192, 216, 82, 192, 252, 189, 133, 253, 29, 135, 255, - 255, 255, 255, 240, 255, 128, 46, 97, 14, 129, 14, 255, 255]) + rom.write_bytes(0x1F714, + [225, 0, 16, 172, 13, 41, 154, 1, 88, 152, 15, 17, 177, 97, 252, 77, 129, 32, 218, 2, 44, 225, 97, + 252, 190, 129, 97, 177, 98, 84, 218, 2, + 253, 141, 131, 68, 225, 98, 253, 30, 131, 49, 165, 201, 49, 164, 105, 49, 192, 34, 77, 164, 105, + 49, 198, 249, 73, 198, 249, 16, 153, 160, 92, 153, + 162, 11, 152, 96, 13, 232, 192, 85, 232, 192, 11, 146, 0, 115, 152, 96, 254, 105, 0, 152, 163, 97, + 254, 107, 129, 254, 171, 133, 169, 200, 97, 254, + 174, 129, 255, 105, 2, 216, 163, 98, 255, 107, 131, 255, 43, 135, 201, 200, 98, 255, 46, 131, 254, + 161, 0, 170, 33, 97, 254, 166, 129, 255, 33, 2, + 202, 33, 98, 255, 38, 131, 187, 35, 250, 195, 35, 250, 187, 43, 250, 195, 43, 250, 187, 83, 250, + 195, 83, 250, 176, 160, 61, 152, 19, 192, 152, 82, + 192, 136, 0, 96, 144, 0, 96, 232, 0, 96, 240, 0, 96, 152, 202, 192, 216, 202, 192, 216, 19, 192, + 216, 82, 192, 252, 189, 133, 253, 29, 135, 255, + 255, 255, 255, 240, 255, 128, 46, 97, 14, 129, 14, 255, 255]) # set Waterfall fairy prizes to be disappointing rom.write_byte(0x348DB, 0x3A) # Red Boomerang becomes Red Boomerang rom.write_byte(0x348EB, 0x05) # Blue Shield becomes Blue Shield @@ -939,8 +1054,7 @@ def patch_rom(world, rom, player, team, enemized): # Remove Statues for upgrade fairy rom.write_bytes(0x01F810, [0x1A, 0x1E, 0x01, 0x1A, 0x1E, 0x01]) - - rom.write_byte(0x180029, 0x01) # Smithy quick item give + rom.write_byte(0x180029, 0x01) # Smithy quick item give # set swordless mode settings rom.write_byte(0x18003F, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer can harm ganon @@ -955,84 +1069,71 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(0x180044, 0x01) # hammer activates tablets # set up clocks for timed modes - if world.shuffle[player] == 'vanilla': - ERtimeincrease = 0 - elif world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']: - ERtimeincrease = 10 - else: - ERtimeincrease = 20 - if world.keyshuffle[player] or world.bigkeyshuffle[player] or world.mapshuffle[player]: - ERtimeincrease = ERtimeincrease + 15 - if world.clock_mode[player] == False: - rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode - rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32) - rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32) - rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32) - rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) - elif world.clock_mode[player] == 'ohko': + if world.clock_mode[player] in ['ohko', 'countdown-ohko']: rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality - rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32) - rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32) - rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32) - rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) - elif world.clock_mode[player] == 'countdown-ohko': - rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality - rom.write_int32(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32) - rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) - rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) - if world.difficulty_adjustments[player] in ['easy', 'normal']: - rom.write_int32(0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32) - else: - rom.write_int32(0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32) - if world.clock_mode[player] == 'stopwatch': + elif world.clock_mode[player] == 'stopwatch': rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode - rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32) - rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) - rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) - rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) - if world.clock_mode[player] == 'countdown': + elif world.clock_mode[player] == 'countdown': rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available - rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32) - rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) - rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) - rom.write_int32(0x18020C, (40 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32) + else: + rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode + + # Set up requested clock settings + if world.clock_mode[player] in ['countdown-ohko', 'stopwatch', 'countdown']: + rom.write_int32(0x180200, world.red_clock_time[player] * 60 * 60) # red clock adjustment time (in frames, sint32) + rom.write_int32(0x180204, world.blue_clock_time[player] * 60 * 60) # blue clock adjustment time (in frames, sint32) + rom.write_int32(0x180208, world.green_clock_time[player] * 60 * 60) # green clock adjustment time (in frames, sint32) + else: + rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32) + rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32) + rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32) + + # Set up requested start time for countdown modes + if world.clock_mode[player] in ['countdown-ohko', 'countdown']: + rom.write_int32(0x18020C, world.countdown_start_time[player] * 60 * 60) # starting time (in frames, sint32) + else: + rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) # set up goals for treasure hunt rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28]) rom.write_byte(0x180167, world.treasure_hunt_count[player] % 256) - rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) + rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) - rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed + rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed - gametype = 0x04 # item + gametype = 0x04 # item if world.shuffle[player] != 'vanilla': - gametype |= 0x02 # entrance + gametype |= 0x02 # entrance if enemized: - gametype |= 0x01 # enemizer - rom.write_byte(0x180211, gametype) # Game type + gametype |= 0x01 # enemizer + rom.write_byte(0x180211, gametype) # Game type # assorted fixes - rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world[player] else 0x00) # Toggle whether to be in real/fake dark world when dying in a DW dungeon before killing aga1 - rom.write_byte(0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence. + rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world[ + player] else 0x00) # Toggle whether to be in real/fake dark world when dying in a DW dungeon before killing aga1 + rom.write_byte(0x180169, + 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence. if world.mode[player] == 'inverted': rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted - rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death - rom.write_byte(0x180173, 0x01) # Bob is enabled + rom.write_byte(0x180171, + 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death + rom.write_byte(0x180173, 0x01) # Bob is enabled rom.write_byte(0x180168, 0x08) # Spike Cave Damage - rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) #Set spike cave and MM spike room Cape usage - rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) #Set spike cave and MM spike room Cape usage - rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest - rom.write_byte(0x50599, 0x00) # disable below ganon chest - rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest - rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player] else 0x00) # pre-open Pyramid Hole - rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0 - rom.write_byte(0xF5D73, 0xF0) # bees are catchable - rom.write_byte(0xF5F10, 0xF0) # bees are catchable + rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) # Set spike cave and MM spike room Cape usage + rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) # Set spike cave and MM spike room Cape usage + rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest + rom.write_byte(0x50599, 0x00) # disable below ganon chest + rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest + rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player] else 0x00) # pre-open Pyramid Hole + rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[ + player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0 + rom.write_byte(0xF5D73, 0xF0) # bees are catchable + rom.write_byte(0xF5F10, 0xF0) # bees are catchable rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00) - rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles + rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles # Starting equipment equip = [0] * (0x340 + 0x4F) @@ -1102,9 +1203,12 @@ def patch_rom(world, rom, player, team, enemized): 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)'}: continue - set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), - 'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1), - 'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)} + set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), + 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), + 'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), + 'Cane of Byrna': (0x351, 1), + 'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), + 'Quake': (0x349, 1)} or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02), 'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20), @@ -1135,8 +1239,9 @@ def patch_rom(world, rom, player, team, enemized): 'Map (Misery Mire)': (0x369, 0x01), 'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08), - 'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)} - set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04), + 'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), + 'Map (Ganons Tower)': (0x368, 0x04)} + set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02), 'Pegasus Boots': (0x355, 1, 0x379, 0x04), 'Shovel': (0x34C, 1, 0x38C, 0x04), 'Flute': (0x34C, 3, 0x38C, 0x01), 'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10), 'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)} @@ -1151,7 +1256,8 @@ def patch_rom(world, rom, player, team, enemized): 'Small Key (Universal)': [0x38B], 'Small Key (Hyrule Castle)': [0x37C, 0x37D]} bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5, 'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8} - rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300} + rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, + 'Rupees (300)': 300} bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10} arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10} bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10} @@ -1172,8 +1278,12 @@ def patch_rom(world, rom, player, team, enemized): equip[0x35C + equip[0x34F]] = bottles[item.name] equip[0x34F] += 1 elif item.name in rupees: - equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False)) - equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False)) + equip[0x360:0x362] = list( + min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', + signed=False)) + equip[0x362:0x364] = list( + min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', + signed=False)) elif item.name in bomb_caps: starting_max_bombs = min(starting_max_bombs + bomb_caps[item.name], 50) elif item.name in arrow_caps: @@ -1243,14 +1353,14 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(0x18005E, world.crystals_needed_for_gt[player]) rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) - # Bitfield - enable text box to show with free roaming items - # - # ---o bmcs - # o - enabled for outside dungeon items - # b - enabled for inside big keys - # m - enabled for inside maps - # c - enabled for inside compasses - # s - enabled for inside small keys + # Bitfield - enable text box to show with free roaming items + # + # ---o bmcs + # o - enabled for outside dungeon items + # b - enabled for inside big keys + # m - enabled for inside maps + # c - enabled for inside compasses + # s - enabled for inside small keys # block HC upstairs doors in rain state in standard mode rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" and world.shuffle[player] != 'vanilla' else 0x00) @@ -1270,14 +1380,14 @@ def patch_rom(world, rom, player, team, enemized): else: rom.write_byte(0x18003C, 0x00) - # Bitfield - enable free items to show up in menu - # - # ----dcba - # d - Compass - # c - Map - # b - Big Key - # a - Small Key - # + # Bitfield - enable free items to show up in menu + # + # ----dcba + # d - Compass + # c - Map + # b - Big Key + # a - Small Key + # rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] is True else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] else 0x00) @@ -1343,35 +1453,38 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(0x18004E, 0) # Escape Fill (nothing) rom.write_int16(0x180183, 300) # Escape fill rupee bow rom.write_bytes(0x180185, [0, 0, 0]) # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [0,0,0]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0,0,0]) # Mantle respawn refills (magic, bombs, arrows) - if world.mode[player] == 'standard': - if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']: + rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) + if world.mode[player] == 'standard' and uncle_location.item and uncle_location.item.player == player: + if uncle_location.item.name in {'Bow', 'Progressive Bow'}: rom.write_byte(0x18004E, 1) # Escape Fill (arrows) rom.write_int16(0x180183, 300) # Escape fill rupee bow rom.write_bytes(0x180185, [0, 0, 70]) # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [0,0,10]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0,0,10]) # Mantle respawn refills (magic, bombs, arrows) - elif uncle_location.item is not None and uncle_location.item.name in ['Bombs (10)']: - rom.write_byte(0x18004E, 2) # Escape Fill (bombs) - rom.write_bytes(0x180185, [0,50,0]) # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [0,3,0]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0,3,0]) # Mantle respawn refills (magic, bombs, arrows) - elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']: - rom.write_byte(0x18004E, 4) # Escape Fill (magic) - rom.write_bytes(0x180185, [0x80,0,0]) # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [0x20,0,0]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0x20,0,0]) # Mantle respawn refills (magic, bombs, arrows) + rom.write_bytes(0x180188, [0, 0, 10]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [0, 0, 10]) # Mantle respawn refills (magic, bombs, arrows) + elif uncle_location.item.name in {'Bombs (10)'}: + rom.write_byte(0x18004E, 2) # Escape Fill (bombs) + rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows) + rom.write_bytes(0x180188, [0, 3, 0]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [0, 3, 0]) # Mantle respawn refills (magic, bombs, arrows) + elif uncle_location.item.name in {'Cane of Somaria', 'Cane of Byrna', 'Fire Rod'}: + rom.write_byte(0x18004E, 4) # Escape Fill (magic) + rom.write_bytes(0x180185, [0x80, 0, 0]) # Uncle respawn refills (magic, bombs, arrows) + rom.write_bytes(0x180188, [0x20, 0, 0]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [0x20, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) # patch swamp: Need to enable permanent drain of water as dam or swamp were moved rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00) # powder patch: remove the need to leave the screen after powder, since it causes problems for potion shop at race game # temporarally we are just nopping out this check we will conver this to a rom fix soon. - rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) + rom.write_bytes(0x02F539, + [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, + 0x4F]) # allow smith into multi-entrance caves in appropriate shuffles - if world.shuffle[player] in ['restricted', 'full', 'crossed', 'insanity', 'madness'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): + if world.shuffle[player] in ['restricted', 'full', 'crossed', 'insanity', 'madness'] or ( + world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item @@ -1386,14 +1499,21 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(0xFED31, 0x0E) # preopen bombable exit rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit # included unconditionally in base2current - #rom.write_byte(0xFE465, 0x1E) # remove small key door on backside of big key door + # rom.write_byte(0xFE465, 0x1E) # remove small key door on backside of big key door else: rom.write_byte(0xFED31, 0x2A) # preopen bombable exit rom.write_byte(0xFEE41, 0x2A) # preopen bombable exit + if world.tile_shuffle[player]: + tile_set = TileSet.get_random_tile_set(world.rom_seeds[player]) + rom.write_byte(0x4BA21, tile_set.get_speed()) + rom.write_byte(0x4BA1D, tile_set.get_len()) + rom.write_bytes(0x4BA2A, tile_set.get_bytes()) + + write_strings(rom, world, player, team) - rom.write_byte(0x18636C, 1 if world.remote_items[player] else 0) + rom.write_byte(0x18637C, 1 if world.remote_items[player] else 0) # set rom name # 21 bytes @@ -1421,16 +1541,11 @@ def patch_rom(world, rom, player, team, enemized): return rom -try: - import RaceRom -except ImportError: - RaceRom = None -def patch_race_rom(rom): - rom.write_bytes(0x180213, [0x01, 0x00]) # Tournament Seed +def patch_race_rom(rom, world, player): + rom.write_bytes(0x180213, [0x01, 0x00]) # Tournament Seed + rom.encrypt(world, player) - if 'RaceRom' in sys.modules: - RaceRom.encrypt(rom) def write_custom_shops(rom, world, player): shops = [shop for shop in world.shops if shop.custom and shop.region.player == player] @@ -1454,7 +1569,10 @@ def write_custom_shops(rom, world, player): for item in shop.inventory: if item is None: break - item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes(item['replacement_price']) + item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [ + item['max'], + ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes( + item['replacement_price']) items_data.extend(item_data) rom.write_bytes(0x184800, shop_data) @@ -1481,9 +1599,9 @@ def hud_format_text(text): return output[:32] -def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes, world=None, player=1, allow_random_on_event=False): +def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options, + world=None, player=1, allow_random_on_event=False): local_random = random if not world else world.rom_seeds[player] - apply_random_sprite_on_event(rom, sprite, local_random, allow_random_on_event, world.sprite_pool[player] if world else []) # enable instant item menu if fastmenu == 'instant': @@ -1511,8 +1629,12 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr rom.write_byte(0x0CFE18, 0x00 if disable_music else rom.orig_buffer[0x0CFE18] if rom.orig_buffer else 0x70) rom.write_byte(0x0CFEC1, 0x00 if disable_music else rom.orig_buffer[0x0CFEC1] if rom.orig_buffer else 0xC0) - rom.write_bytes(0x0D0000, [0x00, 0x00] if disable_music else rom.orig_buffer[0x0D0000:0x0D0002] if rom.orig_buffer else [0xDA, 0x58]) - rom.write_bytes(0x0D00E7, [0xC4, 0x58] if disable_music else rom.orig_buffer[0x0D00E7:0x0D00E9] if rom.orig_buffer else [0xDA, 0x58]) + rom.write_bytes(0x0D0000, + [0x00, 0x00] if disable_music else rom.orig_buffer[0x0D0000:0x0D0002] if rom.orig_buffer else [0xDA, + 0x58]) + rom.write_bytes(0x0D00E7, + [0xC4, 0x58] if disable_music else rom.orig_buffer[0x0D00E7:0x0D00E9] if rom.orig_buffer else [0xDA, + 0x58]) rom.write_byte(0x18021A, 1 if disable_music else 0x00) @@ -1534,43 +1656,71 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr rom.write_byte(0x6FA30, {'red': 0x24, 'blue': 0x2C, 'green': 0x3C, 'yellow': 0x28}[color]) rom.write_byte(0x65561, {'red': 0x05, 'blue': 0x0D, 'green': 0x19, 'yellow': 0x09}[color]) - # reset palette if it was adjusted already - default_ow_palettes(rom) - default_uw_palettes(rom) - if z3pr: - options = { - "randomize_dungeon": uw_palettes == 'random', - "randomize_overworld": ow_palettes == 'random' - } - if any(options.values()): + def buildAndRandomize(option_name, mode): + options = { + option_name: True + } + data_dir = local_path("../../data") if is_bundled() else None offsets_array = build_offset_collections(options, data_dir) - + restore_maseya_colors(rom, offsets_array) + if mode == 'default': + return ColorF = z3pr.ColorF def next_color_generator(): while True: yield ColorF(local_random.random(), local_random.random(), local_random.random()) - z3pr.randomize(rom.buffer, "maseya", offset_collections=offsets_array, random_colors=next_color_generator()) + if mode == 'random': + mode = 'maseya' + z3pr.randomize(rom.buffer, mode, offset_collections=offsets_array, random_colors=next_color_generator()) + + uw_palettes = palettes_options['dungeon'] + ow_palettes = palettes_options['overworld'] + hud_palettes = palettes_options['hud'] + sword_palettes = palettes_options['sword'] + shield_palettes = palettes_options['shield'] + # link_palettes = palettes_options['link'] + buildAndRandomize("randomize_dungeon", uw_palettes) + buildAndRandomize("randomize_overworld", ow_palettes) + buildAndRandomize("randomize_hud", hud_palettes) + buildAndRandomize("randomize_sword", sword_palettes) + buildAndRandomize("randomize_shield", shield_palettes) + # link palette shuffle does not work very well and it's incompatible with random sprite on event + # buildAndRandomize("randomize_link_sprite", link_palettes) + else: + # reset palette if it was adjusted already + default_ow_palettes(rom) + default_uw_palettes(rom) logging.warning("Could not find z3pr palette shuffle. " "If you want improved palette shuffling please install the maseya-z3pr package.") - if ow_palettes == 'random': + if palettes_options['overworld'] == 'random': randomize_ow_palettes(rom, local_random) - if uw_palettes == 'random': + elif palettes_options['overworld'] == 'blackout': + blackout_ow_palettes(rom) + + if palettes_options['dungeon'] == 'blackout': + blackout_uw_palettes(rom) + elif palettes_options['dungeon'] == 'random': randomize_uw_palettes(rom, local_random) - if ow_palettes == 'blackout': - blackout_ow_palettes(rom) - if uw_palettes == 'blackout': - blackout_uw_palettes(rom) - + apply_random_sprite_on_event(rom, sprite, local_random, allow_random_on_event, + world.sprite_pool[player] if world else []) if isinstance(rom, LocalRom): rom.write_crc() +def restore_maseya_colors(rom, offsets_array): + if not rom.orig_buffer: + return + for offsetC in offsets_array: + for address in offsetC: + rom.write_bytes(address, rom.orig_buffer[address:address + 2]) + + def set_color(rom, address, color, shade): r = round(min(color[0], 0xFF) * pow(0.8, shade) * 0x1F / 0xFF) g = round(min(color[1], 0xFF) * pow(0.8, shade) * 0x1F / 0xFF) @@ -1578,72 +1728,109 @@ def set_color(rom, address, color, shade): rom.write_bytes(address, ((b << 10) | (g << 5) | (r << 0)).to_bytes(2, byteorder='little', signed=False)) + def default_ow_palettes(rom): if not rom.orig_buffer: return rom.write_bytes(0xDE604, rom.orig_buffer[0xDE604:0xDEBB4]) for address in [0x067FB4, 0x067F94, 0x067FC6, 0x067FE6, 0x067FE1, 0x05FEA9, 0x05FEB3]: - rom.write_bytes(address, rom.orig_buffer[address:address+2]) + rom.write_bytes(address, rom.orig_buffer[address:address + 2]) + def randomize_ow_palettes(rom, local_random): - grass, grass2, grass3, dirt, dirt2, water, clouds, dwdirt,\ - dwgrass, dwwater, dwdmdirt, dwdmgrass, dwdmclouds1, dwdmclouds2 = [[local_random.randint(60, 215) for _ in range(3)] for _ in range(14)] + grass, grass2, grass3, dirt, dirt2, water, clouds, dwdirt, \ + dwgrass, dwwater, dwdmdirt, dwdmgrass, dwdmclouds1, dwdmclouds2 = [[local_random.randint(60, 215) for _ in range(3)] + for _ in range(14)] dwtree = [c + local_random.randint(-20, 10) for c in dwgrass] treeleaf = [c + local_random.randint(-20, 10) for c in grass] - patches = {0x067FB4: (grass, 0), 0x067F94: (grass, 0), 0x067FC6: (grass, 0), 0x067FE6: (grass, 0), 0x067FE1: (grass, 3), 0x05FEA9: (grass, 0), 0x05FEB3: (dwgrass, 1), - 0x0DD4AC: (grass, 2), 0x0DE6DE: (grass2, 2), 0x0DE6E0: (grass2, 1), 0x0DD4AE: (grass2, 1), 0x0DE9FA: (grass2, 1), 0x0DEA0E: (grass2, 1), 0x0DE9FE: (grass2, 0), - 0x0DD3D2: (grass2, 2), 0x0DE88C: (grass2, 2), 0x0DE8A8: (grass2, 2), 0x0DE9F8: (grass2, 2), 0x0DEA4E: (grass2, 2), 0x0DEAF6: (grass2, 2), 0x0DEB2E: (grass2, 2), 0x0DEB4A: (grass2, 2), - 0x0DE892: (grass, 1), 0x0DE886: (grass, 0), 0x0DE6D2: (grass, 0), 0x0DE6FA: (grass, 3), 0x0DE6FC: (grass, 0), 0x0DE6FE: (grass, 0), 0x0DE70A: (grass, 0), 0x0DE708: (grass, 2), 0x0DE70C: (grass, 1), - 0x0DE6D4: (dirt, 2), 0x0DE6CA: (dirt, 5), 0x0DE6CC: (dirt, 4), 0x0DE6CE: (dirt, 3), 0x0DE6E2: (dirt, 2), 0x0DE6D8: (dirt, 5), 0x0DE6DA: (dirt, 4), 0x0DE6DC: (dirt, 2), - 0x0DE6F0: (dirt, 2), 0x0DE6E6: (dirt, 5), 0x0DE6E8: (dirt, 4), 0x0DE6EA: (dirt, 2), 0x0DE6EC: (dirt, 4), 0x0DE6EE: (dirt, 2), + patches = {0x067FB4: (grass, 0), 0x067F94: (grass, 0), 0x067FC6: (grass, 0), 0x067FE6: (grass, 0), + 0x067FE1: (grass, 3), 0x05FEA9: (grass, 0), 0x05FEB3: (dwgrass, 1), + 0x0DD4AC: (grass, 2), 0x0DE6DE: (grass2, 2), 0x0DE6E0: (grass2, 1), 0x0DD4AE: (grass2, 1), + 0x0DE9FA: (grass2, 1), 0x0DEA0E: (grass2, 1), 0x0DE9FE: (grass2, 0), + 0x0DD3D2: (grass2, 2), 0x0DE88C: (grass2, 2), 0x0DE8A8: (grass2, 2), 0x0DE9F8: (grass2, 2), + 0x0DEA4E: (grass2, 2), 0x0DEAF6: (grass2, 2), 0x0DEB2E: (grass2, 2), 0x0DEB4A: (grass2, 2), + 0x0DE892: (grass, 1), 0x0DE886: (grass, 0), 0x0DE6D2: (grass, 0), 0x0DE6FA: (grass, 3), + 0x0DE6FC: (grass, 0), 0x0DE6FE: (grass, 0), 0x0DE70A: (grass, 0), 0x0DE708: (grass, 2), + 0x0DE70C: (grass, 1), + 0x0DE6D4: (dirt, 2), 0x0DE6CA: (dirt, 5), 0x0DE6CC: (dirt, 4), 0x0DE6CE: (dirt, 3), 0x0DE6E2: (dirt, 2), + 0x0DE6D8: (dirt, 5), 0x0DE6DA: (dirt, 4), 0x0DE6DC: (dirt, 2), + 0x0DE6F0: (dirt, 2), 0x0DE6E6: (dirt, 5), 0x0DE6E8: (dirt, 4), 0x0DE6EA: (dirt, 2), 0x0DE6EC: (dirt, 4), + 0x0DE6EE: (dirt, 2), 0x0DE91E: (grass, 0), 0x0DE920: (dirt, 2), 0x0DE916: (dirt, 3), 0x0DE934: (dirt, 3), - 0x0DE92C: (grass, 0), 0x0DE93A: (grass, 0), 0x0DE91C: (grass, 1), 0x0DE92A: (grass, 1), 0x0DEA1C: (grass, 0), 0x0DEA2A: (grass, 0), 0x0DEA30: (grass, 0), + 0x0DE92C: (grass, 0), 0x0DE93A: (grass, 0), 0x0DE91C: (grass, 1), 0x0DE92A: (grass, 1), + 0x0DEA1C: (grass, 0), 0x0DEA2A: (grass, 0), 0x0DEA30: (grass, 0), 0x0DEA2E: (dirt, 5), - 0x0DE884: (grass, 3), 0x0DE8AE: (grass, 3), 0x0DE8BE: (grass, 3), 0x0DE8E4: (grass, 3), 0x0DE938: (grass, 3), 0x0DE9C4: (grass, 3), 0x0DE6D0: (grass, 4), + 0x0DE884: (grass, 3), 0x0DE8AE: (grass, 3), 0x0DE8BE: (grass, 3), 0x0DE8E4: (grass, 3), + 0x0DE938: (grass, 3), 0x0DE9C4: (grass, 3), 0x0DE6D0: (grass, 4), 0x0DE890: (treeleaf, 1), 0x0DE894: (treeleaf, 0), - 0x0DE924: (water, 3), 0x0DE668: (water, 3), 0x0DE66A: (water, 2), 0x0DE670: (water, 1), 0x0DE918: (water, 1), 0x0DE66C: (water, 0), 0x0DE91A: (water, 0), 0x0DE92E: (water, 1), 0x0DEA1A: (water, 1), 0x0DEA16: (water, 3), 0x0DEA10: (water, 4), + 0x0DE924: (water, 3), 0x0DE668: (water, 3), 0x0DE66A: (water, 2), 0x0DE670: (water, 1), + 0x0DE918: (water, 1), 0x0DE66C: (water, 0), 0x0DE91A: (water, 0), 0x0DE92E: (water, 1), + 0x0DEA1A: (water, 1), 0x0DEA16: (water, 3), 0x0DEA10: (water, 4), 0x0DE66E: (dirt, 3), 0x0DE672: (dirt, 2), 0x0DE932: (dirt, 4), 0x0DE936: (dirt, 2), 0x0DE93C: (dirt, 1), - 0x0DE756: (dirt2, 4), 0x0DE764: (dirt2, 4), 0x0DE772: (dirt2, 4), 0x0DE994: (dirt2, 4), 0x0DE9A2: (dirt2, 4), 0x0DE758: (dirt2, 3), 0x0DE766: (dirt2, 3), 0x0DE774: (dirt2, 3), - 0x0DE996: (dirt2, 3), 0x0DE9A4: (dirt2, 3), 0x0DE75A: (dirt2, 2), 0x0DE768: (dirt2, 2), 0x0DE776: (dirt2, 2), 0x0DE778: (dirt2, 2), 0x0DE998: (dirt2, 2), 0x0DE9A6: (dirt2, 2), - 0x0DE9AC: (dirt2, 1), 0x0DE99E: (dirt2, 1), 0x0DE760: (dirt2, 1), 0x0DE77A: (dirt2, 1), 0x0DE77C: (dirt2, 1), 0x0DE798: (dirt2, 1), 0x0DE980: (dirt2, 1), - 0x0DE75C: (grass3, 2), 0x0DE786: (grass3, 2), 0x0DE794: (grass3, 2), 0x0DE99A: (grass3, 2), 0x0DE75E: (grass3, 1), 0x0DE788: (grass3, 1), 0x0DE796: (grass3, 1), 0x0DE99C: (grass3, 1), - 0x0DE76A: (clouds, 2), 0x0DE9A8: (clouds, 2), 0x0DE76E: (clouds, 0), 0x0DE9AA: (clouds, 0), 0x0DE8DA: (clouds, 0), 0x0DE8D8: (clouds, 0), 0x0DE8D0: (clouds, 0), 0x0DE98C: (clouds, 2), 0x0DE990: (clouds, 0), + 0x0DE756: (dirt2, 4), 0x0DE764: (dirt2, 4), 0x0DE772: (dirt2, 4), 0x0DE994: (dirt2, 4), + 0x0DE9A2: (dirt2, 4), 0x0DE758: (dirt2, 3), 0x0DE766: (dirt2, 3), 0x0DE774: (dirt2, 3), + 0x0DE996: (dirt2, 3), 0x0DE9A4: (dirt2, 3), 0x0DE75A: (dirt2, 2), 0x0DE768: (dirt2, 2), + 0x0DE776: (dirt2, 2), 0x0DE778: (dirt2, 2), 0x0DE998: (dirt2, 2), 0x0DE9A6: (dirt2, 2), + 0x0DE9AC: (dirt2, 1), 0x0DE99E: (dirt2, 1), 0x0DE760: (dirt2, 1), 0x0DE77A: (dirt2, 1), + 0x0DE77C: (dirt2, 1), 0x0DE798: (dirt2, 1), 0x0DE980: (dirt2, 1), + 0x0DE75C: (grass3, 2), 0x0DE786: (grass3, 2), 0x0DE794: (grass3, 2), 0x0DE99A: (grass3, 2), + 0x0DE75E: (grass3, 1), 0x0DE788: (grass3, 1), 0x0DE796: (grass3, 1), 0x0DE99C: (grass3, 1), + 0x0DE76A: (clouds, 2), 0x0DE9A8: (clouds, 2), 0x0DE76E: (clouds, 0), 0x0DE9AA: (clouds, 0), + 0x0DE8DA: (clouds, 0), 0x0DE8D8: (clouds, 0), 0x0DE8D0: (clouds, 0), 0x0DE98C: (clouds, 2), + 0x0DE990: (clouds, 0), 0x0DEB34: (dwtree, 4), 0x0DEB30: (dwtree, 3), 0x0DEB32: (dwtree, 1), - 0x0DE710: (dwdirt, 5), 0x0DE71E: (dwdirt, 5), 0x0DE72C: (dwdirt, 5), 0x0DEAD6: (dwdirt, 5), 0x0DE712: (dwdirt, 4), 0x0DE720: (dwdirt, 4), 0x0DE72E: (dwdirt, 4), 0x0DE660: (dwdirt, 4), - 0x0DEAD8: (dwdirt, 4), 0x0DEADA: (dwdirt, 3), 0x0DE714: (dwdirt, 3), 0x0DE722: (dwdirt, 3), 0x0DE730: (dwdirt, 3), 0x0DE732: (dwdirt, 3), 0x0DE734: (dwdirt, 2), 0x0DE736: (dwdirt, 2), + 0x0DE710: (dwdirt, 5), 0x0DE71E: (dwdirt, 5), 0x0DE72C: (dwdirt, 5), 0x0DEAD6: (dwdirt, 5), + 0x0DE712: (dwdirt, 4), 0x0DE720: (dwdirt, 4), 0x0DE72E: (dwdirt, 4), 0x0DE660: (dwdirt, 4), + 0x0DEAD8: (dwdirt, 4), 0x0DEADA: (dwdirt, 3), 0x0DE714: (dwdirt, 3), 0x0DE722: (dwdirt, 3), + 0x0DE730: (dwdirt, 3), 0x0DE732: (dwdirt, 3), 0x0DE734: (dwdirt, 2), 0x0DE736: (dwdirt, 2), 0x0DE728: (dwdirt, 2), 0x0DE71A: (dwdirt, 2), 0x0DE664: (dwdirt, 2), 0x0DEAE0: (dwdirt, 2), - 0x0DE716: (dwgrass, 3), 0x0DE740: (dwgrass, 3), 0x0DE74E: (dwgrass, 3), 0x0DEAC0: (dwgrass, 3), 0x0DEACE: (dwgrass, 3), 0x0DEADC: (dwgrass, 3), 0x0DEB24: (dwgrass, 3), 0x0DE752: (dwgrass, 2), - 0x0DE718: (dwgrass, 1), 0x0DE742: (dwgrass, 1), 0x0DE750: (dwgrass, 1), 0x0DEB26: (dwgrass, 1), 0x0DEAC2: (dwgrass, 1), 0x0DEAD0: (dwgrass, 1), 0x0DEADE: (dwgrass, 1), - 0x0DE65A: (dwwater, 5), 0x0DE65C: (dwwater, 3), 0x0DEAC8: (dwwater, 3), 0x0DEAD2: (dwwater, 2), 0x0DEABC: (dwwater, 2), 0x0DE662: (dwwater, 2), 0x0DE65E: (dwwater, 1), 0x0DEABE: (dwwater, 1), 0x0DEA98: (dwwater, 2), - 0x0DE79A: (dwdmdirt, 6), 0x0DE7A8: (dwdmdirt, 6), 0x0DE7B6: (dwdmdirt, 6), 0x0DEB60: (dwdmdirt, 6), 0x0DEB6E: (dwdmdirt, 6), 0x0DE93E: (dwdmdirt, 6), 0x0DE94C: (dwdmdirt, 6), 0x0DEBA6: (dwdmdirt, 6), - 0x0DE79C: (dwdmdirt, 4), 0x0DE7AA: (dwdmdirt, 4), 0x0DE7B8: (dwdmdirt, 4), 0x0DEB70: (dwdmdirt, 4), 0x0DEBA8: (dwdmdirt, 4), 0x0DEB72: (dwdmdirt, 3), 0x0DEB74: (dwdmdirt, 3), 0x0DE79E: (dwdmdirt, 3), 0x0DE7AC: (dwdmdirt, 3), 0x0DEBAA: (dwdmdirt, 3), 0x0DE7A0: (dwdmdirt, 3), + 0x0DE716: (dwgrass, 3), 0x0DE740: (dwgrass, 3), 0x0DE74E: (dwgrass, 3), 0x0DEAC0: (dwgrass, 3), + 0x0DEACE: (dwgrass, 3), 0x0DEADC: (dwgrass, 3), 0x0DEB24: (dwgrass, 3), 0x0DE752: (dwgrass, 2), + 0x0DE718: (dwgrass, 1), 0x0DE742: (dwgrass, 1), 0x0DE750: (dwgrass, 1), 0x0DEB26: (dwgrass, 1), + 0x0DEAC2: (dwgrass, 1), 0x0DEAD0: (dwgrass, 1), 0x0DEADE: (dwgrass, 1), + 0x0DE65A: (dwwater, 5), 0x0DE65C: (dwwater, 3), 0x0DEAC8: (dwwater, 3), 0x0DEAD2: (dwwater, 2), + 0x0DEABC: (dwwater, 2), 0x0DE662: (dwwater, 2), 0x0DE65E: (dwwater, 1), 0x0DEABE: (dwwater, 1), + 0x0DEA98: (dwwater, 2), + 0x0DE79A: (dwdmdirt, 6), 0x0DE7A8: (dwdmdirt, 6), 0x0DE7B6: (dwdmdirt, 6), 0x0DEB60: (dwdmdirt, 6), + 0x0DEB6E: (dwdmdirt, 6), 0x0DE93E: (dwdmdirt, 6), 0x0DE94C: (dwdmdirt, 6), 0x0DEBA6: (dwdmdirt, 6), + 0x0DE79C: (dwdmdirt, 4), 0x0DE7AA: (dwdmdirt, 4), 0x0DE7B8: (dwdmdirt, 4), 0x0DEB70: (dwdmdirt, 4), + 0x0DEBA8: (dwdmdirt, 4), 0x0DEB72: (dwdmdirt, 3), 0x0DEB74: (dwdmdirt, 3), 0x0DE79E: (dwdmdirt, 3), + 0x0DE7AC: (dwdmdirt, 3), 0x0DEBAA: (dwdmdirt, 3), 0x0DE7A0: (dwdmdirt, 3), 0x0DE7BC: (dwdmgrass, 3), - 0x0DEBAC: (dwdmdirt, 2), 0x0DE7AE: (dwdmdirt, 2), 0x0DE7C2: (dwdmdirt, 2), 0x0DE7A6: (dwdmdirt, 2), 0x0DEB7A: (dwdmdirt, 2), 0x0DEB6C: (dwdmdirt, 2), 0x0DE7C0: (dwdmdirt, 2), - 0x0DE7A2: (dwdmgrass, 3), 0x0DE7BE: (dwdmgrass, 3), 0x0DE7CC: (dwdmgrass, 3), 0x0DE7DA: (dwdmgrass, 3), 0x0DEB6A: (dwdmgrass, 3), 0x0DE948: (dwdmgrass, 3), 0x0DE956: (dwdmgrass, 3), 0x0DE964: (dwdmgrass, 3), 0x0DE7CE: (dwdmgrass, 1), 0x0DE7A4: (dwdmgrass, 1), 0x0DEBA2: (dwdmgrass, 1), 0x0DEBB0: (dwdmgrass, 1), - 0x0DE644: (dwdmclouds1, 2), 0x0DEB84: (dwdmclouds1, 2), 0x0DE648: (dwdmclouds1, 1), 0x0DEB88: (dwdmclouds1, 1), - 0x0DEBAE: (dwdmclouds2, 2), 0x0DE7B0: (dwdmclouds2, 2), 0x0DE7B4: (dwdmclouds2, 0), 0x0DEB78: (dwdmclouds2, 0), 0x0DEBB2: (dwdmclouds2, 0) + 0x0DEBAC: (dwdmdirt, 2), 0x0DE7AE: (dwdmdirt, 2), 0x0DE7C2: (dwdmdirt, 2), 0x0DE7A6: (dwdmdirt, 2), + 0x0DEB7A: (dwdmdirt, 2), 0x0DEB6C: (dwdmdirt, 2), 0x0DE7C0: (dwdmdirt, 2), + 0x0DE7A2: (dwdmgrass, 3), 0x0DE7BE: (dwdmgrass, 3), 0x0DE7CC: (dwdmgrass, 3), 0x0DE7DA: (dwdmgrass, 3), + 0x0DEB6A: (dwdmgrass, 3), 0x0DE948: (dwdmgrass, 3), 0x0DE956: (dwdmgrass, 3), 0x0DE964: (dwdmgrass, 3), + 0x0DE7CE: (dwdmgrass, 1), 0x0DE7A4: (dwdmgrass, 1), 0x0DEBA2: (dwdmgrass, 1), 0x0DEBB0: (dwdmgrass, 1), + 0x0DE644: (dwdmclouds1, 2), 0x0DEB84: (dwdmclouds1, 2), 0x0DE648: (dwdmclouds1, 1), + 0x0DEB88: (dwdmclouds1, 1), + 0x0DEBAE: (dwdmclouds2, 2), 0x0DE7B0: (dwdmclouds2, 2), 0x0DE7B4: (dwdmclouds2, 0), + 0x0DEB78: (dwdmclouds2, 0), 0x0DEBB2: (dwdmclouds2, 0) } for address, (color, shade) in patches.items(): set_color(rom, address, color, shade) + def blackout_ow_palettes(rom): rom.write_bytes(0xDE604, [0] * 0xC4) for i in range(0xDE6C8, 0xDE86C, 70): rom.write_bytes(i, [0] * 64) - rom.write_bytes(i+66, [0] * 4) + rom.write_bytes(i + 66, [0] * 4) rom.write_bytes(0xDE86C, [0] * 0x348) for address in [0x067FB4, 0x067F94, 0x067FC6, 0x067FE6, 0x067FE1, 0x05FEA9, 0x05FEB3]: - rom.write_bytes(address, [0,0]) + rom.write_bytes(address, [0, 0]) + def default_uw_palettes(rom): if not rom.orig_buffer: return rom.write_bytes(0xDD734, rom.orig_buffer[0xDD734:0xDE544]) + def randomize_uw_palettes(rom, local_random): for dungeon in range(20): wall, pot, chest, floor1, floor2, floor3 = [[local_random.randint(60, 240) for _ in range(3)] for _ in range(6)] @@ -1690,15 +1877,18 @@ def randomize_uw_palettes(rom, local_random): set_color(rom, 0x0DD7E2 + (0xB4 * dungeon), floor3, 3) set_color(rom, 0x0DD796 + (0xB4 * dungeon), floor3, 4) + def blackout_uw_palettes(rom): for i in range(0xDD734, 0xDE544, 180): rom.write_bytes(i, [0] * 38) - rom.write_bytes(i+44, [0] * 76) - rom.write_bytes(i+136, [0] * 44) + rom.write_bytes(i + 44, [0] * 76) + rom.write_bytes(i + 136, [0] * 44) + def get_hash_string(hash): return ", ".join([hash_alphabet[code & 0x1F] for code in hash]) + def write_string_to_rom(rom, target, string): address, maxbytes = text_addresses[target] rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes)) @@ -1739,7 +1929,7 @@ def write_strings(rom, world, player, team): all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player] local_random.shuffle(all_entrances) - #First we take care of the one inconvenient dungeon in the appropriately simple shuffles. + # First we take care of the one inconvenient dungeon in the appropriately simple shuffles. entrances_to_hint = {} entrances_to_hint.update(InconvenientDungeonEntrances) if world.shuffle_ganon: @@ -1750,11 +1940,12 @@ def write_strings(rom, world, player, team): if world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']: for entrance in all_entrances: if entrance.name in entrances_to_hint: - this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.' + this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( + entrance.connected_region) + '.' tt[hint_locations.pop(0)] = this_hint entrances_to_hint = {} break - #Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. + # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. entrances_to_hint.update(InconvenientOtherEntrances) if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: hint_count = 0 @@ -1765,14 +1956,15 @@ def write_strings(rom, world, player, team): for entrance in all_entrances: if entrance.name in entrances_to_hint: if hint_count > 0: - this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.' + this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( + entrance.connected_region) + '.' tt[hint_locations.pop(0)] = this_hint entrances_to_hint.pop(entrance.name) hint_count -= 1 else: break - #Next we handle hints for randomly selected other entrances, curating the selection intelligently based on shuffle. + # Next we handle hints for randomly selected other entrances, curating the selection intelligently based on shuffle. if world.shuffle[player] not in ['simple', 'restricted', 'restricted_legacy']: entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(DungeonEntrances) @@ -1801,7 +1993,8 @@ def write_strings(rom, world, player, team): for entrance in all_entrances: if entrance.name in entrances_to_hint: if hint_count > 0: - this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.' + this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( + entrance.connected_region) + '.' tt[hint_locations.pop(0)] = this_hint entrances_to_hint.pop(entrance.name) hint_count -= 1 @@ -1835,25 +2028,32 @@ def write_strings(rom, world, player, team): this_hint = ('The westmost chests in Misery Mire contain ' + first_item + ' and ' + second_item + '.') tt[hint_locations.pop(0)] = this_hint elif location == 'Tower of Hera - Big Key Chest': - this_hint = 'Waiting in the Tower of Hera basement leads to ' + hint_text(world.get_location(location, player).item) + '.' + this_hint = 'Waiting in the Tower of Hera basement leads to ' + hint_text( + world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Ganons Tower - Big Chest': - this_hint = 'The big chest in Ganon\'s Tower contains ' + hint_text(world.get_location(location, player).item) + '.' + this_hint = 'The big chest in Ganon\'s Tower contains ' + hint_text( + world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Thieves\' Town - Big Chest': - this_hint = 'The big chest in Thieves\' Town contains ' + hint_text(world.get_location(location, player).item) + '.' + this_hint = 'The big chest in Thieves\' Town contains ' + hint_text( + world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Ice Palace - Big Chest': - this_hint = 'The big chest in Ice Palace contains ' + hint_text(world.get_location(location, player).item) + '.' + this_hint = 'The big chest in Ice Palace contains ' + hint_text( + world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Eastern Palace - Big Key Chest': - this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + hint_text(world.get_location(location, player).item) + '.' + this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + hint_text( + world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Sahasrahla': - this_hint = 'Sahasrahla seeks a green pendant for ' + hint_text(world.get_location(location, player).item) + '.' + this_hint = 'Sahasrahla seeks a green pendant for ' + hint_text( + world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Graveyard Cave': - this_hint = 'The cave north of the graveyard contains ' + hint_text(world.get_location(location, player).item) + '.' + this_hint = 'The cave north of the graveyard contains ' + hint_text( + world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint else: this_hint = location + ' contains ' + hint_text(world.get_location(location, player).item) + '.' @@ -1871,7 +2071,7 @@ def write_strings(rom, world, player, team): this_item = items_to_hint.pop(0) this_location = world.find_items(this_item, player) local_random.shuffle(this_location) - #This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool. + # This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool. if this_location: if this_location[0].name == 'Skull Woods - Pinball Room': this_location.pop(0) @@ -1888,19 +2088,21 @@ def write_strings(rom, world, player, team): # We still need the older hints of course. Those are done here. - silverarrows = world.find_items('Silver Bow', player) local_random.shuffle(silverarrows) - silverarrow_hint = (' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' + silverarrow_hint = ( + ' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint prog_bow_locs = world.find_items('Progressive Bow', player) distinguished_prog_bow_loc = next((location for location in prog_bow_locs if location.item.code == 0x65), None) - progressive_silvers = world.difficulty_requirements[player].progressive_bow_limit >= 2 or (world.swords[player] == 'swordless' or world.logic[player] == 'noglitches') + progressive_silvers = world.difficulty_requirements[player].progressive_bow_limit >= 2 or ( + world.swords[player] == 'swordless' or world.logic[player] == 'noglitches') if distinguished_prog_bow_loc: prog_bow_locs.remove(distinguished_prog_bow_loc) - silverarrow_hint = (' %s?' % hint_text(distinguished_prog_bow_loc).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' + silverarrow_hint = (' %s?' % hint_text(distinguished_prog_bow_loc).replace('Ganon\'s', + 'my')) if progressive_silvers else '?\nI think not!' tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint if any(prog_bow_locs): @@ -1908,31 +2110,32 @@ def write_strings(rom, world, player, team): 'my')) if progressive_silvers else '?\nI think not!' tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint - crystal5 = world.find_items('Crystal 5', player)[0] crystal6 = world.find_items('Crystal 6', player)[0] - tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5.hint_text, crystal6.hint_text) + tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % ( + crystal5.hint_text, crystal6.hint_text) greenpendant = world.find_items('Green Pendant', player)[0] tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text - tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[ - player] == 1 else 'You need %d crystals to enter.') % \ - world.crystals_needed_for_gt[player] + if world.crystals_needed_for_gt[player] == 1: + tt['sign_ganons_tower'] = 'You need a crystal to enter.' + else: + tt['sign_ganons_tower'] = f'You need {world.crystals_needed_for_gt[player]} crystals to enter.' if world.goal[player] == 'dungeons': tt['sign_ganon'] = 'You need to complete all the dungeons.' elif world.goal[player] == 'ganonpedestal': tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.' - elif world.goal[player] == "ganon": + elif world.goal[player] == "ganon": if world.crystals_needed_for_ganon[player] == 1: - tt['sign_ganon'] = 'You need 1 crystal to beat Ganon and have beaten Agahnim atop Ganons Tower.' + tt['sign_ganon'] = 'You need a crystal to beat Ganon and have beaten Agahnim atop Ganons Tower.' else: tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \ f'have beaten Agahnim atop Ganons Tower' else: if world.crystals_needed_for_ganon[player] == 1: - tt['sign_ganon'] = 'You need 1 crystal to beat Ganon.' + tt['sign_ganon'] = 'You need a crystal to beat Ganon.' else: tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon.' @@ -1987,15 +2190,18 @@ def write_strings(rom, world, player, team): tt['kakariko_tavern_fisherman'] = TavernMan_texts[local_random.randint(0, len(TavernMan_texts) - 1)] pedestalitem = world.get_location('Master Sword Pedestal', player).item - pedestal_text = 'Some Hot Air' if pedestalitem is None else hint_text(pedestalitem, True) if pedestalitem.pedestal_hint_text is not None else 'Unknown Item' + pedestal_text = 'Some Hot Air' if pedestalitem is None else hint_text(pedestalitem, + True) if pedestalitem.pedestal_hint_text is not None else 'Unknown Item' tt['mastersword_pedestal_translated'] = pedestal_text pedestal_credit_text = 'and the Hot Air' if pedestalitem is None else pedestalitem.pedestal_credit_text if pedestalitem.pedestal_credit_text is not None else 'and the Unknown Item' etheritem = world.get_location('Ether Tablet', player).item - ether_text = 'Some Hot Air' if etheritem is None else hint_text(etheritem, True) if etheritem.pedestal_hint_text is not None else 'Unknown Item' + ether_text = 'Some Hot Air' if etheritem is None else hint_text(etheritem, + True) if etheritem.pedestal_hint_text is not None else 'Unknown Item' tt['tablet_ether_book'] = ether_text bombositem = world.get_location('Bombos Tablet', player).item - bombos_text = 'Some Hot Air' if bombositem is None else hint_text(bombositem, True) if bombositem.pedestal_hint_text is not None else 'Unknown Item' + bombos_text = 'Some Hot Air' if bombositem is None else hint_text(bombositem, + True) if bombositem.pedestal_hint_text is not None else 'Unknown Item' tt['tablet_bombos_book'] = bombos_text # inverted spawn menu changes @@ -2003,6 +2209,13 @@ def write_strings(rom, world, player, team): tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}" tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}" + for at, text in world.plando_texts[player].items(): + + if at not in tt: + raise Exception(f"No text target \"{at}\" found.") + else: + tt[at] = text + rom.write_bytes(0xE0000, tt.getBytes()) credits = Credits() @@ -2046,6 +2259,7 @@ def write_strings(rom, world, player, team): rom.write_bytes(0x181500, data) rom.write_bytes(0x76CC0, [byte for p in pointers for byte in [p & 0xFF, p >> 8 & 0xFF]]) + def set_inverted_mode(world, player, rom): rom.write_byte(snes_to_pc(0x0283E0), 0xF0) # residual portals rom.write_byte(snes_to_pc(0x02B34D), 0xF0) @@ -2156,7 +2370,7 @@ def set_inverted_mode(world, player, rom): rom.write_byte(0x1607C + 0x06, 0xF2) rom.write_int16(0x160CB + 2 * 0x06, 0x0000) rom.write_int16(0x16169 + 2 * 0x06, 0x0000) - rom.write_int16(snes_to_pc(0x02E87B), 0x00AE) # move flute splot 9 + rom.write_int16(snes_to_pc(0x02E87B), 0x00AE) # move flute spot 9 rom.write_int16(snes_to_pc(0x02E89D), 0x0610) rom.write_int16(snes_to_pc(0x02E8BF), 0x077E) rom.write_int16(snes_to_pc(0x02E8E1), 0x0672) @@ -2247,10 +2461,12 @@ def set_inverted_mode(world, player, rom): rom.write_byte(snes_to_pc(0x0280A6), 0xD0) rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05]) + def patch_shuffled_dark_sanc(world, rom, player): dark_sanc = world.get_region('Inverted Dark Sanctuary', player) dark_sanc_entrance = str([i for i in dark_sanc.entrances if i.parent_region.name != 'Menu'][0].name) - room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1] + room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = \ + door_addresses[dark_sanc_entrance][1] door_index = door_addresses[str(dark_sanc_entrance)][0] rom.write_byte(0x180241, 0x01) @@ -2260,6 +2476,7 @@ def patch_shuffled_dark_sanc(world, rom, player): rom.write_int16s(0x180253, [vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x]) rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00]) + InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main', 'Misery Mire': 'Misery Mire', 'Ice Palace': 'Ice Palace', @@ -2351,7 +2568,7 @@ OtherEntrances = {'Blinds Hideout': 'Blind\'s old house', 'Kings Grave': 'The northeastmost grave', 'Bonk Fairy (Light)': 'The rock pile near your home', 'Hookshot Fairy': 'The left paired cave on east DM', - 'Bonk Fairy (Dark)': 'The rock pile near the old bomb shop', + 'Bonk Fairy (Dark)': 'The rock pile near the old bomb shop', 'Dark Lake Hylia Fairy': 'The cave NE dark Lake Hylia', 'C-Shaped House': 'The NE house in Village of Outcasts', 'Dark Death Mountain Fairy': 'The SW cave on dark DM', diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 1d459c21..a8e4b1f7 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -11,7 +11,7 @@ def set_rules(world, player): if world.players > 1: locality_rules(world, player) if world.logic[player] == 'nologic': - logging.getLogger('').info( + logging.info( 'WARNING! Seeds generated under this logic often require major glitches and may be impossible!') if world.players == 1: world.get_region('Menu', player).can_reach_private = lambda state: True @@ -38,7 +38,7 @@ def set_rules(world, player): open_rules(world, player) inverted_rules(world, player) else: - raise NotImplementedError('Not implemented yet') + raise NotImplementedError(f'World state {world.mode[player]} is not implemented yet') if world.logic[player] == 'noglitches': no_glitches_rules(world, player) @@ -104,7 +104,7 @@ def mirrorless_path_to_castle_courtyard(world, player): else: queue.append((entrance.connected_region, new_path)) - raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player}") + raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player} ({world.get_player_names(player)})") def set_rule(spot, rule): spot.access_rule = rule @@ -179,6 +179,10 @@ def locality_rules(world, player): for location in world.get_locations(): if location.player != player: forbid_items_for_player(location, world.local_items[player], player) + if world.non_local_items[player]: + for location in world.get_locations(): + if location.player == player: + forbid_items_for_player(location, world.non_local_items[player], player) non_crossover_items = (item_name_groups["Small Keys"] | item_name_groups["Big Keys"] | progression_items) - { @@ -429,6 +433,7 @@ def global_rules(world, player): def default_rules(world, player): + """Default world rules when world state is not inverted.""" # overworld requirements set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) @@ -444,9 +449,9 @@ def default_rules(world, player): set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Death Mountain Entrance Rock', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Bumper Cave Entrance Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.has('Flute', player)) + set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.has('Flute', player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.can_flute(player) and state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) # bunny cannot lift bushes @@ -500,7 +505,7 @@ def default_rules(world, player): set_rule(world.get_entrance('East Dark World Bridge', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player)) set_rule(world.get_entrance('Lake Hylia Island Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player) and state.has('Flippers', player)) set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # ToDo any fake flipper set up? + set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) set_rule(world.get_entrance('Graveyard Ledge Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.has_Pearl(player) and state.can_lift_rocks(player)) set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -626,7 +631,7 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('East Dark World Bridge', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Flippers', player)) # ToDo any fake flipper set up? (Qirn Jump) + set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hammer Peg Area Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -690,14 +695,8 @@ def inverted_rules(world, player): swordless_rules(world, player) def no_glitches_rules(world, player): - if world.mode[player] != 'inverted': - set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) - set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to - set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) - else: + """""" + if world.mode[player] == 'inverted': set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player))) set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to @@ -708,6 +707,13 @@ def no_glitches_rules(world, player): set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('East Dark World Pier', player), lambda state: state.has('Flippers', player)) + else: + set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) + set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to + set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override @@ -715,14 +721,7 @@ def no_glitches_rules(world, player): add_conditional_lamps(world, player) def fake_flipper_rules(world, player): - if world.mode[player] != 'inverted': - set_rule(world.get_entrance('Zoras River', player), lambda state: True) - set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: True) - set_rule(world.get_entrance('Hobo Bridge', player), lambda state: True) - set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player)) - set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player)) - else: + if world.mode[player] == 'inverted': set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player)) set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player)) set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has_Pearl(player)) @@ -733,6 +732,17 @@ def fake_flipper_rules(world, player): set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: True) set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: True) set_rule(world.get_entrance('East Dark World Pier', player), lambda state: True) + #qirn jump + set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: True) + else: + set_rule(world.get_entrance('Zoras River', player), lambda state: True) + set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: True) + set_rule(world.get_entrance('Hobo Bridge', player), lambda state: True) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player)) + #qirn jump + set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has_Pearl(player)) def forbid_bomb_jump_requirements(world, player): @@ -1117,52 +1127,52 @@ def set_big_bomb_rules(world, player): elif bombshop_entrance.name in Isolated_DW_entrances: # 1. mirror then flute then basic routes # -> M and Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.has('Flute', player) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.can_flute(player) and basic_routes(state)) elif bombshop_entrance.name in Isolated_LW_entrances: # 1. flute then basic routes # Prexisting mirror spot is not permitted, because mirror might have been needed to reach these isolated locations. # -> Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and basic_routes(state)) elif bombshop_entrance.name in West_LW_DM_entrances: # 1. flute then basic routes or mirror # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly. # -> Flute and (M or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and (state.has_Mirror(player) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and (state.has_Mirror(player) or basic_routes(state))) elif bombshop_entrance.name in East_LW_DM_entrances: # 1. flute then basic routes or mirror and hookshot # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly and then east DM via Hookshot # -> Flute and ((M and Hookshot) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state))) elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)': # Same as East_LW_DM_entrances except navigation without BR requires Mitts # -> Flute and ((M and Hookshot and Mitts) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state))) elif bombshop_entrance.name in Castle_ledge_entrances: # 1. mirror on pyramid to castle ledge, grab bomb, return through mirror spot: Needs mirror # 2. flute then basic routes # -> M or (Flute and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.has('Flute', player) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.can_flute(player) and basic_routes(state))) elif bombshop_entrance.name in Desert_mirrorable_ledge_entrances: # Cases when you have mire access: Mirror to reach locations, return via mirror spot, move to center of desert, mirror anagin and: # 1. Have mire access, Mirror to reach locations, return via mirror spot, move to center of desert, mirror again and then basic routes # 2. flute then basic routes # -> (Mire access and M) or Flute) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.has('Flute', player)) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.can_flute(player)) and basic_routes(state)) elif bombshop_entrance.name == 'Old Man Cave (West)': # 1. Lift rock then basic_routes # 2. flute then basic_routes # -> (Flute or G) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or state.can_lift_rocks(player)) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or state.can_lift_rocks(player)) and basic_routes(state)) elif bombshop_entrance.name == 'Graveyard Cave': # 1. flute then basic routes # 2. (has west dark world access) use existing mirror spot (required Pearl), mirror again off ledge # -> (Flute or (M and P and West Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name in Mirror_from_SDW_entrances: # 1. flute then basic routes # 2. (has South dark world access) use existing mirror spot, mirror again off ledge # -> (Flute or (M and South Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name == 'Dark World Potion Shop': # 1. walk down by lifting rock: needs gloves and pearl` # 2. walk down by hammering peg: needs hammer and pearl @@ -1174,11 +1184,11 @@ def set_big_bomb_rules(world, player): # (because otherwise mirror was used to reach the grave, so would cancel a pre-existing mirror spot) # to account for insanity, must consider a way to escape without a cave for basic_routes # -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.has('Flute', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) elif bombshop_entrance.name == 'Waterfall of Wishing': # same as the Normal_LW_entrances case except in insanity it's possible you could be here without Flippers which # means you need an escape route of either Flippers or Flute - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.has('Flute', player)) and (basic_routes(state) or state.has_Mirror(player))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player)) and (basic_routes(state) or state.has_Mirror(player))) def set_inverted_big_bomb_rules(world, player): diff --git a/worlds/alttp/Text.py b/worlds/alttp/Text.py index 93452608..a2fd674c 100644 --- a/worlds/alttp/Text.py +++ b/worlds/alttp/Text.py @@ -37,7 +37,6 @@ Uncle_texts = [ "Chasing tail.\nFly ladies.\nDo not follow.", "I feel like\nI've done this\nbefore…", "Magic Cape can\npass through\nthe barrier!", - "If this is a\nKanzeon seed,\nI'm quitting.", "I am not your\nreal uncle.", "You're going\nto have a very\nbad time.", "Today you\nwill have\nbad luck.", @@ -111,7 +110,6 @@ Triforce_texts = [ "\n We did it!", "\n O M G", " Hello. Will\n you be my\n friend?", - " Berserker\n was\n here!", "The Wind Fish\nwill wake\nsoon. Hoot!", "Meow meow meow\nMeow meow meow\n Oh my god!", "Ahhhhhhhhh\nYa ya yaaaah\nYa ya yaaah", @@ -175,6 +173,7 @@ Blind_texts = [ "Do I like\ndrills? Just\na bit.", "I'd shell out\ngood rupees\nfor a conch.", "Current\naffairs are\nshocking!", + "Agriculture\nis a growing\nfield." ] Ganon1_texts = [ "Start your day\nsmiling with a\ndelicious\nwhole grain\nbreakfast\ncreated for\nyour\nincredible\ninsides.", @@ -259,6 +258,16 @@ TavernMan_texts = [ ] junk_texts = [ + "{C:GREEN}\nAgitha's good\nin Hyrule\nWarriors. >", + "{C:GREEN}\nConsult Fi if\nthe batteries\nare low. >", + "{C:GREEN}\nThere is no\n3rd quest in\nthis game. >", + "{C:GREEN}\nI am Error.\n \n >", + "{C:GREEN}\nThe Wind Fish\nknows all in\nhere. Hoot! >", + "{C:GREEN}\nThere are no\nwallets in\nthis game. >", + "{C:GREEN}\nCrossbow\nTraining is\na fun game. >", + "{C:GREEN}\nThe shrine\ncontains\nMagnesis. >", + "{C:GREEN}\nThe loftwing\nlet the duck\ntake over. >", + "{C:GREEN}\nStasis would\nbe very\noverpowered.>", "{C:GREEN}\nIt’s a secret\nto everybody.\n >", "{C:GREEN}\nDodongo\ndislikes\nsmoke. >", "{C:GREEN}\n> Digdogger\nhates certain\nkind of sound.", @@ -266,7 +275,7 @@ junk_texts = [ "{C:GREEN}\n>Secret power\nis said to be\nin the arrow.", "{C:GREEN}\nAim at the\neyes of Gohma.\n >", "{C:GREEN}\nGrumble,\ngrumble…\n >", - "{C:GREEN}\n10th enemy\nhas the bomb.\n >", + # "{C:GREEN}\n10th enemy\nhas the bomb.\n >", removed as people may assume it applies to this game "{C:GREEN}\nGo to the\nnext room.\n >", "{C:GREEN}\n>Thanks, @\nYou’re the\nhero of Hyrule", "{C:GREEN}\nThere’s always\nmoney in the\nBanana Stand>", @@ -290,9 +299,6 @@ junk_texts = [ "{C:GREEN}\nMist Form\nis in the\nCatacombs. >", "{C:GREEN}\nMaybe you\ncould hire a\ndetective? >", "{C:GREEN}\n> READ\nor the owl\nwill eat you.", - "{C:GREEN}\n> Bunnies\nare cute.", - "{C:GREEN}\nPugs are the\nsuperior dog\nbreed. >", - "{C:GREEN}\nThis is\nBerserker's\nMultiworld.>", "{C:GREEN}\nOther randos\nexist too!\nTry some! >", ] @@ -1229,7 +1235,8 @@ class GoldCreditMapper(CharTextMapper): class GreenCreditMapper(CharTextMapper): char_map = {' ': 0x9F, - '·': 0x52} + '·': 0x52, + '.': 0x52} alpha_offset = -0x29 class RedCreditMapper(CharTextMapper): @@ -1279,6 +1286,7 @@ class LargeCreditBottomMapper(CharTextMapper): class TextTable(object): SIZE = 0x7355 + def __init__(self): self._text = OrderedDict() self.setDefaultText() @@ -1286,6 +1294,9 @@ class TextTable(object): def __getitem__(self, key): return self._text[key] + def __contains__(self, key): + return key in self._text + def __setitem__(self, key, value): if not key in self._text: raise KeyError(key) @@ -1504,7 +1515,7 @@ class TextTable(object): text['tutorial_guard_4'] = CompressedTextMapper.convert("When you has a sword, press B to slash it.") text['tutorial_guard_5'] = CompressedTextMapper.convert("このメッセージはニホンゴでそのまま") # on purpose text['tutorial_guard_6'] = CompressedTextMapper.convert("Are we really still reading these?") - text['tutorial_guard_7'] = CompressedTextMapper.convert("Jeeze! There really are a lot of things.") + text['tutorial_guard_7'] = CompressedTextMapper.convert("Jeez! There really are a lot of things.") text['priest_sanctuary_before_leave'] = CompressedTextMapper.convert("Go be a hero!") text['sanctuary_enter'] = CompressedTextMapper.convert("YAY!\nYou saved Zelda!") text['zelda_sanctuary_story'] = CompressedTextMapper.convert("Do you want to hear me say this again?\n{HARP}\n ≥ no\n yes\n{CHOICE}") @@ -1664,12 +1675,12 @@ class TextTable(object): text['tavern_old_man_know_tree_unactivated_flute'] = CompressedTextMapper.convert("You should play that flute for the weathervane, cause reasons.") text['tavern_old_man_have_flute'] = CompressedTextMapper.convert("Life? Love? Happiness? The question you should really ask is: Was this generated by Stoops Alu or Stoops Jet?") text['chicken_hut_lady'] = CompressedTextMapper.convert("This is\nChristos' hut.\n\nHe's out, searching for a bow.") - text['running_man'] = CompressedTextMapper.convert("Hi, Do you\nknow Veetorp?\n\nYou really\nshould. And\nall the other great guys who made this possible.\nGo thank them.\n\n\nIf you can catch them…") + text['running_man'] = CompressedTextMapper.convert("Catch me,\nIf you can!") text['game_race_sign'] = CompressedTextMapper.convert("Why are you reading this sign? Run!!!") text['sign_bumper_cave'] = CompressedTextMapper.convert("You need Cape, but not Hookshot") text['sign_catfish'] = CompressedTextMapper.convert("toss rocks\ntoss items\ntoss cookies") text['sign_north_village_of_outcasts'] = CompressedTextMapper.convert("↑ Skull Woods\n\n↓ Steve's Town") - text['sign_south_of_bumper_cave'] = CompressedTextMapper.convert("\n→ Karkats cave") + text['sign_south_of_bumper_cave'] = CompressedTextMapper.convert("\n→ Dark Sanctuary") text['sign_east_of_pyramid'] = CompressedTextMapper.convert("\n→ Dark Palace") text['sign_east_of_bomb_shop'] = CompressedTextMapper.convert("\n← Bomb Shoppe") text['sign_east_of_mire'] = CompressedTextMapper.convert("\n← Misery Mire\n no way in.\n no way out.") @@ -1874,7 +1885,7 @@ class TextTable(object): 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_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 copywight 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_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…") text['shop_darkworld_enter'] = CompressedTextMapper.convert("It's dangerous outside, buy my crap for safety.") @@ -1897,7 +1908,7 @@ class TextTable(object): text['ganon_fall_in'] = CompressedTextMapper.convert("You drove\naway my other\nself, Agahnim,\ntwo times…\nBut, I won't\ngive you the\nTriforce.\nI'll defeat\nyou!") # 170 text['ganon_phase_3'] = CompressedTextMapper.convert("Can you beat\nmy darkness\ntechnique?") - text['lost_woods_thief'] = CompressedTextMapper.convert("Have you seen Andy?\n\nHe was out looking for our prized Ether medallion.\nI wonder when he will be back?") + text['lost_woods_thief'] = CompressedTextMapper.convert("Did you just vent?") text['blinds_hut_dude'] = CompressedTextMapper.convert("I'm just some dude. This is Blind's hut.") text['end_triforce'] = CompressedTextMapper.convert("{SPEED2}\n{MENU}\n{NOBORDER}\n G G") text['toppi_fallen'] = CompressedTextMapper.convert("Ouch!\n\nYou Jerk!") @@ -1906,7 +1917,7 @@ class TextTable(object): text['thief_desert_rupee_cave'] = CompressedTextMapper.convert("So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees.") text['thief_ice_rupee_cave'] = CompressedTextMapper.convert("I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am.") text['telepathic_tile_south_east_darkworld_cave'] = CompressedTextMapper.convert("~~ dev cave ~~\n no farming\n required") - text['cukeman'] = CompressedTextMapper.convert("Did you hear that Veetorp beat ajneb174 in a 1 on 1 race at AGDQ?") + text['cukeman'] = CompressedTextMapper.convert("Hey mon!") text['cukeman_2'] = CompressedTextMapper.convert("You found Shabadoo, huh?\nNiiiiice.") text['potion_shop_no_cash'] = CompressedTextMapper.convert("Yo! I'm not running a charity here.") text['kakariko_powdered_chicken'] = CompressedTextMapper.convert("Smallhacker…\n\n\nWas hiding, you found me!\n\n\nOkay, you can leave now.")