From e955fc02d879af919656483defd4ee8281e83cd9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Dec 2023 13:04:24 -0600 Subject: [PATCH 01/35] Implement Return Old Man already in starting inventory --- InitialSram.py | 6 +++++- ItemList.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/InitialSram.py b/InitialSram.py index d7d79a18..a33f2454 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -114,6 +114,9 @@ class InitialSram: equip[0x37B] = 1 equip[0x36E] = 0x80 + if startingstate.has('Return Old Man', player): + self._initial_sram_bytes[0x410] = self._initial_sram_bytes[0x410] | 0x01 + for item in world.precollected_items: if item.player != player: continue @@ -123,7 +126,8 @@ class InitialSram: 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', - 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', + 'Return Old Man']: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), diff --git a/ItemList.py b/ItemList.py index fcbffadf..21d3b446 100644 --- a/ItemList.py +++ b/ItemList.py @@ -227,6 +227,12 @@ def generate_itempool(world, player): loc.locked = True loc.forced_item = loc.item + if 'Return Old Man' in list(map(str, [i for i in world.precollected_items if i.player == player])): + old_man = world.get_location('Old Man', player) + world.push_item(old_man, ItemFactory('Nothing', player), False) + old_man.forced_item = old_man.item + old_man.skip = True + world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) From a6f244cd01ac82bccffdafcfe0fb501828159553 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Dec 2023 13:04:24 -0600 Subject: [PATCH 02/35] Implement Return Old Man already in starting inventory --- InitialSram.py | 6 +++++- ItemList.py | 6 ++++++ Rules.py | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/InitialSram.py b/InitialSram.py index d7d79a18..a33f2454 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -114,6 +114,9 @@ class InitialSram: equip[0x37B] = 1 equip[0x36E] = 0x80 + if startingstate.has('Return Old Man', player): + self._initial_sram_bytes[0x410] = self._initial_sram_bytes[0x410] | 0x01 + for item in world.precollected_items: if item.player != player: continue @@ -123,7 +126,8 @@ class InitialSram: 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', - 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', + 'Return Old Man']: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), diff --git a/ItemList.py b/ItemList.py index fcbffadf..21d3b446 100644 --- a/ItemList.py +++ b/ItemList.py @@ -227,6 +227,12 @@ def generate_itempool(world, player): loc.locked = True loc.forced_item = loc.item + if 'Return Old Man' in list(map(str, [i for i in world.precollected_items if i.player == player])): + old_man = world.get_location('Old Man', player) + world.push_item(old_man, ItemFactory('Nothing', player), False) + old_man.forced_item = old_man.item + old_man.skip = True + world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) diff --git a/Rules.py b/Rules.py index dc7463ac..e8b8a5fa 100644 --- a/Rules.py +++ b/Rules.py @@ -1541,6 +1541,7 @@ def standard_rules(world, player): world.get_entrance('Uncle S&Q', player).hide_path = True set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.has('Zelda Delivered', player)) set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.has('Zelda Delivered', player)) + add_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.has('Zelda Delivered', player)) # these are because of rails if world.shuffle[player] != 'vanilla': # where ever these happen to be From 5983629d6b803f97495b7da0c4665fbc97fb1394 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Dec 2023 13:24:47 -0600 Subject: [PATCH 03/35] Fixed issue with Starting Inventory not printing to Spoiler --- Main.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Main.py b/Main.py index 0fae0394..8c8e3a16 100644 --- a/Main.py +++ b/Main.py @@ -204,6 +204,13 @@ def main(args, seed=None, fish=None): if item: world.push_precollected(item) + if world.customizer and world.customizer.get_start_inventory(): + for p, inv_list in world.customizer.get_start_inventory().items(): + for inv_item in inv_list: + item = ItemFactory(inv_item.strip(), p) + if item: + world.push_precollected(item) + if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -221,12 +228,6 @@ def main(args, seed=None, fish=None): adjust_locations(world, player) place_bosses(world, player) - if world.customizer and world.customizer.get_start_inventory(): - for p, inv_list in world.customizer.get_start_inventory().items(): - for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) - if item: - world.push_precollected(item) if args.print_custom_yaml: world.settings.record_info(world) From f954907af867ee85787dde183e7421a80ffe230f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 9 Dec 2023 16:22:25 -0600 Subject: [PATCH 04/35] Implement Beat Agahnim 1 already in starting inventory --- InitialSram.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/InitialSram.py b/InitialSram.py index a33f2454..7f0f2c89 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -115,7 +115,13 @@ class InitialSram: equip[0x36E] = 0x80 if startingstate.has('Return Old Man', player): - self._initial_sram_bytes[0x410] = self._initial_sram_bytes[0x410] | 0x01 + self._initial_sram_bytes[0x410] |= 0x01 + + if startingstate.has('Beat Agahnim 1', player): + if world.mode[player] == 'standard': + self._initial_sram_bytes[0x3C5] = 0x80 + else: + self._initial_sram_bytes[0x3C5] = 0x03 for item in world.precollected_items: if item.player != player: @@ -127,7 +133,7 @@ class InitialSram: 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', - 'Return Old Man']: + 'Return Old Man', 'Beat Agahnim 1']: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), From 1d0045624b2b59dad574dd72ddaabf3ff0605128 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 12 Dec 2023 23:28:34 -0600 Subject: [PATCH 05/35] Remove song instruments output file --- AdjusterMain.py | 7 ------- Main.py | 3 --- source/classes/SFX.py | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/AdjusterMain.py b/AdjusterMain.py index a3a08924..2bb071f3 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -10,7 +10,6 @@ except ImportError: from Utils import output_path from Rom import LocalRom, apply_rom_settings -from source.classes.SFX import output_song_data from source.tools.BPS import bps_read_vlv @@ -39,9 +38,6 @@ def adjust(args): output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) - if args.shuffle_songinstruments: - output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfilebase) - logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) @@ -77,9 +73,6 @@ def patch(args): output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfile_base)) - if args.shuffle_songinstruments: - output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfile_base) - logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) diff --git a/Main.py b/Main.py index 8c8e3a16..bda0e6c9 100644 --- a/Main.py +++ b/Main.py @@ -35,7 +35,6 @@ from source.item.FillUtil import create_item_pool_config, massage_item_pool, dis from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -from source.classes.SFX import output_song_data version_number = '1.2.0.22' version_branch = '-u' @@ -406,8 +405,6 @@ def main(args, seed=None, fish=None): if world.players > 1 or world.teams > 1: outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else '' outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else '' - if args.shuffle_songinstruments: - output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfilebase) if args.bps: patchfile = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.bps') patch = create_bps_from_data(LocalRom(args.rom, patch=False).buffer, rom.buffer) diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 4b6aa4ed..08085aff 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -263,7 +263,7 @@ def output_song_data(rom, filepath, outfilebase): if tracks > 0: outfile.write(' ' * tracks) outfile.write(f' = {rom.read_byte(snes_to_pc(next(iter(change.tracks.values()))[0])):02X}') - + def randomize_songinstruments(rom): # categorize instruments in pools From 9820f79b0b90af1c0561da581e3e429a481624c1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 12 Dec 2023 23:29:12 -0600 Subject: [PATCH 06/35] Fixed minor incorrect Entrance data --- source/overworld/EntranceShuffle2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 4c96c10a..7e34f711 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -2659,7 +2659,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000), 0x00), 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000), 0x00), 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), - 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000), 0x00), From 159dbcd8dba2c3c1c188c19911682adf24e8ca11 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 13 Dec 2023 14:02:43 -0600 Subject: [PATCH 07/35] Replacing Save Settings on Exit with Settings on Load --- CLI.py | 8 ++- Gui.py | 51 ++++++++++--------- resources/app/cli/args.json | 8 +-- resources/app/gui/lang/en.json | 8 +-- .../app/gui/randomize/generation/widgets.json | 8 +-- source/classes/constants.py | 2 +- source/gui/bottom.py | 9 ++++ 7 files changed, 56 insertions(+), 38 deletions(-) diff --git a/CLI.py b/CLI.py index 5087eabf..4cd17834 100644 --- a/CLI.py +++ b/CLI.py @@ -355,7 +355,7 @@ def parse_settings(): }, "randomSprite": False, "outputpath": os.path.join("."), - "saveonexit": "ask", + "settingsonload": "saved", "outputname": "", "startinventoryarray": {}, "notes": "" @@ -367,6 +367,12 @@ def parse_settings(): # read saved settings file if it exists and set these settings_path = os.path.join(".", "resources", "user", "settings.json") settings = apply_settings_file(settings, settings_path) + if settings["settingsonload"] == "saved": + settings_path = os.path.join(".", "resources", "user", "saved.json") + settings = apply_settings_file(settings, settings_path) + elif settings["settingsonload"] == "lastused": + settings_path = os.path.join(".", "resources", "user", "last.json") + settings = apply_settings_file(settings, settings_path) return settings # Priority fallback is: diff --git a/Gui.py b/Gui.py index a0f4726f..4aecfa03 100755 --- a/Gui.py +++ b/Gui.py @@ -37,19 +37,31 @@ def check_python_version(fish): messagebox.showinfo("Door Shuffle " + ESVersion, fish.translate("cli","cli","old.python.version") % sys.version) -def guiMain(args=None): - # Save settings to file - def save_settings(args): - user_resources_path = os.path.join(".", "resources", "user") - settings_path = os.path.join(user_resources_path) - if not os.path.exists(settings_path): - os.makedirs(settings_path) - for widget in self.pages["adjust"].content.widgets: - args["adjust." + widget] = self.pages["adjust"].content.widgets[widget].storageVar.get() - with open(os.path.join(settings_path, "settings.json"), "w+") as f: - f.write(json.dumps(args, indent=2)) - os.chmod(os.path.join(settings_path, "settings.json"),0o755) +# Save settings to file +def save_settings(gui, args, filename): + user_resources_path = os.path.join(".", "resources", "user") + settings_path = os.path.join(user_resources_path) + if not os.path.exists(settings_path): + os.makedirs(settings_path) + output_args = {} + settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", + "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", + "rom", "enemizercli", "outputpath"] + if filename == "settings.json": + for s in settings: + output_args[s] = args[s] + for widget in gui.pages["adjust"].content.widgets: + output_args["adjust." + widget] = gui.pages["adjust"].content.widgets[widget].storageVar.get() + else: + for k, v in args.items(): + if k not in settings and not k.startswith("adjust."): + output_args[k] = v + with open(os.path.join(settings_path, filename), "w+") as f: + f.write(json.dumps(output_args, indent=2)) + os.chmod(os.path.join(settings_path, filename),0o755) + +def guiMain(args=None): # Save settings from GUI def save_settings_from_gui(confirm): gui_args = vars(create_guiargs(self)) @@ -57,23 +69,14 @@ def guiMain(args=None): gui_args['sprite'] = 'random' elif gui_args['sprite']: gui_args['sprite'] = gui_args['sprite'].name - save_settings(gui_args) + save_settings(self, gui_args, "saved.json") + save_settings(self, gui_args, "settings.json") if confirm: messagebox.showinfo("Overworld Shuffle " + ORVersion, "Settings saved from GUI.") # routine for exiting the app def guiExit(): - skip_exit = False - if self.settings['saveonexit'] == 'ask': - dosave = messagebox.askyesnocancel("Overworld Shuffle " + ORVersion, "Save settings before exit?") - if dosave: - save_settings_from_gui(True) - if dosave is None: - skip_exit = True - elif self.settings['saveonexit'] == 'always': - save_settings_from_gui(False) - if not skip_exit: - sys.exit(0) + sys.exit(0) # make main window # add program title & version number diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 8c21e15a..e723ee76 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -575,11 +575,11 @@ "action": "store_true", "type": "bool" }, - "saveonexit": { + "settingsonload": { "choices": [ - "ask", - "always", - "never" + "default", + "saved", + "lastused" ] }, "outputname": {}, diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 36ffe2fb..0ae493d2 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -236,10 +236,10 @@ "randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.print_custom_yaml": "Print Customizer File", - "randomizer.generation.saveonexit": "Save Settings on Exit", - "randomizer.generation.saveonexit.ask": "Ask Me", - "randomizer.generation.saveonexit.always": "Always", - "randomizer.generation.saveonexit.never": "Never", + "randomizer.generation.settingsonload": "Settings On Load", + "randomizer.generation.settingsonload.default": "Default", + "randomizer.generation.settingsonload.saved": "Saved", + "randomizer.generation.settingsonload.lastused": "Last Used", "randomizer.generation.rom": "Base Rom: ", "randomizer.generation.rom.button": "Select Rom", diff --git a/resources/app/gui/randomize/generation/widgets.json b/resources/app/gui/randomize/generation/widgets.json index 22f9decc..a410f227 100644 --- a/resources/app/gui/randomize/generation/widgets.json +++ b/resources/app/gui/randomize/generation/widgets.json @@ -1,11 +1,11 @@ { "widgets": { - "saveonexit": { + "settingsonload": { "type": "selectbox", "options": [ - "ask", - "always", - "never" + "default", + "saved", + "lastused" ] } } diff --git a/source/classes/constants.py b/source/classes/constants.py index e3fbf1b1..c8e15532 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -148,7 +148,7 @@ SETTINGSTOPROCESS = { "createrom": "create_rom", "calcplaythrough": "calc_playthrough", "print_custom_yaml": "print_custom_yaml", - "saveonexit": "saveonexit" + "settingsonload": "settingsonload" } }, "startinventory": { diff --git a/source/gui/bottom.py b/source/gui/bottom.py index c53842d7..3d37908e 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -68,6 +68,15 @@ def bottom_frame(self, parent, args=None): self.widgets[key].pack(side=LEFT) def generateRom(): + guiargs = create_guiargs(parent) + argsDump = vars(guiargs) + from Gui import save_settings + if parent.randomSprite.get(): + argsDump['sprite'] = 'random' + elif argsDump['sprite']: + argsDump['sprite'] = argsDump['sprite'].name + save_settings(parent, argsDump, "last.json") + guiargs = create_guiargs(parent) # get default values for missing parameters for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): From 0e0df126777039ccaae01c8923013de069a3eddf Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 14 Dec 2023 15:12:48 -0600 Subject: [PATCH 08/35] Added new option to export pre-seed customizer template yaml --- CLI.py | 5 ++- Gui.py | 4 +- Main.py | 24 +++++------ docs/Customizer.md | 6 ++- resources/app/gui/lang/en.json | 1 + .../gui/randomize/generation/checkboxes.json | 1 + source/classes/CustomSettings.py | 41 ++++++++++++------- source/classes/constants.py | 1 + 8 files changed, 51 insertions(+), 32 deletions(-) diff --git a/CLI.py b/CLI.py index 4cd17834..e0039b65 100644 --- a/CLI.py +++ b/CLI.py @@ -34,7 +34,9 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--settingsfile', help="input json file of settings", type=str) parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--customizer', help='input yaml file for customizations', type=str) - parser.add_argument('--print_custom_yaml', help='print example yaml for current settings', + parser.add_argument('--print_template_yaml', help='print example yaml for current settings', + default=False, action="store_true") + parser.add_argument('--print_custom_yaml', help='print example plando yaml for current settings and placements', default=False, action="store_true") parser.add_argument('--mystery', dest="mystery", default=False, action="store_true") @@ -94,6 +96,7 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) parser.add_argument('--settingsfile', dest="filename", help="input json file of settings", type=str) parser.add_argument('--customizer', dest="customizer", help='input yaml file for customizations', type=str) + parser.add_argument('--print_template_yaml', dest="print_template_yaml", default=False, action="store_true") parser.add_argument('--print_custom_yaml', dest="print_custom_yaml", default=False, action="store_true") if player_num: diff --git a/Gui.py b/Gui.py index 4aecfa03..0dcdaa75 100755 --- a/Gui.py +++ b/Gui.py @@ -45,8 +45,8 @@ def save_settings(gui, args, filename): os.makedirs(settings_path) output_args = {} settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", - "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", - "rom", "enemizercli", "outputpath"] + "calc_playthrough", "skip_playthrough", "print_template_yaml", "print_custom_yaml", + "settingsonload", "rom", "enemizercli", "outputpath"] if filename == "settings.json": for s in settings: output_args[s] = args[s] diff --git a/Main.py b/Main.py index bda0e6c9..afcb1568 100644 --- a/Main.py +++ b/Main.py @@ -183,8 +183,6 @@ def main(args, seed=None, fish=None): for player, name in enumerate(team, 1): world.player_names[player].append(name) logger.info('') - world.settings = CustomSettings() - world.settings.create_from_world(world, args) outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' @@ -210,6 +208,12 @@ def main(args, seed=None, fish=None): if item: world.push_precollected(item) + world.settings = CustomSettings() + world.settings.create_from_world(world, args) + + if args.print_template_yaml: + world.settings.record_item_pool(world, True) + world.settings.write_to_file(output_path(f'{outfilebase}_template.yaml')) if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -227,9 +231,6 @@ def main(args, seed=None, fish=None): adjust_locations(world, player) place_bosses(world, player) - if args.print_custom_yaml: - world.settings.record_info(world) - if any(world.potshuffle.values()): logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) for player in range(1, world.players + 1): @@ -247,8 +248,6 @@ def main(args, seed=None, fish=None): update_world_regions(world, player) mark_light_dark_world_regions(world, player) create_dynamic_exits(world, player) - if args.print_custom_yaml: - world.settings.record_overworld(world) init_districts(world) @@ -262,8 +261,6 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_doors_prep(world, player) - if args.print_custom_yaml: - world.settings.record_entrances(world) create_item_pool_config(world) logger.info(world.fish.translate("cli", "cli", "shuffling.dungeons")) @@ -271,8 +268,6 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_doors(world, player) mark_light_dark_world_regions(world, player) - if args.print_custom_yaml: - world.settings.record_doors(world) logger.info(world.fish.translate("cli", "cli", "generating.itempool")) @@ -300,8 +295,6 @@ def main(args, seed=None, fish=None): lock_shop_locations(world, player) massage_item_pool(world) - if args.print_custom_yaml: - world.settings.record_item_pool(world) logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) fill_prizes(world) @@ -351,6 +344,11 @@ def main(args, seed=None, fish=None): ensure_good_pots(world, True) if args.print_custom_yaml: + world.settings.record_info(world) + world.settings.record_overworld(world) + world.settings.record_entrances(world) + world.settings.record_doors(world) + world.settings.record_item_pool(world) world.settings.record_item_placements(world) world.settings.write_to_file(output_path(f'{outfilebase}_custom.yaml')) diff --git a/docs/Customizer.md b/docs/Customizer.md index 872003c5..3f3b31e9 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -6,11 +6,13 @@ This can also be used to roll a mystery or mutli-mystery seed via the GUI. [Exam The cli includes a couple arguments to help: +`--print_template_yaml` will create a yaml file based on the settings used. This does not contain any seed specific information. + `--print_custom_yaml` will create a yaml file based on the seed rolled. Treat it like a spoiler. `--customizer` takes a file as an argument. -Present on the GUI as `Print Customizer File` and `Customizer File` on the Generation Setup tab. +Present on the GUI as `Print Customizer Template`, `Print Customizer File`, and `Customizer File` on the Generation Setup tab. ### meta @@ -44,7 +46,7 @@ Start inventory is not supported here. It has a separate section. ###### Not Yet Implemented -Rom/Adjust flags like sprite, quickswap are not outputing with the print_custom_yaml settings +Rom/Adjust flags like sprite, quickswap are not outputing with the print_template_yaml or print_custom_yaml settings ### item_pool diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 0ae493d2..5d082636 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -234,6 +234,7 @@ "randomizer.generation.createspoiler": "Create Spoiler Log", "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", + "randomizer.generation.print_template_yaml": "Print Customizer Template", "randomizer.generation.print_custom_yaml": "Print Customizer File", "randomizer.generation.settingsonload": "Settings On Load", diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 6e377027..658ea7f0 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,6 +4,7 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, + "print_template_yaml":{ "type": "checkbox" }, "print_custom_yaml": { "type": "checkbox" } } } diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index a3402318..ba9c9422 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -262,10 +262,11 @@ class CustomSettings(object): self.world_rep['meta'] = meta_dict meta_dict['players'] = world.players meta_dict['algorithm'] = world.algorithm - meta_dict['seed'] = world.seed meta_dict['race'] = settings.race meta_dict['user_notes'] = settings.notes self.world_rep['settings'] = settings_dict + if world.precollected_items: + self.world_rep['start_inventory'] = start_inv = {} for p in self.player_range: settings_dict[p] = {} settings_dict[p]['ow_shuffle'] = world.owShuffle[p] @@ -323,6 +324,10 @@ class CustomSettings(object): settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p] settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p] settings_dict[p]['beemizer'] = world.beemizer[p] + if world.precollected_items: + start_inv[p] = [] + for item in world.precollected_items: + start_inv[item.player].append(item.name) # rom adjust stuff # settings_dict[p]['sprite'] = world.sprite[p] @@ -335,34 +340,42 @@ class CustomSettings(object): # settings_dict[p]['ow_palettes'] = world.ow_palettes[p] # settings_dict[p]['uw_palettes'] = world.uw_palettes[p] # settings_dict[p]['shuffle_sfx'] = world.shuffle_sfx[p] + # settings_dict[p]['shuffle_songinstruments'] = world.shuffle_songinstruments[p] # more settings? def record_info(self, world): + self.world_rep['meta']['seed'] = world.seed self.world_rep['bosses'] = bosses = {} - self.world_rep['start_inventory'] = start_inv = {} + self.world_rep['medallions'] = medallions = {} for p in self.player_range: bosses[p] = {} - start_inv[p] = [] + medallions[p] = {} for dungeon in world.dungeons: for level, boss in dungeon.bosses.items(): location = dungeon.name if level is None else f'{dungeon.name} ({level})' if boss and 'Agahnim' not in boss.name: bosses[dungeon.player][location] = boss.name - for item in world.precollected_items: - start_inv[item.player].append(item.name) - - def record_item_pool(self, world): - self.world_rep['item_pool'] = item_pool = {} - self.world_rep['medallions'] = medallions = {} - for p in self.player_range: - item_pool[p] = defaultdict(int) - medallions[p] = {} - for item in world.itempool: - item_pool[item.player][item.name] += 1 for p, req_medals in world.required_medallions.items(): medallions[p]['Misery Mire'] = req_medals[0] medallions[p]['Turtle Rock'] = req_medals[1] + def record_item_pool(self, world, use_custom_pool=False): + if not use_custom_pool or world.custom: + self.world_rep['item_pool'] = item_pool = {} + for p in self.player_range: + if not use_custom_pool or p in world.customitemarray: + item_pool[p] = defaultdict(int) + if use_custom_pool and world.custom: + import source.classes.constants as CONST + for p in world.customitemarray: + for i, c in world.customitemarray[p].items(): + if c > 0: + item = CONST.CUSTOMITEMLABELS[CONST.CUSTOMITEMS.index(i)] + item_pool[p][item] += c + else: + for item in world.itempool: + item_pool[item.player][item.name] += 1 + def record_item_placements(self, world): self.world_rep['placements'] = placements = {} for p in self.player_range: diff --git a/source/classes/constants.py b/source/classes/constants.py index c8e15532..6bf5f55e 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -147,6 +147,7 @@ SETTINGSTOPROCESS = { "createspoiler": "create_spoiler", "createrom": "create_rom", "calcplaythrough": "calc_playthrough", + "print_template_yaml": "print_template_yaml", "print_custom_yaml": "print_custom_yaml", "settingsonload": "settingsonload" } From be18412cdd189c53e4699cf1dedc427d9fbcc9fa Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 15 Dec 2023 00:46:02 -0600 Subject: [PATCH 09/35] Some fixes to Song Instrument shuffle data --- source/classes/SFX.py | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 08085aff..825adef4 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -337,10 +337,10 @@ spc_instruments = { # table @ $19FB1C 0x02: SPCInstrument("Timpani", 0x02, [0xFF, 0xE0], 0xB8, 0x09C0).beat(), 0x03: SPCInstrument("Square Wave", 0x03, [0xFF, 0xE0], 0xB8, 0x0400).bass().mel(), 0x04: SPCInstrument("Saw Wave", 0x04, [0xFF, 0xE0], 0xB8, 0x0400).bass(), - 0x05: SPCInstrument("Clink", 0x05, [0xFF, 0xE0], 0xB8, 0x0470).amb(), + 0x05: SPCInstrument("Clink", 0x05, [0xFF, 0xE0], 0xB8, 0x0470), 0x06: SPCInstrument("Wobbly Lead", 0x06, [0xFF, 0xE0], 0xB8, 0x0470).amb(), 0x07: SPCInstrument("Compound Saw", 0x07, [0xFF, 0xE0], 0xB8, 0x0470), - 0x08: SPCInstrument("Tweet", 0x08, [0xFF, 0xE0], 0xB8, 0x07A0).amb(), + 0x08: SPCInstrument("Tweet", 0x08, [0xFF, 0xE0], 0xB8, 0x07A0).amb().beat(), 0x09: SPCInstrument("Strings A", 0x09, [0x8F, 0xE9], 0xB8, 0x01E0).mel().bass(True), 0x0A: SPCInstrument("Strings B", 0x0A, [0x8A, 0xE9], 0xB8, 0x01E0).mel().bass(True), 0x0B: SPCInstrument("Trombone", 0x0B, [0xFF, 0xE0], 0xB8, 0x0300).mel().bass(True).beat(), @@ -468,7 +468,7 @@ spc_instrument_changes = [ 0x02: [0x1ABBE6], 0x03: [0x1ABC0B]}, 0x09), InstrumentChange(0x07, 0x00, {0x04: [0x1ABB53, 0x1AB8C9]}, 0x16), - InstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x06]), + InstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x01, 0x05, 0x06, 0x17]), InstrumentChange(0x07, 0x01, {0x05: [0x1AB8EB]}, 0x0A), InstrumentChange(0x07, 0x02, {0x04: [0x1AB981]}, 0x16), InstrumentChange(0x07, 0x03, {0x02: [0x1ABC37], @@ -486,7 +486,7 @@ spc_instrument_changes = [ InstrumentChange(0x07, 0x06, {0x05: [0x1ABCE6]}, 0x0A), InstrumentChange(0x08, 0x00, {0x00: [0x1ABD3A], - 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am), + 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am, ban=[0x05]), InstrumentChange(0x08, 0x00, {0x02: [0x1ABD70], 0x05: [0x1ABE06]}, 0x0F), InstrumentChange(0x08, 0x00, {0x03: [0x1ABDAC]}, 0x0A), @@ -495,22 +495,22 @@ spc_instrument_changes = [ InstrumentChange(0x09, 0x00, {0x00: [0x1AC25A], 0x05: [0x1AC28E]}, 0x0A), - InstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14), - InstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14), + InstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + InstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x01, {0x06: [0x1ABF43]}, 0x09), InstrumentChange(0x09, 0x02, {0x00: [0x1AC450], 0x05: [0x1AC56D], 0x07: [0x1AC595]}, 0x0A), - InstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14), + InstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x02, {0x03: [0x1AC4B3], 0x04: [0x1AC510]}, 0x11), InstrumentChange(0x09, 0x02, {0x06: [0x1AC2E9]}, 0x09), InstrumentChange(0x09, 0x03, {0x00: [0x1ABF63]}, 0x0A), - InstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14), + InstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x03, {0x03: [0x1ABFA4]}, 0x11), InstrumentChange(0x09, 0x03, {0x05: [0x1AC01C]}, 0x16), InstrumentChange(0x09, 0x04, {0x00: [0x1AC04D]}, 0x0A), - InstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14), + InstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x04, {0x02: [0x1AC5CF]}, 0x18), InstrumentChange(0x09, 0x04, {0x03: [0x1AC085], 0x04: [0x1AC5ED]}, 0x11), @@ -518,20 +518,20 @@ spc_instrument_changes = [ InstrumentChange(0x09, 0x04, {0x06: [0x1AC146]}, 0x12), InstrumentChange(0x09, 0x05, {0x00: [0x1AC178], 0x07: [0x1AC229]}, 0x0A), - InstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14), + InstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x05, {0x02: [0x1AC19E]}, 0x18), InstrumentChange(0x09, 0x05, {0x03: [0x1AC1D3], 0x04: [0x1AC1F4]}, 0x12), InstrumentChange(0x09, 0x06, {0x00: [0x1AC317], 0x07: [0x1AC3ED]}, 0x0A), - InstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14), + InstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x06, {0x02: [0x1AC33A]}, 0x18), InstrumentChange(0x09, 0x06, {0x03: [0x1AC36F], 0x04: [0x1AC3A4], 0x05: [0x1AC3D9]}, 0x12), InstrumentChange(0x09, 0x07, {0x00: [0x1AC40A], 0x05: [0x1AC43C]}, 0x0A), - InstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14), + InstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x07, {0x02: [0x1AC492]}, 0x18), InstrumentChange(0x09, 0x07, {0x03: [0x1AC680], 0x04: [0x1AC6C1]}, 0x11), @@ -563,21 +563,21 @@ spc_instrument_changes = [ 0x05: [0x1AC87E]}, 0x0B), InstrumentChange(0x0C, 0x01, {0x02: [0x1AC89A], 0x03: [0x1AC8AD]}, 0x11), - InstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E), + InstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E, type=Rh|Am, ban=[0x05]), InstrumentChange(0x0C, 0x01, {0x05: [0x1AC8C3]}, 0x02), InstrumentChange(0x0C, 0x02, {0x02: [0x1AC8E0], 0x03: [0x1AC8F3]}, 0x11), - InstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E), + InstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E, type=Rh|Am, ban=[0x05]), InstrumentChange(0x0C, 0x02, {0x05: [0x1AC909]}, 0x02), InstrumentChange(0x0D, 0x00, {0x00: [0x1AD003], 0x03: [0x1AD02C], 0x04: [0x1AD03A]}, 0x11), - InstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02), + InstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), InstrumentChange(0x0D, 0x00, {0x02: [0x1AD07F]}, 0x14), InstrumentChange(0x0D, 0x01, {0x00: [0x1ACD10], 0x04: [0x1ACD9A]}, 0x0B), - InstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02), + InstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), InstrumentChange(0x0D, 0x01, {0x03: [0x1ACD7F], 0x05: [0x1ACDCA]}, 0x11), InstrumentChange(0x0D, 0x03, {0x00: [0x1ACE8E], @@ -585,7 +585,7 @@ spc_instrument_changes = [ InstrumentChange(0x0D, 0x03, {0x02: [0x1ACED4]}, 0x14), InstrumentChange(0x0D, 0x03, {0x03: [0x1ACEE0], 0x04: [0x1ACF07]}, 0x11), - InstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02), + InstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), InstrumentChange(0x0E, 0x00, {0x00: [0x1AD29C]}, 0x16), InstrumentChange(0x0E, 0x00, {0x01: [0x1AD2AD], @@ -710,7 +710,7 @@ spc_instrument_changes = [ InstrumentChange(0x13, 0x00, {0x06: [0x1B9650], 0x07: [0x1B9696]}, 0x0F), InstrumentChange(0x13, 0x00, {0x06: [0x1B967B], - 0x07: [0x1B96C0]}, 0x02), + 0x07: [0x1B96C0]}, 0x02, ban=[0x0B]), InstrumentChange(0x14, 0x00, {0x00: [0x1B9901, 0x1B97A8]}, 0x15), InstrumentChange(0x14, 0x01, {0x01: [0x1B97C4], @@ -723,13 +723,13 @@ spc_instrument_changes = [ 0x01: [0x1B9A50], 0x02: [0x1B9A6D], 0x03: [0x1B9A8A]}, 0x0B), - InstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02), - InstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02), + InstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + InstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), InstrumentChange(0x15, 0x01, {0x01: [0x1B9984], 0x02: [0x1B99AA], 0x03: [0x1B99D7]}, 0x0B), InstrumentChange(0x15, 0x01, {0x04: [0x1B9A04]}, 0x14), - InstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02), + InstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), InstrumentChange(0x15, 0x02, {0x01: [0x1B9B58], 0x02: [0x1B9B7E], 0x03: [0x1B9BAB]}, 0x0B), @@ -749,10 +749,10 @@ spc_instrument_changes = [ 0x05: [0x1B9EE4]}, 0x09), InstrumentChange(0x17, 0x00, {0x00: [0x1BA287], - 0x03: [0x1BA26A]}, 0x0E, type=Am|Rh, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + 0x03: [0x1BA26A]}, 0x0E, type=[0x0E, 0x0F, 0x18]), InstrumentChange(0x17, 0x01, {0x01: [0x1BA20F], - 0x02: [0x1BA231], - 0x03: [0x1BA24F]}, 0x0E, type=Am|Rh, ban=[0x01, 0x05, 0x06, 0x10]), + 0x03: [0x1BA24F]}, 0x0E, type=Rh, ban=[0x0B, 0x0D, 0x16]), + InstrumentChange(0x17, 0x01, {0x02: [0x1BA231]}, 0x0F), InstrumentChange(0x19, 0x00, {0x00: [0x1BA476], 0x01: [0x1BA49C], @@ -775,7 +775,7 @@ spc_instrument_changes = [ 0x04: [0x1BA783], 0x05: [0x1BA7A2], 0x06: [0x1BA7B9], - 0x07: [0x1BA7D0]}, 0x0E), + 0x07: [0x1BA7D0]}, 0x0E, type=Rh|Am, ban=[0x01, 0x05, 0x06, 0x10]), InstrumentChange(0x1A, 0x01, {0x00: [0x1BA5C1], 0x05: [0x1BA68F], 0x06: [0x1BA6A6], @@ -820,8 +820,8 @@ spc_instrument_changes = [ 0x03: [0x1BAEC6], 0x04: [0x1BAEE4]}, 0x0B), InstrumentChange(0x1F, 0x00, {0x00: [0x1BAE9C]}, 0x18), - InstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13), - InstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02), + InstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), + InstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02, ban=[0x10]), InstrumentChange(0x1F, 0x01, {0x03: [0x1BAE15], 0x04: [0x1BAE32], 0x05: [0x1BAE4F]}, 0x11), @@ -829,7 +829,7 @@ spc_instrument_changes = [ InstrumentChange(0x1F, 0x02, {0x03: [0x1BAF53], 0x04: [0x1BAF69], 0x05: [0x1BAF7F]}, 0x11), - InstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13), + InstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), InstrumentChange(0x20, 0x00, {0x00: [0x1AD49A], 0x01: [0x1AD4BA], From 80eac44ac0a061a76fb8e4b7749997826a77deb1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 15 Dec 2023 11:35:27 -0600 Subject: [PATCH 10/35] Fixed issue with Insanity ER and Pyramid Crack exit --- source/overworld/EntranceShuffle2.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 7e34f711..c909107b 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -1686,13 +1686,18 @@ def connect_exit(exit_name, entrancename, avail): if exit.connected_region is not None: exit.connected_region.entrances.remove(exit) - exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) + dest_region = entrance.parent_region + if dest_region.name == 'Pyramid Crack': + # Needs to logically exit into greater Pyramid Area + dest_region = entrance.parent_region.entrances[0].parent_region + + exit.connect(dest_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) if exit_name != 'Chris Houlihan Room Exit': if avail.coupled: avail.entrances.remove(entrancename) avail.exits.remove(exit_name) world.spoiler.set_entrance(entrance.name, exit.name, 'exit', player) - logging.getLogger('').debug(f'Connected (exit) {entrance.name} to {exit.name}') + logging.getLogger('').debug(f'Connected (exit) {exit.name} to {entrance.name}') def connect_two_way(entrancename, exit_name, avail): From bd5232bf24ee43369de03718d4ca8bb4fe252798 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 03:42:52 -0600 Subject: [PATCH 11/35] Fixed missing OWR brand labels --- Gui.py | 2 +- resources/app/cli/lang/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gui.py b/Gui.py index 0dcdaa75..11ba1383 100755 --- a/Gui.py +++ b/Gui.py @@ -34,7 +34,7 @@ def check_python_version(fish): import sys version = sys.version_info if version.major < 3 or version.minor < 7: - messagebox.showinfo("Door Shuffle " + ESVersion, fish.translate("cli","cli","old.python.version") % sys.version) + messagebox.showinfo("Overworld Shuffle %s (DR %s)" % (ORVersion, ESVersion), fish.translate("cli","cli","old.python.version") % sys.version) # Save settings to file diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 7d028961..87c72e3d 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -2,7 +2,7 @@ "cli": { "yes": "Yes", "no": "No", - "app.title": "ALttP Door Randomizer Version %s : --seed %s --code %s", + "app.title": "ALttP Overworld Randomizer Version %s : --seed %s --code %s", "version": "Version", "seed": "Seed", "player": "Player", From d8319ca72771043a4256f36b2255c794e4a1cfea Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 04:18:35 -0600 Subject: [PATCH 12/35] Renamed a couple buttons --- Gui.py | 4 ++-- resources/app/gui/lang/en.json | 3 ++- source/gui/bottom.py | 19 ++++++++++++------- source/gui/loadcliargs.py | 8 ++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Gui.py b/Gui.py index 11ba1383..a0aac7af 100755 --- a/Gui.py +++ b/Gui.py @@ -175,8 +175,8 @@ def guiMain(args=None): self.pages["bottom"].pages = {} self.pages["bottom"].pages["content"] = bottom_frame(self, self, None) ## Save Settings Button - savesettingsButton = Button(self.pages["bottom"].pages["content"], text='Save Settings to File', command=lambda: save_settings_from_gui(True)) - savesettingsButton.pack(side=RIGHT) + savesettingsButton = self.pages["bottom"].pages["content"].widgets["savesettings"].pieces["button"] + savesettingsButton.configure(command=lambda: save_settings_from_gui(True)) # set bottom frame to main window self.pages["bottom"].pages["content"].pack(side=BOTTOM, fill=X, padx=5, pady=5) diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 5d082636..cc33e73f 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -390,10 +390,11 @@ "bottom.content.names": "Player names", "bottom.content.seed": "Seed #", "bottom.content.generationcount": "Count", - "bottom.content.go": "Generate Patched Rom", + "bottom.content.go": "Generate Game", "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", + "bottom.content.savesettings": "Save Settings", "bottom.content.outputdir": "Select Destination", "bottom.content.docs": "Open Documentation" } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 3d37908e..9cc05d23 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -150,9 +150,10 @@ def bottom_frame(self, parent, args=None): # button self.widgets[widget].type = "button" - self.widgets[widget].pieces["button"] = Button(self, text='Generate Patched Rom', command=generateRom) + self.widgets[widget].pieces["button"] = Button(self, command=generateRom) # button: pack self.widgets[widget].pieces["button"].pack(side=LEFT) + self.widgets[widget].pieces["button"].configure(bg="#CCCCCC") def open_output(): if output_path.cached_path is None: @@ -170,12 +171,8 @@ def bottom_frame(self, parent, args=None): args.outputpath = parent.settings["outputpath"] = folder_selected ## Output Button - # widget ID widget = "outputdir" - - # Empty object self.widgets[widget] = Empty() - # pieces self.widgets[widget].pieces = {} # storagevar @@ -183,8 +180,16 @@ def bottom_frame(self, parent, args=None): # button self.widgets[widget].type = "button" - self.widgets[widget].pieces["button"] = Button(self, text='Open Output Directory', command=select_output) - # button: pack + self.widgets[widget].pieces["button"] = Button(self, command=select_output) + self.widgets[widget].pieces["button"].pack(side=RIGHT) + + ## Save Settings Button + widget = "savesettings" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self) self.widgets[widget].pieces["button"].pack(side=RIGHT) ## Documentation Button diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index 069b1fbd..e7e217b9 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -158,6 +158,14 @@ def loadcliargs(gui, args, settings=None): label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Save Settings button + mainpage = "bottom" + subpage = "content" + widget = "savesettings" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Output Directory button mainpage = "bottom" subpage = "content" From 18fde2a3f3af3f9b5ea97e18df9f3016604e28bd Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 04:23:34 -0600 Subject: [PATCH 13/35] Changed Print Template Yaml from checkbox to button --- Gui.py | 2 +- Main.py | 201 +++++++++++------- docs/Customizer.md | 4 +- resources/app/gui/lang/en.json | 2 +- .../gui/randomize/generation/checkboxes.json | 1 - source/classes/constants.py | 1 - source/gui/bottom.py | 37 +++- source/gui/loadcliargs.py | 8 + 8 files changed, 173 insertions(+), 83 deletions(-) diff --git a/Gui.py b/Gui.py index a0aac7af..899935ea 100755 --- a/Gui.py +++ b/Gui.py @@ -45,7 +45,7 @@ def save_settings(gui, args, filename): os.makedirs(settings_path) output_args = {} settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", - "calc_playthrough", "skip_playthrough", "print_template_yaml", "print_custom_yaml", + "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", "rom", "enemizercli", "outputpath"] if filename == "settings.json": for s in settings: diff --git a/Main.py b/Main.py index afcb1568..347aa451 100644 --- a/Main.py +++ b/Main.py @@ -56,32 +56,25 @@ def check_python_version(): def main(args, seed=None, fish=None): check_python_version() + + if args.print_template_yaml: + return export_yaml(args, fish) + if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) output_path.cached_path = args.outputpath start = time.perf_counter() + world = init_world(args, fish) + if args.securerandom: random.use_secure() seeded = False - # initialize the world - if args.code: - for player, code in args.code.items(): - if code: - Settings.adjust_args_from_code(code, player, args) - customized = None - if args.customizer: - customized = CustomSettings() - customized.load_yaml(args.customizer) - seed = customized.determine_seed(seed) + if world.customizer: + seed = world.customizer.determine_seed(seed) seeded = True - customized.adjust_args(args) - world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, - args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, - args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) - world.customizer = customized if customized else None - logger = logging.getLogger('') + world.customizer.adjust_args(args) if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -93,53 +86,9 @@ def main(args, seed=None, fish=None): if args.securerandom: world.seed = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(9)) - world.boots_hint = args.boots_hint.copy() - world.remote_items = args.remote_items.copy() - world.mapshuffle = args.mapshuffle.copy() - world.compassshuffle = args.compassshuffle.copy() - world.keyshuffle = args.keyshuffle.copy() - world.bigkeyshuffle = args.bigkeyshuffle.copy() - world.bombbag = args.bombbag.copy() - world.flute_mode = args.flute_mode.copy() - world.bow_mode = args.bow_mode.copy() world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} - world.crystals_ganon_orig = args.crystals_ganon.copy() - world.crystals_gt_orig = args.crystals_gt.copy() - world.owTerrain = args.ow_terrain.copy() - world.owKeepSimilar = args.ow_keepsimilar.copy() - world.owWhirlpoolShuffle = args.ow_whirlpool.copy() - world.owFluteShuffle = args.ow_fluteshuffle.copy() - world.shuffle_bonk_drops = args.bonk_drops.copy() - world.open_pyramid = args.openpyramid.copy() - world.boss_shuffle = args.shufflebosses.copy() - world.enemy_shuffle = args.shuffleenemies.copy() - world.enemy_health = args.enemy_health.copy() - world.enemy_damage = args.enemy_damage.copy() - world.beemizer = args.beemizer.copy() world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} - world.door_type_mode = args.door_type_mode.copy() - world.trap_door_mode = args.trap_door_mode.copy() - world.key_logic_algorithm = args.key_logic_algorithm.copy() - world.decoupledoors = args.decoupledoors.copy() - world.door_self_loops = args.door_self_loops.copy() - world.experimental = args.experimental.copy() - world.dungeon_counters = args.dungeon_counters.copy() - world.fish = fish - world.shopsanity = args.shopsanity.copy() - world.dropshuffle = args.dropshuffle.copy() - world.pottery = args.pottery.copy() - world.potshuffle = args.shufflepots.copy() - world.mixed_travel = args.mixed_travel.copy() - world.standardize_palettes = args.standardize_palettes.copy() - world.shufflelinks = args.shufflelinks.copy() - world.shuffletavern = args.shuffletavern.copy() - world.pseudoboots = args.pseudoboots.copy() - world.overworld_map = args.overworld_map.copy() - world.take_any = args.take_any.copy() - world.restrict_boss_items = args.restrict_boss_items.copy() - world.collection_rate = args.collection_rate.copy() - world.colorizepots = args.colorizepots.copy() world.treasure_hunt_count = {} world.treasure_hunt_total = {} @@ -193,27 +142,11 @@ def main(args, seed=None, fish=None): if hasattr(world,"escape_assist") and player in world.escape_assist: world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it - if args.usestartinventory[player]: - for tok in filter(None, args.startinventory[player].split(',')): - name = tok.strip() - name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' - item = ItemFactory(name, player) - if item: - world.push_precollected(item) - - if world.customizer and world.customizer.get_start_inventory(): - for p, inv_list in world.customizer.get_start_inventory().items(): - for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) - if item: - world.push_precollected(item) + set_starting_inventory(world, args) world.settings = CustomSettings() world.settings.create_from_world(world, args) - if args.print_template_yaml: - world.settings.record_item_pool(world, True) - world.settings.write_to_file(output_path(f'{outfilebase}_template.yaml')) if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -466,6 +399,120 @@ def main(args, seed=None, fish=None): return world +def export_yaml(args, fish): + if args.outputpath: + os.makedirs(args.outputpath, exist_ok=True) + output_path.cached_path = args.outputpath + + outfilebase = f'{args.outputname if args.outputname else "export"}' + logger = logging.getLogger('') + + world = init_world(args, fish) + + from OverworldShuffle import __version__ as ORVersion + logger.info( + world.fish.translate("cli","cli","app.title") + "\n", + ORVersion, + "(%s)" % outfilebase, + Settings.make_code(world, 1) if world.players == 1 else '' + ) + + for k,v in {"DR":__version__,"OR":ORVersion}.items(): + logger.info((k + ' Version:').ljust(16) + '%s' % v) + + set_starting_inventory(world, args) + + world.settings = CustomSettings() + world.settings.create_from_world(world, args) + + world.settings.record_item_pool(world, True) + world.settings.write_to_file(output_path(f'{outfilebase}.yaml')) + + return world + + +def init_world(args, fish): + if args.code: + for player, code in args.code.items(): + if code: + Settings.adjust_args_from_code(code, player, args) + + world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, + args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, + args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + + world.boots_hint = args.boots_hint.copy() + world.remote_items = args.remote_items.copy() + world.mapshuffle = args.mapshuffle.copy() + world.compassshuffle = args.compassshuffle.copy() + world.keyshuffle = args.keyshuffle.copy() + world.bigkeyshuffle = args.bigkeyshuffle.copy() + world.bombbag = args.bombbag.copy() + world.flute_mode = args.flute_mode.copy() + world.bow_mode = args.bow_mode.copy() + world.crystals_ganon_orig = args.crystals_ganon.copy() + world.crystals_gt_orig = args.crystals_gt.copy() + world.owTerrain = args.ow_terrain.copy() + world.owKeepSimilar = args.ow_keepsimilar.copy() + world.owWhirlpoolShuffle = args.ow_whirlpool.copy() + world.owFluteShuffle = args.ow_fluteshuffle.copy() + world.shuffle_bonk_drops = args.bonk_drops.copy() + world.open_pyramid = args.openpyramid.copy() + world.boss_shuffle = args.shufflebosses.copy() + world.enemy_shuffle = args.shuffleenemies.copy() + world.enemy_health = args.enemy_health.copy() + world.enemy_damage = args.enemy_damage.copy() + world.beemizer = args.beemizer.copy() + world.intensity = {player: 'random' if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} + world.door_type_mode = args.door_type_mode.copy() + world.trap_door_mode = args.trap_door_mode.copy() + world.key_logic_algorithm = args.key_logic_algorithm.copy() + world.decoupledoors = args.decoupledoors.copy() + world.door_self_loops = args.door_self_loops.copy() + world.experimental = args.experimental.copy() + world.dungeon_counters = args.dungeon_counters.copy() + world.fish = fish + world.shopsanity = args.shopsanity.copy() + world.dropshuffle = args.dropshuffle.copy() + world.pottery = args.pottery.copy() + world.potshuffle = args.shufflepots.copy() + world.mixed_travel = args.mixed_travel.copy() + world.standardize_palettes = args.standardize_palettes.copy() + world.shufflelinks = args.shufflelinks.copy() + world.shuffletavern = args.shuffletavern.copy() + world.pseudoboots = args.pseudoboots.copy() + world.overworld_map = args.overworld_map.copy() + world.take_any = args.take_any.copy() + world.restrict_boss_items = args.restrict_boss_items.copy() + world.collection_rate = args.collection_rate.copy() + world.colorizepots = args.colorizepots.copy() + + world.customizer = None + if args.customizer: + world.customizer = CustomSettings() + world.customizer.load_yaml(args.customizer) + + return world + + +def set_starting_inventory(world, args): + for player in range(1, world.players + 1): + if args.usestartinventory[player]: + for tok in filter(None, args.startinventory[player].split(',')): + name = tok.strip() + name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' + item = ItemFactory(name, player) + if item: + world.push_precollected(item) + + if world.customizer and world.customizer.get_start_inventory(): + for p, inv_list in world.customizer.get_start_inventory().items(): + for inv_item in inv_list: + item = ItemFactory(inv_item.strip(), p) + if item: + world.push_precollected(item) + + def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, diff --git a/docs/Customizer.md b/docs/Customizer.md index 3f3b31e9..c881119f 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -8,11 +8,13 @@ The cli includes a couple arguments to help: `--print_template_yaml` will create a yaml file based on the settings used. This does not contain any seed specific information. +Present on the GUI as `Export Yaml` on the bottom. + `--print_custom_yaml` will create a yaml file based on the seed rolled. Treat it like a spoiler. `--customizer` takes a file as an argument. -Present on the GUI as `Print Customizer Template`, `Print Customizer File`, and `Customizer File` on the Generation Setup tab. +Present on the GUI as `Print Customizer File` and `Customizer File` on the Generation Setup tab. ### meta diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index cc33e73f..9b4a0690 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -234,7 +234,6 @@ "randomizer.generation.createspoiler": "Create Spoiler Log", "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", - "randomizer.generation.print_template_yaml": "Print Customizer Template", "randomizer.generation.print_custom_yaml": "Print Customizer File", "randomizer.generation.settingsonload": "Settings On Load", @@ -391,6 +390,7 @@ "bottom.content.seed": "Seed #", "bottom.content.generationcount": "Count", "bottom.content.go": "Generate Game", + "bottom.content.exportyaml": "Export Yaml", "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 658ea7f0..6e377027 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,7 +4,6 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, - "print_template_yaml":{ "type": "checkbox" }, "print_custom_yaml": { "type": "checkbox" } } } diff --git a/source/classes/constants.py b/source/classes/constants.py index 6bf5f55e..c8e15532 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -147,7 +147,6 @@ SETTINGSTOPROCESS = { "createspoiler": "create_spoiler", "createrom": "create_rom", "calcplaythrough": "calc_playthrough", - "print_template_yaml": "print_template_yaml", "print_custom_yaml": "print_custom_yaml", "settingsonload": "settingsonload" } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 9cc05d23..fddc29a1 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -6,7 +6,7 @@ import random import re from CLI import parse_cli from Fill import FillError -from Main import main, EnemizerError +from Main import main, export_yaml, EnemizerError from Utils import local_path, output_path, open_file, update_deprecated_args import source.classes.constants as CONST from source.gui.randomize.multiworld import multiworld_page @@ -155,6 +155,41 @@ def bottom_frame(self, parent, args=None): self.widgets[widget].pieces["button"].pack(side=LEFT) self.widgets[widget].pieces["button"].configure(bg="#CCCCCC") + def exportYaml(): + guiargs = create_guiargs(parent) + # get default values for missing parameters + for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): + if k not in vars(guiargs): + setattr(guiargs, k, v) + elif type(v) is dict: # use same settings for every player + setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)}) + + filename = None + try: + from tkinter import filedialog + filename = filedialog.asksaveasfilename(initialdir=guiargs.outputpath, title="Save file", filetypes=(("Customizer Yamls", "*.yaml"), ("All files", "*.*"))) + if filename is not None and filename != '': + guiargs.outputpath = parent.settings["outputpath"] = os.path.dirname(filename) + guiargs.outputname = os.path.splitext(os.path.basename(filename))[0] + export_yaml(args=guiargs, fish=parent.fish) + except (FillError, EnemizerError, Exception, RuntimeError) as e: + logging.exception(e) + messagebox.showerror(title="Error while exporting yaml", message=str(e)) + else: + if filename is not None and filename != '': + successMsg = "File Exported" + # FIXME: English + messagebox.showinfo(title="Success", message=successMsg) + + ## Export Yaml Button + widget = "exportyaml" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self, command=exportYaml) + self.widgets[widget].pieces["button"].pack(side=LEFT) + def open_output(): if output_path.cached_path is None: if args and args.outputpath: diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index e7e217b9..6e6695c0 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -158,6 +158,14 @@ def loadcliargs(gui, args, settings=None): label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Export Yaml button + mainpage = "bottom" + subpage = "content" + widget = "exportyaml" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Save Settings button mainpage = "bottom" subpage = "content" From 55caf240114755457763de1650a2f36568ce18b6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 04:23:34 -0600 Subject: [PATCH 14/35] Changed Print Template Yaml from checkbox to button --- Gui.py | 2 +- Main.py | 201 +++++++++++------- docs/Customizer.md | 4 +- resources/app/gui/lang/en.json | 2 +- .../gui/randomize/generation/checkboxes.json | 1 - source/classes/constants.py | 1 - source/gui/bottom.py | 37 +++- source/gui/loadcliargs.py | 8 + 8 files changed, 173 insertions(+), 83 deletions(-) diff --git a/Gui.py b/Gui.py index a0aac7af..899935ea 100755 --- a/Gui.py +++ b/Gui.py @@ -45,7 +45,7 @@ def save_settings(gui, args, filename): os.makedirs(settings_path) output_args = {} settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", - "calc_playthrough", "skip_playthrough", "print_template_yaml", "print_custom_yaml", + "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", "rom", "enemizercli", "outputpath"] if filename == "settings.json": for s in settings: diff --git a/Main.py b/Main.py index afcb1568..347aa451 100644 --- a/Main.py +++ b/Main.py @@ -56,32 +56,25 @@ def check_python_version(): def main(args, seed=None, fish=None): check_python_version() + + if args.print_template_yaml: + return export_yaml(args, fish) + if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) output_path.cached_path = args.outputpath start = time.perf_counter() + world = init_world(args, fish) + if args.securerandom: random.use_secure() seeded = False - # initialize the world - if args.code: - for player, code in args.code.items(): - if code: - Settings.adjust_args_from_code(code, player, args) - customized = None - if args.customizer: - customized = CustomSettings() - customized.load_yaml(args.customizer) - seed = customized.determine_seed(seed) + if world.customizer: + seed = world.customizer.determine_seed(seed) seeded = True - customized.adjust_args(args) - world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, - args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, - args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) - world.customizer = customized if customized else None - logger = logging.getLogger('') + world.customizer.adjust_args(args) if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -93,53 +86,9 @@ def main(args, seed=None, fish=None): if args.securerandom: world.seed = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(9)) - world.boots_hint = args.boots_hint.copy() - world.remote_items = args.remote_items.copy() - world.mapshuffle = args.mapshuffle.copy() - world.compassshuffle = args.compassshuffle.copy() - world.keyshuffle = args.keyshuffle.copy() - world.bigkeyshuffle = args.bigkeyshuffle.copy() - world.bombbag = args.bombbag.copy() - world.flute_mode = args.flute_mode.copy() - world.bow_mode = args.bow_mode.copy() world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} - world.crystals_ganon_orig = args.crystals_ganon.copy() - world.crystals_gt_orig = args.crystals_gt.copy() - world.owTerrain = args.ow_terrain.copy() - world.owKeepSimilar = args.ow_keepsimilar.copy() - world.owWhirlpoolShuffle = args.ow_whirlpool.copy() - world.owFluteShuffle = args.ow_fluteshuffle.copy() - world.shuffle_bonk_drops = args.bonk_drops.copy() - world.open_pyramid = args.openpyramid.copy() - world.boss_shuffle = args.shufflebosses.copy() - world.enemy_shuffle = args.shuffleenemies.copy() - world.enemy_health = args.enemy_health.copy() - world.enemy_damage = args.enemy_damage.copy() - world.beemizer = args.beemizer.copy() world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} - world.door_type_mode = args.door_type_mode.copy() - world.trap_door_mode = args.trap_door_mode.copy() - world.key_logic_algorithm = args.key_logic_algorithm.copy() - world.decoupledoors = args.decoupledoors.copy() - world.door_self_loops = args.door_self_loops.copy() - world.experimental = args.experimental.copy() - world.dungeon_counters = args.dungeon_counters.copy() - world.fish = fish - world.shopsanity = args.shopsanity.copy() - world.dropshuffle = args.dropshuffle.copy() - world.pottery = args.pottery.copy() - world.potshuffle = args.shufflepots.copy() - world.mixed_travel = args.mixed_travel.copy() - world.standardize_palettes = args.standardize_palettes.copy() - world.shufflelinks = args.shufflelinks.copy() - world.shuffletavern = args.shuffletavern.copy() - world.pseudoboots = args.pseudoboots.copy() - world.overworld_map = args.overworld_map.copy() - world.take_any = args.take_any.copy() - world.restrict_boss_items = args.restrict_boss_items.copy() - world.collection_rate = args.collection_rate.copy() - world.colorizepots = args.colorizepots.copy() world.treasure_hunt_count = {} world.treasure_hunt_total = {} @@ -193,27 +142,11 @@ def main(args, seed=None, fish=None): if hasattr(world,"escape_assist") and player in world.escape_assist: world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it - if args.usestartinventory[player]: - for tok in filter(None, args.startinventory[player].split(',')): - name = tok.strip() - name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' - item = ItemFactory(name, player) - if item: - world.push_precollected(item) - - if world.customizer and world.customizer.get_start_inventory(): - for p, inv_list in world.customizer.get_start_inventory().items(): - for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) - if item: - world.push_precollected(item) + set_starting_inventory(world, args) world.settings = CustomSettings() world.settings.create_from_world(world, args) - if args.print_template_yaml: - world.settings.record_item_pool(world, True) - world.settings.write_to_file(output_path(f'{outfilebase}_template.yaml')) if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -466,6 +399,120 @@ def main(args, seed=None, fish=None): return world +def export_yaml(args, fish): + if args.outputpath: + os.makedirs(args.outputpath, exist_ok=True) + output_path.cached_path = args.outputpath + + outfilebase = f'{args.outputname if args.outputname else "export"}' + logger = logging.getLogger('') + + world = init_world(args, fish) + + from OverworldShuffle import __version__ as ORVersion + logger.info( + world.fish.translate("cli","cli","app.title") + "\n", + ORVersion, + "(%s)" % outfilebase, + Settings.make_code(world, 1) if world.players == 1 else '' + ) + + for k,v in {"DR":__version__,"OR":ORVersion}.items(): + logger.info((k + ' Version:').ljust(16) + '%s' % v) + + set_starting_inventory(world, args) + + world.settings = CustomSettings() + world.settings.create_from_world(world, args) + + world.settings.record_item_pool(world, True) + world.settings.write_to_file(output_path(f'{outfilebase}.yaml')) + + return world + + +def init_world(args, fish): + if args.code: + for player, code in args.code.items(): + if code: + Settings.adjust_args_from_code(code, player, args) + + world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, + args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, + args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + + world.boots_hint = args.boots_hint.copy() + world.remote_items = args.remote_items.copy() + world.mapshuffle = args.mapshuffle.copy() + world.compassshuffle = args.compassshuffle.copy() + world.keyshuffle = args.keyshuffle.copy() + world.bigkeyshuffle = args.bigkeyshuffle.copy() + world.bombbag = args.bombbag.copy() + world.flute_mode = args.flute_mode.copy() + world.bow_mode = args.bow_mode.copy() + world.crystals_ganon_orig = args.crystals_ganon.copy() + world.crystals_gt_orig = args.crystals_gt.copy() + world.owTerrain = args.ow_terrain.copy() + world.owKeepSimilar = args.ow_keepsimilar.copy() + world.owWhirlpoolShuffle = args.ow_whirlpool.copy() + world.owFluteShuffle = args.ow_fluteshuffle.copy() + world.shuffle_bonk_drops = args.bonk_drops.copy() + world.open_pyramid = args.openpyramid.copy() + world.boss_shuffle = args.shufflebosses.copy() + world.enemy_shuffle = args.shuffleenemies.copy() + world.enemy_health = args.enemy_health.copy() + world.enemy_damage = args.enemy_damage.copy() + world.beemizer = args.beemizer.copy() + world.intensity = {player: 'random' if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} + world.door_type_mode = args.door_type_mode.copy() + world.trap_door_mode = args.trap_door_mode.copy() + world.key_logic_algorithm = args.key_logic_algorithm.copy() + world.decoupledoors = args.decoupledoors.copy() + world.door_self_loops = args.door_self_loops.copy() + world.experimental = args.experimental.copy() + world.dungeon_counters = args.dungeon_counters.copy() + world.fish = fish + world.shopsanity = args.shopsanity.copy() + world.dropshuffle = args.dropshuffle.copy() + world.pottery = args.pottery.copy() + world.potshuffle = args.shufflepots.copy() + world.mixed_travel = args.mixed_travel.copy() + world.standardize_palettes = args.standardize_palettes.copy() + world.shufflelinks = args.shufflelinks.copy() + world.shuffletavern = args.shuffletavern.copy() + world.pseudoboots = args.pseudoboots.copy() + world.overworld_map = args.overworld_map.copy() + world.take_any = args.take_any.copy() + world.restrict_boss_items = args.restrict_boss_items.copy() + world.collection_rate = args.collection_rate.copy() + world.colorizepots = args.colorizepots.copy() + + world.customizer = None + if args.customizer: + world.customizer = CustomSettings() + world.customizer.load_yaml(args.customizer) + + return world + + +def set_starting_inventory(world, args): + for player in range(1, world.players + 1): + if args.usestartinventory[player]: + for tok in filter(None, args.startinventory[player].split(',')): + name = tok.strip() + name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' + item = ItemFactory(name, player) + if item: + world.push_precollected(item) + + if world.customizer and world.customizer.get_start_inventory(): + for p, inv_list in world.customizer.get_start_inventory().items(): + for inv_item in inv_list: + item = ItemFactory(inv_item.strip(), p) + if item: + world.push_precollected(item) + + def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, diff --git a/docs/Customizer.md b/docs/Customizer.md index 3f3b31e9..c881119f 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -8,11 +8,13 @@ The cli includes a couple arguments to help: `--print_template_yaml` will create a yaml file based on the settings used. This does not contain any seed specific information. +Present on the GUI as `Export Yaml` on the bottom. + `--print_custom_yaml` will create a yaml file based on the seed rolled. Treat it like a spoiler. `--customizer` takes a file as an argument. -Present on the GUI as `Print Customizer Template`, `Print Customizer File`, and `Customizer File` on the Generation Setup tab. +Present on the GUI as `Print Customizer File` and `Customizer File` on the Generation Setup tab. ### meta diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index cc33e73f..9b4a0690 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -234,7 +234,6 @@ "randomizer.generation.createspoiler": "Create Spoiler Log", "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", - "randomizer.generation.print_template_yaml": "Print Customizer Template", "randomizer.generation.print_custom_yaml": "Print Customizer File", "randomizer.generation.settingsonload": "Settings On Load", @@ -391,6 +390,7 @@ "bottom.content.seed": "Seed #", "bottom.content.generationcount": "Count", "bottom.content.go": "Generate Game", + "bottom.content.exportyaml": "Export Yaml", "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 658ea7f0..6e377027 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,7 +4,6 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, - "print_template_yaml":{ "type": "checkbox" }, "print_custom_yaml": { "type": "checkbox" } } } diff --git a/source/classes/constants.py b/source/classes/constants.py index 6bf5f55e..c8e15532 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -147,7 +147,6 @@ SETTINGSTOPROCESS = { "createspoiler": "create_spoiler", "createrom": "create_rom", "calcplaythrough": "calc_playthrough", - "print_template_yaml": "print_template_yaml", "print_custom_yaml": "print_custom_yaml", "settingsonload": "settingsonload" } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 9cc05d23..14859951 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -6,7 +6,7 @@ import random import re from CLI import parse_cli from Fill import FillError -from Main import main, EnemizerError +from Main import main, export_yaml, EnemizerError from Utils import local_path, output_path, open_file, update_deprecated_args import source.classes.constants as CONST from source.gui.randomize.multiworld import multiworld_page @@ -155,6 +155,41 @@ def bottom_frame(self, parent, args=None): self.widgets[widget].pieces["button"].pack(side=LEFT) self.widgets[widget].pieces["button"].configure(bg="#CCCCCC") + def exportYaml(): + guiargs = create_guiargs(parent) + # get default values for missing parameters + for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): + if k not in vars(guiargs): + setattr(guiargs, k, v) + elif type(v) is dict: # use same settings for every player + setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)}) + + filename = None + try: + from tkinter import filedialog + filename = filedialog.asksaveasfilename(initialdir=guiargs.outputpath, title="Save file", filetypes=(("Yaml Files", (".yaml", ".yml")), ("All Files", "*"))) + if filename is not None and filename != '': + guiargs.outputpath = parent.settings["outputpath"] = os.path.dirname(filename) + guiargs.outputname = os.path.splitext(os.path.basename(filename))[0] + export_yaml(args=guiargs, fish=parent.fish) + except (FillError, EnemizerError, Exception, RuntimeError) as e: + logging.exception(e) + messagebox.showerror(title="Error while exporting yaml", message=str(e)) + else: + if filename is not None and filename != '': + successMsg = "File Exported" + # FIXME: English + messagebox.showinfo(title="Success", message=successMsg) + + ## Export Yaml Button + widget = "exportyaml" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self, command=exportYaml) + self.widgets[widget].pieces["button"].pack(side=LEFT) + def open_output(): if output_path.cached_path is None: if args and args.outputpath: diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index e7e217b9..6e6695c0 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -158,6 +158,14 @@ def loadcliargs(gui, args, settings=None): label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Export Yaml button + mainpage = "bottom" + subpage = "content" + widget = "exportyaml" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Save Settings button mainpage = "bottom" subpage = "content" From 95fc30a8bda53dbebecd2d616fcf85b812aff2ab Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Dec 2023 15:27:49 -0600 Subject: [PATCH 15/35] Implement Beat Agahnim 1 already in starting inventory --- InitialSram.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/InitialSram.py b/InitialSram.py index 7f0f2c89..b3a969e9 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -118,10 +118,11 @@ class InitialSram: self._initial_sram_bytes[0x410] |= 0x01 if startingstate.has('Beat Agahnim 1', player): + self.pre_open_lumberjack() if world.mode[player] == 'standard': - self._initial_sram_bytes[0x3C5] = 0x80 + self.set_progress_indicator(0x80) else: - self._initial_sram_bytes[0x3C5] = 0x03 + self.set_progress_indicator(0x03) for item in world.precollected_items: if item.player != player: From 29504b7d53f210ffdc153cdbc18e9262ab318b95 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 18 Dec 2023 14:10:14 -0700 Subject: [PATCH 16/35] fix: accept shufflebosses and shuffleenemies --- source/classes/CustomSettings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index ba9c9422..3a48132e 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -147,8 +147,8 @@ class CustomSettings(object): args.mapshuffle[p] = True args.compassshuffle[p] = True - args.shufflebosses[p] = get_setting(settings['boss_shuffle'], args.shufflebosses[p]) - args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], args.shuffleenemies[p]) + args.shufflebosses[p] = get_setting(settings['boss_shuffle'], get_setting(settings['shufflebosses'], args.shufflebosses[p])) + args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], get_setting(settings['shuffleenemies'], args.shuffleenemies[p])) args.enemy_health[p] = get_setting(settings['enemy_health'], args.enemy_health[p]) args.enemy_damage[p] = get_setting(settings['enemy_damage'], args.enemy_damage[p]) args.shufflepots[p] = get_setting(settings['shufflepots'], args.shufflepots[p]) @@ -311,8 +311,8 @@ class CustomSettings(object): settings_dict[p]['keyshuffle'] = world.keyshuffle[p] settings_dict[p]['mapshuffle'] = world.mapshuffle[p] settings_dict[p]['compassshuffle'] = world.compassshuffle[p] - settings_dict[p]['shufflebosses'] = world.boss_shuffle[p] - settings_dict[p]['shuffleenemies'] = world.enemy_shuffle[p] + settings_dict[p]['boss_shuffle'] = world.boss_shuffle[p] + settings_dict[p]['enemy_shuffle'] = world.enemy_shuffle[p] settings_dict[p]['enemy_health'] = world.enemy_health[p] settings_dict[p]['enemy_damage'] = world.enemy_damage[p] settings_dict[p]['shufflepots'] = world.potshuffle[p] From 2d467efdedcc40b387295ee20275ff9f1c45d1b2 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 13 Dec 2023 11:45:59 -0700 Subject: [PATCH 17/35] fix: don't bother blocking rain doors in no logic --- Rom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index 6095ace0..6aafa704 100644 --- a/Rom.py +++ b/Rom.py @@ -1434,10 +1434,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) # block HC upstairs doors in rain state in standard mode - prevent_rain = world.mode[player] == "standard" and world.shuffle[player] != 'vanilla' + prevent_rain = world.mode[player] == 'standard' and world.shuffle[player] != 'vanilla' and world.logic[player] != 'nologic' rom.write_byte(0x18008A, 0x01 if prevent_rain else 0x00) # block sanc door in rain state and the dungeon is not vanilla - rom.write_byte(0x13f0fa, 0x01 if world.mode[player] == "standard" and world.doorShuffle[player] != 'vanilla' else 0x00) + block_sanc = world.mode[player] == 'standard' and world.doorShuffle[player] != 'vanilla' and world.logic[player] != 'nologic' + rom.write_byte(0x13f0fa, 0x01 if block_sanc else 0x00) if prevent_rain: portals = [world.get_portal('Hyrule Castle East', player), world.get_portal('Hyrule Castle West', player)] From bd7ce54dd35f37af1319966e598fc5d28b728e85 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 13 Dec 2023 13:59:34 -0700 Subject: [PATCH 18/35] fix: minor fix for take_anys fix: money balancing - initialization in a good case --- BaseClasses.py | 3 +++ Fill.py | 1 + ItemList.py | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index 94287939..19f45551 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1555,6 +1555,9 @@ class Region(object): return self.is_dark_world if self.world.mode[player] != 'inverted' else self.is_light_world + def is_outdoors(self): + return self.type in {RegionType.LightWorld, RegionType.DarkWorld} + def __str__(self): return str(self.__unicode__()) diff --git a/Fill.py b/Fill.py index 64abd448..a600ce17 100644 --- a/Fill.py +++ b/Fill.py @@ -1007,6 +1007,7 @@ def balance_money_progression(world): logger.debug(f'Money balancing needed: Player {target_player} short {difference}') else: difference = 0 + target_player = next(p for p in solvent) while difference > 0: swap_targets = [x for x in unchecked_locations if x not in sphere_locations and x.item.name.startswith('Rupees') and x.item.player == target_player] if len(swap_targets) == 0: diff --git a/ItemList.py b/ItemList.py index 21d3b446..6a8761f7 100644 --- a/ItemList.py +++ b/ItemList.py @@ -558,7 +558,9 @@ def set_up_take_anys(world, player, skip_adjustments=False): world.dynamic_regions.append(take_any) target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) reg = regions.pop() - entrance = world.get_region(reg, player).entrances[0] + entrance = next((ent for ent in world.get_region(reg, player).entrances if ent.parent_region.is_outdoors()), None) + if entrance is None: + raise Exception(f'No outside entrance found for {reg}') connect_entrance(world, entrance, take_any, player) entrance.target = target take_any.shop = Shop(take_any, room_id, take_any_type, 0xE3, True, not world.shopsanity[player], 33 + num*2) From 8f3b3dc760737028fd76ac8b73be43c1248e7128 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 14 Dec 2023 10:13:22 -0700 Subject: [PATCH 19/35] feat: MW progresssion balancing tweaked to be percentage based instead of raw count. Tries to keep each player's locations in each sphere within 80% of the player with the most locations available. (Measured with percentage instead of raw count.) Old algo tried to keep everyone within 20 locations of each other. Difficult if one player has a lot more locations than another. fix: Potential fix for early Trinexx start --- BaseClasses.py | 8 ++++---- Fill.py | 46 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 19f45551..c57e6a1b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -565,7 +565,7 @@ class World(object): if not sphere: # ran out of places and did not finish yet, quit if log_error: - missing_locations = ", ".join([x.name for x in prog_locations]) + missing_locations = ", ".join([f'{x.name} (#{x.player})' for x in prog_locations]) logging.getLogger('').error(f'Cannot reach the following locations: {missing_locations}') return False @@ -600,7 +600,7 @@ class CollectionState(object): self.opened_doors = {player: set() for player in range(1, parent.players + 1)} self.dungeons_to_check = {player: defaultdict(dict) for player in range(1, parent.players + 1)} self.dungeon_limits = None - self.placing_item = None + self.placing_items = None # self.trace = None def update_reachable_regions(self, player): @@ -878,7 +878,7 @@ class CollectionState(object): return door_candidates door_candidates, skip = [], set() if (state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name != 'Universal' - and state.placing_item and state.placing_item.name == small_key_name): + and state.placing_items and any(i.name == small_key_name and i.player == player for i in state.placing_items)): key_logic = state.world.key_logic[player][dungeon_name] for door, paired in key_logic.sm_doors.items(): if door.name in key_logic.door_rules: @@ -923,7 +923,7 @@ class CollectionState(object): player: defaultdict(dict, {name: copy.copy(checklist) for name, checklist in self.dungeons_to_check[player].items()}) for player in range(1, self.world.players + 1)} - ret.placing_item = self.placing_item + ret.placing_items = self.placing_items return ret def apply_dungeon_exploration(self, rrp, player, dungeon_name, checklist): diff --git a/Fill.py b/Fill.py index a600ce17..5c1214a1 100644 --- a/Fill.py +++ b/Fill.py @@ -3,6 +3,7 @@ import collections import itertools import logging import math +from collections import Counter from contextlib import suppress from BaseClasses import CollectionState, FillError, LocationType @@ -71,13 +72,13 @@ def fill_dungeons_restrictive(world, shuffled_locations): def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False, vanilla=False): - def sweep_from_pool(placing_item=None): + def sweep_from_pool(placing_items=None): new_state = base_state.copy() for item in itempool: new_state.collect(item, True) - new_state.placing_item = placing_item + new_state.placing_items = placing_items new_state.sweep_for_events() - new_state.placing_item = None + new_state.placing_items = None return new_state unplaced_items = [] @@ -94,7 +95,7 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing while any(player_items.values()) and locations: items_to_place = [[itempool.remove(items[-1]), items.pop()][-1] for items in player_items.values() if items] - maximum_exploration_state = sweep_from_pool(placing_item=items_to_place[0]) + maximum_exploration_state = sweep_from_pool(placing_items=items_to_place) has_beaten_game = world.has_beaten_game(maximum_exploration_state) for item_to_place in items_to_place: @@ -734,24 +735,44 @@ def balance_multiworld_progression(world): checked_locations = set() unchecked_locations = set(world.get_locations()) + total_locations_count = Counter(location.player for location in world.get_locations() if not location.locked and not location.forced_item) + reachable_locations_count = {} for player in range(1, world.players + 1): reachable_locations_count[player] = 0 + sphere_num = 1 + moved_item_count = 0 def get_sphere_locations(sphere_state, locations): sphere_state.sweep_for_events(key_only=True, locations=locations) return {loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)} + def item_percentage(player, num): + return num / total_locations_count[player] + while True: sphere_locations = get_sphere_locations(state, unchecked_locations) for location in sphere_locations: unchecked_locations.remove(location) - reachable_locations_count[location.player] += 1 + if not location.locked and not location.forced_item: + reachable_locations_count[location.player] += 1 + + logging.debug(f'Sphere {sphere_num}') + logging.debug(f'Reachable locations: {reachable_locations_count}') + debug_percentages = { + player: round(item_percentage(player, num), 2) + for player, num in reachable_locations_count.items() + } + logging.debug(f'Reachable percentages: {debug_percentages}\n') + sphere_num += 1 if checked_locations: - threshold = max(reachable_locations_count.values()) - 20 + max_percentage = max(map(lambda p: item_percentage(p, reachable_locations_count[p]), reachable_locations_count)) + threshold_percentages = {player: max_percentage * .8 for player in range(1, world.players + 1)} + logging.debug(f'Thresholds: {threshold_percentages}') - balancing_players = {player for player, reachables in reachable_locations_count.items() if reachables < threshold} + balancing_players = {player for player, reachables in reachable_locations_count.items() + if item_percentage(player, reachables) < threshold_percentages[player]} if balancing_players: balancing_state = state.copy() balancing_unchecked_locations = unchecked_locations.copy() @@ -769,7 +790,8 @@ def balance_multiworld_progression(world): for location in balancing_sphere: balancing_unchecked_locations.remove(location) balancing_reachables[location.player] += 1 - if world.has_beaten_game(balancing_state) or all(reachables >= threshold for reachables in balancing_reachables.values()): + if world.has_beaten_game(balancing_state) or all(item_percentage(player, reachables) >= threshold_percentages[player] + for player, reachables in balancing_reachables.items()): break elif not balancing_sphere: raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') @@ -796,7 +818,8 @@ def balance_multiworld_progression(world): items_to_replace.append(testing) else: reduced_sphere = get_sphere_locations(reducing_state, locations_to_test) - if reachable_locations_count[player] + len(reduced_sphere) < threshold: + p = item_percentage(player, reachable_locations_count[player] + len(reduced_sphere)) + if p < threshold_percentages[player]: items_to_replace.append(testing) replaced_items = False @@ -821,6 +844,7 @@ def balance_multiworld_progression(world): new_location.event, old_location.event = True, False logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, " f"displacing {old_location.item} into {old_location}") + moved_item_count += 1 state.collect(new_location.item, True, new_location) replaced_items = True break @@ -828,6 +852,7 @@ def balance_multiworld_progression(world): logging.warning(f"Could not Progression Balance {old_location.item}") if replaced_items: + logging.debug(f'Moved {moved_item_count} items so far\n') unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]} for location in get_sphere_locations(state, unlocked): unchecked_locations.remove(location) @@ -842,7 +867,8 @@ def balance_multiworld_progression(world): if world.has_beaten_game(state): break elif not sphere_locations: - raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') + logging.warning('Progression Balancing ran out of paths.') + break def check_shop_swap(l, make_item_free=False): From 7def7081b93c77e1b1d0d94bdf087c3142538fb6 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 18 Dec 2023 14:10:30 -0700 Subject: [PATCH 20/35] fix: ganonhunt playthrough --- Rules.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index e8b8a5fa..b2073ab1 100644 --- a/Rules.py +++ b/Rules.py @@ -864,8 +864,11 @@ def global_rules(world, player): add_mc_rule('Agahnim 1') add_mc_rule('Agahnim 2') - set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player) + set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times + if world.goal[player] != 'ganonhunt': + add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) + set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop From 6ed937971d6df5c9fa2aff37f36b14d372a2ff01 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 22 Dec 2023 14:41:05 -0700 Subject: [PATCH 21/35] fix(vanilla_fill): make uncle weapon non-random, make medallions vanilla fix(enemizer): enemy bans --- ItemList.py | 12 +- source/enemizer/enemy_deny.yaml | 710 ++++++++++++++++++++++++++++++++ 2 files changed, 720 insertions(+), 2 deletions(-) create mode 100644 source/enemizer/enemy_deny.yaml diff --git a/ItemList.py b/ItemList.py index 6a8761f7..d603ae0e 100644 --- a/ItemList.py +++ b/ItemList.py @@ -341,6 +341,8 @@ def generate_itempool(world, player): if not found_sword and world.swords[player] != 'swordless': found_sword = True possible_weapons.append(item) + if world.algorithm == 'vanilla_fill': # skip other possibilities + continue if (item in ['Progressive Bow', 'Bow'] and not found_bow and not world.bow_mode[player].startswith('retro')): found_bow = True @@ -434,9 +436,15 @@ def generate_itempool(world, player): if tr_medallion == 'Random': tr_medallion = None if not mm_medallion: - mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] + if world.algorithm == 'vanilla_fill': + mm_medallion = 'Ether' + else: + mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] if not tr_medallion: - tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] + if world.algorithm == 'vanilla_fill': + tr_medallion = 'Quake' + else: + tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] world.required_medallions[player] = (mm_medallion, tr_medallion) # shuffle bottle refills diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml new file mode 100644 index 00000000..46842052 --- /dev/null +++ b/source/enemizer/enemy_deny.yaml @@ -0,0 +1,710 @@ +UwGeneralDeny: + - [ 0x0002, 0, [ "RollerVerticalDown", "Statue" ] ] #"Sewers - Rat Pots - Rat 1" + - [ 0x0002, 1, [ "RollerVerticalDown", "Statue" ] ] #"Sewers - Rat Pots - Rat 2" + - [ 0x0002, 2, [ "RollerVerticalUp", "Statue" ] ] #"Sewers - Rat Pots - Rat 3" + - [ 0x0002, 3, [ "RollerVerticalUp", "Statue" ] ] #"Sewers - Rat Pots - Rat 4" + - [ 0x0002, 4, [ "Statue" ] ] #"Sewers - Rat Pots - Rat 5" + - [ 0x0002, 15, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Sewers - Rat Pots - Rat 6" + - [ 0x0002, 16, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Sewers - Rat Pots - Rat 7" + - [ 0x0004, 1, ["Statue"]] + - [ 0x0004, 2, ["Statue"]] + - [ 0x0004, 3, ["Statue"]] + - [ 0x0004, 4, ["Statue"]] + - [ 0x0004, 15, ["Statue"]] + - [ 0x000a, 0, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Palace of Darkness - Basement Ledge - Terrorpin 1" + - [ 0x000a, 1, [ "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Palace of Darkness - Basement Ledge - Terrorpin 2" + - [ 0x000b, 1, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Callback - Terrorpin 1" + - [ 0x000e, 0, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Entrance - Freezor" + - [ 0x000e, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari Key - Top Bari" + - [ 0x000e, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari Key - Middle Bari" + - [ 0x0016, 0, [ "RollerVerticalDown", "RollerVerticalUp", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 1" + - [ 0x0016, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 2" + - [ 0x0016, 2, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Blue Bari" + - [ 0x0016, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 3" + - [ 0x0017, 5, [ "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Bumper Room - Fire Bar (Clockwise)" + - [ 0x0019, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Dark Maze - Kodongo 1" + - [ 0x0019, 1, [ "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 2" + - [ 0x0019, 2, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 3" + - [ 0x0019, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Palace of Darkness - Dark Maze - Kodongo 4" + - [ 0x001a, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Compass Room - Mini Helmasaur 1" + - [ 0x001a, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Compass Room - Mini Helmasaur 2" + - [ 0x001b, 3, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Palace of Darkness - Mimics 2 - Red Eyegore" + - [ 0x001b, 4, [ "RollerVerticalUp" ] ] #"Palace of Darkness - Mimics 2 - Green Eyegore L" + - [ 0x001e, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Red Bari 3" + - [ 0x001e, 4, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Red Bari 4" + - [ 0x001e, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 1" + - [ 0x001e, 6, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 2" + - [ 0x001f, 0, [ "RollerHorizontalRight" ] ] #"Ice Palace - Big Key View - Pengator 1" + - [ 0x001f, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0021, 3, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 2" + - [ 0x0021, 4, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 3" + - [ 0x0024, 6, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0026, 1, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Statue" ] ] #"Swamp Palace - Big Spoon - Red Bari 1" + - [ 0x0026, 8, [ "AntiFairyCircle", "Bumper", "Statue" ] ] #"Swamp Palace - Big Spoon - Red Bari 3" + - [ 0x0026, 9, [ "RollerHorizontalRight", "Statue" ] ] #"Swamp Palace - Big Spoon - Kyameron" + - [ 0x0026, 10, [ "Statue" ] ] # multiple push statues in this room can cause issues + - [ 0x0026, 11, [ "Statue" ] ] # multiple push statues in this room can cause issues + - [ 0x0027, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "FirebarCW" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 1" + - [ 0x0027, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 2" + - [ 0x0027, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 3" + - [ 0x0027, 4, ["Bumper", "BigSpike", "AntiFairyCircle", "RollerVerticalDown", "RollerVerticalUp"]] + - [ 0x0027, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Kodongo 1" + - [ 0x0028, 4, [ "RollerVerticalUp" ] ] #"Swamp Palace - Entrance Ledge - Spike Trap" + - [ 0x002a, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] #"Palace of Darkness - Arena Main - Hardhat Beetle 1" + - [ 0x002a, 3, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 2" + - [ 0x002a, 4, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ]] + - [ 0x002a, 6, [ "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 5" + - [ 0x002b, 5, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Fairies - Red Bari 2" + - [ 0x002e, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 1" + - [ 0x002e, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 2" + - [ 0x002e, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 3" + - [ 0x002e, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 4" + - [ 0x002e, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 5" + - [ 0x002e, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 6" + - [ 0x0034, 0, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Hover 1" + - [ 0x0034, 1, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Hover 2" + - [ 0x0034, 2, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Kyameron" + - [ 0x0034, 4, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Zol" + - [ 0x0035, 6, [ "RollerHorizontalRight" ] ] #"Swamp Palace - West Lever - Stalfos 2" + - [ 0x0035, 9, [ "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Swamp Palace - West Lever - Blue Bari" + - [ 0x0036, 7, [ "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Lobby - Hover 3" + - [ 0x0037, 7, [ "RollerHorizontalRight" ] ] #"Swamp Palace - Water 1 - Blue Bari" + - [ 0x0038, 4, [ "RollerHorizontalRight" ] ] #"Swamp Palace - Long Hall - Kyameron 2" + - [ 0x0039, 3, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Mini Helmasaur" + - [ 0x0039, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 1" + - [ 0x0039, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Hardhat Beetle" + - [ 0x0039, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 2" + - [ 0x003b, 1, [ "Bumper" ]] + - [ 0x003c, 0, ["BigSpike"]] + - [ 0x003c, 1, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hookshot Cave - Blue Bari 1" + - [ 0x003c, 2, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hookshot Cave - Blue Bari 2" + - [ 0x003d, 9, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Spark (Counterclockwise)" + - [ 0x003d, 10, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Spark (Clockwise) 1" + - [ 0x003d, 12, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Bunny Beam" + - [ 0x003d, 13, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Antifairy" + - [ 0x003f, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper", "Statue"] ] #"Ice Palace - P Room - Stalfos Knight 1" + - [ 0x003f, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Statue"] ] #"Ice Palace - P Room - Stalfos Knight 2" + - [ 0x003f, 4, [ "Wizzrobe", "Statue", "Bumper", "BigSpike", "AntiFairyCircle"]] # Wizzrobes can't spawn on pots + - [ 0x0040, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Bridge - Blue Guard 1 + - [ 0x0040, 1, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Bridge - Blue Guard 2 + - [ 0x0041, 0, [ "RollerHorizontalLeft" ] ] #"Sewers - Dark Cactus - Rat 1" + - [ 0x0041, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Sewers - Dark Cactus - Rat 2" + - [ 0x0041, 2, [ "RollerVerticalUp" ] ] #"Sewers - Dark Cactus - Rat 3" + - [ 0x0042, 0, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 1" + - [ 0x0042, 1, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 2" + - [ 0x0042, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 3" + - [ 0x0042, 3, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 4" + - [ 0x0042, 4, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 5" + - [ 0x0042, 5, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 6" + - [ 0x0044, 4, [ "RollerVerticalUp", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Joke Room - Zol" + - [ 0x0044, 6, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "BigSpike" ] ] #"Thieves' Town - Joke Room - Red Bari" + - [ 0x0044, 8, [ "Statue", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Joke Room - Blue Bari 4" + - [ 0x0045, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Thieves' Town - Basement Block Totems - Red Zazak" + - [ 0x0045, 4, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0045, 7, [ "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Cells - Blue Zazak 4" + - [ 0x0045, 8, [ "RollerHorizontalRight" ] ] #"Thieves' Town - Cells - Zol" + - [ 0x0046, 0, [ "RollerVerticalUp", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 1" + - [ 0x0046, 2, [ "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 2" + - [ 0x0046, 4, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 3" + - [ 0x0049, 5, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 2" + - [ 0x0049, 7, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 4" + - [ 0x0049, 8, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Skull Woods - Bari Pits - Gibdo 5" + - [ 0x004b, 0, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Palace of Darkness - Mimics 1 - Red Eyegore" + - [ 0x004b, 1, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Warp Hint - Antifairy 1" + - [ 0x004b, 5, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 1" + - [ 0x004b, 6, [ "AntiFairyCircle", "BigSpike" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 2" + - [ 0x004b, 7, [ "AntiFairyCircle", "BigSpike" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 3" + - [ 0x004e, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 1" + - [ 0x004e, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 2" + - [ 0x004e, 2, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 3" + - [ 0x0050, 0, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Guard" + - [ 0x0050, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 1" + - [ 0x0050, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 2" + - [ 0x0052, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Guard" + - [ 0x0052, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 1" + - [ 0x0052, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 2" + - [ 0x0053, 1, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - Bridge - Beamos 1" + - [ 0x0053, 5, [ "RollerVerticalDown" ] ] #"Desert Palace - Popo Genocide - Popo TL" + - [ 0x0053, 7, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - Bridge - Popo 5" + - [ 0x0055, 1, [ "RollerVerticalUp", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Secret Passage Exit - Green Knife Guard 1" + - [ 0x0057, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0057, 1, ["Statue"]] # Statue switch issues + - [ 0x0057, 2, [ "RollerVerticalUp", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Spike Trap" + - [ 0x0057, 3, ["Statue"]] # Statue switch issues + - [ 0x0057, 4, ["Statue"]] # Statue switch issues + - [ 0x0057, 5, ["Statue"]] # Statue switch issues + - [ 0x0057, 7, [ "RollerVerticalUp", "RollerVerticalDown", "Statue" ] ] #"Skull Woods - Big Key Room - Gibdo 2" + - [ 0x0057, 8, ["Statue"]] # Statue switch issues + - [ 0x0057, 9, ["Statue"]] # Statue switch issues + - [ 0x0057, 10, ["Statue"]] # Statue switch issues + - [ 0x0057, 11, ["Statue"]] # Statue switch issues + - [ 0x0057, 12, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "BigSpike", "SpikeBlock", "Statue"]] #"Skull Woods - Big Key Room - Gibdo 6" + - [ 0x0057, 13, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Blue Bari 1" + - [ 0x0057, 14, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Blue Bari 2" + - [ 0x0058, 0, ["Statue"]] + - [ 0x0058, 1, ["Statue"]] + - [ 0x0058, 2, ["Statue"]] + - [ 0x0058, 3, ["Statue"]] + - [ 0x0058, 4, ["Statue"]] + - [ 0x0058, 6, ["Statue"]] + - [ 0x0058, 7, [ "RollerHorizontalLeft", "Statue" ] ] #"Skull Woods - Lever Room - Hardhat Beetle 2" + - [ 0x0058, 8, ["Statue"]] + - [ 0x0059, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Mini Moldorm 1" + - [ 0x0059, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Mini Moldorm 2" + - [ 0x0059, 9, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Gibdo 1" + - [ 0x005e, 3, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Pit Trap - Big Spike Trap" + - [ 0x005e, 4, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ice Palace - Pit Trap - Fire Bar (Clockwise)" + - [ 0x005f, 0, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari University - Blue Bari 1" + - [ 0x005f, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Ice Palace - Bari University - Blue Bari 2" + - [ 0x0060, 0, [ "RollerVerticalUp", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hyrule Castle - West - Blue Guard" + - [ 0x0062, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hyrule Castle - East - Blue Guard" + - [ 0x0064, 2, [ "Bumper" , "Beamos" ] ] #"Thieves' Town - Attic Hall Left - Keese 2" + - [ 0x0064, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0064, 4, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Hall Left - Rat 1" + - [ 0x0065, 0, [ "RollerVerticalUp", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Attic Window - Rat 1" + - [ 0x0065, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Window - Rat 2" + - [ 0x0065, 2, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Attic Window - Rat 3" + - [ 0x0066, 0, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Swamp Palace - Waterfall Room - Hover 1" + - [ 0x0066, 2, [ "AntiFairyCircle", "Bumper"]] + - [ 0x0067, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Blue Bari 1" + - [ 0x0067, 2, ["Bumper"]] #"Skull Woods - Firebar Pits - Blue Bari 2" + - [ 0x0067, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 1" + - [ 0x0067, 4, [ "AntiFairyCircle", "Bumper" ]] + - [ 0x0067, 5, [ "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 3" + - [ 0x0067, 6, [ "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 4" + - [ 0x0067, 7, [ "Beamos", "AntiFairyCircle", "Bumper", "BunnyBeam" ] ] #"Skull Woods - Firebar Pits - Fire Bar (Clockwise)" + - [ 0x006a, 0, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 1" + - [ 0x006a, 1, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 2" + - [ 0x006a, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Palace of Darkness - Dark Alley - Antifairy 1" + - [ 0x006a, 4, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 3" + - [ 0x006a, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 4" + - [ 0x006b, 7, [ "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Mimics 1 - Spike Trap 1" + - [ 0x0071, 0, [ "RollerHorizontalLeft" ] ] #"Hyrule Castle - Basement Trap - Green Guard" + - [ 0x0074, 0, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - North Hallway - Red Devalant 1" + - [ 0x0074, 1, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - North Hallway - Red Devalant 2" + - [ 0x0074, 4, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - North Hallway - Leever 1" + - [ 0x0074, 5, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - North Hallway - Leever 2" + - [ 0x0076, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Hover 1" + - [ 0x0076, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Kyameron" + - [ 0x0076, 3, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Hover 2" + - [ 0x0076, 4, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Zol" + - [ 0x0076, 6, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Swamp Palace - Toilet Left - Blue Bari" + - [ 0x007b, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - DMs Room - Blue Bari 1" + - [ 0x007b, 1, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - DMs Room - Blue Bari 2" + - [ 0x007b, 6, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - DMs Room - Statue" + - [ 0x007b, 7, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - DMs Room - Hardhat Beetle" + - [ 0x007c, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Fire Bar (Counterclockwise)" + - [ 0x007c, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Spike Trap" + - [ 0x007c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Fire Bar (Clockwise)" + - [ 0x007c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Hardhat Beetle" + - [ 0x007d, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 1" + - [ 0x007d, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 2" + - [ 0x007d, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 3" + - [ 0x007d, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 4" + - [ 0x007d, 4, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [ 0x007d, 7, ["RollerVerticalUp", "RollerHorizontalLeft", "StalfosKnight", "Geldman", "Blob", "Stal"]] #"Ganon's Tower - The Zoo - Mini Helmasaur" + - [ 0x007d, 8, ["RollerVerticalUp", "RollerHorizontalLeft", "RollerHorizontalRight", "StalfosKnight", "Geldman", "Blob", "Stal"]] #"Ganon's Tower - The Zoo - Red Bari" + - [ 0x007d, 10, ["StalfosKnight", "Geldman", "Blob", "Stal"]] +# todo - consider adding firesnake to 0-3: has a hard time moving, could block hookshots for quite a while + - [ 0x007f, 0, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 1" + - [ 0x007f, 1, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper", "ArmosStatue" ] ] #"Ice Palace - Big Spikes - Red Bari 2" + - [ 0x007f, 2, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 3" + - [ 0x007f, 3, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 4" + - [ 0x007f, 4, [ "RollerVerticalDown" ] ] #"Ice Palace - Big Spikes - Big Spike Trap 1" + - [ 0x007f, 5, [ "RollerVerticalDown" ] ] #"Ice Palace - Big Spikes - Big Spike Trap 2" + - [ 0x0082, 0, [ "RollerVerticalDown" ] ] #"Hyrule Castle - Basement Playpit - Blue Guard 1" + - [ 0x0082, 2, [ "RollerVerticalUp" ] ] #"Hyrule Castle - Basement Playpit - Blue Guard 3" + - [ 0x0083, 0, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Desert Palace - Left Hallway - Blue Devalant 1" + - [ 0x0084, 0, [ "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Main Room - Left - Leever 1" + - [ 0x0084, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Main Room - Left - Leever 2" + - [ 0x0085, 2, [ "RollerHorizontalRight" ] ] #"Desert Palace - Compass Room - Popo TL" + - [ 0x0085, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Right Hallway - Leever 2" + - [ 0x008b, 3, ["RollerHorizontalRight"]] + - [ 0x008b, 4, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "BigSpike"]] #"Ganon's Tower - Map Room - Spike Trap" + - [ 0x008b, 6, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Map Room - Fire Bar (Clockwise)" + - [ 0x008b, 7, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Map Room - Fire Bar (Counterclockwise)" + - [ 0x008c, 14, ["AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x008d, 1, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Tile Room - Yomo Medusa T" + - [ 0x008d, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Tile Room - Spike Trap" + - [ 0x008d, 8, [ "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Tile Room - Stalfos" + - [ 0x008d, 9, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Tile Room - Fire Bar (Clockwise)" + - [ 0x008d, 10, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ganon's Tower - Tile Room - Blue Bari 1" + - [ 0x008d, 12, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ganon's Tower - Tile Room - Blue Bari 2" + - [ 0x008e, 2, [ "Wizzrobe", "Statue"] ] # Wizzrobes can't spawn on pots + - [ 0x0092, 8, [ "RollerVerticalUp", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Misery Mire - Dark Weave - Spike Trap" + - [ 0x0092, 9, [ "RollerHorizontalRight" ] ] #"Misery Mire - Dark Weave - Antifairy 3" + - [ 0x0092, 10, [ "RollerHorizontalLeft" ] ] #"Misery Mire - Dark Weave - Stalfos" + - [ 0x0095, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 1" + - [ 0x0095, 1, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 2" + - [ 0x0095, 2, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 3" + - [ 0x0095, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 4" + - [ 0x0096, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 1 - Fire Bar (Clockwise)" + - [ 0x0098, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 1" + - [ 0x0098, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 2" + - [ 0x0098, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 3" + - [ 0x0098, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 4" + - [ 0x0098, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 5" + - [ 0x009b, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 1" + - [ 0x009b, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 2" + - [ 0x009b, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] + - [ 0x009b, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 4" + - [ 0x009b, 8, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 5" + - [ 0x009b, 9, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 6" + - [ 0x009b, 10, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 7" + - [ 0x009c, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Mini Helmasaur" + - [ 0x009c, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 2" + - [ 0x009c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 3" + - [ 0x009c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 4" + - [ 0x009c, 5, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 5" + - [ 0x009d, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Compass Room - Gibdo 2" + - [ 0x009d, 6, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Ganon's Tower - Compass Room - Blue Bari 1" + - [ 0x009d, 7, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Compass Room - Blue Bari 2" + - [ 0x009d, 8, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Ganon's Tower - Compass Room - Blue Bari 3" + - [ 0x009e, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 1" + - [ 0x009e, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 2" + - [ 0x009e, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Stalfos Knight" + - [ 0x009e, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 3" + - [ 0x00a0, 1, [ "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Boss Antechamber - Antifairy" + - [ 0x00a1, 2, [ "Statue", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Fish Room - Spark (Clockwise) 2" + - [ 0x00a1, 7, [ "Wizzrobe", "Statue"] ] # Wizzrobes can't spawn on pots + - [ 0x00a5, 2, [ "BigSpike" ] ] #"GT Wizzrobes 1 - Wizzrobe 3" + - [ 0x00a5, 10, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Laser Bridge - Red Spear Guard" + - [ 0x00a8, 1, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 2" + - [ 0x00a8, 3, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 4" + - [ 0x00a9, 1, [ "RollerHorizontalRight", "RollerHorizontalLeft" ] ] + - [ 0x00aa, 4, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - East Wing - Top - Stalfos 3" + - [ 0x00aa, 5, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - East Wing - Top - Popo B 2" + - [ 0x00ab, 7, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Thieves' Town - Spike Dodge - Spike Trap 6" + - [ 0x00ae, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 1" + - [ 0x00ae, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 2" + - [ 0x00af, 0, [ "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Ice Clock - Fire Bar (Clockwise)" + - [ 0x00b1, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 1" + - [ 0x00b1, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 2" + - [ 0x00b1, 4, ["Bumper", "BigSpike", "AntiFairyCircle" ]] + - [ 0x00b2, 1, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00b2, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00b2, 6, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Misery Mire - Sluggula Cross - Sluggula TR" + - [ 0x00b2, 8, [ "RollerVerticalDown" ] ] #"Misery Mire - Popo Push - Medusa 1" + - [ 0x00b2, 9, [ "RollerVerticalUp" ] ] #"Misery Mire - Sluggula Cross - Sluggula BL" + - [ 0x00b3, 0, [ "RollerVerticalUp", "RollerHorizontalRight", "BigSpike", "SpikeBlock" ] ] #"Misery Mire - Spike Room - Stalfos 1" + - [ 0x00b3, 2, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "Bumper" ] ] #"Misery Mire - Spike Room - Beamos" + - [ 0x00b3, 3, [ "AntiFairyCircle", "Bumper" ] ] #"Misery Mire - Spike Room - Yomo Medusa" + - [ 0x00b6, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Tile Room - Zol 1" + - [ 0x00b6, 8, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Tile Room - Zol 2" + - [ 0x00ba, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Antifairy 1" + - [ 0x00ba, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Antifairy 2" + - [ 0x00ba, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Popo B 1" + - [ 0x00ba, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Popo B 2" + - [ 0x00bb, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Gibo 1" + - [ 0x00bb, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Antifairy 1" + - [ 0x00bb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Gibo 3" + - [ 0x00bb, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Fire Snake" + - [ 0x00bb, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Gibo 4" + - [ 0x00bb, 8, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Gibo 5" + - [ 0x00bb, 9, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Antifairy 2" + - [ 0x00bc, 6, [ "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Toilet - Stalfos 2" + - [ 0x00bc, 7, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Toilet - Stalfos 3" + - [ 0x00bc, 8, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Thieves' Town - Toilet - Stalfos 4" + - [ 0x00bf, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on collision + - [ 0x00c1, 3, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Misery Mire - 4 Rails - Stalfos 1" + - [ 0x00c2, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Misery Mire - Main Lobby - blue - Fire Snake 1" + - [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur" + - [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4" + - [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1" + - [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2" + - [ 0x00cb, 9, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] + - [ 0x00cb, 10, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] + - [ 0x00cb, 11, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00cc, 8, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) + - [ 0x00cc, 12, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) + - [ 0x00ce, 0, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCCW", "Bumper" ] ] #"Ice Palace - Over Boss - top - Red Bari 1" + - [ 0x00ce, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCW", "Bumper" ] ] #"Ice Palace - Over Boss - top - Red Bari 2" + - [ 0x00ce, 3, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Over Boss - top - Statue" + - [ 0x00ce, 4, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 5, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 6, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 7, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00d0, 0, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 1, [ "AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x00d0, 4, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 5, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 6, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 7, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 9, [ "AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x00d0, 6, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Dark Maze - Blue Guard 2 + - [ 0x00d2, 8, [ "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Mire 2 - Popo BL" + - [ 0x00d5, 4, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Eye Bridge - Hardhat Beetle" + - [ 0x00d8, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Red Eyegore L" + - [ 0x00d8, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Red Eyegore R" + - [ 0x00d8, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B TL" + - [ 0x00d8, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B TR" + - [ 0x00d8, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B LT" + - [ 0x00d8, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B RT" + - [ 0x00d8, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo LB" + - [ 0x00d8, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo RB" + - [ 0x00d8, 8, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Kill Room 1 - Red Eyegore" + - [ 0x00d9, 1, [ "RollerHorizontalRight" ] ] #"Eastern Palace - Dodgeball - Green Eyegore 1" + - [ 0x00db, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00dc, 2, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] + - [ 0x00dc, 9, [ "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Thieves' Town - Grand Room SE - Fire Snake 2" + - [ 0x00df, 0, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Paradox Cave - Top - Mini Moldorm 1" + - [ 0x00df, 1, [ "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle" ] ] #"Paradox Cave - Top - Mini Moldorm 2" + - [ 0x00e4, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 1" + - [ 0x00e4, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 2" + - [ 0x00e4, 2, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 3" + - [ 0x00e5, 4, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 5" + - [ 0x00e5, 5, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 6" + - [ 0x00e7, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 1" + - [ 0x00e7, 1, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 2" + - [ 0x00e7, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 3" + - [ 0x00e7, 3, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 4" + - [ 0x00e7, 4, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 5" + - [ 0x00e7, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 6" + - [ 0x00e7, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 7" + - [ 0x00e8, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 1" + - [ 0x00e8, 1, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 2" + - [ 0x00ee, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 1" + - [ 0x00ee, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 2" + - [ 0x00ee, 2, [ "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 3" + - [ 0x00ee, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Blue Bari 1" + - [ 0x00ee, 4, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Blue Bari 2" + - [ 0x00ef, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Paradox Cave - Middle - Mini Moldorm 2" + - [ 0x00f1, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 1" + - [ 0x00f1, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 2" + - [ 0x00f1, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 3" + - [ 0x00f1, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 4" + - [ 0x00f1, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 5" + - [ 0x00f1, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 6" + - [ 0x00fd, 0, [ "Bumper" ] ] + - [ 0x0107, 1, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] + - [ 0x0107, 2, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] + - [0x010b, 6, ["RollerHorizontalRight"]] + - [0x010c, 6, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] + - [0x010c, 7, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] +OwGeneralDeny: + - [0x03, 2, ["Gibo"]] # OldMan eating Gibo + - [0x03, 4, ["Gibo"]] # OldMan eating Gibo + - [0x03, 5, ["Gibo"]] # OldMan eating Gibo + - [0x03, 6, ["Gibo"]] # OldMan eating Gibo + - [0x03, 8, ["Gibo"]] # OldMan eating Gibo + - [0x03, 9, ["Gibo"]] # OldMan eating Gibo + - [0x03, 10, ["Gibo"]] # OldMan eating Gibo + - [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal + - [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here + - [0x40, 0, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 7, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x5e, 0, ["Gibo"]] # kiki eating Gibo + - [0x5e, 1, ["Gibo"]] # kiki eating Gibo + - [0x5e, 2, ["Gibo"]] # kiki eating Gibo + - [0x5e, 3, ["Gibo"]] # kiki eating Gibo + - [0x5e, 4, ["RollerVerticalUp", "Gibo"]] # forbid that one roller for kiki pod, and the kiki eating Gibo + - [0x5e, 5, ["Gibo"]] # kiki eating Gibo + - [0x5e, 6, ["Gibo"]] # kiki eating Gibo + - [0x5e, 7, ["Gibo"]] # kiki eating Gibo + - [0x5e, 8, ["Gibo"]] # kiki eating Gibo + - [0x5e, 9, ["Gibo"]] # kiki eating Gibo + - [0x5e, 10, ["Gibo"]] # kiki eating Gibo + - [0x5e, 11, ["Gibo"]] # kiki eating Gibo + - [0x5e, 12, ["Gibo"]] # kiki eating Gibo + - [0x5e, 13, ["Gibo"]] # kiki eating Gibo + - [0x5e, 14, ["Gibo"]] # kiki eating Gibo + - [0x5e, 15, ["Gibo"]] # kiki eating Gibo + - [0x5e, 16, ["Gibo"]] # kiki eating Gibo + - [0x5e, 17, ["Gibo"]] # kiki eating Gibo + - [0x5e, 18, ["Gibo"]] # kiki eating Gibo + - [0x5e, 19, ["Gibo"]] # kiki eating Gibo + - [0x5e, 20, ["Gibo"]] # kiki eating Gibo + - [0x77, 1, ["Bumper"]] # soft-lock potential near ladder +UwEnemyDrop: + - [0x0085, 9, ["Babasu"]] # ran off the edge and didn't return + - [0x00cb, 3, ["Zoro"]] # layer issues + - [0x00cb, 5, ["Zoro"]] # layer issues + - [0x00cb, 9, ["Zoro"]] # layer issues + - [0x00cb, 10, ["Zoro"]] # layer issues + - [0x00cc, 5, ["Babasu"]] # little hard to see and kill appropriately +# the following are behind rails or otherwise unactivate-able + - [0x0077, 4, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] # can't activate here + - [0x0077, 5, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] + - [0x008D, 10, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [0x008D, 12, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [0x00b0, 7, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay + - [0x00b0, 8, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay +# the following are not allowed at certain pits (or on conveyors near pits) +# because they despawned or clipped away or immediately fell, etc + - [0x003d, 9, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x003d, 10, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0044, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 4, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 5, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 6, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 8, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0049, 10, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007b, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007b, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007f, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0095, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x00b5, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00b5, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00b5, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 4, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 5, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Bumper", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 6, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00e6, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + # wizzrobe despawn issues - on pots/blocks - too close to some object + - [0x0013, 3, ["Wizzrobe"]] + - [0x0016, 0, ["Wizzrobe"]] + - [0x0016, 1, ["Wizzrobe"]] + - [0x0016, 2, ["Wizzrobe"]] + - [0x0016, 3, ["Wizzrobe"]] + - [0x0017, 5, ["Wizzrobe", "Stal"]] + - [0x0019, 2, ["Wizzrobe"]] + - [0x0019, 3, ["Wizzrobe"]] + - [0x001e, 1, ["Wizzrobe"]] + - [0x001e, 2, ["Wizzrobe"]] + - [0x0027, 5, ["Wizzrobe"]] + - [0x0027, 6, ["Wizzrobe"]] + - [0x002a, 3, ["Wizzrobe"]] + - [0x002a, 7, ["Wizzrobe"]] + - [0x002e, 4, ["Wizzrobe"]] + - [0x0035, 5, ["Wizzrobe"]] + - [0x0036, 8, ["Wizzrobe"]] + - [0x003b, 0, ["Wizzrobe"]] + - [0x003b, 2, ["Wizzrobe"]] + - [0x003b, 4, ["Wizzrobe"]] + - [0x003b, 6, ["Wizzrobe"]] + - [0x003c, 1, ["Wizzrobe"]] + - [0x003d, 11, ["Wizzrobe"]] + - [0x003d, 12, ["Wizzrobe"]] + - [0x003d, 13, ["Wizzrobe"]] + - [0x004b, 2, ["Wizzrobe"]] + - [0x004b, 6, ["Wizzrobe"]] + - [0x004b, 7, ["Wizzrobe"]] + - [0x004e, 3, ["Wizzrobe", "Stal"]] + - [0x0054, 3, ["Wizzrobe", "Stal"]] + - [0x0055, 2, ["Wizzrobe"]] # slightly on wall + - [0x005e, 4, ["Wizzrobe", "Stal"]] + - [0x0065, 3, ["Wizzrobe"]] + - [0x0067, 5, ["Wizzrobe"]] + - [0x0067, 6, ["Wizzrobe"]] + - [0x0067, 7, ["Wizzrobe", "Stal"]] + - [0x0067, 8, ["Wizzrobe", "Stal"]] + - [0x0074, 5, ["Wizzrobe"]] + - [0x007c, 1, ["Wizzrobe", "Stal"]] + - [0x007c, 3, ["Wizzrobe", "Stal"]] + - [0x007e, 1, ["Wizzrobe", "Stal"]] + - [0x007e, 6, ["Wizzrobe", "Stal"]] + - [0x0083, 9, ["Wizzrobe"]] + - [0x008b, 6, ["Wizzrobe", "Stal"]] + - [0x008b, 7, ["Wizzrobe", "Stal"]] + - [0x008d, 9, ["Wizzrobe", "Stal"]] + - [0x0096, 0, ["Wizzrobe", "Stal"]] + - [0x009b, 11, ["Wizzrobe"]] + - [0x009f, 5, ["Wizzrobe", "Stal"]] + - [0x00a1, 1, ["Wizzrobe"]] + - [0x00aa, 5, ["Wizzrobe"]] + - [0x00af, 0, ["Wizzrobe", "Stal"]] + - [0x00b0, 1, ["Wizzrobe"]] + - [0x00b0, 2, ["Wizzrobe"]] + - [0x00b2, 4, ["Wizzrobe"]] + - [0x00b8, 2, ["Wizzrobe"]] + - [0x00bf, 1, ["Wizzrobe"]] + - [0x00c1, 3, ["Wizzrobe", "Stal"]] + - [0x00c2, 0, ["Wizzrobe"]] + - [0x00c2, 3, ["Wizzrobe"]] + - [0x00c2, 7, ["Wizzrobe"]] + - [0x00ce, 4, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 5, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 6, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 7, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00d0, 0, ["Wizzrobe"]] + - [0x00d0, 2, ["Wizzrobe"]] + - [0x00d0, 4, ["Wizzrobe"]] + - [0x00d0, 5, ["Wizzrobe"]] + - [0x00d0, 9, ["Wizzrobe"]] + - [0x00d5, 4, ["Wizzrobe"]] + - [0x00df, 1, ["Wizzrobe"]] # slightly on wall + - [0x00e7, 4, ["Wizzrobe"]] # slightly on wall + - [0x00fd, 1, ["Wizzrobe"]] # slightly on rock + - [0x010c, 4, ["Wizzrobe"]] + - [0x010c, 5, ["Wizzrobe"]] + # other mimic cave spots are in the rail section + + # enemies that have problems with conveyors + - [0x003b, 0, ["GreenMimic", "RedMimic"]] + - [0x003b, 1, ["GreenMimic", "RedMimic"]] + - [0x003b, 2, ["GreenMimic", "RedMimic"]] + - [0x003b, 3, ["GreenMimic", "RedMimic"]] + - [0x003b, 4, ["GreenMimic", "RedMimic"]] + - [0x003b, 5, ["GreenMimic", "RedMimic"]] + - [0x003b, 6, ["GreenMimic", "RedMimic"]] + - [0x003d, 6, ["GreenMimic", "RedMimic"]] + - [0x003d, 7, ["GreenMimic", "RedMimic"]] + - [0x003e, 8, ["GreenMimic", "RedMimic"]] + - [0x003e, 9, ["GreenMimic", "RedMimic"]] + - [0x003e, 10, ["GreenMimic", "RedMimic"]] + - [0x003e, 11, ["GreenMimic", "RedMimic"]] + - [0x0044, 0, ["GreenMimic", "RedMimic"]] + - [0x0044, 1, ["GreenMimic", "RedMimic"]] + - [0x0044, 2, ["GreenMimic", "RedMimic"]] + - [0x0044, 3, ["GreenMimic", "RedMimic"]] + - [0x0044, 5, ["GreenMimic", "RedMimic"]] + - [0x004c, 0, ["GreenMimic", "RedMimic"]] + - [0x004c, 1, ["GreenMimic", "RedMimic"]] + - [0x004c, 2, ["GreenMimic", "RedMimic"]] + - [0x004c, 3, ["GreenMimic", "RedMimic"]] + - [0x004c, 4, ["GreenMimic", "RedMimic"]] + - [0x004c, 5, ["GreenMimic", "RedMimic"]] + - [0x004c, 6, ["GreenMimic", "RedMimic"]] + - [0x004c, 7, ["GreenMimic", "RedMimic"]] + - [0x005d, 3, ["GreenMimic", "RedMimic"]] + - [0x005d, 4, ["GreenMimic", "RedMimic"]] + - [0x005d, 5, ["GreenMimic", "RedMimic"]] + - [0x005d, 6, ["GreenMimic", "RedMimic"]] + - [0x005d, 8, ["GreenMimic", "RedMimic"]] + - [0x005d, 9, ["GreenMimic", "RedMimic"]] + - [0x005d, 10, ["GreenMimic", "RedMimic"]] + - [0x005d, 11, ["GreenMimic", "RedMimic"]] + - [0x005d, 12, ["GreenMimic", "RedMimic"]] +# - [0x006d, ?, ["GreenMimic", "RedMimic"]] # conveyor doesn't hit edge +# - [0x008b, ?, ["GreenMimic", "RedMimic"]] # conveyor doesn't hit edge + - [0x0092, 2, ["GreenMimic", "RedMimic"]] + - [0x00a5, 0, ["GreenMimic", "RedMimic"]] + - [0x00a5, 1, ["GreenMimic", "RedMimic"]] + - [0x00a5, 4, ["GreenMimic", "RedMimic"]] + - [0x00a5, 5, ["GreenMimic", "RedMimic"]] + - [0x00a5, 6, ["GreenMimic", "RedMimic"]] + - [0x00bb, 1, ["GreenMimic", "RedMimic"]] + - [0x00bb, 4, ["GreenMimic", "RedMimic"]] + - [0x00bb, 5, ["GreenMimic", "RedMimic"]] + - [0x00bb, 6, ["GreenMimic", "RedMimic"]] + - [0x00bb, 7, ["GreenMimic", "RedMimic"]] + - [0x00bb, 8, ["GreenMimic", "RedMimic"]] + - [0x00bb, 9, ["GreenMimic", "RedMimic"]] + - [0x00bb, 10, ["GreenMimic", "RedMimic"]] + - [0x00bc, 0, ["GreenMimic", "RedMimic"]] + - [0x00bc, 1, ["GreenMimic", "RedMimic"]] + - [0x00bc, 2, ["GreenMimic", "RedMimic"]] + - [0x00bc, 3, ["GreenMimic", "RedMimic"]] + - [0x00bc, 4, ["GreenMimic", "RedMimic"]] + - [0x00bc, 5, ["GreenMimic", "RedMimic"]] + - [0x00c1, 5, ["GreenMimic", "RedMimic"]] + - [0x00c1, 8, ["GreenMimic", "RedMimic"]] + - [0x00c1, 9, ["GreenMimic", "RedMimic"]] + - [0x00c1, 10, ["GreenMimic", "RedMimic"]] + - [0x00c1, 11, ["GreenMimic", "RedMimic"]] + - [0x00d1, 5, ["GreenMimic", "RedMimic"]] + - [0x00d1, 6, ["GreenMimic", "RedMimic"]] + + # the following are all slightly in the wall on spawn - not too applicable right now, these don't drop anyway + - [0x0064, 0, ["Leever"]] + - [0x00e5, 3, ["Wizzrobe"]] + - [0x00e5, 4, ["Leever", "Wizzrobe"]] + - [0x00e5, 5, ["Leever", "Wizzrobe"]] + # the pit one in 0xe6 room is in the pit section + - [0x00e6, 1, ["Leever", "Wizzrobe"]] + - [0x00e6, 2, ["Leever", "Wizzrobe"]] + - [0x00e6, 3, ["Leever", "Wizzrobe"]] + - [0x00e6, 4, ["Leever", "Wizzrobe"]] + - [0x00e7, 0, ["Wizzrobe"]] + - [0x00e7, 1, ["Wizzrobe"]] + - [0x00e7, 2, ["Wizzrobe"]] + - [0x00e7, 3, ["Leever"]] + - [0x00e7, 4, ["Leever", "Wizzrobe"]] + - [0x00e7, 5, ["Leever", "Wizzrobe"]] + - [0x00e7, 6, ["Wizzrobe"]] + - [0x00f0, 2, ["Leever"]] # clipped away + - [0x00f0, 3, ["Leever"]] + - [0x00f0, 4, ["Leever"]] + - [0x00f0, 5, ["Leever"]] + - [0x00f0, 6, ["Leever"]] + - [0x00f0, 8, ["Leever"]] + - [0x00f1, 0, ["Leever", "Wizzrobe"]] + - [0x00f1, 1, ["Leever", "Wizzrobe"]] + - [0x00f1, 2, ["Wizzrobe"]] + - [0x00f1, 3, ["Wizzrobe"]] + - [0x00f1, 4, ["Wizzrobe"]] + - [0x00f1, 5, ["Wizzrobe"]] + - [0x00f1, 6, ["Leever", "Wizzrobe"]] + - [0x00f1, 7, ["Leever", "Wizzrobe"]] + - [0x00f1, 8, ["Leever", "Wizzrobe"]] + - [0x00f1, 9, ["Leever", "Wizzrobe"]] + From 92f7d4da9276c929c886519fc924005ec7b8b33c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Dec 2023 20:40:32 -0600 Subject: [PATCH 22/35] Added new option to export pre-seed customizer template yaml --- Main.py | 12 ++++++++---- source/classes/CustomSettings.py | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Main.py b/Main.py index 347aa451..27ab9447 100644 --- a/Main.py +++ b/Main.py @@ -75,6 +75,7 @@ def main(args, seed=None, fish=None): seed = world.customizer.determine_seed(seed) seeded = True world.customizer.adjust_args(args) + world = init_world(args, fish) if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -437,10 +438,17 @@ def init_world(args, fish): if code: Settings.adjust_args_from_code(code, player, args) + customized = None + if args.customizer: + customized = CustomSettings() + customized.load_yaml(args.customizer) + customized.adjust_args(args, False) + world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + world.customizer = customized world.boots_hint = args.boots_hint.copy() world.remote_items = args.remote_items.copy() world.mapshuffle = args.mapshuffle.copy() @@ -487,10 +495,6 @@ def init_world(args, fish): world.collection_rate = args.collection_rate.copy() world.colorizepots = args.colorizepots.copy() - world.customizer = None - if args.customizer: - world.customizer = CustomSettings() - world.customizer.load_yaml(args.customizer) return world diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 3a48132e..f15a38f0 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -48,11 +48,13 @@ class CustomSettings(object): meta = defaultdict(lambda: None, self.file_source['meta']) return meta['players'] - def adjust_args(self, args): + def adjust_args(self, args, resolve_weighted=True): def get_setting(value: Any, default): if value or value == 0: if isinstance(value, dict): - return random.choices(list(value.keys()), list(value.values()), k=1)[0] + if resolve_weighted: + return random.choices(list(value.keys()), list(value.values()), k=1)[0] + return None else: return value return default From ba904f3f63752f2a1f26e8aa84f989d99b266421 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 00:32:38 -0600 Subject: [PATCH 23/35] Fixed duplicate exit issue in copy_world --- Main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Main.py b/Main.py index 27ab9447..3599fee3 100644 --- a/Main.py +++ b/Main.py @@ -634,7 +634,9 @@ def copy_world(world): for exit in region.exits: if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) - ret.get_entrance(exit.name, exit.player).connect(dest_region) + src_exit = ret.get_entrance(exit.name, exit.player) + if exit.name not in [e.name for e in dest_region.entrances]: + src_exit.connect(dest_region) # fill locations for location in world.get_locations(): @@ -796,7 +798,9 @@ def copy_world_premature(world, player): for exit in region.exits: if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) - ret.get_entrance(exit.name, exit.player).connect(dest_region) + src_exit = ret.get_entrance(exit.name, exit.player) + if exit.name not in [e.name for e in dest_region.entrances]: + src_exit.connect(dest_region) from OverworldShuffle import categorize_world_regions categorize_world_regions(ret, player) From 121091c5a322eb3524fe9bcbc21754709ec6a05f Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Dec 2023 17:22:36 -0700 Subject: [PATCH 24/35] fix(generation): reduce memory usage for bunny walk calculations fix(key logic): make partial the default --- BaseClasses.py | 2 +- CLI.py | 2 +- RELEASENOTES.md | 14 +++++++++++++- Rules.py | 12 +++++++----- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index c57e6a1b..ea311db9 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -170,7 +170,7 @@ class World(object): set_player_attr('door_self_loops', False) set_player_attr('door_type_mode', 'original') set_player_attr('trap_door_mode', 'optional') - set_player_attr('key_logic_algorithm', 'default') + set_player_attr('key_logic_algorithm', 'partial') set_player_attr('shopsanity', False) set_player_attr('mixed_travel', 'prevent') diff --git a/CLI.py b/CLI.py index e0039b65..ac628018 100644 --- a/CLI.py +++ b/CLI.py @@ -228,7 +228,7 @@ def parse_settings(): "intensity": 3, "door_type_mode": "original", "trap_door_mode": "optional", - "key_logic_algorithm": "default", + "key_logic_algorithm": "partial", "decoupledoors": False, "door_self_loops": False, "experimental": False, diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0c754ac6..f299105b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,7 +109,19 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes -* 1.2.0.22u +* 1.4.0.0v + * Generation: fix for bunny walk logic taking up too much memory + * Key Logic: Partial is now the new default +* 1.3.0.9v + * Ganonhunt: playthrough no longer collects crystals + * Vanilla Fill: Uncle weapon is always a sword, medallions for Mire/TR will be vanilla + * Customizer: support shufflebosses/shuffleenemies as well as boss_shuffle/enemy_shuffle +* 1.3.0.8v + * No Logic Standard ER: Rain doors aren't blocked if no logic is enabled. + * MW Progression Balancing: Change to be percentage based instead of raw count. (80% threshold) + * Take anys: Good Bee cave chosen as take any should no longer prevent generation + * Money balancing: Fixed generation issue + 1.2.0.22u * Flute can't be activated in rain state (except glitched modes) (Thanks codemann!) * ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used) * Logic issues: diff --git a/Rules.py b/Rules.py index b2073ab1..91c35c63 100644 --- a/Rules.py +++ b/Rules.py @@ -1711,16 +1711,17 @@ def set_bunny_rules(world, player, inverted): # for each such entrance a new option is added that consist of: # a) being able to reach it, and # b) being able to access all entrances from there to `region` - seen = {region} - queue = deque([(region, [])]) + queue = deque([(region, [], {region})]) + seen_sets = set([frozenset({region})]) while queue: (current, path) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region - if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: + new_seen = seen.union({new_region}) + if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_seen in seen_sets: continue new_path = path + [entrance.access_rule] - seen.add(new_region) + seen_sets.add(frozenset(new_seen)) if not is_link(new_region): if world.logic[player] == 'owglitches': if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: @@ -1757,7 +1758,8 @@ def set_bunny_rules(world, player, inverted): else: continue if is_bunny(new_region): - queue.append((new_region, new_path)) + # todo: if not owg or hmg and entrance is in bunny_impassible_doors, then skip this nonsense? + queue.append((new_region, new_path, new_seen)) else: # we have reached pure light world, so we have a new possible option possible_options.append(path_to_access_rule(new_path, entrance)) From eb0276926debfd1424491ca8d2b5da421a1066f4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 03:51:49 -0600 Subject: [PATCH 25/35] Fixed duplicate exit issue in copy_world --- Main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Main.py b/Main.py index 3599fee3..5ec9f234 100644 --- a/Main.py +++ b/Main.py @@ -635,8 +635,11 @@ def copy_world(world): if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) src_exit = ret.get_entrance(exit.name, exit.player) - if exit.name not in [e.name for e in dest_region.entrances]: - src_exit.connect(dest_region) + if exit.name not in [e.name for e in dest_region.entrances if e.connected_region is not None]: + if exit.name in [e.name for e in dest_region.entrances]: + src_exit.connected_region = dest_region + else: + src_exit.connect(dest_region) # fill locations for location in world.get_locations(): @@ -799,8 +802,11 @@ def copy_world_premature(world, player): if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) src_exit = ret.get_entrance(exit.name, exit.player) - if exit.name not in [e.name for e in dest_region.entrances]: - src_exit.connect(dest_region) + if exit.name not in [e.name for e in dest_region.entrances if e.connected_region is not None]: + if exit.name in [e.name for e in dest_region.entrances]: + src_exit.connected_region = dest_region + else: + src_exit.connect(dest_region) from OverworldShuffle import categorize_world_regions categorize_world_regions(ret, player) From 785188904d52b198dc2224d2c3337a785ceb8a74 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 03:52:57 -0600 Subject: [PATCH 26/35] Added missing OW Terrain exits --- OWEdges.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWEdges.py b/OWEdges.py index 751bd307..90786d6f 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1643,6 +1643,7 @@ OWExitTypes = { 'Lake Hylia East Pier', 'Lake Hylia Water D Approach', 'Lake Hylia Water D Leave', + 'Ice Cave Pier', 'Desert Pass Ladder (South)', 'Desert Pass Rocks (North)', 'Desert Pass Rocks (South)', @@ -1702,6 +1703,7 @@ OWExitTypes = { 'Ice Lake Northeast Pier', 'Ice Lake Northeast Pier Hop', 'Ice Lake Iceberg Water Entry', + 'Shopping Mall Pier', 'Bomber Corner Water Drop', 'Bomber Corner Pier' ], From e1a86221ea41b904a58ff8512c20b0da39bdc107 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 03:54:50 -0600 Subject: [PATCH 27/35] Fixed logic with Laser Bridge with Byrna Item Functionality --- BaseClasses.py | 4 +++- Rules.py | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ea311db9..37d365c6 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1300,7 +1300,9 @@ class CollectionState(object): return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.has_sword(player)) def can_avoid_lasers(self, player): - return self.has('Mirror Shield', player) or self.has('Cane of Byrna', player) or self.has('Cape', player) + return (self.has('Mirror Shield', player) or + self.has('Cape', player) or + (self.has('Cane of Byrna', player) and self.world.difficulty_adjustments[player] not in ['hard', 'expert'])) def is_not_bunny(self, region, player): return self.has_Pearl(player) or not region.can_cause_bunny(player) diff --git a/Rules.py b/Rules.py index 91c35c63..3945be15 100644 --- a/Rules.py +++ b/Rules.py @@ -604,10 +604,10 @@ def global_rules(world, player): set_rule(location, lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Final Abyss Balcony Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Final Abyss Ledge Path', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.can_avoid_lasers(player)) set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Prize', player)) From 359d2b132e82898239dfb35fe85ab36b633ba47b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 04:14:44 -0600 Subject: [PATCH 28/35] fix(generation): reduce memory usage for bunny walk calculations --- Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index 3945be15..6600d819 100644 --- a/Rules.py +++ b/Rules.py @@ -1714,7 +1714,7 @@ def set_bunny_rules(world, player, inverted): queue = deque([(region, [], {region})]) seen_sets = set([frozenset({region})]) while queue: - (current, path) = queue.popleft() + (current, path, seen) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region new_seen = seen.union({new_region}) From 24d1eefb8c626c756ad3cf3ce84206b379f00fb9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 30 Dec 2023 16:48:38 -0600 Subject: [PATCH 29/35] Adding back in missing logger --- Main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Main.py b/Main.py index 5ec9f234..c1fb8cba 100644 --- a/Main.py +++ b/Main.py @@ -68,6 +68,8 @@ def main(args, seed=None, fish=None): world = init_world(args, fish) + logger = logging.getLogger('') + if args.securerandom: random.use_secure() seeded = False From c521980982430a806d7d1d16f6c0b835dec0ef89 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 31 Dec 2023 03:17:43 -0600 Subject: [PATCH 30/35] Added sparkles to Bonk Drop locations --- Regions.py | 52 +++---- Rom.py | 8 +- asm/owrando.asm | 334 +++++++++++++++++++++++------------------- data/base2current.bps | Bin 107745 -> 107783 bytes 4 files changed, 211 insertions(+), 183 deletions(-) diff --git a/Regions.py b/Regions.py index 7dd3617a..f9152e08 100644 --- a/Regions.py +++ b/Regions.py @@ -1287,32 +1287,32 @@ bonk_prize_table = { 'Forgotten Forest Southwest Tree': (0x0e, 0x10, False, '', 'Forgotten Forest Area', 'in a tree'), 'Forgotten Forest Central Tree': (0x0f, 0x08, False, '', 'Forgotten Forest Area', 'in a tree'), #'Forgotten Forest Southeast Tree': (0x10, 0x04, False, '', 'Forgotten Forest Area', 'in a tree'), - 'Hyrule Castle Tree': (0x11, 0x10, False, '', 'Hyrule Castle Courtyard', 'in a tree'), - 'Wooden Bridge Tree': (0x12, 0x10, False, '', 'Wooden Bridge Area', 'in a tree'), - 'Eastern Palace Tree': (0x13, 0x10, True, '', 'Eastern Palace Area', 'in a tree'), - 'Flute Boy South Tree': (0x14, 0x10, True, '', 'Flute Boy Area', 'in a tree'), - 'Flute Boy East Tree': (0x15, 0x08, True, '', 'Flute Boy Area', 'in a tree'), - 'Central Bonk Rocks Tree': (0x16, 0x10, False, '', 'Central Bonk Rocks Area', 'in a tree'), - 'Tree Line Tree 2': (0x17, 0x10, True, '', 'Tree Line Area', 'in a tree'), - 'Tree Line Tree 4': (0x18, 0x08, True, '', 'Tree Line Area', 'in a tree'), - 'Flute Boy Approach South Tree': (0x19, 0x10, False, '', 'Flute Boy Approach Area', 'in a tree'), - 'Flute Boy Approach North Tree': (0x1a, 0x08, False, '', 'Flute Boy Approach Area', 'in a tree'), - 'Dark Lumberjack Tree': (0x1b, 0x10, False, '', 'Dark Lumberjack Area', 'in a tree'), - 'Dark Fortune Bonk Rocks (Drop 1)': (0x1c, 0x10, False, '', 'Dark Fortune Area', 'encased in stone'), - 'Dark Fortune Bonk Rocks (Drop 2)': (0x1d, 0x08, False, '', 'Dark Fortune Area', 'encased in stone'), - 'Dark Graveyard West Bonk Rocks': (0x1e, 0x10, False, '', 'Dark Graveyard Area', 'encased in stone'), - 'Dark Graveyard North Bonk Rocks': (0x1f, 0x08, False, '', 'Dark Graveyard North', 'encased in stone'), - 'Dark Graveyard Tomb Bonk Rocks': (0x20, 0x04, False, '', 'Dark Graveyard North', 'encased in stone'), - 'Qirn Jump West Tree': (0x21, 0x10, False, '', 'Qirn Jump Area', 'in a tree'), - 'Qirn Jump East Tree': (0x22, 0x08, False, '', 'Qirn Jump East Bank', 'in a tree'), - 'Dark Witch Tree': (0x23, 0x10, False, '', 'Dark Witch Area', 'in a tree'), - 'Pyramid Tree': (0x24, 0x10, False, '', 'Pyramid Area', 'in a tree'), - 'Palace of Darkness Tree': (0x25, 0x10, False, '', 'Palace of Darkness Area', 'in a tree'), - 'Dark Tree Line Tree 2': (0x26, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Dark Tree Line Tree 3': (0x27, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Dark Tree Line Tree 4': (0x28, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Hype Cave Statue': (0x29, 0x10, False, '', 'Hype Cave Area', 'encased in stone'), - 'Cold Fairy Statue': (0x2a, 0x02, False, '', 'Good Bee Cave', 'encased in stone') + 'Hyrule Castle Tree': (0x10, 0x10, False, '', 'Hyrule Castle Courtyard', 'in a tree'), + 'Wooden Bridge Tree': (0x11, 0x10, False, '', 'Wooden Bridge Area', 'in a tree'), + 'Eastern Palace Tree': (0x12, 0x10, True, '', 'Eastern Palace Area', 'in a tree'), + 'Flute Boy South Tree': (0x13, 0x10, True, '', 'Flute Boy Area', 'in a tree'), + 'Flute Boy East Tree': (0x14, 0x08, True, '', 'Flute Boy Area', 'in a tree'), + 'Central Bonk Rocks Tree': (0x15, 0x10, False, '', 'Central Bonk Rocks Area', 'in a tree'), + 'Tree Line Tree 2': (0x16, 0x10, True, '', 'Tree Line Area', 'in a tree'), + 'Tree Line Tree 4': (0x17, 0x08, True, '', 'Tree Line Area', 'in a tree'), + 'Flute Boy Approach South Tree': (0x18, 0x10, False, '', 'Flute Boy Approach Area', 'in a tree'), + 'Flute Boy Approach North Tree': (0x19, 0x08, False, '', 'Flute Boy Approach Area', 'in a tree'), + 'Dark Lumberjack Tree': (0x1a, 0x10, False, '', 'Dark Lumberjack Area', 'in a tree'), + 'Dark Fortune Bonk Rocks (Drop 1)': (0x1b, 0x10, False, '', 'Dark Fortune Area', 'encased in stone'), + 'Dark Fortune Bonk Rocks (Drop 2)': (0x1c, 0x08, False, '', 'Dark Fortune Area', 'encased in stone'), + 'Dark Graveyard West Bonk Rocks': (0x1d, 0x10, False, '', 'Dark Graveyard Area', 'encased in stone'), + 'Dark Graveyard North Bonk Rocks': (0x1e, 0x08, False, '', 'Dark Graveyard North', 'encased in stone'), + 'Dark Graveyard Tomb Bonk Rocks': (0x1f, 0x04, False, '', 'Dark Graveyard North', 'encased in stone'), + 'Qirn Jump West Tree': (0x20, 0x10, False, '', 'Qirn Jump Area', 'in a tree'), + 'Qirn Jump East Tree': (0x21, 0x08, False, '', 'Qirn Jump East Bank', 'in a tree'), + 'Dark Witch Tree': (0x22, 0x10, False, '', 'Dark Witch Area', 'in a tree'), + 'Pyramid Tree': (0x23, 0x10, False, '', 'Pyramid Area', 'in a tree'), + 'Palace of Darkness Tree': (0x24, 0x10, False, '', 'Palace of Darkness Area', 'in a tree'), + 'Dark Tree Line Tree 2': (0x25, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Dark Tree Line Tree 3': (0x26, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Dark Tree Line Tree 4': (0x27, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Hype Cave Statue': (0x28, 0x10, False, '', 'Hype Cave Area', 'encased in stone'), + 'Cold Fairy Statue': (0x29, 0x02, False, '', 'Good Bee Cave', 'encased in stone') } bonk_table_by_location_id = {0x2ABB00+(data[0]*6)+3: name for name, data in bonk_prize_table.items()} diff --git a/Rom.py b/Rom.py index 6aafa704..d6bb10ad 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'fe9e7870071daa40829c1072829bf30b' +RANDOMIZERBASEHASH = '3f08ebba5a2c79b373dbd0c9151ade9b' class JsonRom(object): @@ -802,10 +802,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # setting spriteID to D8, a placeholder sprite we use to inform ROM to spawn a dynamic item #for address in bonk_addresses: for address in [b for b in bonk_addresses if b != 0x4D0AE]: # temp fix for screen 1A murahdahla sprite replacement - rom.write_byte(address, 0xD8) + rom.write_byte(address, 0xD9) # temporary fix for screen 1A - rom.write_byte(snes_to_pc(0x09AE32), 0xD8) - rom.write_byte(snes_to_pc(0x09AE35), 0xD8) + rom.write_byte(snes_to_pc(0x09AE32), 0xD9) + rom.write_byte(snes_to_pc(0x09AE35), 0xD9) rom.write_byte(snes_to_pc(0x06918E), 0x80) # skip good bee bottle check diff --git a/asm/owrando.asm b/asm/owrando.asm index 485bab50..bb9856b4 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -161,15 +161,22 @@ jsl.l OWWorldCheck16 : nop org $02b16e ; AND #$3F : ORA 7EF3CA and #$7f : eor #$40 : nop #2 -org $06AD4C -jsl.l OWBonkDrops : nop #4 -org $1EDE6F -jsl.l OWBonkGoodBeeDrop : bra + +org $09C3C4 +jsl.l OWBonkDropPrepSprite : nop #2 +org $09C801 +jsl.l OWBonkDropPrepSprite : nop #2 +org $06D052 +jsl.l OWBonkDropSparkle +org $06AD49 +jsl.l OWBonkDropsOverworld : nop +org $1EDE6A +jsl.l OWBonkDropSparkle : BNE GoldBee_Dormant_exit +jsl.l OWBonkDropsUnderworld : bra + GoldBee_SpawnSelf_SetProperties: phb : lda.b #$1E : pha : plb ; switch to bank 1E jsr GoldBee_SpawnSelf+12 plb : rtl -nop #3 +nop #2 + ;Code @@ -408,7 +415,66 @@ LoadMapDarkOrMixed: dw $0400+$0210 ; bottom right } -OWBonkGoodBeeDrop: +OWBonkDropPrepSprite: +{ + LDA.b $1B : BEQ + + LDA.w $0FB5 ; what we wrote over + PHA + BRA .continue + + + STZ.w $0F20,X : STZ.w $0E30,X ; what we wrote over + PHA + + .continue + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BEQ .return + + LDA.w $0E20,X : CMP.b #$D9 : BNE + + LDA.b #$03 : STA.w $0F20,X + BRA .prep + + CMP.b #$B2 : BEQ .prep + PLA : RTL + + .prep + STZ.w !SPRITE_REDRAW,X + PHB : PHK : PLB : PHY + TXY : JSR OWBonkDropLookup : BCC .done + ; found match ; X = rec + 1 + INX : LDA.w OWBonkPrizeData,X : PHA + JSR OWBonkDropCollected : PLA : BCC .done + TYX : LDA.b #$01 : STA.w !SPRITE_REDRAW,X + .done + TYX : PLY : PLB + + .return + PLA : RTL +} + +OWBonkDropSparkle: +{ + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BEQ .nosparkle + LDA.w $0E90,X : BEQ .nosparkle + LDA.w !SPRITE_REDRAW,X : BNE .nosparkle + JSL Sprite_SpawnSparkleGarnish + ; move sparkle down 1 tile + PHX : TYX : PLY + LDA.l $7FF81E,X : CLC : ADC.b #$10 : STA.l $7FF81E,X + LDA.l $7FF85A,X : ADC.b #$00 : STA.l $7FF85A,X + PHY : TXY : PLX + + .nosparkle + LDA $0E20,X : CMP.b #$D9 : BEQ .greenrupee + CMP.b #$B2 : BEQ .goodbee + RTL + + .goodbee + LDA $0E90,X ; what we wrote over + RTL + + .greenrupee + JSL Sprite_DrawRippleIfInWater ; what we wrote over + RTL +} + +OWBonkDropsUnderworld: { LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE .shuffled .vanilla ; what we wrote over @@ -416,20 +482,63 @@ OWBonkGoodBeeDrop: LDA.l BottleContentsOne : ORA.l BottleContentsTwo ORA.l BottleContentsThree : ORA.l BottleContentsFour RTL + .shuffled LDA.w $0DD0,X : BNE + - JMP .return+1 + BRA .return+1 + PHY : TXY - LDA.l RoomDataWRAM[$0120].high : AND.b #$02 : PHA : BNE + ; check if collected - LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx - + - LDA.l OWBonkPrizeTable[42].mw_player : BEQ + ; multiworld item - LDA.l OWBonkPrizeTable[42].loot - JMP .spawn_item - + + JSL OWBonkDrops - .determine_type ; S = Collected - LDA.l OWBonkPrizeTable[42].loot ; A = item id + .return + PLY + LDA #$08 ; makes original good bee not spawn + RTL +} + +OWBonkDropsOverworld: +{ + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE .shuffled + BRA .vanilla + + .shuffled + LDA.w $0DD0,Y : BNE + + BRA .vanilla + + LDA.w $0E20,Y : CMP.b #$D9 : BEQ + + BRA .vanilla+3 + + + LDA.b #$00 : STA.w $0F20,Y ; restore proper layer + JSL OWBonkDrops + + .vanilla + LDA.w $0E20,Y : CMP.b #$D8 ; what we wrote over + RTL +} + +OWBonkDrops: +{ + PHB : PHK : PLB + LDA.b $1B : BEQ + + LDX.b #((UWBonkPrizeData-OWBonkPrizeData)+1) + BRA .found_match + + + JSR OWBonkDropLookup : BCS .found_match + JMP .return+2 + + .found_match + INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) + JSR OWBonkDropCollected : PHA : BCS .load_item_and_mw ; S = Collected, FlagBitmask, X (row + 2) + LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx + ; JSLSpriteSFX_QueueSFX3WithPan + + .load_item_and_mw + LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X + PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + + ; multiworld item + DEX : PLA ; A = item id; X = row + 3 + JMP .spawn_item + + DEX : PLA ; A = item id; X = row + 3 + + .determine_type ; A = item id; X = row + 3; S = Collected, FlagBitmask, X (row + 2) CMP.b #$B0 : BNE + LDA.b #$79 : JMP .sprite_transform ; transform to bees + CMP.b #$42 : BNE + @@ -461,140 +570,12 @@ OWBonkGoodBeeDrop: LDA.b #$AC : BRA .sprite_transform ; transform to apples + CMP.b #$B2 : BNE + LDA.b #$E3 : BRA .sprite_transform ; transform to fairy - + CMP.b #$B3 : BNE .spawn_item - INX : INX : LDA.l OWBonkPrizeTable[42].vert_offset - CLC : ADC.b #$08 : PHA - LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y - LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX - LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken - - .sprite_transform - JSL.l OWBonkSpritePrep - - .mark_collected ; S = Collected - PLA : BNE + - LDA.l RoomDataWRAM[$0120].high : ORA.b #$02 : STA.l RoomDataWRAM[$0120].high - - REP #$20 - LDA.l TotalItemCounter : INC : STA.l TotalItemCounter - SEP #$20 - + BRA .return - - ; spawn itemget item - .spawn_item ; A = item id ; Y = bonk sprite slot ; S = Collected - PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : BRA .return - + PHA - - LDA.b #$01 : STA !FORCE_HEART_SPAWN - - LDA.b #$EB : STA.l $7FFE00 - JSL Sprite_SpawnDynamically+15 ; +15 to skip finding a new slot, use existing sprite - - LDA.b #$01 : STA.w !SPRITE_REDRAW,Y - - PLA : STA.w $0E80,Y - - ; affects the rate the item moves in the Y/X direction - LDA.b #$00 : STA.w $0D40,Y - LDA.b #$0A : STA.w $0D50,Y - - LDA.b #$1A : STA.w $0F80,Y ; amount of force (gives height to the arch) - LDA.b #$FF : STA.w $0B58,Y ; stun timer - LDA.b #$30 : STA.w $0F10,Y ; aux delay timer 4 ?? dunno what that means - - LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on - - ; sets the tile type that is underneath the sprite, water - TYX : LDA.b #$09 : STA.l $7FF9C2,X ; TODO: Figure out how to get the game to set this - - ; sets OW event bitmask flag, uses free RAM - LDA.l OWBonkPrizeTable[42].flag : STA.w $0ED0,Y - - ; determines the initial spawn point of item - LDA.w $0D00,Y : SEC : SBC.l OWBonkPrizeTable[42].vert_offset : STA.w $0D00,Y - LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y - - .return - PLY - LDA #$08 ; makes original good bee not spawn - RTL -} - -; Y = sprite slot index of bonk sprite -OWBonkDrops: -{ - CMP.b #$D8 : BEQ + - RTL - + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE + - JSL.l Sprite_TransmuteToBomb : RTL - + LDA.w $0DD0,Y : BNE + - RTL - + - - ; loop thru rando bonk table to find match - PHB : PHK : PLB - LDA.b $8A - LDX.b #(41*6) ; 41 bonk items, 6 bytes each - - CMP.w OWBonkPrizeData,X : BNE + - INX - LDA.w $0D10,Y : LSR A : LSR A : LSR A : LSR A - EOR.w $0D00,Y : CMP.w OWBonkPrizeData,X : BNE ++ ; X = row + 1 - BRA .found_match - ++ DEX : LDA.b $8A - + CPX.b #$00 : BNE + - PLB : RTL - + DEX : DEX : DEX : DEX : DEX : DEX : BRA - - - .found_match - INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) - LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 1,S : PHA : BNE + ; S = Collected, FlagBitmask, X (row + 2) - LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx - + - LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X - PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + - ; multiworld item - DEX : PLA ; X = row + 3 - JMP .spawn_item - + DEX : PLA ; X = row + 3 - - .determine_type ; A = item id ; S = Collected, FlagBitmask, X (row + 2) - CMP.b #$B0 : BNE + - LDA.b #$79 : JMP .sprite_transform ; transform to bees - + CMP.b #$42 : BNE + - JSL.l Sprite_TransmuteToBomb ; transform a heart to bomb, vanilla behavior - JMP .mark_collected - + CMP.b #$34 : BNE + - LDA.b #$D9 : CLC : JMP .sprite_transform ; transform to single rupee - + CMP.b #$35 : BNE + - LDA.b #$DA : CLC : JMP .sprite_transform ; transform to blue rupee - + CMP.b #$36 : BNE + - LDA.b #$DB : CLC : BRA .sprite_transform ; transform to red rupee - + CMP.b #$27 : BNE + - LDA.b #$DC : CLC : BRA .sprite_transform ; transform to 1 bomb - + CMP.b #$28 : BNE + - LDA.b #$DD : CLC : BRA .sprite_transform ; transform to 4 bombs - + CMP.b #$31 : BNE + - LDA.b #$DE : CLC : BRA .sprite_transform ; transform to 8 bombs - + CMP.b #$45 : BNE + - LDA.b #$DF : CLC : BRA .sprite_transform ; transform to small magic - + CMP.b #$B4 : BNE + - LDA.b #$E0 : CLC : BRA .sprite_transform ; transform to big magic - + CMP.b #$B5 : BNE + - LDA.b #$79 : JSL.l OWBonkSpritePrep - JSL.l GoldBee_SpawnSelf_SetProperties ; transform to good bee - BRA .mark_collected - + CMP.b #$44 : BNE + - LDA.b #$E2 : CLC : BRA .sprite_transform ; transform to 10 arrows - + CMP.b #$B1 : BNE + - LDA.b #$AC : BRA .sprite_transform ; transform to apples - + CMP.b #$B2 : BNE + - LDA.b #$E3 : BRA .sprite_transform ; transform to fairy + CMP.b #$B3 : BNE .spawn_item INX : INX : LDA.w OWBonkPrizeData,X ; X = row + 5 CLC : ADC.b #$08 : PHA LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX - LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken + LDA.b #$0B ; BRA .sprite_transform ; transform to chicken .sprite_transform JSL.l OWBonkSpritePrep @@ -602,16 +583,22 @@ OWBonkDrops: .mark_collected ; S = Collected, FlagBitmask, X (row + 2) PLA : BNE + ; S = FlagBitmask, X (row + 2) TYX : JSL Sprite_IsOnscreen : BCC + + LDA.b $1B : BEQ ++ + LDA.l RoomDataWRAM[$0120].high : ORA 1,S : STA.l RoomDataWRAM[$0120].high + LDA.w $0400 : ORA 1,S : STA.w $0400 + BRA .increment_collection + ++ LDX.b $8A : LDA.l OverworldEventDataWRAM,X : ORA 1,S : STA.l OverworldEventDataWRAM,X + .increment_collection REP #$20 LDA.l TotalItemCounter : INC : STA.l TotalItemCounter SEP #$20 - + JMP .return + + BRA .return ; spawn itemget item - .spawn_item ; A = item id ; Y = tree sprite slot ; S = Collected, FlagBitmask, X (row + 2) - PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : JMP .return ; S = FlagBitmask, X (row + 2) + .spawn_item ; A = item id ; Y = bonk sprite slot ; S = Collected, FlagBitmask, X (row + 2) + PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : BRA .return ; S = FlagBitmask, X (row + 2) + PHA LDA.b #$01 : STA !FORCE_HEART_SPAWN @@ -633,18 +620,57 @@ OWBonkDrops: LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on - ; sets OW event bitmask flag, uses free RAM + LDA.b $1B : BEQ + + ; sets the tile type that is underneath the sprite, water + TYX : LDA.b #$09 : STA.l $7FF9C2,X ; TODO: Figure out how to get the game to set this + + + + ; sets bitmask flag, uses free RAM PLA : STA.w $0ED0,Y ; S = X (row + 2) ; determines the initial spawn point of item PLX : INX : INX : INX LDA.w $0D00,Y : SEC : SBC.w OWBonkPrizeData,X : STA.w $0D00,Y LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y - - PLB : RTL + + BRA .return+2 .return - PLA : PLA : PLB : RTL + PLA : PLA : PLB + RTL +} + +; Y = sprite slot; returns X = row + 1 +OWBonkDropLookup: +{ + ; loop thru rando bonk table to find match + LDA.b $8A + LDX.b #((UWBonkPrizeData-OWBonkPrizeData)-sizeof(OWBonkPrizeTable)) ; 41 bonk items, 6 bytes each + - CMP.w OWBonkPrizeData,X : BNE + + INX + LDA.w $0D10,Y : LSR A : LSR A : LSR A : LSR A + EOR.w $0D00,Y : CMP.w OWBonkPrizeData,X : BNE ++ ; X = row + 1 + SEC : RTS + ++ DEX : LDA.b $8A + + CPX.b #$00 : BNE + + CLC : RTS + + DEX : DEX : DEX : DEX : DEX : DEX : BRA - +} + +; S = FlagBitmask ; returns SEC if collected +OWBonkDropCollected: +{ + ; check if collected + CLC + LDA.b $1B : BEQ + + LDA.l RoomDataWRAM[$0120].high : AND.b 3,S : BEQ .return ; S = Collected, FlagBitmask, X (row + 2) + SEC : RTS + + + LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 3,S : BEQ .return ; S = Collected, FlagBitmask, X (row + 2) + SEC : RTS + + .return + RTS } ; A = SpriteID, Y = Sprite Slot Index, X = free/overwritten @@ -1717,7 +1743,7 @@ db $18, $a8, $10, $b2, $00, $20 db $18, $36, $08, $35, $00, $20 db $1a, $8a, $10, $42, $00, $20 db $1a, $1d, $08, $b2, $00, $20 -db $ff, $77, $04, $35, $00, $20 ; pre aga ONLY ; hijacked murahdahla bonk tree +;db $1a, $77, $04, $35, $00, $20 ; pre aga ONLY ; hijacked murahdahla bonk tree db $1b, $46, $10, $b1, $00, $10 db $1d, $6b, $10, $b1, $00, $20 db $1e, $72, $10, $b2, $00, $20 @@ -1728,6 +1754,7 @@ db $2e, $9c, $10, $b2, $00, $20 db $2e, $b4, $08, $b0, $00, $20 db $32, $29, $10, $42, $00, $20 db $32, $9a, $08, $b2, $00, $20 +;db $34, $xx, $10, $xx, $00, $1c ; pre aga ONLY db $42, $66, $10, $b2, $00, $20 db $51, $08, $10, $b2, $00, $04 db $51, $09, $08, $b2, $00, $04 @@ -1743,6 +1770,7 @@ db $6e, $8c, $10, $35, $00, $10 db $6e, $90, $08, $b0, $00, $10 db $6e, $a4, $04, $b1, $00, $10 db $74, $4e, $10, $b1, $00, $1c +UWBonkPrizeData: db $ff, $00, $02, $b5, $00, $08 ; temporary fix - murahdahla replaces one of the bonk tree prizes diff --git a/data/base2current.bps b/data/base2current.bps index a21ad3abf7406231397ccf4c057391d8ef5b7d7a..c31273e3cc04262cd7610b9c7a1a14d8fea61339 100644 GIT binary patch delta 1198 zcmW-eZA?>F7{~9q_x9cbrNGp!gxJ*z8%#tI7-%%x3?e9QIx7P0Gt*6CmY7BQjc zTogm0-ohPRyCV3KE^8}kwZo3x)x{XI+%DN}7WcyI6ijSbTyC}xDj#&Go)72uJkNRl z&+|{tU70x@XTsfpzSo!28R5VKHsupx(7fafEawX+Qe66px58gTunbvqt22-r$xQ8~ zGp#+tW)tpMu0JmtSGJ!N&gx6aHb>-VeHmFezM+jrn?B67%x`KjPOm{Zsx?nA>`LSaKELK`94v$Vel zrQYPw20o0CSOQUzL_u^~)FI>&{y3Pg5wP4N(3X5KE^M@9*Z6QA6giiLQ1U{}9J&(h z=tWV{+KXcN<&Y$yo8s?C#&I2g7X2c+(Yw-K4*eE7d=l+m;(jbe!%fNcvjT51thyWI z(0%bbdLULp)Xp0r`Y-aGC6h8_a<{&c0vrKChn6;Tj|q7X773&$viQ#5pJ~omHbS59 zYy28j{*gD*|I@9lv_Mn6qD2ELDAT?#qPzwiA}NykK6;-8?`?W%sHm*x0>dyG22hMv zT+@^ZeI#HgiUfeB#k#b>*b5yOqPB9NWk6@NZ3n1h9FuBFCgaOdnTg4aYRHbX)NrWU zOB()ZfMqe0UY=H1KXuNO0U7zKO4{sWZnl4Ku+eK88gOSNVuVy}x;|iYGm;;ibF)%c z&?dH*4$qTnLCmdJb7O8+dC{zeni;9<2je5hmuxFPVt9r3j5j`VGTKXH*TdrG)G^!b}TzG;<&IzQnt zW$?X2Z2Rm;{Whlts`SN&P>cPAsap3y(&9|gV|7^L&v9)6m#Bb{hIoQYfvP6%u+`=b z#BFj|(fE(o-s_ilDy+GFf~igP%cZzB%`1atm@|A|9`~w3`)6UB{4(YoY?1RZ=iw2# z7;^zO$h$EAf(PVpFz>(`c~a5&kDF%(0_M&IBitcZ;`$;imakx5h6VCfg+APvdi;2HKPg$=p17-d&78YIW$1f4eEb<9mXjDc~Y# zs1u9xro_6VgOPk29E2?^Dmy}rqW8sECCGLNud#C;tYjsB%20>w@hJM0L?SV?{&{$c zkV3n5IAf&E7PR}E7u9(}Qr0A z^E(mX?y1~pGtj+QJ$=?EfQM1B$Hab<)8UyTYl(O)+Ntk;b94lPxvjqVMQu7d)hkMI z8FZn}Hxrjhrwr_G9Jt5BSb2H4IYqBAJOtVEwenV&o&v)gL>{*1gj2<~Xej0wUt$ur z*v0pZ8<{n^t-gL^clxr_)2b=a?tun}w9@QN?3wmj=X^!EojTCyn<|)y0SjW^TThJ# z@F-g063`JWpp`@$MX7Xo>c{vE)UBtqC>hDGK(sTs>r=F7Qb226qv!YU3g5n zj1WbM&%1q3_B{B29h*(8h2n$2C8j#7&v*96E1PvEx9oM`1{P#9>q^}I5f<#GnGLl+ zT+)NRX-T4?{_ih2j$=8%aC#iy)Hh56j$vp3bUIuf;~A*g*6$ZK0X+vA{f<{bIcIZ* zqnB`)=6`S1mIaO)v`5wn&7d*Gc1M+4-tlL13T@U{X}L2E$kb&|7}p(kB?zk>@`B<~ zM@gosm~*JPT9m2cE3SBvL(L$un~_*1F}ETnOVdfW;fLzWM5mxtO(uE=)~V}>PTOF$ znorCOELFcDdKVU{V_K}#WXg1UL?*Z%Zddb2d=_q0FA}`~*Q#F=4Z>BbPum^}$>6ew zKcF{{Q-nbpq zW1!)+A;v?(HpLuChS`9n_?M1=#eJrAq+_V%%7qSslap@p#UmYrCnwz!^IBhrS%mCu z9v&k^S!s%gM+j0@y2Hc$gefac^Kc(w%1SdlEF(}^=`Mf0sHK~gW_h^5ZAWX|!mx?Z zGlwFtKI!SNN>ohsV_$SWhi#^DT;9;-Tb*&Pt9ZS0K8%x&(y*P_8kkPpxTRN%n-7IT z5#0Y#ink6yHrqKVM|vGaa;WJRlv10=FnXCt^$f09rqjjry78DlRoX*%}=J13eEom Dv}Ptf From 108b67cbcfaa67e8a3559e3248d994943db3afe3 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 2 Jan 2024 17:09:08 -0700 Subject: [PATCH 31/35] fix: fix up some vanilla key logic fix: fix tile pattern --- DoorShuffle.py | 22 +++++++++++++++++++--- Main.py | 10 ++++++++++ RELEASENOTES.md | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 474df204..7f824683 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -7,7 +7,7 @@ from typing import DefaultDict, Dict, List from itertools import chain from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys -from BaseClasses import PotFlags, LocationType, Direction +from BaseClasses import PotFlags, LocationType, Direction, KeyRuleType from Doors import reset_portals from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts from Dungeons import dungeon_bigs, dungeon_hints @@ -261,8 +261,24 @@ def vanilla_key_logic(world, player): world.key_logic[player][builder.name] = key_layout.key_logic world.key_layout[player][builder.name] = key_layout log_key_logic(builder.name, key_layout.key_logic) - # if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: - # validate_vanilla_key_logic(world, player) + # special adjustments for vanilla + if world.mode[player] != 'standard': + # adjust hc doors + def adjust_hc_door(door_rule): + if door_rule.new_rules[KeyRuleType.WorstCase] == 3: + door_rule.new_rules[KeyRuleType.WorstCase] = 2 + door_rule.small_key_num = 2 + + rules = world.key_logic[player]['Hyrule Castle'].door_rules + adjust_hc_door(rules['Sewers Secret Room Key Door S']) + adjust_hc_door(rules['Hyrule Dungeon Map Room Key Door S']) + adjust_hc_door(rules['Sewers Dark Cross Key Door N']) + # adjust pod front door + pod_front = world.key_logic[player]['Palace of Darkness'].door_rules['PoD Middle Cage N'] + if pod_front.new_rules[KeyRuleType.WorstCase] == 6: + pod_front.new_rules[KeyRuleType.WorstCase] = 1 + pod_front.small_key_num = 1 + # gt logic? I'm unsure it needs adjusting def validate_vanilla_reservation(dungeon, world, player): diff --git a/Main.py b/Main.py index c1fb8cba..735942a2 100644 --- a/Main.py +++ b/Main.py @@ -573,6 +573,10 @@ def copy_world(world): ret.intensity = world.intensity.copy() ret.decoupledoors = world.decoupledoors.copy() ret.door_self_loops = world.door_self_loops.copy() + ret.door_type_mode = world.door_type_mode.copy() + ret.trap_door_mode = world.trap_door_mode.copy() + ret.key_logic_algorithm = world.key_logic_algorithm.copy() + ret.aga_randomness = world.aga_randomness.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() ret.dropshuffle = world.dropshuffle.copy() @@ -754,6 +758,12 @@ def copy_world_premature(world, player): ret.enemy_damage = world.enemy_damage.copy() ret.beemizer = world.beemizer.copy() ret.intensity = world.intensity.copy() + ret.decoupledoors = world.decoupledoors.copy() + ret.door_self_loops = world.door_self_loops.copy() + ret.door_type_mode = world.door_type_mode.copy() + ret.trap_door_mode = world.trap_door_mode.copy() + ret.key_logic_algorithm = world.key_logic_algorithm.copy() + ret.aga_randomness = world.aga_randomness.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() ret.dropshuffle = world.dropshuffle.copy() diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f299105b..9cbf5b6b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,8 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.4.0.1v + * Key logic: Vanilla key logic fixes. Statically set some HC logic and PoD front door * 1.4.0.0v * Generation: fix for bunny walk logic taking up too much memory * Key Logic: Partial is now the new default From 6edc2959514cb638772bcd154b8953b44b85a518 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 2 Jan 2024 18:14:31 -0700 Subject: [PATCH 32/35] fix: vanilla key logic adjustment (dropshuffle must be none for reduction) --- DoorShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 7f824683..99577f37 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -262,7 +262,7 @@ def vanilla_key_logic(world, player): world.key_layout[player][builder.name] = key_layout log_key_logic(builder.name, key_layout.key_logic) # special adjustments for vanilla - if world.mode[player] != 'standard': + if world.mode[player] != 'standard' and world.dropshuffle[player] == 'none': # adjust hc doors def adjust_hc_door(door_rule): if door_rule.new_rules[KeyRuleType.WorstCase] == 3: From 7c5a5f39fe89b9858addadd7b65a0968433a2655 Mon Sep 17 00:00:00 2001 From: Cody Bailey Date: Sun, 19 Nov 2023 17:32:45 -0500 Subject: [PATCH 33/35] Add aga_randomness to exposed settings --- BaseClasses.py | 2 +- CLI.py | 3 ++- Main.py | 1 + Rom.py | 2 +- resources/app/cli/args.json | 4 ++++ source/classes/CustomSettings.py | 2 ++ source/tools/MysteryUtils.py | 1 + 7 files changed, 12 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 37d365c6..e8f13ade 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -64,7 +64,6 @@ class World(object): self.dark_world_light_cone = False self.clock_mode = 'none' self.rupoor_cost = 10 - self.aga_randomness = True self.lock_aga_door_in_escape = False self.save_and_quit_from_boss = True self.override_bomb_check = False @@ -171,6 +170,7 @@ class World(object): set_player_attr('door_type_mode', 'original') set_player_attr('trap_door_mode', 'optional') set_player_attr('key_logic_algorithm', 'partial') + set_player_attr('aga_randomness', True) set_player_attr('shopsanity', False) set_player_attr('mixed_travel', 'prevent') diff --git a/CLI.py b/CLI.py index ac628018..92decc8e 100644 --- a/CLI.py +++ b/CLI.py @@ -145,7 +145,7 @@ def parse_cli(argv, no_defaults=False): 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_songinstruments', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', - 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']: + 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'aga_randomness']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -235,6 +235,7 @@ def parse_settings(): "dungeon_counters": "default", "mixed_travel": "prevent", "standardize_palettes": "standardize", + 'aga_randomness': True, "triforce_pool": 0, "triforce_goal": 0, diff --git a/Main.py b/Main.py index 735942a2..cd6c80f0 100644 --- a/Main.py +++ b/Main.py @@ -496,6 +496,7 @@ def init_world(args, fish): world.restrict_boss_items = args.restrict_boss_items.copy() world.collection_rate = args.collection_rate.copy() world.colorizepots = args.colorizepots.copy() + world.aga_randomness = args.aga_randomness.copy() return world diff --git a/Rom.py b/Rom.py index d6bb10ad..85a7dd67 100644 --- a/Rom.py +++ b/Rom.py @@ -1391,7 +1391,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped 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(0x180086, 0x00 if world.aga_randomness[player] 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) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index e723ee76..276b581c 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -575,6 +575,10 @@ "action": "store_true", "type": "bool" }, + "aga_randomness": { + "action": "store_false", + "type": "bool" + }, "settingsonload": { "choices": [ "default", diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index f15a38f0..0cf93eb3 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -169,6 +169,7 @@ class CustomSettings(object): args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p]) args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p]) args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p]) + args.aga_randomness[p] = get_setting(settings['aga_randomness'], args.aga_randomness[p]) # mystery usage args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p]) @@ -326,6 +327,7 @@ class CustomSettings(object): settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p] settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p] settings_dict[p]['beemizer'] = world.beemizer[p] + settings_dict[p]['aga_randomness'] = world.aga_randomness[p] if world.precollected_items: start_inv[p] = [] for item in world.precollected_items: diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index afaf6108..2b8ba457 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -114,6 +114,7 @@ def roll_settings(weights): ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery ret.colorizepots = get_choice_default('colorizepots', default='on') == 'on' ret.shufflepots = get_choice('pot_shuffle') == 'on' + ret.aga_randomness = get_choice('aga_randomness') == 'on' ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent' ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize') From b5795e4bf258ff7c1fa190d0c6d65a9c7a5db4e1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 27 Dec 2023 13:00:44 -0700 Subject: [PATCH 34/35] Merge branch 'KrisDavie-hmg_logic' into DoorDevVolatile --- BaseClasses.py | 53 +++- EntranceShuffle.py | 2 +- Fill.py | 5 +- ItemList.py | 13 +- KeyDoorShuffle.py | 16 + Main.py | 23 +- OWEdges.py | 49 +-- OverworldGlitchRules.py | 181 ++++++------ OverworldShuffle.py | 4 +- Rom.py | 6 +- Rules.py | 72 ++++- UnderworldGlitchRules.py | 278 ++++++++++++++++++ resources/app/cli/args.json | 1 + resources/app/cli/lang/en.json | 15 +- resources/app/gui/lang/en.json | 1 + resources/app/gui/randomize/item/widgets.json | 1 + source/overworld/EntranceShuffle2.py | 2 +- source/tools/MysteryUtils.py | 5 +- test/customizer/hmg/fireless_ice.yaml | 35 +++ test/customizer/hmg/hammer_in_swamp.yaml | 42 +++ test/customizer/hmg/mearl_in_pod.yaml | 34 +++ test/customizer/hmg/mirrorless_swamp.yaml | 42 +++ test/customizer/hmg/pod_as_connector.yaml | 44 +++ test/customizer/hmg/swamp_as_connector.yaml | 55 ++++ .../hmg/swamp_small_in_swamp_back.yaml | 44 +++ test/suite/hmg/entrance_bunny_pocket_sw.yaml | 34 +++ test/suite/hmg/fireless_ice.yaml | 17 ++ test/suite/hmg/flippers_locked_flippers.yaml | 20 ++ test/suite/hmg/flippers_wraps.yaml | 29 ++ test/suite/hmg/hera_from_mire.yaml | 26 ++ .../hmg/inverted_inaccessible_desert.yaml | 26 ++ test/suite/hmg/inverted_moon_pearl_locs.yaml | 31 ++ test/suite/hmg/moon_pearl_locs.yaml | 48 +++ test/suite/hmg/pearlless_sw.yaml | 25 ++ test/suite/hmg/swamp_from_mire.yaml | 43 +++ 35 files changed, 1168 insertions(+), 154 deletions(-) create mode 100644 UnderworldGlitchRules.py create mode 100644 test/customizer/hmg/fireless_ice.yaml create mode 100644 test/customizer/hmg/hammer_in_swamp.yaml create mode 100644 test/customizer/hmg/mearl_in_pod.yaml create mode 100644 test/customizer/hmg/mirrorless_swamp.yaml create mode 100644 test/customizer/hmg/pod_as_connector.yaml create mode 100644 test/customizer/hmg/swamp_as_connector.yaml create mode 100644 test/customizer/hmg/swamp_small_in_swamp_back.yaml create mode 100644 test/suite/hmg/entrance_bunny_pocket_sw.yaml create mode 100644 test/suite/hmg/fireless_ice.yaml create mode 100644 test/suite/hmg/flippers_locked_flippers.yaml create mode 100644 test/suite/hmg/flippers_wraps.yaml create mode 100644 test/suite/hmg/hera_from_mire.yaml create mode 100644 test/suite/hmg/inverted_inaccessible_desert.yaml create mode 100644 test/suite/hmg/inverted_moon_pearl_locs.yaml create mode 100644 test/suite/hmg/moon_pearl_locs.yaml create mode 100644 test/suite/hmg/pearlless_sw.yaml create mode 100644 test/suite/hmg/swamp_from_mire.yaml diff --git a/BaseClasses.py b/BaseClasses.py index e8f13ade..3145ee16 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -133,7 +133,7 @@ class World(object): set_player_attr('can_access_trock_front', None) set_player_attr('can_access_trock_big_chest', None) set_player_attr('can_access_trock_middle', None) - set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] + set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'hybridglitches', 'nologic'] or shuffle[player] in ['lean', 'swapped', 'crossed', 'insanity']) set_player_attr('mapshuffle', False) set_player_attr('compassshuffle', False) @@ -603,6 +603,42 @@ class CollectionState(object): self.placing_items = None # self.trace = None + def can_reach_from(self, spot, start, player=None): + old_state = self.copy() + # old_state.path = {old_state.world.get_region(start, player)} + old_state.stale[player] = False + old_state.reachable_regions[player] = dict() + old_state.blocked_connections[player] = dict() + rrp = old_state.reachable_regions[player] + bc = old_state.blocked_connections[player] + + # init on first call - this can't be done on construction since the regions don't exist yet + start = self.world.get_region(start, player) + if start in self.reachable_regions[player]: + rrp[start] = self.reachable_regions[player][start] + for conn in start.exits: + bc[conn] = self.blocked_connections[player][conn] + else: + rrp[start] = CrystalBarrier.Orange + for conn in start.exits: + bc[conn] = CrystalBarrier.Orange + + queue = deque(old_state.blocked_connections[player].items()) + + old_state.traverse_world(queue, rrp, bc, player) + if old_state.world.key_logic_algorithm[player] == 'default': + unresolved_events = [x for y in old_state.reachable_regions[player] for x in y.locations + if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) + and x not in old_state.locations_checked and x.can_reach(old_state)] + unresolved_events = old_state._do_not_flood_the_keys(unresolved_events) + if len(unresolved_events) == 0: + old_state.check_key_doors_in_dungeons(rrp, player) + + if self.world.get_region(spot, player) in rrp: + return True + else: + return False + def update_reachable_regions(self, player): self.stale[player] = False rrp = self.reachable_regions[player] @@ -1020,7 +1056,7 @@ class CollectionState(object): # try to resolve a name if resolution_hint == 'Location': spot = self.world.get_location(spot, player) - elif resolution_hint in ['Entrance', 'OWEdge', 'OWTerrain', 'Ledge', 'Portal', 'Whirlpool', 'Mirror', 'Flute']: + elif resolution_hint in ['Entrance', 'OWEdge', 'OWTerrain', 'OpenTerrain', 'Ledge', 'OWG', 'Portal', 'Whirlpool', 'Mirror', 'Flute']: spot = self.world.get_entrance(spot, player) else: # default to Region @@ -1168,6 +1204,12 @@ class CollectionState(object): def can_lift_rocks(self, player): return self.has('Power Glove', player) or self.has('Titans Mitts', player) + + def can_bomb_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) and self.can_use_bombs(player) + + def can_dash_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) def has_bottle(self, player): return self.bottle_count(player) > 0 @@ -1347,6 +1389,9 @@ class CollectionState(object): def can_superbunny_mirror_with_sword(self, player): return self.has_Mirror(player) and self.has_sword(player) + + def can_bunny_pocket(self, player): + return self.has_Boots(player) and (self.has_Mirror(player) or self.has_bottle(player)) def collect(self, item, event=False, location=None): if location: @@ -1631,7 +1676,7 @@ class Entrance(object): if region not in explored_regions: explored_regions[region] = path for exit in region.exits: - if exit.connected_region and (not ignore_ledges or exit.spot_type != 'Ledge') \ + if exit.connected_region and (not ignore_ledges or exit.spot_type not in ['Ledge', 'OWG']) \ and exit.name not in ['Dig Game To Ledge Drop'] \ and exit.access_rule(state): if exit.connected_region == destination: @@ -3422,7 +3467,7 @@ er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10} # byte 1: LLLW WSS? (logic, mode, sword) -logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} +logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5} world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} diff --git a/EntranceShuffle.py b/EntranceShuffle.py index ff875c59..9bae0b11 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -814,7 +814,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, must # Keeps track of entrances that cannot be used to access each exit / cave invalid_cave_connections = defaultdict(set) - # if world.logic[player] in ['owglitches', 'nologic']: + # if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: # import OverworldGlitchRules # for entrance in OverworldGlitchRules.get_non_mandatory_exits(world, player): # if entrance in must_be_exits: diff --git a/Fill.py b/Fill.py index 5c1214a1..6bb233b3 100644 --- a/Fill.py +++ b/Fill.py @@ -175,6 +175,9 @@ def valid_key_placement(item, location, key_pool, collection_state, world): if dungeon: if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True + # Small key and big key in Swamp and Hera are placed without logic + if world.logic[item.player] == 'hybridglitches' and dungeon.name in ['Tower of Hera', 'Swamp Palace'] and dungeon.name in item.name: + return True key_logic = world.key_logic[item.player][dungeon.name] unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player]) prize_loc = None @@ -420,7 +423,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # fill in gtower locations with trash first for player in range(1, world.players + 1): if (not gftower_trash or not world.ganonstower_vanilla[player] - or world.logic[player] in ['owglitches', 'nologic']): + or world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']): continue gt_count, total_count = calc_trash_locations(world, player) scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7) diff --git a/ItemList.py b/ItemList.py index d603ae0e..9af16cf6 100644 --- a/ItemList.py +++ b/ItemList.py @@ -327,6 +327,11 @@ def generate_itempool(world, player): for _ in range(0, amt): pool.append('Rupees (20)') + if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: + # In HMG force swamp smalls in pots to allow getting out of swamp palace + placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)' + placed_items['Swamp Palace - Pot Row Pot Key'] = 'Small Key (Swamp Palace)' + start_inventory = list(world.precollected_items) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) @@ -1056,7 +1061,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt return random.choice([True, False]) if progressive == 'random' else progressive == 'on' # provide boots to boots glitch dependent modes - if logic in ['owglitches', 'nologic']: + if logic in ['owglitches', 'hybridglitches', 'nologic']: precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.extend(['Rupees (20)']) @@ -1362,12 +1367,12 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer start_inventory = [x for x in world.precollected_items if x.player == player] if not start_inventory: - if world.logic[player] in ['owglitches', 'nologic']: + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and all(x.name != 'Pegasus Boots' for x in start_inventory): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: pool.remove('Pegasus Boots') pool.append('Rupees (20)') - if world.swords[player] == 'assured': + if world.swords[player] == 'assured' and all(' Sword' not in x.name for x in start_inventory): precollected_items.append('Progressive Sword') if 'Progressive Sword' in pool: pool.remove('Progressive Sword') @@ -1503,7 +1508,7 @@ def make_customizer_pool(world, player): sphere_0 = world.customizer.get_start_inventory() no_start_inventory = not sphere_0 or not sphere_0[player] init_equip = [] if no_start_inventory else sphere_0[player] - if (world.logic[player] in ['owglitches', 'nologic'] + if (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 73bf24bf..6f0cce5d 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -2106,6 +2106,22 @@ def validate_key_placement(key_layout, world, player): if i.player == player and i.name == smallkey_name: keys_outside += 1 + if world.logic[player] == 'hybridglitches': + # Swamp keylogic + if smallkey_name.endswith('(Swamp Palace)'): + swamp_entrance = world.get_location('Swamp Palace - Entrance', player) + # Swamp small not vanilla + if swamp_entrance.item is None or (swamp_entrance.item.name != smallkey_name or swamp_entrance.item.player != player): + mire_keylayout = world.key_layout[player]['Misery Mire'] + mire_smallkey_name = dungeon_keys[mire_keylayout.sector.name] + # Check if any mire keys are in swamp (excluding entrance), if none then add one to keys_outside + mire_keys_in_swamp = sum([1 if x.item.name == mire_smallkey_name else 0 for x in key_layout.item_locations if x.item is not None and x != swamp_entrance]) + if mire_keys_in_swamp == 0: + keys_outside +=1 + # Mire keylogic + if smallkey_name.endswith('(Tower of Hera)'): + # TODO: Make sure that mire medallion isn't in hera basement, or if it is, the small key is available downstairs + big_key_outside = True for code, counter in key_layout.key_counters.items(): if len(counter.child_doors) == 0: continue diff --git a/Main.py b/Main.py index cd6c80f0..efc97d89 100644 --- a/Main.py +++ b/Main.py @@ -28,6 +28,7 @@ from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dunge from Fill import dungeon_tracking from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, fill_specific_items, create_farm_locations +from UnderworldGlitchRules import create_hybridmajor_connections, create_hybridmajor_connectors from Utils import output_path, parse_player_names from source.item.District import init_districts @@ -78,6 +79,11 @@ def main(args, seed=None, fish=None): seeded = True world.customizer.adjust_args(args) world = init_world(args, fish) + + for i in zip(args.logic.values(), args.door_shuffle.values()): + if i[0] == 'hybridglitches' and i[1] != 'vanilla': + raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle")) + if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -166,6 +172,8 @@ def main(args, seed=None, fish=None): create_dungeons(world, player) adjust_locations(world, player) place_bosses(world, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(world, player) if any(world.potshuffle.values()): logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) @@ -191,6 +199,8 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_entrances_new(world, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(world, player) logger.info(world.fish.translate("cli", "cli", "shuffling.prep")) @@ -594,8 +604,10 @@ def copy_world(world): for player in range(1, world.players + 1): create_regions(ret, player) update_world_regions(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(ret, player) create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) @@ -703,6 +715,8 @@ def copy_world(world): for player in range(1, world.players + 1): categorize_world_regions(ret, player) create_farm_locations(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(ret, player) set_rules(ret, player) return ret @@ -782,8 +796,10 @@ def copy_world_premature(world, player): create_regions(ret, player) update_world_regions(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(ret, player) create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) @@ -844,6 +860,9 @@ def copy_world_premature(world, player): for portal in world.dungeon_portals[player]: connect_portal(portal, ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(ret, player) + set_rules(ret, player) return ret diff --git a/OWEdges.py b/OWEdges.py index 90786d6f..ef9c9182 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1558,23 +1558,39 @@ OWExitTypes = { 'Ice Lake Southeast Water Drop', 'Bomber Corner Waterfall Water Drop' ], - 'OWTerrain': ['Lost Woods Bush (West)', - 'Lost Woods Bush (East)', - 'Old Man Drop Off', + 'OpenTerrain': ['Old Man Drop Off', 'Spectacle Rock Approach', 'Spectacle Rock Leave', - 'DM Hammer Bridge (West)', - 'DM Hammer Bridge (East)', 'Floating Island Bridge (East)', - 'Fairy Ascension Rocks (Inner)', - 'DM Broken Bridge (West)', - 'DM Broken Bridge (East)', 'Spiral Mimic Bridge (West)', 'Spiral Mimic Bridge (East)', 'Spiral Ledge Approach', 'Mimic Ledge Approach', - 'Fairy Ascension Rocks (Outer)', 'Floating Island Bridge (West)', + 'Graveyard Ladder (Bottom)', + 'Graveyard Ladder (Top)', + 'Hyrule Castle Main Gate (South)', + 'Hyrule Castle Main Gate (North)', + 'Stone Bridge (Northbound)', + 'Stone Bridge (Southbound)', + 'Checkerboard Ledge Approach', + 'Checkerboard Ledge Leave', + 'Cave 45 Approach', + 'Cave 45 Leave', + 'Middle Aged Man', + 'Desert Pass Ladder (South)', + 'Desert Pass Ladder (North)', + 'GT Approach', + 'GT Leave', + ], + 'OWTerrain': ['Lost Woods Bush (West)', + 'Lost Woods Bush (East)', + 'DM Hammer Bridge (West)', + 'DM Hammer Bridge (East)', + 'Fairy Ascension Rocks (Inner)', + 'DM Broken Bridge (West)', + 'DM Broken Bridge (East)', + 'Fairy Ascension Rocks (Outer)', 'TR Pegs Ledge Entry', 'TR Pegs Ledge Leave', 'Mountain Pass Rock (Outer)', @@ -1587,8 +1603,6 @@ OWExitTypes = { 'Lost Woods Pass Rock (North)', 'Lost Woods Pass Rock (South)', 'Kings Grave Rocks (Outer)', - 'Graveyard Ladder (Bottom)', - 'Graveyard Ladder (Top)', 'Kings Grave Rocks (Inner)', 'River Bend Water Drop', 'River Bend West Pier', @@ -1602,12 +1616,10 @@ OWExitTypes = { 'Kakariko Yard Bush (South)', 'Kakariko Southwest Bush (South)', 'Kakariko Yard Bush (North)', - 'Hyrule Castle Main Gate (South)', 'Hyrule Castle East Rock (Inner)', 'Hyrule Castle Southwest Bush (North)', 'Hyrule Castle Southwest Bush (South)', 'Hyrule Castle Courtyard Bush (South)', - 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle East Rock (Outer)', 'Wooden Bridge Bush (South)', @@ -1615,17 +1627,11 @@ OWExitTypes = { 'Blacksmith Ledge Peg (West)', 'Blacksmith Ledge Peg (East)', 'Maze Race Game', - 'Stone Bridge (Northbound)', - 'Stone Bridge (Southbound)', 'Desert Statue Move', - 'Checkerboard Ledge Approach', 'Desert Ledge Rocks (Outer)', 'Desert Ledge Rocks (Inner)', - 'Checkerboard Ledge Leave', 'Flute Boy Bush (South)', - 'Cave 45 Approach', 'Flute Boy Bush (North)', - 'Cave 45 Leave', 'C Whirlpool Rock (Bottom)', 'C Whirlpool Rock (Top)', 'C Whirlpool Pegs (Outer)', @@ -1644,19 +1650,14 @@ OWExitTypes = { 'Lake Hylia Water D Approach', 'Lake Hylia Water D Leave', 'Ice Cave Pier', - 'Desert Pass Ladder (South)', 'Desert Pass Rocks (North)', 'Desert Pass Rocks (South)', - 'Desert Pass Ladder (North)', - 'Middle Aged Man', 'Octoballoon Water Drop', 'Octoballoon Pier', 'Skull Woods Rock (East)', 'Skull Woods Rock (West)', 'Skull Woods Forgotten Bush (West)', 'Skull Woods Forgotten Bush (East)', - 'GT Approach', - 'GT Leave', 'East Dark Death Mountain Bushes', 'Bumper Cave Rock (Outer)', 'Bumper Cave Rock (Inner)', diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 0e05246d..4f6c2454 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -5,102 +5,86 @@ Helper functions to deliver entrance/exit/region sets to OWG rules. from BaseClasses import Entrance from OWEdges import OWTileRegions +# Cave regions that superbunny can get through - but only with a sword. +sword_required_superbunny_mirror_regions = ["Spiral Cave (Top)"] -def get_sword_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with a sword. - """ - yield 'Spiral Cave (Top)' +# Cave regions that superbunny can get through - but only with boots. +boots_required_superbunny_mirror_regions = ["Two Brothers House"] -def get_boots_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with boots. - """ - yield 'Two Brothers House' +# Cave locations that superbunny can access - but only with boots. +boots_required_superbunny_mirror_locations = [ + "Sahasrahla's Hut - Left", + "Sahasrahla's Hut - Middle", + "Sahasrahla's Hut - Right", +] -def get_boots_required_superbunny_mirror_locations(): - """ - Cave locations that superbunny can access - but only with boots. - """ - yield 'Sahasrahla\'s Hut - Left' - yield 'Sahasrahla\'s Hut - Middle' - yield 'Sahasrahla\'s Hut - Right' +# Entrances that can't be superbunny-mirrored into. +invalid_mirror_bunny_entrances = [ + "Hype Cave", + "Bonk Fairy (Dark)", + "Thieves Town", + "Hammer Peg Cave", + "Brewery", + "Hookshot Cave", + "Dark Lake Hylia Ledge Fairy", + "Dark Lake Hylia Ledge Spike Cave", + "Palace of Darkness", + "Misery Mire", + "Turtle Rock", + "Bonk Rock Cave", + "Bonk Fairy (Light)", + "50 Rupee Cave", + "20 Rupee Cave", + "Checkerboard Cave", + "Light Hype Fairy", + "Waterfall of Wishing", + "Light World Bomb Hut", + "Mini Moldorm Cave", + "Ice Rod Cave", + "Sanctuary Grave", + "Kings Grave", + "Sanctuary Grave", + "Hyrule Castle Secret Entrance Drop", + "Skull Woods Second Section Hole", + "Skull Woods First Section Hole (North)", +] + +# Interior locations that can be accessed with superbunny state. +superbunny_accessible_locations = [ + "Waterfall of Wishing - Left", + "Waterfall of Wishing - Right", + "King's Tomb", + "Floodgate", + "Floodgate Chest", + "Cave 45", + "Bonk Rock Cave", + "Brewery", + "C-Shaped House", + "Chest Game", + "Mire Shed - Left", + "Mire Shed - Right", + "Secret Passage", + "Ice Rod Cave", + "Pyramid Fairy - Left", + "Pyramid Fairy - Right", + "Superbunny Cave - Top", + "Superbunny Cave - Bottom", + "Blind's Hideout - Left", + "Blind's Hideout - Right", + "Blind's Hideout - Far Left", + "Blind's Hideout - Far Right", + "Kakariko Well - Left", + "Kakariko Well - Middle", + "Kakariko Well - Right", + "Kakariko Well - Bottom", + "Kakariko Tavern", + "Library", + "Spiral Cave", +] + boots_required_superbunny_mirror_locations # TODO: Add pottery locations -def get_invalid_mirror_bunny_entrances(): - """ - Entrances that can't be superbunny-mirrored into. - """ - yield 'Skull Woods Final Section' - yield 'Hype Cave' - yield 'Bonk Fairy (Dark)' - yield 'Thieves Town' - yield 'Hammer Peg Cave' - yield 'Brewery' - yield 'Hookshot Cave' - yield 'Dark Lake Hylia Ledge Fairy' - yield 'Dark Lake Hylia Ledge Spike Cave' - yield 'Palace of Darkness' - yield 'Misery Mire' - yield 'Turtle Rock' - yield 'Bonk Rock Cave' - yield 'Bonk Fairy (Light)' - yield '50 Rupee Cave' - yield '20 Rupee Cave' - yield 'Checkerboard Cave' - yield 'Light Hype Fairy' - yield 'Waterfall of Wishing' - yield 'Light World Bomb Hut' - yield 'Mini Moldorm Cave' - yield 'Ice Rod Cave' - yield 'Sanctuary Grave' - yield 'Kings Grave' - yield 'Sanctuary Grave' - yield 'Hyrule Castle Secret Entrance Drop' - yield 'Skull Woods Second Section Hole' - yield 'Skull Woods First Section Hole (North)' - - -def get_superbunny_accessible_locations(): - """ - Interior locations that can be accessed with superbunny state. - """ - - yield 'Waterfall of Wishing - Left' - yield 'Waterfall of Wishing - Right' - yield 'King\'s Tomb' - yield 'Floodgate' - yield 'Floodgate Chest' - yield 'Cave 45' - yield 'Bonk Rock Cave' - yield 'Brewery' - yield 'C-Shaped House' - yield 'Chest Game' - yield 'Mire Shed - Left' - yield 'Mire Shed - Right' - yield 'Secret Passage' - yield 'Ice Rod Cave' - yield 'Pyramid Fairy - Left' - yield 'Pyramid Fairy - Right' - yield 'Superbunny Cave - Top' - yield 'Superbunny Cave - Bottom' - yield 'Blind\'s Hideout - Left' - yield 'Blind\'s Hideout - Right' - yield 'Blind\'s Hideout - Far Left' - yield 'Blind\'s Hideout - Far Right' - yield 'Kakariko Well - Left' - yield 'Kakariko Well - Middle' - yield 'Kakariko Well - Right' - yield 'Kakariko Well - Bottom' - yield 'Kakariko Tavern' - yield 'Library' - yield 'Spiral Cave' - for location in get_boots_required_superbunny_mirror_locations(): - yield location - - def get_non_mandatory_exits(world, player): """ Entrances that can be reached with full equipment using overworld glitches and don't need to be an exit. @@ -295,8 +279,9 @@ def overworld_glitches_rules(world, player): #set_owg_rules(player, world, get_glitched_speed_drops_dw(world, player), lambda state: state.can_get_glitched_speed_dw(player)) # Mirror clip spots. + # TODO: Should this also require can_boots_clip set_owg_rules(player, world, get_mirror_clip_spots(world, player), lambda state: state.has_Mirror(player)) - + # Mirror offset spots. for data in get_mirror_offset_spots(world, player): set_owg_rules(player, world, [data[0:3]], lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player) and state.can_reach(data[3], None, player)) @@ -319,6 +304,18 @@ def overworld_glitches_rules(world, player): add_additional_rule(world.get_entrance('Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) add_additional_rule(world.get_entrance('Dark Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) + # Bunny pocket + if not world.is_tile_swapped(0x00, player): + add_alternate_rule(world.get_entrance("Skull Woods Final Section", player), lambda state: state.can_bunny_pocket(player) and state.has("Fire Rod", player)) + if world.is_tile_swapped(0x05, player): + add_alternate_rule(world.get_entrance("DM Hammer Bridge (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + add_alternate_rule(world.get_entrance("DM Hammer Bridge (East)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + if not world.is_tile_swapped(0x18, player): + add_alternate_rule(world.get_entrance("Bush Yard Pegs (Inner)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + add_alternate_rule(world.get_entrance("Bush Yard Pegs (Outer)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + if world.is_tile_swapped(0x22, player): + add_alternate_rule(world.get_entrance("Blacksmith Ledge Peg (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + def add_alternate_rule(entrance, rule): old_rule = entrance.access_rule @@ -335,7 +332,7 @@ def create_no_logic_connections(player, world, connections): parent = world.get_region(parent_region, player) target = world.get_region(target_region, player) connection = Entrance(player, entrance, parent) - connection.spot_type = 'Ledge' + connection.spot_type = 'OWG' parent.exits.append(connection) connection.connect(target) @@ -387,6 +384,8 @@ boots_clips_local = [ # (name, from_region, to_region) ('Maze Race Item Get Ledge Clip', 'Maze Race Area', 'Maze Race Prize'), + #('Hobo Water Clip', 'Stone Bridge South Area', 'Stone Bridge Water'), # TODO: Doesn't work with OW Free Terrain + ('Tree Line Water Clip', 'Tree Line Area', 'Tree Line Water'), #requires flippers ('Dark Tree Line Water Clip', 'Dark Tree Line Area', 'Dark Tree Line Water'), #requires flippers diff --git a/OverworldShuffle.py b/OverworldShuffle.py index a7380f47..c3d080c8 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -200,7 +200,7 @@ def link_overworld(world, player): categorize_world_regions(world, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(world, player) # crossed shuffle @@ -1486,7 +1486,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F elif exit.connected_region.name not in explored_regions \ and (exit.connected_region.type == region.type or exit.name in OWExitTypes['OWEdge'] or (cross_world and exit.name in (OWExitTypes['Portal'] + OWExitTypes['Mirror']))) \ - and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in OWExitTypes['Ledge']): + and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in OWExitTypes['Ledge', 'OWG']): explore_region(exit.connected_region.name, exit.connected_region) if build_copy_world: diff --git a/Rom.py b/Rom.py index 85a7dd67..2a5c1c38 100644 --- a/Rom.py +++ b/Rom.py @@ -1365,9 +1365,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180211, gametype) # Game type warningflags = 0x00 # none - if world.logic[player] in ['owglitches', 'nologic']: + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: warningflags |= 0x20 - if world.logic[player] in ['minorglitches', 'owglitches', 'nologic']: + if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic']: warningflags |= 0x40 rom.write_byte(0x180212, warningflags) # Warning flags @@ -1599,7 +1599,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill - rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'nologic']) else 0x00) + rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']) else 0x00) # remove shield from uncle rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) diff --git a/Rules.py b/Rules.py index 6600d819..ca69aa3b 100644 --- a/Rules.py +++ b/Rules.py @@ -9,6 +9,7 @@ from Dungeons import dungeon_table from RoomData import DoorKind from OWEdges import OWExitTypes from OverworldGlitchRules import overworld_glitches_rules +from UnderworldGlitchRules import underworld_glitches_rules def set_rules(world, player): @@ -32,7 +33,7 @@ def set_rules(world, player): logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.') no_glitches_rules(world, player) fake_flipper_rules(world, player) - elif world.logic[player] == 'owglitches': + elif world.logic[player] in ['owglitches', 'hybridglitches']: logging.getLogger('').info('There is a chance OWG has bugged edge case rulesets, especially in inverted. Definitely file a report on GitHub if you see anything strange.') # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. @@ -84,6 +85,9 @@ def set_rules(world, player): if not world.is_copied_world: set_bunny_rules(world, player, world.mode[player] == 'inverted') + # These rules go here because they overwrite/add to some of the above rules + if world.logic[player] == 'hybridglitches': + underworld_glitches_rules(world, player) def mirrorless_path_to_location(world, startName, targetName, player): # If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch. @@ -845,6 +849,9 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Crystal Circles to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_blunt_weapon(player) or state.has('Cane of Byrna', player)) # or state.has_beam_sword(player) add_key_logic_rules(world, player) + + if world.logic[player] == 'hybridglitches': + add_hmg_key_logic_rules(world, player) # End of door rando rules. if world.restrict_boss_items[player] != 'none': @@ -1644,6 +1651,8 @@ def find_rules_for_zelda_delivery(world, player): def set_bunny_rules(world, player, inverted): # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. + all_single_exit_dungeons = ['Eastern Palace', 'Tower of Hera', 'Castle Tower', 'Palace of Darkness', 'Swamp Palace', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Ganons Tower'] + hmg_single_exit_dungeons = [d for d in all_single_exit_dungeons if d not in ['Tower of Hera', 'Misery Mire', 'Thieves Town']] bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', @@ -1668,6 +1677,9 @@ def set_bunny_rules(world, player, inverted): 'Take - Any # 3 Item 1', 'Take - Any # 3 Item 2', 'Take - Any # 4 Item 1', 'Take - Any # 4 Item 2', ] + bunny_pocket_entrances = ['Skull Woods Final Section', 'Bush Yard Pegs (Inner)', 'Bush Yard Pegs (Outer)', + 'DM Hammer Bridge (West)', 'DM Hammer Bridge (East)', 'Blacksmith Ledge Peg (West)' + ] def path_to_access_rule(path, entrance): return lambda state: state.can_reach(entrance) and all(rule_func(state) for rule_func in path) @@ -1687,13 +1699,32 @@ def set_bunny_rules(world, player, inverted): return region.is_dark_world else: return region.is_light_world + + # Is it possible to do bunny pocket here + def can_bunny_pocket_to(world, entrance_name, player): + def can_activate_bunny_pocket(region): + explored_regions.append(region.name) + for ent in region.entrances: + if (is_link(ent.parent_region) or + (ent.parent_region.type == RegionType.Dungeon and ent.parent_region.name in bunny_revivable_entrances)): + return True + for ent in region.entrances: + if ent.spot_type in ['OWEdge', 'Ledge', 'OpenTerrain'] and ent.parent_region.name not in explored_regions: + if can_activate_bunny_pocket(ent.parent_region): + return True + return False + + entrance = world.get_entrance(entrance_name, player) + explored_regions = [] + return can_activate_bunny_pocket(entrance.parent_region) + def get_rule_to_add(region, location=None, connecting_entrance=None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: if region.type != RegionType.Dungeon \ - and (location is None or location.name not in OverworldGlitchRules.get_superbunny_accessible_locations()) \ + and (location is None or location.name not in OverworldGlitchRules.superbunny_accessible_locations) \ and not is_link(region): return lambda state: state.has_Pearl(player) else: @@ -1723,14 +1754,18 @@ def set_bunny_rules(world, player, inverted): new_path = path + [entrance.access_rule] seen_sets.add(frozenset(new_seen)) if not is_link(new_region): - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: + # Is this a bunny pocketable entrance? if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: + continue + if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player): continue if entrance.name in drop_dungeon_entrances: lobby = entrance.connected_region else: - lobby = next(exit.connected_region for exit in current.exits if exit.connected_region.type == RegionType.Dungeon) + portal_regions = [world.get_region(reg, player) for reg in region.dungeon.regions if reg.endswith('Portal')] + lobby = next(reg.connected_region for portal_reg in portal_regions for reg in portal_reg.exits if reg.name.startswith('Enter ')) if lobby.name in bunny_revivable_entrances: possible_options.append(path_to_access_rule(new_path, entrance)) elif lobby.name in superbunny_revivable_entrances: @@ -1739,21 +1774,23 @@ def set_bunny_rules(world, player, inverted): possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) continue elif region.type == RegionType.Cave and new_region.type != RegionType.Cave: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: continue - if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): + if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player): + continue + if region.name in OverworldGlitchRules.sword_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) - elif region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions(): + elif region.name in OverworldGlitchRules.boots_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) - elif location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): - if location.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_locations(): + elif location and location.name in OverworldGlitchRules.superbunny_accessible_locations: + if location.name in OverworldGlitchRules.boots_required_superbunny_mirror_locations: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) elif region.name == 'Kakariko Well (top)': possible_options.append(path_to_access_rule(new_path, entrance)) else: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player)], entrance)) continue - elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): + elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.superbunny_accessible_locations: possible_options.append(path_to_access_rule(new_path, entrance)) else: continue @@ -1767,7 +1804,6 @@ def set_bunny_rules(world, player, inverted): # Add requirements for bunny-impassible caves if they occur in the light world for region in [world.get_region(name, player) for name in bunny_impassable_caves]: - if not is_bunny(region): continue rule = get_rule_to_add(region) @@ -1802,6 +1838,11 @@ def set_bunny_rules(world, player, inverted): continue add_rule(location, get_rule_to_add(region, location)) + for ent_name in bunny_pocket_entrances: + bunny_exit = world.get_entrance(ent_name, player) + if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region) and not can_bunny_pocket_to(world, ent_name, player): + add_rule(bunny_exit, lambda state: state.has_Pearl(player)) + drop_dungeon_entrances = { "Sewer Drop", @@ -1929,6 +1970,11 @@ bunny_impassible_if_trapped = { 'GT Speed Torch WN', 'Ice Lobby SE' } +def add_hmg_key_logic_rules(world, player): + for toh_loc in world.key_logic[player]['Tower of Hera'].bk_restricted: + set_always_allow(world.get_location(toh_loc.name, player), allow_big_key_in_big_chest('Big Key (Tower of Hera)', player)) + set_always_allow(world.get_location('Swamp Palace - Entrance', player), allow_big_key_in_big_chest('Big Key (Swamp Palace)', player)) + def add_key_logic_rules(world, player): key_logic = world.key_logic[player] diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py new file mode 100644 index 00000000..c01e242f --- /dev/null +++ b/UnderworldGlitchRules.py @@ -0,0 +1,278 @@ +from BaseClasses import Entrance +import Rules +from OverworldGlitchRules import create_no_logic_connections + +kikiskip_spots = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal")] + +mireheraswamp_spots = [ + ("Mire to Hera Clip", "Mire Torches Top", "Hera Portal"), + ("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal"), +] + +icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop")] + +thievesdesert_spots = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert West Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert South Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert East Portal"), +] + +specrock_spots = [("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave (Top)")] + +paradox_spots = [("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area")] + + +# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions +kikiskip_connectors = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Exit")] + + +mireheraswamp_connectors = [ + ("Mire to Hera Clip", "Mire Torches Top", "Tower of Hera Exit"), + ("Mire to Hera Clip", "Mire Torches Top", "Swamp Palace Exit"), +] + + +thievesdesert_connectors = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (West)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (South)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (East)"), +] + +specrock_connectors = [ + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit (Top)"), + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit"), +] + + +# Create connections between dungeons/locations +def create_hybridmajor_connections(world, player): + for spots in [ + kikiskip_spots, + mireheraswamp_spots, + icepalace_spots, + thievesdesert_spots, + specrock_spots, + paradox_spots, + ]: + create_no_logic_connections(player, world, spots) + + +# Turn dungeons into connectors +def create_hybridmajor_connectors(world, player): + for connectors in [ + kikiskip_connectors, + mireheraswamp_connectors, + thievesdesert_connectors, + specrock_connectors, + ]: + new_connectors = [(connector[0], connector[1], world.get_entrance(connector[2], player).connected_region) for connector in connectors] + create_no_logic_connections(player, world, new_connectors) + + +# For some entrances, we need to fake having pearl, because we're in fake DW/LW. +# This creates a copy of the input state that has Moon Pearl. +def fake_pearl_state(state, player): + if state.has("Moon Pearl", player): + return state + fake_state = state.copy() + fake_state.prog_items["Moon Pearl", player] += 1 + return fake_state + + +# Sets the rules on where we can actually go using this clip. +# Behavior differs based on what type of ER shuffle we're playing. +def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str): + fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] + fix_fake_worlds = world.fix_fake_world[player] + + dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0] + if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix + # Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially. + + # entrance doesn't exist until you fire rod it from the other side + if dungeon_entrance.name == "Skull Woods Final Section": + Rules.set_rule(clip, lambda state: False) + + elif dungeon_entrance.name == "Misery Mire": + if world.swords[player] == "swordless": + Rules.add_rule(clip, lambda state: state.has_misery_mire_medallion(player)) + else: + Rules.add_rule(clip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + + elif dungeon_entrance.name == "Agahnims Tower": + Rules.add_rule( + clip, + lambda state: state.has("Cape", player) + or state.has_beam_sword(player) + or state.has("Beat Agahnim 1", player), + ) + + # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + elif not fix_fake_worlds: # full, dungeonsfull; fixed dungeon exits, but no fake worlds fix + # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. + Rules.add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player))) + # exiting restriction + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + + # Otherwise, the shuffle type is lean, lite, crossed, or insanity; all of these do not need additional rules on where we can go, + # since the clip links directly to the exterior region. + + +def underworld_glitches_rules(world, player): + # Ice Palace Entrance Clip, needs bombs or cane of somaria to exit bomb drop room + Rules.add_rule( + world.get_entrance("Ice Bomb Drop SE", player), + lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player) + and (state.can_use_bombs(player) or state.has("Cane of Somaria", player)), + combine="or", + ) + + # Kiki Skip + kks = world.get_entrance("Kiki Skip", player) + Rules.set_rule(kks, lambda state: state.can_bomb_clip(kks.parent_region, player)) + dungeon_reentry_rules(world, player, kks, "Palace of Darkness Portal", "Palace of Darkness Exit") + + # Mire -> Hera -> Swamp + def mire_clip(state): + return state.can_reach("Mire Torches Top", "Region", player) and state.can_dash_clip( + world.get_region("Mire Torches Top", player), player + ) + + def hera_clip(state): + return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip( + world.get_region("Hera 4F", player), player + ) + + Rules.add_rule( + world.get_entrance("Hera Startile Corner NW", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) + Rules.add_rule( + world.get_location("Tower of Hera - Big Chest", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) + + mire_to_hera = world.get_entrance("Mire to Hera Clip", player) + mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) + Rules.set_rule(mire_to_hera, mire_clip) + Rules.set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)) + + # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys + dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") + dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") + # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots + # Flippers required for all of these doors to prevent locks when flooding + for door in [ + "Swamp Trench 1 Approach ES", + "Swamp Hammer Switch SW", + "Swamp Entrance Down Stairs", + "Swamp Pot Row WS", + "Swamp Trench 1 Key Ledge NW", + "Swamp Hub WN", + "Swamp Hub North Ledge N", + "Swamp Crystal Switch EN", + "Swamp Push Statue S", + "Swamp Waterway NW", + "Swamp T SW", + ]: + Rules.add_rule( + world.get_entrance(door, player), + lambda state: mire_clip(state) + and state.has("Small Key (Misery Mire)", player, count=6) + and state.has("Flippers", player), + combine="or", + ) + # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") + + Rules.add_rule( + world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or" + ) + + # Build the rule for SP moat. + # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. + # First we require a certain type of entrance shuffle, then build the rule from its pieces. + if not world.swamp_patch_required[player]: + if world.shuffle[player] in [ + "vanilla", + "dungeonssimple", + "dungeonsfull", + "dungeonscrossed", + ]: + rule_map = { + "Mire Portal": (lambda state: state.can_reach("Mire Torches Top", "Entrance", player)), + "Hera Portal": (lambda state: state.can_reach("Hera Startile Corner NW", "Entrance", player)), + } + inverted = world.mode[player] == "inverted" + + def hera_rule(state): + return (state.has("Moon Pearl", player) or not inverted) and rule_map.get( + world.get_entrance("Tower of Hera", player).connected_region.name, lambda state: False + )(state) + + def gt_rule(state): + return (state.has("Moon Pearl", player) or inverted) and rule_map.get( + world.get_entrance(("Ganons Tower"), player).connected_region.name, lambda state: False + )(state) + + def mirrorless_moat_rule(state): + return ( + state.can_reach("Old Man S&Q", "Entrance", player) + and state.has("Flippers", player) + and mire_clip(state) + and (hera_rule(state) or gt_rule(state)) + ) + + Rules.add_rule( + world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or" + ) + + # Thieves -> Desert + Rules.add_rule( + world.get_entrance("Thieves to Desert Clip", player), + lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player), + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert West Portal", + "Desert Palace Exit (West)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert South Portal", + "Desert Palace Exit (South)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert East Portal", + "Desert Palace Exit (East)", + ) + + # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up + paradox_left_chests = ["Paradox Cave Lower - Far Left", "Paradox Cave Lower - Left", "Paradox Cave Lower - Middle"] + for location in paradox_left_chests: + Rules.add_rule( + world.get_location(location, player), + lambda state: state.can_dash_clip(world.get_location(location, player).parent_region, player), + "or", + ) + + # Collecting right chests in Paradox Cave using a dash clip on left side -> dash citrus, 1f right, teleport up, then hitting the switch + paradox_right_chests = ["Paradox Cave Lower - Right", "Paradox Cave Lower - Far Right"] + for location in paradox_right_chests: + Rules.add_rule( + world.get_location(location, player), + lambda state: ( + state.can_dash_clip(world.get_location(location, player).parent_region, player) + and state.can_hit_crystal(player) + ), + "or", + ) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 276b581c..09e897f7 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -23,6 +23,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 87c72e3d..3ad7f887 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -56,7 +56,8 @@ "building.collection.spheres": "Building up collection spheres", "building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.", "building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items.", - "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s" + "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s", + "hybridglitches.door.shuffle": "Hybrid Major Glitches is not currently compatible with Door Shuffle." }, "help": { "lang": [ "App Language, if available, defaults to English" ], @@ -64,11 +65,13 @@ "bps": [ "Output BPS patches instead of ROMs"], "logic": [ "Select Enforcement of Item Requirements. (default: %(default)s)", - "No Glitches: No Glitch knowledge required.", - "Minor Glitches: May require Fake Flippers, Bunny Revival", - " and Dark Room Navigation.", - "No Logic: Distribute items without regard for", - " item requirements." + "No Glitches: No Glitch knowledge required.", + "Minor Glitches: May require Fake Flippers, Bunny Revival", + " and Dark Room Navigation.", + "Overworld Glitches: May require overworld clips and teleports.", + "Hybrid Major Glitches: May require underworld clips.", + "No Logic: Distribute items without regard for", + " item requirements." ], "mode": [ "Select game mode. (default: %(default)s)", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 9b4a0690..e23c02cd 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -263,6 +263,7 @@ "randomizer.item.logiclevel.noglitches": "No Glitches", "randomizer.item.logiclevel.minorglitches": "Minor Glitches", "randomizer.item.logiclevel.owglitches": "Overworld Glitches", + "randomizer.item.logiclevel.hybridglitches": "Hybrid Major Glitches", "randomizer.item.logiclevel.nologic": "No Logic", "randomizer.item.goal": "Goal", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 652554ab..0c65b694 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -21,6 +21,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index c909107b..95a3e5dd 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -1317,7 +1317,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_cave_connections = defaultdict(set) - if avail.world.logic[avail.player] in ['owglitches', 'nologic']: + if avail.world.logic[avail.player] in ['owglitches', 'hybridglitches', 'nologic']: import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(avail.world, avail.player): invalid_connections[entrance] = set() diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 2b8ba457..ca80dff7 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -46,8 +46,9 @@ def roll_settings(weights): ret.algorithm = get_choice('algorithm') - glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches', - 'owg': 'owglitches', 'minorglitches': 'minorglitches'} + glitch_map = {'none': 'noglitches', 'minorglitches': 'minorglitches', 'no_logic': 'nologic', + 'hmg': 'hybridglitches', 'hybridglitches': 'hybridglitches', + 'owg': 'owglitches', 'owglitches': 'owglitches'} glitches_required = get_choice('glitches_required') if glitches_required is not None: if glitches_required not in glitch_map.keys(): diff --git a/test/customizer/hmg/fireless_ice.yaml b/test/customizer/hmg/fireless_ice.yaml new file mode 100644 index 00000000..86affd94 --- /dev/null +++ b/test/customizer/hmg/fireless_ice.yaml @@ -0,0 +1,35 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Ice Palace - Compass Chest: Fire Rod + Ice Palace - Freezor Chest: Bombos diff --git a/test/customizer/hmg/hammer_in_swamp.yaml b/test/customizer/hmg/hammer_in_swamp.yaml new file mode 100644 index 00000000..f2e3aae7 --- /dev/null +++ b/test/customizer/hmg/hammer_in_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Hammer diff --git a/test/customizer/hmg/mearl_in_pod.yaml b/test/customizer/hmg/mearl_in_pod.yaml new file mode 100644 index 00000000..ec2d5b9a --- /dev/null +++ b/test/customizer/hmg/mearl_in_pod.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Palace of Darkness - Shooter Room: Moon Pearl diff --git a/test/customizer/hmg/mirrorless_swamp.yaml b/test/customizer/hmg/mirrorless_swamp.yaml new file mode 100644 index 00000000..c8a109c3 --- /dev/null +++ b/test/customizer/hmg/mirrorless_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Magic Mirror diff --git a/test/customizer/hmg/pod_as_connector.yaml b/test/customizer/hmg/pod_as_connector.yaml new file mode 100644 index 00000000..48f72d6e --- /dev/null +++ b/test/customizer/hmg/pod_as_connector.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Palace of Darkness Exit + Lake Hylia Fortune Teller: Spectacle Rock Cave Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl diff --git a/test/customizer/hmg/swamp_as_connector.yaml b/test/customizer/hmg/swamp_as_connector.yaml new file mode 100644 index 00000000..ae343937 --- /dev/null +++ b/test/customizer/hmg/swamp_as_connector.yaml @@ -0,0 +1,55 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Swamp Palace Exit + Lake Hylia Fortune Teller: Misery Mire Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl + diff --git a/test/customizer/hmg/swamp_small_in_swamp_back.yaml b/test/customizer/hmg/swamp_small_in_swamp_back.yaml new file mode 100644 index 00000000..b57cc9b6 --- /dev/null +++ b/test/customizer/hmg/swamp_small_in_swamp_back.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +# placements: +# 1: +# Swamp Palace - Entrance: Boss Heart Container diff --git a/test/suite/hmg/entrance_bunny_pocket_sw.yaml b/test/suite/hmg/entrance_bunny_pocket_sw.yaml new file mode 100644 index 00000000..eba7743d --- /dev/null +++ b/test/suite/hmg/entrance_bunny_pocket_sw.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches + shuffle: crossed +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Pyramid Fairy - Left: True +entrances: + 1: + entrances: + Skull Woods Final Section: Pyramid Fairy + two-way: + Chicken House: Two Brothers House Exit (West) + Skull Woods Second Section Door (West): Two Brothers House Exit (East) + diff --git a/test/suite/hmg/fireless_ice.yaml b/test/suite/hmg/fireless_ice.yaml new file mode 100644 index 00000000..79b31f59 --- /dev/null +++ b/test/suite/hmg/fireless_ice.yaml @@ -0,0 +1,17 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove +placements: + 1: + Ice Palace - Map Chest: Bombos + Ice Palace - Iced T Room: Fire Rod diff --git a/test/suite/hmg/flippers_locked_flippers.yaml b/test/suite/hmg/flippers_locked_flippers.yaml new file mode 100644 index 00000000..be71e07b --- /dev/null +++ b/test/suite/hmg/flippers_locked_flippers.yaml @@ -0,0 +1,20 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword +advanced_placements: + 1: + - type: Verification + item: Flippers + locations: + Zora's Ledge: True + Hobo: True + Ice Palace - Boss: True + Swamp Palace - Entrance: False + diff --git a/test/suite/hmg/flippers_wraps.yaml b/test/suite/hmg/flippers_wraps.yaml new file mode 100644 index 00000000..3418b4ec --- /dev/null +++ b/test/suite/hmg/flippers_wraps.yaml @@ -0,0 +1,29 @@ +meta: + players: 1 +settings: + 1: + logic: owglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword + - Flippers +placements: + 1: + Peg Cave: Magic Mirror +advanced_placements: + 1: + - type: Verification + item: Hammer + locations: + Link's House: True + Magic Bat: False +advanced_placements: + 1: + - type: Verification + item: Progressive Glove + locations: + Link's House: True + Ice Palace - Freezor Chest: False + diff --git a/test/suite/hmg/hera_from_mire.yaml b/test/suite/hmg/hera_from_mire.yaml new file mode 100644 index 00000000..3e7d0c49 --- /dev/null +++ b/test/suite/hmg/hera_from_mire.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Big Key (Tower of Hera) + locations: + Tower of Hera - Big Key Chest: True + Tower of Hera - Basement Cage: True + Tower of Hera - Map Chest: True + Tower of Hera - Compass Chest: True + Tower of Hera - Big Chest: True + Tower of Hera - Boss: True \ No newline at end of file diff --git a/test/suite/hmg/inverted_inaccessible_desert.yaml b/test/suite/hmg/inverted_inaccessible_desert.yaml new file mode 100644 index 00000000..bb8ad8aa --- /dev/null +++ b/test/suite/hmg/inverted_inaccessible_desert.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted + shuffle: crossed + +start_inventory: + 1: + - Pegasus Boots + - Progressive Sword + - Hammer + - Fire Rod +placements: + 1: + Desert Palace - Boss: Moon Pearl +entrances: + 1: + two-way: + Skull Woods Final Section: Desert Palace Exit (West) + Skull Woods Second Section Door (West): Desert Palace Exit (East) + Thieves Town: Thieves Town Exit + Hyrule Castle Entrance (East): Desert Palace Exit (South) + Hyrule Castle Entrance (West): Desert Palace Exit (North) + diff --git a/test/suite/hmg/inverted_moon_pearl_locs.yaml b/test/suite/hmg/inverted_moon_pearl_locs.yaml new file mode 100644 index 00000000..9ae1a9e1 --- /dev/null +++ b/test/suite/hmg/inverted_moon_pearl_locs.yaml @@ -0,0 +1,31 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted +start_inventory: + 1: + - Flippers + - Progressive Sword + - Progressive Sword + - Book of Mudora + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Tower of Hera - Big Chest: True + Desert Palace - Big Chest: True + Eastern Palace - Big Chest: True + Bombos Tablet: True + Cave 45: True + + + + diff --git a/test/suite/hmg/moon_pearl_locs.yaml b/test/suite/hmg/moon_pearl_locs.yaml new file mode 100644 index 00000000..a18d53d1 --- /dev/null +++ b/test/suite/hmg/moon_pearl_locs.yaml @@ -0,0 +1,48 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Pegasus Boots + - Flippers + - Fire Rod + - Book of Mudora + - Progressive Sword + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Skull Woods - Compass Chest: True + Skull Woods - Bridge Room: True + Palace of Darkness - Shooter Room: True + Palace of Darkness - The Arena - Bridge: True + Palace of Darkness - Stalfos Basement: True + Palace of Darkness - Big Key Chest: True + Palace of Darkness - The Arena - Ledge: True + Palace of Darkness - Map Chest: True + Palace of Darkness - Compass Chest: True + Palace of Darkness - Dark Basement - Left: True + Palace of Darkness - Dark Basement - Right: True + Palace of Darkness - Dark Maze - Top: True + Palace of Darkness - Dark Maze - Bottom: True + Palace of Darkness - Big Chest: True + Palace of Darkness - Harmless Hellway: True + Palace of Darkness - Boss: True + Bombos Tablet: True + C-Shaped House: True + Pyramid Fairy - Left: True + Swamp Palace - Entrance: False + Thieves' Town - Map Chest: False + + + + diff --git a/test/suite/hmg/pearlless_sw.yaml b/test/suite/hmg/pearlless_sw.yaml new file mode 100644 index 00000000..c26ce1d9 --- /dev/null +++ b/test/suite/hmg/pearlless_sw.yaml @@ -0,0 +1,25 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Skull Woods - Bridge Room: True diff --git a/test/suite/hmg/swamp_from_mire.yaml b/test/suite/hmg/swamp_from_mire.yaml new file mode 100644 index 00000000..f3873909 --- /dev/null +++ b/test/suite/hmg/swamp_from_mire.yaml @@ -0,0 +1,43 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Small Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True + - type: Verification + item: Big Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True \ No newline at end of file From e246684f3747798b95084b2e403cb002bbf89763 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 5 Jan 2024 13:09:03 -0600 Subject: [PATCH 35/35] Version bump 0.3.4.0 --- CHANGELOG.md | 18 ++++++++++++++++++ OverworldShuffle.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a5a53d..98cb8905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 0.3.4.0 +- \~Merged in some things from DR v1.4.0.0-v~ + - Improved bunny-walking algorithm + - Improved multiworld balancing +- Implemented Hyrid Major Glitches logic (thanks Muffins/Espeon) +- Added sparkles to Bonk Drop locations for better visibility +- Some tweaks/improvements to Shuffle Song Instruments +- Replaced Save Settings on Exit with Settings on Load +- Added new button in GUI to export a Yaml file based on current settings +- Allow starting Aga-Defeated and Old-Man-Rescued in inventory + +## 0.3.3.2 +- \~Merged in DR v1.2.0.22~ +- Added Shuffle Song Instruments as post-gen option +- Allow user to change and save output directory within the GUI +- Fixed issue with Smith not deleting on S+Q when no path is possible +- Fixed various MSU inaccuracies + ## 0.3.3.1 - \~Merged in DR v1.2.0.21~ - Fixed issue with Old Man death spawning on Pyramid/Castle diff --git a/OverworldShuffle.py b/OverworldShuffle.py index c3d080c8..311b2342 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.3.2' +version_number = '0.3.4.0' # branch indicator is intentionally different across branches version_branch = '-u'