From b513bbef1b8d946e5496ea468552564729906911 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 23 Mar 2023 15:33:29 -0600 Subject: [PATCH 01/28] Logic fix: hookshot needed for pots in GT Conveyor Cross --- DoorShuffle.py | 2 ++ Doors.py | 2 ++ Dungeons.py | 14 ++++++++------ Regions.py | 3 ++- Rules.py | 4 ++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index d7255077..56fda019 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3806,6 +3806,8 @@ logical_connections = [ ('GT Blocked Stairs Block Path', 'GT Big Chest'), ('GT Speed Torch South Path', 'GT Speed Torch'), ('GT Speed Torch North Path', 'GT Speed Torch Upper'), + ('GT Conveyor Cross Hammer Path', 'GT Conveyor Cross Across Pits'), + ('GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross'), ('GT Hookshot East-Mid Path', 'GT Hookshot Mid Platform'), ('GT Hookshot Mid-East Path', 'GT Hookshot East Platform'), ('GT Hookshot North-Mid Path', 'GT Hookshot Mid Platform'), diff --git a/Doors.py b/Doors.py index 0bd13742..e97505ee 100644 --- a/Doors.py +++ b/Doors.py @@ -1115,6 +1115,8 @@ def create_doors(world, player): create_door(player, 'GT Invisible Catwalk NE', Nrml).dir(No, 0x9c, Right, High).pos(2), create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2), create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0), + create_door(player, 'GT Conveyor Cross Hammer Path', Lgcl), + create_door(player, 'GT Conveyor Cross Hookshot Path', Lgcl), create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0), create_door(player, 'GT Hookshot East-Mid Path', Lgcl), create_door(player, 'GT Hookshot Mid-East Path', Lgcl), diff --git a/Dungeons.py b/Dungeons.py index 9d862b05..3835ef62 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -190,10 +190,11 @@ gt_regions = [ 'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor', 'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal', 'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', - 'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', 'GT Hookshot North Platform', - 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', - 'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', - 'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', + 'GT Conveyor Cross', 'GT Conveyor Cross Across Pits', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', + 'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', + 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', 'GT Double Switch Entry', + 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', 'GT Double Switch Left', + 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', 'GT Double Switch Exit', 'GT Spike Crystal Left', 'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section', 'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot', @@ -204,8 +205,9 @@ gt_regions = [ 'GT Dash Hall', 'GT Hidden Spikes', 'GT Cannonball Bridge', 'GT Refill', 'GT Gauntlet 1', 'GT Gauntlet 2', 'GT Gauntlet 3', 'GT Gauntlet 4', 'GT Gauntlet 5', 'GT Beam Dash', 'GT Lanmolas 2', 'GT Quad Pot', 'GT Wizzrobes 1', 'GT Dashing Bridge', 'GT Wizzrobes 2', 'GT Conveyor Bridge', 'GT Torch Cross', 'GT Staredown', 'GT Falling Torches', - 'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle', 'GT Crystal Circles - Ranged Crystal', - 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm', 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door', + 'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle', + 'GT Crystal Circles - Ranged Crystal', 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm', + 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door', 'GT Frozen Over', 'GT Brightly Lit Hall', 'GT Agahnim 2', 'Ganons Tower Portal' ] diff --git a/Regions.py b/Regions.py index 6be2bbf3..a90e9f99 100644 --- a/Regions.py +++ b/Regions.py @@ -769,7 +769,8 @@ def create_dungeon_regions(world, player): ['GT Compass Room EN', 'GT Compass Room Warp']), create_dungeon_region(player, 'GT Invisible Bridges', 'Ganon\'s Tower', None, ['GT Invisible Bridges WS']), create_dungeon_region(player, 'GT Invisible Catwalk', 'Ganon\'s Tower', None, ['GT Invisible Catwalk ES', 'GT Invisible Catwalk WS', 'GT Invisible Catwalk NW', 'GT Invisible Catwalk NE']), - create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross WN']), + create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross Hammer Path']), + create_dungeon_region(player, 'GT Conveyor Cross Across Pits', 'Ganon\'s Tower', None, ['GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross WN']), create_dungeon_region(player, 'GT Hookshot East Platform', 'Ganon\'s Tower', None, ['GT Hookshot EN', 'GT Hookshot East-Mid Path']), create_dungeon_region(player, 'GT Hookshot Mid Platform', 'Ganon\'s Tower', None, ['GT Hookshot Mid-East Path', 'GT Hookshot Mid-South Path', 'GT Hookshot Mid-North Path']), create_dungeon_region(player, 'GT Hookshot North Platform', 'Ganon\'s Tower', None, ['GT Hookshot NW', 'GT Hookshot North-Mid Path']), diff --git a/Rules.py b/Rules.py index 9e048b38..576fa5a0 100644 --- a/Rules.py +++ b/Rules.py @@ -387,8 +387,8 @@ def global_rules(world, player): set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player)) if not world.get_door('GT Speed Torch SE', player).entranceFlag: set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player)) From 4a1efb141ab124f0007b1addffb4b9322e40d3af Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 24 Mar 2023 09:12:46 -0600 Subject: [PATCH 02/28] Add collection_rate to customizer --- source/classes/CustomSettings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 53105764..ba5bd705 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -116,6 +116,7 @@ class CustomSettings(object): args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p]) args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p]) args.experimental[p] = get_setting(settings['experimental'], args.experimental[p]) + args.collection_rate[p] = get_setting(settings['collection_rate'], args.collection_rate[p]) args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p]) args.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p]) args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p]) @@ -244,6 +245,7 @@ class CustomSettings(object): settings_dict[p]['crystals_gt'] = world.crystals_gt_orig[p] settings_dict[p]['crystals_ganon'] = world.crystals_ganon_orig[p] settings_dict[p]['experimental'] = world.experimental[p] + settings_dict[p]['collection_rate'] = world.collection_rate[p] settings_dict[p]['openpyramid'] = world.open_pyramid[p] settings_dict[p]['bigkeyshuffle'] = world.bigkeyshuffle[p] settings_dict[p]['keyshuffle'] = world.keyshuffle[p] From 3c18dc6e3a4bb9d8e795d6808045ac48d65ec0aa Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 6 Apr 2023 11:22:12 -0600 Subject: [PATCH 03/28] Re-vamp multiple settings as buttons. Minor logic clean up. --- DoorShuffle.py | 2 +- Rules.py | 1 - resources/app/gui/lang/en.json | 3 +- resources/app/gui/randomize/item/widgets.json | 30 ++++--- source/classes/constants.py | 1 + source/gui/loadcliargs.py | 5 +- source/gui/randomize/item.py | 13 ++- source/gui/widgets.py | 87 ++++++++++--------- 8 files changed, 85 insertions(+), 57 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index a0a6a8f8..407509c8 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3295,7 +3295,7 @@ def find_inaccessible_regions(world, player): while len(queue) > 0: next_region = queue.popleft() visited_regions.add(next_region) - if next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave + if world.mode[player] == 'inverted' and next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave for ent in next_region.entrances: parent = ent.parent_region if parent and parent.type is not RegionType.Dungeon and parent not in queue and parent not in visited_regions: diff --git a/Rules.py b/Rules.py index e1ee26cb..a1c795a3 100644 --- a/Rules.py +++ b/Rules.py @@ -192,7 +192,6 @@ def global_rules(world, player): set_rule(world.get_location('Purple Chest', player), lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest # underworld rules - set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) # can erase block - overridden in noglitches set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and state.can_reach('Potion Shop Area', 'Region', player)) diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 0395b3bc..eb53f640 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -226,6 +226,7 @@ "randomizer.item.worldstate.open": "Open", "randomizer.item.worldstate.inverted": "Inverted", "randomizer.item.worldstate.retro": "Retro", + "randomizer.item.retro": "Enable Retro", "randomizer.item.logiclevel": "Logic Level", "randomizer.item.logiclevel.noglitches": "No Glitches", @@ -320,7 +321,7 @@ "randomizer.item.potshuffle": "Pot Shuffle (Legacy)", "randomizer.item.dropshuffle": "Shuffle Enemy Key Drops", - "randomizer.item.keydropshuffle": "Key Drop Shuffle (Legacy)", + "randomizer.item.keydropshuffle": "Enable Key Drop Shuffle (Legacy)", "randomizer.item.take_any": "Take Any Caves", "randomizer.item.take_any.none": "None", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index fad77e14..9857c45e 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -11,12 +11,8 @@ "options": [ "standard", "open", - "inverted", - "retro" - ], - "config": { - "command": "worldstate" - } + "inverted" + ] }, "logiclevel": { "type": "selectbox", @@ -65,6 +61,12 @@ } }, "rightItemFrame": { + "retro": { + "type": "button", + "config": { + "command": "retro" + } + }, "sortingalgo": { "type": "selectbox", "default": "balanced", @@ -140,20 +142,28 @@ "colorizepots": { "type": "checkbox", "config": { - "padx": [50,0] + "padx": [ + 50, + 0 + ] } }, "potshuffle": { "type": "checkbox", "config": { - "padx": [50,0] + "padx": [ + 50, + 0 + ] } }, "dropshuffle": { "type": "checkbox" - }, + } + }, + "leftPoolFrame2": { "keydropshuffle": { - "type": "checkbox", + "type": "button", "config": { "command": "keydropshuffle" } diff --git a/source/classes/constants.py b/source/classes/constants.py index 3f0fb620..812042b8 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -66,6 +66,7 @@ SETTINGSTOPROCESS = { "crystals_ganon": "crystals_ganon", "weapons": "swords", + "retro": "retro", "sortingalgo": "algorithm", "accessibility": "accessibility", "restrict_boss_items": "restrict_boss_items", diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index 6d358853..aec14a72 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -57,7 +57,10 @@ def loadcliargs(gui, args, settings=None): pagewidgets[widget].selectbox.options = theseOptions elif thisType == "spinbox": pagewidgets[widget].label.configure(text=label) - pagewidgets[widget].storageVar.set(args[arg]) + elif thisType == 'button': + pagewidgets[widget].button.configure(text=label) + if hasattr(pagewidgets[widget], 'storageVar'): + pagewidgets[widget].storageVar.set(args[arg]) # If we're on the Game Options page and it's not about Hints if subpage == "gameoptions" and widget not in ["hints", "collection_rate"]: # Check if we've got settings diff --git a/source/gui/randomize/item.py b/source/gui/randomize/item.py index 6898c75f..63a96997 100644 --- a/source/gui/randomize/item.py +++ b/source/gui/randomize/item.py @@ -43,7 +43,10 @@ def item_page(parent): self.frames["leftPoolHeader"].pack(side=TOP, anchor=W) self.frames["leftPoolFrame"] = Frame(self.frames["leftPoolContainer"]) - self.frames["leftPoolFrame"].pack(side=LEFT, fill=Y) + self.frames["leftPoolFrame"].pack(side=TOP, fill=Y) + + self.frames["leftPoolFrame2"] = Frame(self.frames["leftPoolContainer"]) + self.frames["leftPoolFrame2"].pack(side=LEFT, fill=Y) self.frames["rightPoolFrame"] = Frame(self.frames["poolFrame"]) self.frames["rightPoolFrame"].pack(side=RIGHT) @@ -62,14 +65,16 @@ def item_page(parent): for key in dictWidgets: self.widgets[key] = dictWidgets[key] packAttrs = {"anchor":E} - if self.widgets[key].type == "checkbox" or framename == "leftPoolFrame": + if key == "retro": + packAttrs["side"] = RIGHT + if self.widgets[key].type == "checkbox" or framename.startswith("leftPoolFrame"): packAttrs["anchor"] = W if framename == "checkboxes": packAttrs["side"] = LEFT - packAttrs["padx"] = (10,0) + packAttrs["padx"] = (10, 0) elif framename == "leftPoolHeader": packAttrs["side"] = LEFT - packAttrs["padx"] = (0,20) + packAttrs["padx"] = (0, 20) packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) self.widgets[key].pack(packAttrs) diff --git a/source/gui/widgets.py b/source/gui/widgets.py index 4723a526..be664369 100644 --- a/source/gui/widgets.py +++ b/source/gui/widgets.py @@ -1,4 +1,6 @@ + from tkinter import messagebox, Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X +from tkinter import Button from source.classes.Empty import Empty # Override Spinbox to include mousewheel support for changing value @@ -172,6 +174,22 @@ def make_textbox(self, parent, label, storageVar, manager, managerAttrs): widget.textbox.pack(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None) return widget + +def make_button(self, parent, label, manager, managerAttrs, config): + self = Frame(parent) + if config and "command" in config: + self.command = config["command"] + else: + self.command = lambda: None + + self.button = Button(parent, text=label, command=lambda: widget_command(self, self.command)) + if managerAttrs is not None: + self.button.pack(managerAttrs) + else: + self.button.pack(anchor='w') + return self + + # Make a generic widget def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(), options=None, config=None): @@ -201,6 +219,8 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage if thisStorageVar is None: thisStorageVar = StringVar() widget = make_textbox(self, parent, label, thisStorageVar, manager, managerAttrs) + elif type == 'button': + widget = make_button(self, parent, label, manager, managerAttrs, config) widget.type = type return widget @@ -243,47 +263,36 @@ def add_padding_from_config(packAttrs, defn): def widget_command(widget, command=""): root = widget.winfo_toplevel() text_output = "" - if command == "worldstate": - if widget.storageVar.get() == 'retro': - temp_widget = root.pages["randomizer"].pages["dungeon"].widgets["smallkeyshuffle"] - text_output += f'\n {temp_widget.label.cget("text")}' - temp_widget.storageVar.set('universal') + if command == "retro": + temp_widget = root.pages["randomizer"].pages["dungeon"].widgets["smallkeyshuffle"] + text_output += f'\n {temp_widget.label.cget("text")}' + temp_widget.storageVar.set('universal') - temp_widget = root.pages["randomizer"].pages["item"].widgets["bow_mode"] - text_output += f'\n {temp_widget.label.cget("text")}' - if temp_widget.storageVar.get() == 'progressive': - temp_widget.storageVar.set('retro') - elif temp_widget.storageVar.get() == 'silvers': - temp_widget.storageVar.set('retro_silvers') + temp_widget = root.pages["randomizer"].pages["item"].widgets["bow_mode"] + text_output += f'\n {temp_widget.label.cget("text")}' + if temp_widget.storageVar.get() == 'progressive': + temp_widget.storageVar.set('retro') + elif temp_widget.storageVar.get() == 'silvers': + temp_widget.storageVar.set('retro_silvers') - temp_widget = root.pages["randomizer"].pages["item"].widgets["take_any"] - text_output += f'\n {temp_widget.label.cget("text")}' - if temp_widget.storageVar.get() == 'none': - temp_widget.storageVar.set('random') + temp_widget = root.pages["randomizer"].pages["item"].widgets["take_any"] + text_output += f'\n {temp_widget.label.cget("text")}' + if temp_widget.storageVar.get() == 'none': + temp_widget.storageVar.set('random') - widget.storageVar.set('open') - messagebox.showinfo('', f'The following settings were changed:{text_output}') + messagebox.showinfo('', f'The following settings were changed:{text_output}') elif command == "keydropshuffle": - if widget.storageVar.get() > 0: - temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"] - if temp_widget.storageVar.get() == 'none': - text_output += f'\n {temp_widget.label.cget("text")}' - temp_widget.storageVar.set('keys') + temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"] + text_output += f'\n {temp_widget.label.cget("text")}' + if temp_widget.storageVar.get() == 'none': + + temp_widget.storageVar.set('keys') + + temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"] + text_output += f'\n {temp_widget.checkbox.cget("text")}' + if temp_widget.storageVar.get() == 0: + temp_widget.storageVar.set(1) + + if text_output: + messagebox.showinfo('', f'The following settings were changed:{text_output}') - temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"] - if temp_widget.storageVar.get() == 0: - temp_widget.storageVar.set(1) - text_output += f'\n {temp_widget.checkbox.cget("text")}' - if text_output: - messagebox.showinfo('', f'The following settings were changed:{text_output}') - else: - temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"] - if temp_widget.storageVar.get() == 'keys': - text_output += f'\n {temp_widget.label.cget("text")}' - temp_widget.storageVar.set('none') - temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"] - if temp_widget.storageVar.get() == 1: - temp_widget.storageVar.set(0) - text_output += f'\n {temp_widget.checkbox.cget("text")}' - if text_output: - messagebox.showinfo('', f'The following settings were changed:{text_output}') From 10eac87922ccd381425ba2a00ff85cd7d312a30d Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 6 Apr 2023 11:23:04 -0600 Subject: [PATCH 04/28] Logic fix: hookshot needed for pots in GT conveyor cross --- DoorShuffle.py | 2 ++ Doors.py | 2 ++ Dungeons.py | 14 ++++++++------ Regions.py | 3 ++- Rules.py | 4 ++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 407509c8..b236cd9e 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3806,6 +3806,8 @@ logical_connections = [ ('GT Blocked Stairs Block Path', 'GT Big Chest'), ('GT Speed Torch South Path', 'GT Speed Torch'), ('GT Speed Torch North Path', 'GT Speed Torch Upper'), + ('GT Conveyor Cross Hammer Path', 'GT Conveyor Cross Across Pits'), + ('GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross'), ('GT Hookshot East-Mid Path', 'GT Hookshot Mid Platform'), ('GT Hookshot Mid-East Path', 'GT Hookshot East Platform'), ('GT Hookshot North-Mid Path', 'GT Hookshot Mid Platform'), diff --git a/Doors.py b/Doors.py index 0bd13742..e97505ee 100644 --- a/Doors.py +++ b/Doors.py @@ -1115,6 +1115,8 @@ def create_doors(world, player): create_door(player, 'GT Invisible Catwalk NE', Nrml).dir(No, 0x9c, Right, High).pos(2), create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2), create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0), + create_door(player, 'GT Conveyor Cross Hammer Path', Lgcl), + create_door(player, 'GT Conveyor Cross Hookshot Path', Lgcl), create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0), create_door(player, 'GT Hookshot East-Mid Path', Lgcl), create_door(player, 'GT Hookshot Mid-East Path', Lgcl), diff --git a/Dungeons.py b/Dungeons.py index 9d862b05..3835ef62 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -190,10 +190,11 @@ gt_regions = [ 'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor', 'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal', 'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', - 'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', 'GT Hookshot North Platform', - 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', - 'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', - 'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', + 'GT Conveyor Cross', 'GT Conveyor Cross Across Pits', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', + 'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', + 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', 'GT Double Switch Entry', + 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', 'GT Double Switch Left', + 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', 'GT Double Switch Exit', 'GT Spike Crystal Left', 'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section', 'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot', @@ -204,8 +205,9 @@ gt_regions = [ 'GT Dash Hall', 'GT Hidden Spikes', 'GT Cannonball Bridge', 'GT Refill', 'GT Gauntlet 1', 'GT Gauntlet 2', 'GT Gauntlet 3', 'GT Gauntlet 4', 'GT Gauntlet 5', 'GT Beam Dash', 'GT Lanmolas 2', 'GT Quad Pot', 'GT Wizzrobes 1', 'GT Dashing Bridge', 'GT Wizzrobes 2', 'GT Conveyor Bridge', 'GT Torch Cross', 'GT Staredown', 'GT Falling Torches', - 'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle', 'GT Crystal Circles - Ranged Crystal', - 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm', 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door', + 'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle', + 'GT Crystal Circles - Ranged Crystal', 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm', + 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door', 'GT Frozen Over', 'GT Brightly Lit Hall', 'GT Agahnim 2', 'Ganons Tower Portal' ] diff --git a/Regions.py b/Regions.py index 44f065db..bcbb76d4 100644 --- a/Regions.py +++ b/Regions.py @@ -786,7 +786,8 @@ def create_dungeon_regions(world, player): ['GT Compass Room EN', 'GT Compass Room Warp']), create_dungeon_region(player, 'GT Invisible Bridges', 'Ganon\'s Tower', None, ['GT Invisible Bridges WS']), create_dungeon_region(player, 'GT Invisible Catwalk', 'Ganon\'s Tower', None, ['GT Invisible Catwalk ES', 'GT Invisible Catwalk WS', 'GT Invisible Catwalk NW', 'GT Invisible Catwalk NE']), - create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross WN']), + create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross Hammer Path']), + create_dungeon_region(player, 'GT Conveyor Cross Across Pits', 'Ganon\'s Tower', None, ['GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross WN']), create_dungeon_region(player, 'GT Hookshot East Platform', 'Ganon\'s Tower', None, ['GT Hookshot EN', 'GT Hookshot East-Mid Path']), create_dungeon_region(player, 'GT Hookshot Mid Platform', 'Ganon\'s Tower', None, ['GT Hookshot Mid-East Path', 'GT Hookshot Mid-South Path', 'GT Hookshot Mid-North Path']), create_dungeon_region(player, 'GT Hookshot North Platform', 'Ganon\'s Tower', None, ['GT Hookshot NW', 'GT Hookshot North-Mid Path']), diff --git a/Rules.py b/Rules.py index a1c795a3..e8dc7aa5 100644 --- a/Rules.py +++ b/Rules.py @@ -467,8 +467,8 @@ def global_rules(world, player): set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player)) if not world.get_door('GT Speed Torch SE', player).entranceFlag: set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player)) From 85afd0c09ebb415511fbb95efe06a29292b89b16 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 6 Apr 2023 11:23:54 -0600 Subject: [PATCH 05/28] Add collection_rate to customizer --- source/classes/CustomSettings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 53105764..ba5bd705 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -116,6 +116,7 @@ class CustomSettings(object): args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p]) args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p]) args.experimental[p] = get_setting(settings['experimental'], args.experimental[p]) + args.collection_rate[p] = get_setting(settings['collection_rate'], args.collection_rate[p]) args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p]) args.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p]) args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p]) @@ -244,6 +245,7 @@ class CustomSettings(object): settings_dict[p]['crystals_gt'] = world.crystals_gt_orig[p] settings_dict[p]['crystals_ganon'] = world.crystals_ganon_orig[p] settings_dict[p]['experimental'] = world.experimental[p] + settings_dict[p]['collection_rate'] = world.collection_rate[p] settings_dict[p]['openpyramid'] = world.open_pyramid[p] settings_dict[p]['bigkeyshuffle'] = world.bigkeyshuffle[p] settings_dict[p]['keyshuffle'] = world.keyshuffle[p] From ef63511bd8305b2d44845b44b4ffc7d64f6063e1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 6 Apr 2023 11:25:32 -0600 Subject: [PATCH 06/28] Update notes --- RELEASENOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 730f7bda..71b3ec75 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -111,6 +111,7 @@ These are now independent of retro mode and have three options: None, Random, an * 1.2.0.15u * GUI reorganization + * Logic fix for pots in GT conveyor cross * Auto option for pyramid open (trinity or ER + crystals goal) * World model refactor (combining inverted and normal world models) * Partitioned fix for lamp logic and links house @@ -118,6 +119,7 @@ These are now independent of retro mode and have three options: None, Random, an * Reduced universal keys in pool slightly for non-vanilla dungeons * Fake world fix finally * Some extra restrictions on links house placement for lite/lean + * Collection_rate works in customizer files * 1.2.0.14u * Small fix for key logic validation (got rid of a false negative) * Customized doors in ice cross work properly now From 546f5ca10c95d94aca92b3713823e73acedf2b9e Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 7 Apr 2023 15:00:14 -0600 Subject: [PATCH 07/28] Fix for gui --- source/gui/bottom.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 93976c75..e2e0ffc4 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -215,7 +215,8 @@ def create_guiargs(parent): arg = options[mainpage][subpage][widget] if subpage != "" else options[mainpage][widget] page = parent.pages[mainpage].pages[subpage] if subpage != "" else parent.pages[mainpage] pagewidgets = page.content.customWidgets if mainpage == "custom" else page.content.startingWidgets if mainpage == "startinventory" else page.widgets - setattr(guiargs, arg, pagewidgets[widget].storageVar.get()) + if hasattr(pagewidgets[widget], 'storageVar'): + setattr(guiargs, arg, pagewidgets[widget].storageVar.get()) # Get EnemizerCLI setting guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].widgets["enemizercli"].storageVar.get() @@ -287,10 +288,6 @@ def create_guiargs(parent): guiargs = update_deprecated_args(guiargs) # Key drop shuffle stuff - if guiargs.keydropshuffle: - guiargs.dropshuffle = 1 - guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery - if (hasattr(guiargs, 'retro') and guiargs.retro) or guiargs.mode == 'retro': if guiargs.bow_mode == 'progressive': guiargs.bow_mode = 'retro' From 4af94194614f734b51254de2c150d6d5810eb732 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 7 Apr 2023 16:01:08 -0600 Subject: [PATCH 08/28] GUI cleanup Fix Chest Game prize display --- source/gui/bottom.py | 9 --------- source/overworld/EntranceShuffle2.py | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/source/gui/bottom.py b/source/gui/bottom.py index e2e0ffc4..beb77a1f 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -287,13 +287,4 @@ def create_guiargs(parent): guiargs = update_deprecated_args(guiargs) - # Key drop shuffle stuff - if (hasattr(guiargs, 'retro') and guiargs.retro) or guiargs.mode == 'retro': - if guiargs.bow_mode == 'progressive': - guiargs.bow_mode = 'retro' - elif guiargs.bow_mode == 'silvers': - guiargs.bow_mode = 'retro_silvers' - guiargs.take_any = 'random' if guiargs.take_any == 'none' else guiargs.take_any - guiargs.keyshuffle = 'universal' - return guiargs diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 77790bda..572f3bb3 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -2744,7 +2744,7 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00), 'Hype Cave': (0x940, 0xc80), 'Bonk Fairy (Dark)': (0x740, 0xa80), - 'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x800, 0x7a0), + 'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x080, 0x7a0), 'Hammer Peg Cave': (0x4c0, 0x940), 'Red Shield Shop': (0x500, 0x680), 'Dark Sanctuary Hint': (0x720, 0x4a0), From 2abfc6ea80ed0bbf0fe27e9e1f36172ec44bf8fb Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 13 Apr 2023 16:19:18 -0600 Subject: [PATCH 09/28] Swap E/W on Mire Torches/Mire Attic Hint --- DoorShuffle.py | 2 +- Doors.py | 4 ++-- Regions.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index b236cd9e..a0add44b 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -4156,7 +4156,7 @@ interior_doors = [ ('Mire Neglected Room SE', 'Mire Chest View NE'), ('Mire BK Chest Ledge WS', 'Mire Warping Pool ES'), # technically one-way ('Mire Torches Top SW', 'Mire Torches Bottom NW'), - ('Mire Torches Bottom WS', 'Mire Attic Hint ES'), + ('Mire Torches Bottom ES', 'Mire Attic Hint WS'), ('Mire Dark Shooters SE', 'Mire Key Rupees NE'), ('Mire Dark Shooters SW', 'Mire Block X NW'), ('Mire Tall Dark and Roomy WS', 'Mire Crystal Right ES'), diff --git a/Doors.py b/Doors.py index e97505ee..4020caff 100644 --- a/Doors.py +++ b/Doors.py @@ -924,9 +924,9 @@ def create_doors(world, player): create_door(player, 'Mire Torches Top SW', Intr).dir(So, 0x97, Left, High).pos(1), create_door(player, 'Mire Torches Bottom Holes', Hole), create_door(player, 'Mire Torches Bottom NW', Intr).dir(No, 0x97, Left, High).pos(1), - create_door(player, 'Mire Torches Bottom WS', Intr).dir(We, 0x97, Bot, High).pos(0), + create_door(player, 'Mire Torches Bottom ES', Intr).dir(Ea, 0x97, Bot, High).pos(0), create_door(player, 'Mire Torches Top Holes', Hole), - create_door(player, 'Mire Attic Hint ES', Intr).dir(Ea, 0x97, Bot, High).pos(0), + create_door(player, 'Mire Attic Hint WS', Intr).dir(We, 0x97, Bot, High).pos(0), create_door(player, 'Mire Attic Hint Hole', Hole), create_door(player, 'Mire Dark Shooters Up Stairs', Sprl).dir(Up, 0x93, 0, LTH).ss(A, 0x32, 0xec), create_door(player, 'Mire Dark Shooters SW', Intr).dir(So, 0x93, Left, High).pos(0), diff --git a/Regions.py b/Regions.py index bcbb76d4..7b0e2535 100644 --- a/Regions.py +++ b/Regions.py @@ -691,8 +691,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Mire BK Chest Ledge', 'Misery Mire', ['Misery Mire - Big Key Chest'], ['Mire BK Chest Ledge WS']), create_dungeon_region(player, 'Mire Warping Pool', 'Misery Mire', None, ['Mire Warping Pool ES', 'Mire Warping Pool Warp']), create_dungeon_region(player, 'Mire Torches Top', 'Misery Mire', None, ['Mire Torches Top Down Stairs', 'Mire Torches Top SW', 'Mire Torches Top Holes']), - create_dungeon_region(player, 'Mire Torches Bottom', 'Misery Mire', None, ['Mire Torches Bottom NW', 'Mire Torches Bottom WS', 'Mire Torches Bottom Holes']), - create_dungeon_region(player, 'Mire Attic Hint', 'Misery Mire', None, ['Mire Attic Hint ES', 'Mire Attic Hint Hole']), + create_dungeon_region(player, 'Mire Torches Bottom', 'Misery Mire', None, ['Mire Torches Bottom NW', 'Mire Torches Bottom ES', 'Mire Torches Bottom Holes']), + create_dungeon_region(player, 'Mire Attic Hint', 'Misery Mire', None, ['Mire Attic Hint WS', 'Mire Attic Hint Hole']), create_dungeon_region(player, 'Mire Dark Shooters', 'Misery Mire', None, ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE']), create_dungeon_region(player, 'Mire Key Rupees', 'Misery Mire', None, ['Mire Key Rupees NE']), create_dungeon_region(player, 'Mire Block X', 'Misery Mire', None, ['Mire Block X NW', 'Mire Block X WS']), From 072bbfac1fbba43c1dffbc836786e369e4dfe17b Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 13 Apr 2023 16:20:54 -0600 Subject: [PATCH 10/28] Version bump and release note --- Main.py | 2 +- RELEASENOTES.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 8f97ef46..62d519ed 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.15' +version_number = '1.2.0.16' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 71b3ec75..4ae675a6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,9 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.16u + * Fix for Mire Attic Hint door (direction was swapped) + * Dungeon at Chest Game displays correctly on OW map option * 1.2.0.15u * GUI reorganization * Logic fix for pots in GT conveyor cross From ea8bd117fcc9dcbe34c65f05adacd7ceff74cf08 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 14 Apr 2023 14:17:57 -0600 Subject: [PATCH 11/28] Fix for partial key logic for vanilla mire Fix for Kholdstare shell collision --- RELEASENOTES.md | 2 ++ Rom.py | 2 +- Rules.py | 38 +++++++++++++++++++++++++++++++++++++- data/base2current.bps | Bin 93979 -> 94044 bytes 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4ae675a6..a84d0551 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -110,6 +110,8 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes * 1.2.0.16u + * Fix for partial key logic on vanilla Mire + * Fix for Kholdstare Shell collision when at Lanmo 2 * Fix for Mire Attic Hint door (direction was swapped) * Dungeon at Chest Game displays correctly on OW map option * 1.2.0.15u diff --git a/Rom.py b/Rom.py index b7285b2e..ea0b9c68 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '29863ca305a8474c452cd13b3f921898' +RANDOMIZERBASEHASH = '9903cdfc3fc69112919ec49fb63e09ab' class JsonRom(object): diff --git a/Rules.py b/Rules.py index e8dc7aa5..fc608ec2 100644 --- a/Rules.py +++ b/Rules.py @@ -2000,7 +2000,9 @@ def add_key_logic_rules(world, player): key_logic = world.key_logic[player] eval_func = eval_small_key_door if world.key_logic_algorithm[player] == 'strict' and world.keyshuffle[player] == 'wild': - eval_func = eval_small_key_door_strict + eval_func = eval_small_key_door_strict + elif world.key_logic_algorithm[player] != 'default': + eval_func = eval_small_key_door_partial for d_name, d_logic in key_logic.items(): for door_name, rule in d_logic.door_rules.items(): door_entrance = world.get_entrance(door_name, player) @@ -2056,6 +2058,36 @@ def eval_small_key_door_main(state, door_name, dungeon, player): return door_openable +def eval_small_key_door_partial_main(state, door_name, dungeon, player): + if state.is_door_open(door_name, player): + return True + key_logic = state.world.key_logic[player][dungeon] + if door_name not in key_logic.door_rules: + return False + door_rule = key_logic.door_rules[door_name] + door_openable = False + for ruleType, number in door_rule.new_rules.items(): + if door_openable: + return True + if ruleType == KeyRuleType.WorstCase: + number = min(number, door_rule.small_key_num) + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) + elif ruleType == KeyRuleType.AllowSmall: + small_loc_item = door_rule.small_location.item + if small_loc_item and small_loc_item.name == key_logic.small_key_name and small_loc_item.player == player: + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) + elif isinstance(ruleType, tuple): + lock, lock_item = ruleType + # this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't + for loc in door_rule.alternate_big_key_loc: + spot = state.world.get_location(loc, player) + if spot.item and spot.item.name == lock_item: + number = min(number, door_rule.alternate_small_key) + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) + break + return door_openable + + def eval_small_key_door_strict_main(state, door_name, dungeon, player): if state.is_door_open(door_name, player): return True @@ -2070,6 +2102,10 @@ def eval_small_key_door(door_name, dungeon, player): return lambda state: eval_small_key_door_main(state, door_name, dungeon, player) +def eval_small_key_door_partial(door_name, dungeon, player): + return lambda state: eval_small_key_door_partial_main(state, door_name, dungeon, player) + + def eval_small_key_door_strict(door_name, dungeon, player): return lambda state: eval_small_key_door_strict_main(state, door_name, dungeon, player) diff --git a/data/base2current.bps b/data/base2current.bps index 832779a30453f400fad4258196d067eb09eafe59..e58c28ab1b469f73a96a04d2e5d5d50a2ca92b26 100644 GIT binary patch delta 2245 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 From d5e69b28f294ff148e7b197a8de617d6d364c524 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 14 Apr 2023 15:23:38 -0600 Subject: [PATCH 12/28] Fix for Non-ER Inverted Experimental (Aga and GT weren't logically swapped) Fix for customizer setting crystals to 0 for either GT/Ganon --- Main.py | 2 +- RELEASENOTES.md | 3 +++ source/classes/CustomSettings.py | 5 +++-- source/overworld/EntranceShuffle2.py | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Main.py b/Main.py index 62d519ed..107b0694 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.16' +version_number = '1.2.0.17' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a84d0551..4f622e60 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,9 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.17u + * Fix for Non-ER Inverted Experimental (Aga and GT weren't logically swapped) + * Fix for customizer setting crystals to 0 for either GT/Ganon * 1.2.0.16u * Fix for partial key logic on vanilla Mire * Fix for Kholdstare Shell collision when at Lanmo 2 diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index ba5bd705..deb9f4d2 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -2,6 +2,7 @@ import os import urllib.request import urllib.parse import yaml +from typing import Any from yaml.representer import Representer from collections import defaultdict from pathlib import Path @@ -46,8 +47,8 @@ class CustomSettings(object): return meta['players'] def adjust_args(self, args): - def get_setting(value, default): - if value: + 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] else: diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 572f3bb3..61f0a80d 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -56,6 +56,8 @@ def link_entrances_new(world, player): one_way_map.update(drop_map) one_way_map.update(single_entrance_map) if avail_pool.inverted: + default_map['Ganons Tower'] = 'Agahnims Tower Exit' + default_map['Agahnims Tower'] = 'Ganons Tower Exit' default_map['Old Man Cave (West)'] = 'Bumper Cave Exit (Bottom)' default_map['Death Mountain Return Cave (West)'] = 'Bumper Cave Exit (Top)' default_map['Bumper Cave (Bottom)'] = 'Old Man Cave Exit (West)' @@ -64,8 +66,6 @@ def link_entrances_new(world, player): default_map['Old Man Cave (East)'] = 'Death Mountain Return Cave Exit (West)' one_way_map['Bumper Cave (Top)'] = 'Dark Death Mountain Healer Fairy' del default_map['Bumper Cave (Top)'] - del one_way_map['Big Bomb Shop'] - one_way_map['Inverted Big Bomb Shop'] = 'Inverted Big Bomb Shop' avail_pool.default_map = default_map avail_pool.one_way_map = one_way_map From 0ab54def8b9ac8908b3627df92c01a69d1dcfe09 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 8 Jun 2023 10:25:05 -0600 Subject: [PATCH 13/28] Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead. --- Fill.py | 6 +++++- RELEASENOTES.md | 1 + source/item/FillUtil.py | 12 +----------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Fill.py b/Fill.py index 9acf7d45..f6582a16 100644 --- a/Fill.py +++ b/Fill.py @@ -279,6 +279,10 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp if spot_to_fill: return spot_to_fill return None + # explicitly fail these cases + elif world.algorithm in ['dungeon_only', 'major_only']: + raise FillError(f'Rare placement for {world.algorithm} detected. {item_to_place} unable to be placed.' + f' Try a different seed') else: other_locations = [x for x in locations if x not in attempted] for location in other_locations: @@ -419,7 +423,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None else: max_trash = gt_count scaled_trash = math.floor(max_trash * scale_factor) - if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']: + if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt'] or world.algorithm == 'dungeon_only': gftower_trash_count = random.randint(scaled_trash, max_trash) else: gftower_trash_count = random.randint(0, scaled_trash) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4f622e60..33b0a50e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -110,6 +110,7 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes * 1.2.0.17u + * Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead * Fix for Non-ER Inverted Experimental (Aga and GT weren't logically swapped) * Fix for customizer setting crystals to 0 for either GT/Ganon * 1.2.0.16u diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 58558263..6a055584 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -151,9 +151,6 @@ def create_item_pool_config(world): config.item_pool[player] = determine_major_items(world, player) config.location_groups[0].locations = set(groups.locations) config.reserved_locations[player].update(groups.locations) - backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops'] - + mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops']) - config.location_groups[1].locations = set(backup) elif world.algorithm == 'dungeon_only': config.location_groups = [ LocationGroup('Dungeons'), @@ -171,9 +168,6 @@ def create_item_pool_config(world): for player in range(1, world.players + 1): config.item_pool[player] = determine_major_items(world, player) config.location_groups[0].locations = set(dungeon_set) - backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major'] - + mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops']) - config.location_groups[1].locations = set(backup) def district_item_pool_config(world): @@ -419,11 +413,7 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion if item_to_place.name in config.item_pool[item_to_place.player]: restricted = config.location_groups[0].locations filtered = [l for l in locations if l.name in restricted] - if len(filtered) == 0: - restricted = config.location_groups[1].locations - filtered = [l for l in locations if l.name in restricted] - # bias toward certain location in overflow? (thinking about this for major_bias) - return filtered if len(filtered) > 0 else locations + return filtered if world.algorithm == 'district': config = world.item_pool_config if ((isinstance(item_to_place,str) and item_to_place == 'Placeholder') From 8955e8b26fce4ead88c8fa5da936bc83b82ea929 Mon Sep 17 00:00:00 2001 From: Cody Bailey Date: Wed, 14 Jun 2023 15:31:49 -0400 Subject: [PATCH 14/28] Fix JSON metadata --- BaseClasses.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BaseClasses.py b/BaseClasses.py index a7170779..af1436eb 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2499,6 +2499,7 @@ class Spoiler(object): 'pseudoboots': self.world.pseudoboots, 'triforcegoal': self.world.treasure_hunt_count, 'triforcepool': self.world.treasure_hunt_total, + 'race': self.world.settings.world_rep['meta']['race'], 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} } @@ -2601,6 +2602,7 @@ class Spoiler(object): self.set_lobby(portal.name, portal.door.name, player) def to_json(self): + self.parse_meta() self.parse_data() out = OrderedDict() out['Entrances'] = list(self.entrances.values()) From 5da0836d9f14edae5a30b1eea6b27179331be95b Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 16 Jun 2023 09:15:06 -0600 Subject: [PATCH 15/28] Fixed logic with mirror in west dark world --- EntranceShuffle.py | 2 ++ OverworldShuffle.py | 2 +- Regions.py | 3 ++- Rules.py | 2 ++ source/overworld/EntranceShuffle2.py | 2 ++ 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 2e5162e4..3a44b677 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2169,6 +2169,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('West Dark World Gap', 'West Dark World'), ('Broken Bridge Pass (Top)', 'East Dark World'), ('Broken Bridge Pass (Bottom)', 'Northeast Dark World'), + ('Dark Graveyard Bush (South)', 'Dark Graveyard North'), + ('Dark Graveyard Bush (North)', 'West Dark World'), ('Peg Area Rocks (Left)', 'Hammer Peg Area'), ('Peg Area Rocks (Right)', 'West Dark World'), ('Village of Outcasts Heavy Rock', 'West Dark World'), diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 099d5a9c..3793f272 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -48,7 +48,7 @@ mirror_connections = { 'East Dark Death Mountain (Bushes)': ['Fairy Ascension Plateau'], 'East Dark Death Mountain (Bottom)': ['East Death Mountain (Bottom)'], - 'West Dark World': ['Graveyard Ledge', 'Kings Grave Area'], + 'Dark Graveyard North': ['Graveyard Ledge', 'Kings Grave Area'], 'Bumper Cave Ledge': ['Death Mountain Return Ledge'], 'Bumper Cave Entrance': ['Death Mountain Entrance'], diff --git a/Regions.py b/Regions.py index 7b0e2535..7546e012 100644 --- a/Regions.py +++ b/Regions.py @@ -85,7 +85,8 @@ def create_regions(world, player): create_dw_region(player, 'West Dark World', ['Frog'], ['Dark Lumberjack Shop', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Brewery', 'Red Shield Shop', 'Skull Woods Forest', 'Bumper Cave Entrance Rock', 'West Dark World Water Drop', 'Grassy Lawn Pegs (Bottom)', 'Peg Area Rocks (Left)', 'Village of Outcasts Drop', - 'West Dark World Teleporter', 'WDW Flute']), + 'West Dark World Teleporter', 'WDW Flute', 'Dark Graveyard Bush (South)']), + create_dw_region(player, 'Dark Graveyard North', None, ['Dark Graveyard Bush (North)']), create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']), create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section'], 'a deep, dark forest'), diff --git a/Rules.py b/Rules.py index fc608ec2..80392396 100644 --- a/Rules.py +++ b/Rules.py @@ -965,6 +965,8 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('East Dark Death Mountain Bushes', player), player) add_bunny_rule(world.get_entrance('Bumper Cave Entrance Rock', player), player) + add_bunny_rule(world.get_entrance('Dark Graveyard Bush (South)', player), player) + add_bunny_rule(world.get_entrance('Dark Graveyard Bush (North)', player), player) add_bunny_rule(world.get_entrance('Dark Witch Rock (North)', player), player) add_bunny_rule(world.get_entrance('Dark Witch Rock (South)', player), player) add_bunny_rule(world.get_entrance('Grassy Lawn Pegs (Bottom)', player), player) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 61f0a80d..6afc7b9e 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -1962,6 +1962,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Grassy Lawn Pegs (Top)', 'West Dark World'), ('Grassy Lawn Pegs (Bottom)', 'Dark Grassy Lawn'), ('West Dark World Gap', 'West Dark World'), + ('Dark Graveyard Bush (South)', 'Dark Graveyard North'), + ('Dark Graveyard Bush (North)', 'West Dark World'), ('Broken Bridge Pass (Top)', 'East Dark World'), ('Broken Bridge Pass (Bottom)', 'Northeast Dark World'), ('Peg Area Rocks (Left)', 'Hammer Peg Area'), From 8afdbaca25724942f0d22d0ad9c886341bdce04c Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 16 Jun 2023 09:22:08 -0600 Subject: [PATCH 16/28] Update release notes --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 33b0a50e..e3d09589 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -110,6 +110,7 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes * 1.2.0.17u + * Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only) * Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead * Fix for Non-ER Inverted Experimental (Aga and GT weren't logically swapped) * Fix for customizer setting crystals to 0 for either GT/Ganon From 0c640bf9dd252fa98a7723d8359a9066208c81ce Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 10 Jul 2023 10:35:18 -0600 Subject: [PATCH 17/28] Fix for pyrmaid hole logic --- Main.py | 2 +- RELEASENOTES.md | 3 +++ Rules.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Main.py b/Main.py index 107b0694..e3f3df67 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.17' +version_number = '1.2.0.18' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e3d09589..a1013f6c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,9 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.18u + * Fixed an issue with pyramid hole being in logic when it is not opened. + * * 1.2.0.17u * Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only) * Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead diff --git a/Rules.py b/Rules.py index 80392396..26e70f5f 100644 --- a/Rules.py +++ b/Rules.py @@ -887,7 +887,7 @@ def ow_inverted_rules(world, player): set_rule(world.get_entrance('Hyrule Castle Main Gate', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player)) set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) - set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.open_pyramid[player] or world.goal[player] == 'trinity' or state.has('Beat Agahnim 2', player)) + set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player)) else: set_rule(world.get_entrance('East Dark Death Mountain Teleporter (Top)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has('Hammer', player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('East Dark Death Mountain Teleporter (Bottom)', player), lambda state: state.can_lift_heavy_rocks(player)) From 5d2ceaf75c22522a9cb15b45760e907d4df582cf Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 10 Jul 2023 13:56:05 -0600 Subject: [PATCH 18/28] Updated baserom for crystal custscene and hera music fix Updated a couple of error messages and when they are displayed Updated Ganonhunt goal text to be more consistent across randomizers --- ItemList.py | 3 ++- Main.py | 4 +++- README.md | 2 +- RELEASENOTES.md | 7 +++++-- Rom.py | 2 +- data/base2current.bps | Bin 94044 -> 94169 bytes resources/app/gui/lang/en.json | 2 +- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ItemList.py b/ItemList.py index b9e3f4c8..a44a3f6e 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1287,7 +1287,8 @@ def make_customizer_pool(world, player): bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None) if not bow_found: missing_items.append('Progressive Bow') - logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}') + if missing_items: + logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}') g, t = set_default_triforce(world.goal[player], world.treasure_hunt_count[player], world.treasure_hunt_total[player]) diff --git a/Main.py b/Main.py index e3f3df67..2edf2c03 100644 --- a/Main.py +++ b/Main.py @@ -628,7 +628,9 @@ def create_playthrough(world): logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations)) if not sphere: - logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) + if world.accessibility[location.item.player] != 'none': + logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), + [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) if any([location.name not in optional_locations and world.accessibility[location.item.player] != 'none' for location in sphere_candidates]): raise RuntimeError(world.fish.translate("cli", "cli", "cannot.reach.progression")) else: diff --git a/README.md b/README.md index fbe9eed5..21a444cc 100644 --- a/README.md +++ b/README.md @@ -404,7 +404,7 @@ CLI: `--logic owglitches` New supported goals: * Trinity: Find one of 3 triforces to win. One is at pedestal. One is with Ganon. One is with Murahdahla who wants you to find 8 of 10 triforce pieces to complete. -* Triforce Hunt + Ganon: Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI +* Ganonhunt: Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI * Completionist: All dungeons not enough for you? You have to obtain every item in the game too. This option turns on the collection rate counter and forces accessibility to be 100% locations. Finish by defeating Ganon. diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a1013f6c..5dc0ee0a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -57,7 +57,7 @@ Please see [Customizer documentation](docs/Customizer.md) on how to create custo ## New Goals -### Triforce Hunt + Ganon +### Ganonhunt Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI ### Completionist @@ -111,7 +111,10 @@ These are now independent of retro mode and have three options: None, Random, an * 1.2.0.18u * Fixed an issue with pyramid hole being in logic when it is not opened. - * + * Crystal cutscene at GT use new symmetrical layouts (thanks Codemann) + * Fix for Hera Boss music (thanks Codemann) + * Fixed accessibility: none using a spoiling message + * Fixed warning message about custom item pool when it is fine * 1.2.0.17u * Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only) * Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead diff --git a/Rom.py b/Rom.py index ea0b9c68..482080b2 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '9903cdfc3fc69112919ec49fb63e09ab' +RANDOMIZERBASEHASH = '467681d6160233f7af2761c631e26985' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index e58c28ab1b469f73a96a04d2e5d5d50a2ca92b26..9b3d673a51005eba1d3d336d10206d582cb1852b 100644 GIT binary patch delta 8191 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 delta 8271 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°`X4)5(K{T}vV8Q!2ldJX#&(odD~x(*$f+FJ%jrb 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 WZ`*gx12YFmOGf(@j^A=;)c*lzQAw!) diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index eb53f640..9d56137c 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -241,7 +241,7 @@ "randomizer.item.goal.triforcehunt": "Triforce Hunt", "randomizer.item.goal.trinity": "Trinity", "randomizer.item.goal.crystals": "Crystals", - "randomizer.item.goal.ganonhunt": "Triforce Hunt + Ganon", + "randomizer.item.goal.ganonhunt": "Ganonhunt", "randomizer.item.goal.completionist": "Completionist", "randomizer.item.crystals_gt": "Crystals to open GT", From d135405ed3bb76bd68d16e20ac1235eb65294129 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 12 Jul 2023 09:16:00 -0600 Subject: [PATCH 19/28] Customizer: Exception raised for placements of items that are not in pool --- ItemList.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ItemList.py b/ItemList.py index a44a3f6e..5ffb5152 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1397,6 +1397,8 @@ def fill_specific_items(world): track_dungeon_items(item_to_place, loc, world) loc.event = (event_flag or item_to_place.advancement or item_to_place.bigkey or item_to_place.smallkey) + else: + raise Exception(f'Did not find "{item}" in item pool to place at "{location}"') advanced_placements = world.customizer.get_advanced_placements() if advanced_placements: for player, placement_list in advanced_placements.items(): @@ -1406,7 +1408,7 @@ def fill_specific_items(world): item_to_place, event_flag = get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool) if not item_to_place: - continue + raise Exception(f'Did not find "{item}" in item pool to place for a LocationGroup"') locations = placement['locations'] handled = False while not handled: From 1b81151941eca3fb963e010b1bb023ead6ed262d Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 12 Jul 2023 09:50:53 -0600 Subject: [PATCH 20/28] Customizer: Fixed issue with Assured sword and start inventory --- ItemList.py | 31 +++++++++++++++++-------------- RELEASENOTES.md | 4 +++- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/ItemList.py b/ItemList.py index 5ffb5152..168d841d 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1297,20 +1297,23 @@ def make_customizer_pool(world, player): if pieces < t: pool.extend(['Triforce Piece'] * (t - pieces)) - if not world.customizer.get_start_inventory(): - if world.logic[player] in ['owglitches', 'nologic']: - precollected_items.append('Pegasus Boots') - if 'Pegasus Boots' in pool: - pool.remove('Pegasus Boots') - pool.append('Rupees (20)') - if world.swords[player] == 'assured': - precollected_items.append('Progressive Sword') - if 'Progressive Sword' in pool: - pool.remove('Progressive Sword') - pool.append('Rupees (50)') - elif 'Fighter Sword' in pool: - pool.remove('Fighter Sword') - pool.append('Rupees (50)') + 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'] + and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))): + precollected_items.append('Pegasus Boots') + if 'Pegasus Boots' in pool: + pool.remove('Pegasus Boots') + pool.append('Rupees (20)') + if world.swords[player] == 'assured' and (no_start_inventory or all(' Sword' not in x for x in init_equip)): + precollected_items.append('Progressive Sword') + if 'Progressive Sword' in pool: + pool.remove('Progressive Sword') + pool.append('Rupees (50)') + elif 'Fighter Sword' in pool: + pool.remove('Fighter Sword') + pool.append('Rupees (50)') return pool, placed_items, precollected_items, clock_mode, 1 diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5dc0ee0a..5f11c963 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -113,7 +113,9 @@ These are now independent of retro mode and have three options: None, Random, an * Fixed an issue with pyramid hole being in logic when it is not opened. * Crystal cutscene at GT use new symmetrical layouts (thanks Codemann) * Fix for Hera Boss music (thanks Codemann) - * Fixed accessibility: none using a spoiling message + * Customizer: fixed an issue with assured sword and start_inventory + * Customizer: warns when trying to specifically place an item that's not in the item pool + * Fixed "accessibility: none" displaying a spoiling message * Fixed warning message about custom item pool when it is fine * 1.2.0.17u * Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only) From 213d3d3aa0798722225e9c3707c2945b96c1d3b9 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 19 Jul 2023 12:31:50 -0600 Subject: [PATCH 21/28] Fixed an issue where certain vanilla door types would not allow other types to be placed. Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead if no alternatives are found. --- DoorShuffle.py | 28 +++++++++++++++------------- ItemList.py | 2 ++ RELEASENOTES.md | 2 ++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index a0add44b..35cc7624 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1962,7 +1962,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_cu if flex_map[dungeon] > 0: queue.append(dungeon) # time to re-assign - reassign_big_key_doors(bk_map, world, player) + reassign_big_key_doors(bk_map, used_doors, world, player) for name, big_list in bk_map.items(): used_doors.update(flatten_pair_list(big_list)) return used_doors @@ -2047,7 +2047,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_ else: builder.key_doors_num -= 1 # time to re-assign - reassign_key_doors(small_map, world, player) + reassign_key_doors(small_map, used_doors, world, player) for dungeon_name in pool: if world.keyshuffle[player] != 'universal': builder = world.dungeon_layouts[player][dungeon_name] @@ -2129,7 +2129,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_ suggestion_map[dungeon] = pair queue.append(dungeon) # time to re-assign - reassign_bd_doors(bd_map, world, player) + reassign_bd_doors(bd_map, used_doors, world, player) for name, pair in bd_map.items(): used_doors.update(flatten_pair_list(pair[0])) used_doors.update(flatten_pair_list(pair[1])) @@ -2539,7 +2539,7 @@ def find_current_bk_doors(builder): return current_doors -def reassign_big_key_doors(bk_map, world, player): +def reassign_big_key_doors(bk_map, used_doors, world, player): logger = logging.getLogger('') for name, big_doors in bk_map.items(): flat_proposal = flatten_pair_list(big_doors) @@ -2547,11 +2547,12 @@ def reassign_big_key_doors(bk_map, world, player): queue = deque(find_current_bk_doors(builder)) while len(queue) > 0: d = queue.pop() - if d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: + if (d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal + and d not in used_doors and d.dest not in used_doors): if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.bigKey = False - elif d.type is DoorType.Normal and d not in flat_proposal: + elif d.type is DoorType.Normal and d not in flat_proposal and d not in used_doors: if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.bigKey = False @@ -2795,7 +2796,7 @@ def find_valid_bd_combination(builder, suggested, world, player): return bomb_proposal, dash_proposal, ttl_needed -def reassign_bd_doors(bd_map, world, player): +def reassign_bd_doors(bd_map, used_doors, world, player): for name, pair in bd_map.items(): flat_bomb_proposal = flatten_pair_list(pair[0]) flat_dash_proposal = flatten_pair_list(pair[1]) @@ -2808,10 +2809,10 @@ def reassign_bd_doors(bd_map, world, player): queue = deque(find_current_bd_doors(builder, world)) while len(queue) > 0: d = queue.pop() - if d.type is DoorType.Interior and not_in_proposal(d): + if d.type is DoorType.Interior and not_in_proposal(d) and d not in used_doors and d.dest not in used_doors: if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) - elif d.type is DoorType.Normal and not_in_proposal(d): + elif d.type is DoorType.Normal and not_in_proposal(d) and d not in used_doors: if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) do_bombable_dashable(pair[0], DoorKind.Bombable, world, player) @@ -3003,7 +3004,7 @@ def valid_key_door_pair(door1, door2): return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1 -def reassign_key_doors(small_map, world, player): +def reassign_key_doors(small_map, used_doors, world, player): logger = logging.getLogger('') for name, small_doors in small_map.items(): logger.debug(f'Key doors for {name}') @@ -3013,7 +3014,7 @@ def reassign_key_doors(small_map, world, player): queue = deque(find_current_key_doors(builder)) while len(queue) > 0: d = queue.pop() - if d.type is DoorType.SpiralStairs and d not in proposal: + if d.type is DoorType.SpiralStairs and d not in proposal and d not in used_doors: room = world.get_room(d.roomIndex, player) if room.doorList[d.doorListPos][1] == DoorKind.StairKeyLow: room.delete(d.doorListPos) @@ -3023,13 +3024,14 @@ def reassign_key_doors(small_map, world, player): else: room.delete(d.doorListPos) d.smallKey = False - elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: + elif (d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal + and d not in used_doors and d.dest not in used_doors): if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False d.dest.smallKey = False queue.remove(d.dest) - elif d.type is DoorType.Normal and d not in flat_proposal: + elif d.type is DoorType.Normal and d not in flat_proposal and d not in used_doors: if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False diff --git a/ItemList.py b/ItemList.py index 168d841d..9ca15555 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1396,6 +1396,7 @@ def fill_specific_items(world): dungeon_pool, prize_set, prize_pool) if item_to_place: world.push_item(loc, item_to_place, False) + loc.locked = True track_outside_keys(item_to_place, loc, world) track_dungeon_items(item_to_place, loc, world) loc.event = (event_flag or item_to_place.advancement @@ -1431,6 +1432,7 @@ def fill_specific_items(world): if loc.item: continue world.push_item(loc, item_to_place, False) + loc.locked = True track_outside_keys(item_to_place, loc, world) track_dungeon_items(item_to_place, loc, world) loc.event = (event_flag or item_to_place.advancement diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5f11c963..7010ecb6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -113,6 +113,8 @@ These are now independent of retro mode and have three options: None, Random, an * Fixed an issue with pyramid hole being in logic when it is not opened. * Crystal cutscene at GT use new symmetrical layouts (thanks Codemann) * Fix for Hera Boss music (thanks Codemann) + * Fixed an issue where certain vanilla door types would not allow other types to be placed. + * Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead is no alternative are found. * Customizer: fixed an issue with assured sword and start_inventory * Customizer: warns when trying to specifically place an item that's not in the item pool * Fixed "accessibility: none" displaying a spoiling message From b6275d0688f7ab92cb5811f7aac8243c956ddabd Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 19 Jul 2023 12:57:07 -0600 Subject: [PATCH 22/28] Typo --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7010ecb6..45577d04 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -114,7 +114,7 @@ These are now independent of retro mode and have three options: None, Random, an * Crystal cutscene at GT use new symmetrical layouts (thanks Codemann) * Fix for Hera Boss music (thanks Codemann) * Fixed an issue where certain vanilla door types would not allow other types to be placed. - * Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead is no alternative are found. + * Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead if no alternatives are found. * Customizer: fixed an issue with assured sword and start_inventory * Customizer: warns when trying to specifically place an item that's not in the item pool * Fixed "accessibility: none" displaying a spoiling message From ea70d7cb5add7a6c952103ace2fdb3ade4405794 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Wed, 26 Jul 2023 02:40:40 +0200 Subject: [PATCH 23/28] Move Triforce Pieces Min and Max handling to Main --- CLI.py | 2 +- Main.py | 22 ++++++++++++++++++++-- source/tools/MysteryUtils.py | 16 +++++++--------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/CLI.py b/CLI.py index 312c1d80..85283053 100644 --- a/CLI.py +++ b/CLI.py @@ -229,7 +229,7 @@ def parse_settings(): "triforce_pool_max": 0, "triforce_goal_min": 0, "triforce_goal_max": 0, - "triforce_min_difference": 10, + "triforce_min_difference": 0, "code": "", "multi": 1, diff --git a/Main.py b/Main.py index 2edf2c03..049f5932 100644 --- a/Main.py +++ b/Main.py @@ -124,8 +124,6 @@ def main(args, seed=None, fish=None): world.potshuffle = args.shufflepots.copy() world.mixed_travel = args.mixed_travel.copy() world.standardize_palettes = args.standardize_palettes.copy() - world.treasure_hunt_count = {k: int(v) for k, v in args.triforce_goal.items()} - world.treasure_hunt_total = {k: int(v) for k, v in args.triforce_pool.items()} world.shufflelinks = args.shufflelinks.copy() world.shuffletavern = args.shuffletavern.copy() world.pseudoboots = args.pseudoboots.copy() @@ -135,6 +133,26 @@ def main(args, seed=None, fish=None): world.collection_rate = args.collection_rate.copy() world.colorizepots = args.colorizepots.copy() + world.treasure_hunt_count = {} + world.treasure_hunt_total = {} + for p in args.triforce_goal: + if int(args.triforce_goal[p]) != 0 or int(args.triforce_pool[p]) != 0 or int(args.triforce_goal_min[p]) != 0 or int(args.triforce_goal_max[p]) != 0 or int(args.triforce_pool_min[p]) != 0 or int(args.triforce_pool_max[p]) != 0: + if int(args.triforce_goal[p]) != 0: + world.treasure_hunt_count[p] = int(args.triforce_goal[p]) + elif int(args.triforce_goal_min[p]) != 0 and int(args.triforce_goal_max[p]) != 0: + world.treasure_hunt_count[p] = random.randint(int(args.triforce_goal_min[p]), int(args.triforce_goal_max[p])) + else: + world.treasure_hunt_count[p] = 8 if world.goal[p] == 'trinity' else 20 + if int(args.triforce_pool[p]) != 0: + world.treasure_hunt_total[p] = int(args.triforce_pool[p]) + elif int(args.triforce_pool_min[p]) != 0 and int(args.triforce_pool_max[p]) != 0: + world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), int(args.triforce_pool_max[p])) + else: + world.treasure_hunt_total[p] = 10 if world.goal[p] == 'trinity' else 30 + else: + # this will be handled in ItemList.py and custom item pool is used to determine the numbers + world.treasure_hunt_count[p], world.treasure_hunt_total[p] = 0, 0 + world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.finish_init() diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 8f371b6c..32ee9d5f 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -125,15 +125,13 @@ def roll_settings(weights): ret.crystals_ganon = get_choice('ganon_open') - from ItemList import set_default_triforce - default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0) - goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal) - goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal) - pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool) - pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool) - ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) - min_diff = get_choice_default('triforce_min_difference', default=default_tf_pool-default_tf_goal) - ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) + ret.triforce_pool = get_choice_default('triforce_pool', default=0) + ret.triforce_goal = get_choice_default('triforce_goal', default=0) + ret.triforce_pool_min = get_choice_default('triforce_pool_min', default=0) + ret.triforce_pool_max = get_choice_default('triforce_pool_max', default=0) + ret.triforce_goal_min = get_choice_default('triforce_goal_min', default=0) + ret.triforce_goal_max = get_choice_default('triforce_goal_max', default=0) + ret.triforce_min_difference = get_choice_default('triforce_min_difference', default=0) ret.mode = get_choice('world_state') if ret.mode == 'retro': From 66bd8960a1738d3b91b63f920ce05a9c9a91e078 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:03:45 +0200 Subject: [PATCH 24/28] Support Triforce Piece settings in Customizer YAML --- source/classes/CustomSettings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index deb9f4d2..ca0f2062 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -144,6 +144,11 @@ class CustomSettings(object): args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p]) args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p]) args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p]) + args.triforce_goal_min[p] = get_setting(settings['triforce_goal_min'], args.triforce_goal_min[p]) + args.triforce_goal_max[p] = get_setting(settings['triforce_goal_max'], args.triforce_goal_max[p]) + args.triforce_pool_min[p] = get_setting(settings['triforce_pool_min'], args.triforce_pool_min[p]) + args.triforce_pool_max[p] = get_setting(settings['triforce_pool_max'], args.triforce_pool_max[p]) + args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p]) args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p]) # mystery usage From bf9ad536fe3aa34f7130815c8ec41fa6671c622f Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:27:47 +0200 Subject: [PATCH 25/28] Implement triforce_max_difference --- CLI.py | 3 ++- Main.py | 2 +- README.md | 3 ++- mystery_example.yml | 1 + mystery_testsuite.yml | 1 + resources/app/cli/args.json | 1 + source/classes/CustomSettings.py | 1 + source/tools/MysteryUtils.py | 1 + 8 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CLI.py b/CLI.py index 85283053..2c925c6c 100644 --- a/CLI.py +++ b/CLI.py @@ -132,7 +132,7 @@ def parse_cli(argv, no_defaults=False): 'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', - 'usestartinventory', 'bombbag', 'overworld_map', 'restrict_boss_items', + 'usestartinventory', 'bombbag', 'overworld_map', 'restrict_boss_items', 'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', @@ -230,6 +230,7 @@ def parse_settings(): "triforce_goal_min": 0, "triforce_goal_max": 0, "triforce_min_difference": 0, + "triforce_max_difference": 10000, "code": "", "multi": 1, diff --git a/Main.py b/Main.py index 049f5932..17444ce7 100644 --- a/Main.py +++ b/Main.py @@ -146,7 +146,7 @@ def main(args, seed=None, fish=None): if int(args.triforce_pool[p]) != 0: world.treasure_hunt_total[p] = int(args.triforce_pool[p]) elif int(args.triforce_pool_min[p]) != 0 and int(args.triforce_pool_max[p]) != 0: - world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), int(args.triforce_pool_max[p])) + world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), min(int(args.triforce_pool_max[p]), world.treasure_hunt_count[p] + int(args.triforce_max_difference[p]))) else: world.treasure_hunt_total[p] = 10 if world.goal[p] == 'trinity' else 30 else: diff --git a/README.md b/README.md index 21a444cc..df21ad17 100644 --- a/README.md +++ b/README.md @@ -573,13 +573,14 @@ Create bps patch(es) instead of generating rom(s) for distribution. `--bps` ### Triforce Hunt Settings -A collection of settings to control the triforce piece pool for the CLI/Mystery +A collection of settings to control the triforce piece pool if not specified through --triforce_goal and --triforce_pool * --triforce_goal_min: Minimum number of pieces to collect to win * --triforce_goal_max: Maximum number of pieces to collect to win * --triforce_pool_min: Minimum number of pieces in item pool * --triforce_pool_max: Maximum number of pieces in item pool * --triforce_min_difference: Minimum difference between pool and goal to win +* --triforce_max_difference: Maximum difference between pool and goal to win ### Seed diff --git a/mystery_example.yml b/mystery_example.yml index 344875f4..92abd726 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -107,6 +107,7 @@ triforce_pool_min: 20 triforce_pool_max: 40 triforce_min_difference: 10 + triforce_max_difference: 15 dungeon_items: standard: 10 mc: 3 diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index f919b7cc..d6e46832 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -83,6 +83,7 @@ triforce_goal_max: 30 triforce_pool_min: 30 triforce_pool_max: 40 triforce_min_difference: 10 +triforce_max_difference: 12 map_shuffle: on: 1 off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 5254a1c4..2eaf847e 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -339,6 +339,7 @@ "triforce_goal_min": {}, "triforce_goal_max": {}, "triforce_min_difference": {}, + "triforce_max_difference": {}, "custom": { "type": "bool", "help": "suppress" diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index ca0f2062..a0cea069 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -149,6 +149,7 @@ class CustomSettings(object): args.triforce_pool_min[p] = get_setting(settings['triforce_pool_min'], args.triforce_pool_min[p]) args.triforce_pool_max[p] = get_setting(settings['triforce_pool_max'], args.triforce_pool_max[p]) 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]) # mystery usage diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 32ee9d5f..0405efff 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -132,6 +132,7 @@ def roll_settings(weights): ret.triforce_goal_min = get_choice_default('triforce_goal_min', default=0) ret.triforce_goal_max = get_choice_default('triforce_goal_max', default=0) ret.triforce_min_difference = get_choice_default('triforce_min_difference', default=0) + ret.triforce_max_difference = get_choice_default('triforce_max_difference', default=10000) ret.mode = get_choice('world_state') if ret.mode == 'retro': From 8d17d9564075fefaa275a40044bb123367c1cce1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 27 Jul 2023 13:45:44 -0600 Subject: [PATCH 26/28] Fixed a generation bug Add ganonhunt to pyramid open --- BaseClasses.py | 2 +- DoorShuffle.py | 20 +++++++++----------- RELEASENOTES.md | 3 +++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index af1436eb..c9d4be13 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -283,7 +283,7 @@ class World(object): else: if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: return False - elif self.goal[player] in ['crystals', 'trinity']: + elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']: return True else: return False diff --git a/DoorShuffle.py b/DoorShuffle.py index 35cc7624..462eb406 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -2547,13 +2547,12 @@ def reassign_big_key_doors(bk_map, used_doors, world, player): queue = deque(find_current_bk_doors(builder)) while len(queue) > 0: d = queue.pop() - if (d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal - and d not in used_doors and d.dest not in used_doors): - if not d.entranceFlag: + if d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: + if not d.entranceFlag and d not in used_doors and d.dest not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.bigKey = False - elif d.type is DoorType.Normal and d not in flat_proposal and d not in used_doors: - if not d.entranceFlag: + elif d.type is DoorType.Normal and d not in flat_proposal : + if not d.entranceFlag and d not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.bigKey = False for obj in big_doors: @@ -3014,7 +3013,7 @@ def reassign_key_doors(small_map, used_doors, world, player): queue = deque(find_current_key_doors(builder)) while len(queue) > 0: d = queue.pop() - if d.type is DoorType.SpiralStairs and d not in proposal and d not in used_doors: + if d.type is DoorType.SpiralStairs and d not in proposal: room = world.get_room(d.roomIndex, player) if room.doorList[d.doorListPos][1] == DoorKind.StairKeyLow: room.delete(d.doorListPos) @@ -3024,15 +3023,14 @@ def reassign_key_doors(small_map, used_doors, world, player): else: room.delete(d.doorListPos) d.smallKey = False - elif (d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal - and d not in used_doors and d.dest not in used_doors): - if not d.entranceFlag: + elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: + if not d.entranceFlag and d not in used_doors and d.dest not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False d.dest.smallKey = False queue.remove(d.dest) - elif d.type is DoorType.Normal and d not in flat_proposal and d not in used_doors: - if not d.entranceFlag: + elif d.type is DoorType.Normal and d not in flat_proposal: + if not d.entranceFlag and d not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False for dp in world.paired_doors[player]: diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 45577d04..0b8d2151 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,9 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.19u + * Fixed a bug with dungeon generation + * Changed the "Ganonhunt" goal to use open pyramid on the Auto setting * 1.2.0.18u * Fixed an issue with pyramid hole being in logic when it is not opened. * Crystal cutscene at GT use new symmetrical layouts (thanks Codemann) From 75fc1f7cc3b06fb451564ef161275aff54883039 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 28 Jul 2023 12:29:47 -0600 Subject: [PATCH 27/28] Fixed customizer example Fixed /missing command in multiworld for non-pottery lottery settings --- Main.py | 2 +- MultiClient.py | 15 ++++++++++++--- RELEASENOTES.md | 5 ++++- docs/customizer_example.yaml | 5 ++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Main.py b/Main.py index 17444ce7..dd5ebc3e 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.18' +version_number = '1.2.0.19' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/MultiClient.py b/MultiClient.py index fbde673d..646c60cf 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -66,6 +66,8 @@ class Context: self.lookup_name_to_id = {} self.lookup_id_to_name = {} + self.pottery_locations_enabled = None + def color_code(*args): codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43, @@ -96,6 +98,8 @@ SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes? ITEM_SRAM_SIZE = 0x250 SHOP_SRAM_LEN = 0x29 # 41 tracked items +POT_LOCATION_TABLE = 0x142A60 + RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte @@ -826,12 +830,14 @@ def get_location_name_from_address(ctx, address): def filter_location(ctx, location): + if location in location_table_pot_items: + tile_idx, mask = location_table_pot_items[location] + tracking_data = ctx.pottery_locations_enabled + tile_pots = tracking_data[tile_idx] | (tracking_data[tile_idx+1] << 8) + return (mask & tile_pots) == 0 if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data and PotShuffle.key_drop_data[location][0] == 'Drop'): return True - if (not ctx.pottery_mode and location in PotShuffle.key_drop_data - and PotShuffle.key_drop_data[location][0] == 'Pot'): - return True if not ctx.shop_mode and location in Regions.flat_normal_shops: return True if not ctx.retro_mode and location in Regions.flat_retro_shops: @@ -996,6 +1002,9 @@ async def game_watcher(ctx : Context): logging.warning("ROM change detected, please reconnect to the multiworld server") await disconnect(ctx) + if ctx.pottery_locations_enabled is None: + ctx.pottery_locations_enabled = await snes_read(ctx, POT_LOCATION_TABLE, 0x250) + gamemode = await snes_read(ctx, WRAM_START + 0x10, 1) if gamemode is None or gamemode[0] not in INGAME_MODES: continue diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0b8d2151..7416496f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -110,8 +110,11 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes * 1.2.0.19u - * Fixed a bug with dungeon generation + * Added min/max for triforce pool, goal, and differnce for CLI and Customizer. (Thanks Catobat) + * Fixed a bug with dungeon generation + * Multiworld: Fixed /missing command to not list all the pots * Changed the "Ganonhunt" goal to use open pyramid on the Auto setting + * Customizer: Fixed the example yaml for shopsanity * 1.2.0.18u * Fixed an issue with pyramid hole being in logic when it is not opened. * Crystal cutscene at GT use new symmetrical layouts (thanks Codemann) diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index 3d0c7624..76514990 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -1,7 +1,7 @@ meta: algorithm: balanced players: 1 - seed: 42 + seed: 41 # note to self: seed 42 had an interesting Swamp Palace problem names: Lonk settings: 1: @@ -56,6 +56,9 @@ item_pool: Sanctuary Heart Container: 3 Shovel: 3 Single Arrow: 1 + Green Potion: 1 + Blue Potion: 1 + Red Potion: 1 placements: 1: Palace of Darkness - Big Chest: Hammer From 1592fac79293265aba0e564d9c11e4db602467d7 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 28 Jul 2023 12:32:28 -0600 Subject: [PATCH 28/28] Typo --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7416496f..3231157f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -110,7 +110,7 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes * 1.2.0.19u - * Added min/max for triforce pool, goal, and differnce for CLI and Customizer. (Thanks Catobat) + * Added min/max for triforce pool, goal, and difference for CLI and Customizer. (Thanks Catobat) * Fixed a bug with dungeon generation * Multiworld: Fixed /missing command to not list all the pots * Changed the "Ganonhunt" goal to use open pyramid on the Auto setting