From c3266a904ad27eabbcc60f66dd249dca5f5f9ea6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 22 Mar 2023 18:38:26 -0500 Subject: [PATCH 01/15] Disallow some LH placements in DM in Lite/Lean ER --- source/overworld/EntranceShuffle2.py | 51 +++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index c6898b3a..d456f64e 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -528,9 +528,14 @@ def do_links_house(entrances, exits, avail, cross_world): # can't have links house on eddm in restricted because Inverted Aga Tower isn't available # todo: inverted full may have the same problem if both links house and a mandatory connector is chosen # from the 3 inverted options - if avail.world.shuffle[avail.player] in ['restricted'] and avail.world.is_tile_swapped(0x03, avail.player): + if avail.world.shuffle[avail.player] in ['restricted', 'lite', 'lean'] and avail.world.is_tile_swapped(0x03, avail.player): avail.links_on_mountain = True forbidden.extend(['Spike Cave', 'Dark Death Mountain Fairy']) + + if avail.world.shuffle[avail.player] in ['lite', 'lean']: + if avail.world.is_tile_swapped(0x05, avail.player): + avail.links_on_mountain = True + forbidden.extend(['Cave Shop (Dark Death Mountain)']) else: avail.links_on_mountain = True @@ -558,15 +563,26 @@ def do_links_house(entrances, exits, avail, cross_world): if links_house in dm_spots and avail.world.owShuffle[avail.player] == 'vanilla': if avail.links_on_mountain: return # connector is fine - multi_exit_caves = figure_out_connectors(exits) - entrance_pool = entrances if avail.coupled else avail.decoupled_entrances - if cross_world: - possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List] - possible_exits = [e for e in entrance_pool if e not in dm_spots] + if avail.world.shuffle[avail.player] in ['lite', 'lean']: + rem_exits = [e for e in avail.exits if e in Connector_Exit_Set and e not in Dungeon_Exit_Set] + multi_exit_caves = figure_out_connectors(rem_exits) + if cross_world: + possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List] + possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots] + else: + world_list = LW_Entrances if not avail.inverted else DW_Entrances + possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List and e in world_list] + possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots and e in world_list] else: - world_list = LW_Entrances if not avail.inverted else DW_Entrances - possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List and e in world_list] - possible_exits = [e for e in entrance_pool if e not in dm_spots and e in world_list] + multi_exit_caves = figure_out_connectors(exits) + entrance_pool = entrances if avail.coupled else avail.decoupled_entrances + if cross_world: + possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List] + possible_exits = [e for e in entrance_pool if e not in dm_spots] + else: + world_list = LW_Entrances if not avail.inverted else DW_Entrances + possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List and e in world_list] + possible_exits = [e for e in entrance_pool if e not in dm_spots and e in world_list] chosen_cave = random.choice(multi_exit_caves) shuffle_connector_exits(chosen_cave) possible_dm_exits.sort() @@ -2036,6 +2052,23 @@ Connector_Exit_Set = { 'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)' } +Dungeon_Exit_Set = { + 'Eastern Palace Exit', + 'Tower of Hera Exit', + 'Agahnims Tower Exit', + 'Palace of Darkness Exit', + 'Swamp Palace Exit', + 'Skull Woods Final Section Exit', + 'Thieves Town Exit', + 'Ice Palace Exit', + 'Misery Mire Exit', + 'Ganons Tower Exit', + 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', + 'Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', + 'Desert Palace Exit (South)', 'Desert Palace Exit (East)', 'Desert Palace Exit (West)', + 'Turtle Rock Exit (Front)', 'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)' +} + # Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups Must_Exit_Invalid_Connections = defaultdict(set) From 28e5b7503d9edbbe9e26c0de92f0bcfef5216fa7 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 27 Mar 2023 21:28:46 -0500 Subject: [PATCH 02/15] Improved can_reach_smith search --- OverworldShuffle.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index b9e2dc8f..4d137416 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -973,27 +973,26 @@ def can_reach_smith(world, player): region = world.get_region(region_name, player) for exit in region.exits: if not found and exit.connected_region is not None: - if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], world.precollected_items)) and exit.spot_type == 'Flute': - fluteregion = exit.connected_region - for flutespot in fluteregion.exits: - if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: - explore_region(flutespot.connected_region.name, flutespot.connected_region) - elif exit.connected_region.name not in explored_regions \ - and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ - and exit.access_rule(blank_state): - explore_region(exit.connected_region.name, exit.connected_region) - elif exit.name == 'Sanctuary S': - sanc_region = exit.connected_region - if len(sanc_region.exits) and sanc_region.exits[0].name == 'Sanctuary Exit': - explore_region(sanc_region.exits[0].connected_region.name, sanc_region.exits[0].connected_region) + if exit.spot_type == 'Flute': + if any(map(lambda i: i.name == 'Ocarina (Activated)', world.precollected_items)): + for flutespot in exit.connected_region.exits: + if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: + explore_region(flutespot.connected_region.name, flutespot.connected_region) elif exit.connected_region.name == 'Blacksmiths Hut' and exit.access_rule(blank_state): found = True + return + elif exit.connected_region.name not in explored_regions: + if (region.type == RegionType.Dungeon and exit.connected_region.name.endswith(' Portal')) \ + or (exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ + and exit.access_rule(blank_state)): + explore_region(exit.connected_region.name, exit.connected_region) blank_state = CollectionState(world) if world.mode[player] == 'standard': blank_state.collect(ItemFactory('Zelda Delivered', player), True) - if world.logic[player] in ['noglitches', 'minorglitches'] and world.get_region('Frog Prison', player).type == (RegionType.DarkWorld if not invFlag else RegionType.LightWorld): + if world.logic[player] in ['noglitches', 'minorglitches'] and not world.is_tile_swapped(0x29, player): blank_state.collect(ItemFactory('Titans Mitts', player), True) + blank_state.collect(ItemFactory('Moon Pearl', player), True) found = False explored_regions = list() @@ -1004,7 +1003,11 @@ def can_reach_smith(world, player): explore_region(start_region) if not found: if not invFlag: - explore_region('Sanctuary') + if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla': + sanc_mirror = world.get_entrance('Sanctuary Mirror Route', player) + explore_region(sanc_mirror.connected_region.name, sanc_mirror.connected_region) + else: + explore_region('Sanctuary') else: explore_region('Dark Sanctuary Hint') return found From f1f75ec760de99b742228812746f8df470c386e5 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 29 Mar 2023 09:58:48 -0500 Subject: [PATCH 03/15] Major GUI reorganization --- Gui.py | 8 +- .../app/gui/custom/overview/widgets.json | 18 ++ resources/app/gui/lang/en.json | 127 +++++++------- .../app/gui/randomize/dungeon/keysanity.json | 8 + .../app/gui/randomize/dungeon/widgets.json | 57 +++--- .../app/gui/randomize/entrando/widgets.json | 74 ++++---- .../gui/randomize/generation/checkboxes.json | 5 +- resources/app/gui/randomize/item/widgets.json | 165 ++++++++++++------ .../app/gui/randomize/overworld/widgets.json | 32 ++-- source/classes/constants.py | 79 +++++---- source/gui/bottom.py | 18 +- source/gui/custom/overview.py | 23 ++- source/gui/loadcliargs.py | 37 ++-- source/gui/randomize/dungeon.py | 14 +- source/gui/randomize/entrando.py | 5 +- source/gui/randomize/item.py | 43 ++++- source/gui/randomize/overworld.py | 21 +-- source/gui/startinventory/overview.py | 23 ++- source/gui/widgets.py | 67 ++++++- 19 files changed, 517 insertions(+), 307 deletions(-) diff --git a/Gui.py b/Gui.py index 5ca88ac1..a0f4726f 100755 --- a/Gui.py +++ b/Gui.py @@ -144,14 +144,14 @@ def guiMain(args=None): self.pages["randomizer"].pages["entrance"] = entrando_page(self.pages["randomizer"].notebook) self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["entrance"], text="Entrances") + # Dungeons + self.pages["randomizer"].pages["dungeon"] = dungeon_page(self.pages["randomizer"].notebook) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["dungeon"], text="Dungeons") + # Enemizer self.pages["randomizer"].pages["enemizer"],self.settings = enemizer_page(self.pages["randomizer"].notebook,self.settings) self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["enemizer"], text="Enemizer") - # Dungeon Shuffle - self.pages["randomizer"].pages["dungeon"] = dungeon_page(self.pages["randomizer"].notebook) - self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["dungeon"], text="Dungeon Shuffle") - # Multiworld # self.pages["randomizer"].pages["multiworld"],self.settings = multiworld_page(self.pages["randomizer"].notebook,self.settings) # self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["multiworld"], text="Multiworld") diff --git a/resources/app/gui/custom/overview/widgets.json b/resources/app/gui/custom/overview/widgets.json index ee7be421..03c7fbaf 100644 --- a/resources/app/gui/custom/overview/widgets.json +++ b/resources/app/gui/custom/overview/widgets.json @@ -1,4 +1,22 @@ { + "startHeader": { + "usestartinventory": { + "type": "checkbox", + "config": { + "padx": [10,0], + "pady": [10,10] + } + } + }, + "customHeader": { + "usecustompool": { + "type": "checkbox", + "config": { + "padx": [10,0], + "pady": [10,10] + } + } + }, "itemList1": { "bow": { "type": "textbox", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 5598f292..feadd457 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -57,21 +57,7 @@ "randomizer.dungeon.smallkeyshuffle.wild": "Randomized", "randomizer.dungeon.smallkeyshuffle.universal": "Universal", "randomizer.dungeon.bigkeyshuffle": "Big Keys", - "randomizer.dungeon.keydropshuffle": "Key Drop Shuffle (Legacy)", "randomizer.dungeon.decoupledoors": "Decouple Doors", - "randomizer.dungeon.dropshuffle": "Shuffle Enemy Key Drops", - "randomizer.dungeon.potshuffle": "Pot Shuffle (Legacy)", - "randomizer.dungeon.pottery": "Pottery", - "randomizer.dungeon.pottery.none": "None", - "randomizer.dungeon.pottery.keys": "Key Pots", - "randomizer.dungeon.pottery.cave": "Cave Pots", - "randomizer.dungeon.pottery.cavekeys": "Cave+Key Pots", - "randomizer.dungeon.pottery.reduced": "Reduced Dungeon Pots (Dynamic)", - "randomizer.dungeon.pottery.clustered": "Clustered Dungeon Pots (Dynamic)", - "randomizer.dungeon.pottery.nonempty": "Excludes Empty Pots", - "randomizer.dungeon.pottery.dungeon": "Dungeon Pots", - "randomizer.dungeon.pottery.lottery": "Lottery (All Pots and Large Blocks)", - "randomizer.dungeon.colorizepots": "Colorize Randomized Pots", "randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle", "randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla", @@ -123,7 +109,7 @@ "randomizer.enemizer.enemyshuffle.none": "None", "randomizer.enemizer.enemyshuffle.shuffled": "Shuffled", "randomizer.enemizer.enemyshuffle.random": "Random", - "randomizer.enemizer.enemyshuffle.legacy": "Random (including Thieves)", + "randomizer.enemizer.enemyshuffle.legacy": "Random (50/50 Thieves)", "randomizer.enemizer.bossshuffle": "Boss Shuffle", "randomizer.enemizer.bossshuffle.none": "None", @@ -168,8 +154,6 @@ "randomizer.overworld.whirlpool": "Whirlpool Shuffle", - "randomizer.overworld.bonk_drops": "Bonk Drops", - "randomizer.overworld.overworldflute": "Flute Shuffle", "randomizer.overworld.overworldflute.vanilla": "Vanilla", "randomizer.overworld.overworldflute.balanced": "Balanced", @@ -200,11 +184,6 @@ "randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full", "randomizer.entrance.entranceshuffle.dungeonssimple": "Dungeons + Simple", - "randomizer.entrance.take_any": "Take Any Caves", - "randomizer.entrance.take_any.none": "None", - "randomizer.entrance.take_any.random": "Random", - "randomizer.entrance.take_any.fixed": "Fixed", - "randomizer.gameoptions.nobgm": "Disable Music & MSU-1", "randomizer.gameoptions.quickswap": "L/R Quickswapping", "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", @@ -253,9 +232,6 @@ "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.print_custom_yaml": "Print Customizer File", - "randomizer.generation.usestartinventory": "Use Starting Inventory", - "randomizer.generation.usecustompool": "Use Custom Item Pool", - "randomizer.generation.race": "Generate \"Race\" ROM", "randomizer.generation.saveonexit": "Save Settings on Exit", "randomizer.generation.saveonexit.ask": "Ask Me", @@ -267,10 +243,10 @@ "randomizer.generation.rom.dialog.romfiles": "Rom Files", "randomizer.generation.rom.dialog.allfiles": "All Files", - "randomizer.item.hints": "Include Helpful Hints", + "randomizer.item.hints": "Hints", + "randomizer.item.race": "Generate \"Race\" ROM", "randomizer.item.retro": "Retro mode", - "randomizer.item.pseudoboots": "Start with Pseudo Boots", - "randomizer.item.bombbag": "Bombbag", + "randomizer.item.pseudoboots": "Pseudoboots", "randomizer.item.worldstate": "World State", "randomizer.item.worldstate.standard": "Standard", @@ -322,24 +298,66 @@ "randomizer.item.weapons.swordless": "Swordless", "randomizer.item.weapons.vanilla": "Vanilla", - "randomizer.item.beemizer": "Beemizer", - "randomizer.item.beemizer.0": "No Bee Traps", - "randomizer.item.beemizer.1": "25% Bee Traps", - "randomizer.item.beemizer.2": "40% Traps, 20% Refills", - "randomizer.item.beemizer.3": "50% Traps, 50% Refills", - "randomizer.item.beemizer.4": "100% Bee Traps", + "randomizer.item.sortingalgo": "Item Fill", + "randomizer.item.sortingalgo.balanced": "Balanced", + "randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill", + "randomizer.item.sortingalgo.major_only": "Major Location Restriction", + "randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction", + "randomizer.item.sortingalgo.district": "District Restriction", - "randomizer.item.itempool": "Item Pool", - "randomizer.item.itempool.normal": "Normal", - "randomizer.item.itempool.hard": "Hard", - "randomizer.item.itempool.expert": "Expert", + "randomizer.item.accessibility": "Accessibility", + "randomizer.item.accessibility.items": "100% Inventory", + "randomizer.item.accessibility.locations": "100% Locations", + "randomizer.item.accessibility.none": "Beatable", - "randomizer.item.shopsanity": "Shopsanity", + "randomizer.item.restrict_boss_items": "Forbidden Boss Items", + "randomizer.item.restrict_boss_items.none": "None", + "randomizer.item.restrict_boss_items.mapcompass": "Map & Compass", + "randomizer.item.restrict_boss_items.dungeon": "Map & Compass & Keys", "randomizer.item.itemfunction": "Item Functionality", "randomizer.item.itemfunction.normal": "Normal", "randomizer.item.itemfunction.hard": "Hard", "randomizer.item.itemfunction.expert": "Expert", + + "randomizer.item.timer": "Timer Setting", + "randomizer.item.timer.none": "No Timer", + "randomizer.item.timer.display": "Stopwatch", + "randomizer.item.timer.timed": "Timed", + "randomizer.item.timer.timed-ohko": "Timed OHKO", + "randomizer.item.timer.ohko": "OHKO", + "randomizer.item.timer.timed-countdown": "Timed Countdown", + + "randomizer.item.shopsanity": "Shopsanity", + + "randomizer.item.bonk_drops": "Bonk Drops", + + "randomizer.item.pottery": "Pottery", + "randomizer.item.pottery.none": "None", + "randomizer.item.pottery.keys": "Key Pots", + "randomizer.item.pottery.cave": "Cave Pots", + "randomizer.item.pottery.cavekeys": "Cave+Key Pots", + "randomizer.item.pottery.reduced": "Reduced Dungeon Pots (Dynamic)", + "randomizer.item.pottery.clustered": "Clustered Dungeon Pots (Dynamic)", + "randomizer.item.pottery.nonempty": "Excludes Empty Pots", + "randomizer.item.pottery.dungeon": "Dungeon Pots", + "randomizer.item.pottery.lottery": "Lottery (All Pots and Large Blocks)", + + "randomizer.item.colorizepots": "Colorize Randomized Pots", + "randomizer.item.potshuffle": "Pot Shuffle (Legacy)", + + "randomizer.item.dropshuffle": "Shuffle Enemy Key Drops", + "randomizer.item.keydropshuffle": "Key Drop Shuffle (Legacy)", + + "randomizer.item.take_any": "Take Any Caves", + "randomizer.item.take_any.none": "None", + "randomizer.item.take_any.random": "Random", + "randomizer.item.take_any.fixed": "Fixed", + + "randomizer.item.itempool": "Item Pool", + "randomizer.item.itempool.normal": "Normal", + "randomizer.item.itempool.hard": "Hard", + "randomizer.item.itempool.expert": "Expert", "randomizer.item.flute_mode": "Flute Mode", "randomizer.item.flute_mode.normal": "Normal", @@ -351,30 +369,17 @@ "randomizer.item.bow_mode.retro": "Retro (Progressive)", "randomizer.item.bow_mode.retro_silvers": "Retro + Silvers", - "randomizer.item.timer": "Timer Setting", - "randomizer.item.timer.none": "No Timer", - "randomizer.item.timer.display": "Stopwatch", - "randomizer.item.timer.timed": "Timed", - "randomizer.item.timer.timed-ohko": "Timed OHKO", - "randomizer.item.timer.ohko": "OHKO", - "randomizer.item.timer.timed-countdown": "Timed Countdown", + "randomizer.item.beemizer": "Beemizer", + "randomizer.item.beemizer.0": "No Bee Traps", + "randomizer.item.beemizer.1": "25% Bee Traps", + "randomizer.item.beemizer.2": "40% Traps, 20% Refills", + "randomizer.item.beemizer.3": "50% Traps, 50% Refills", + "randomizer.item.beemizer.4": "100% Bee Traps", - "randomizer.item.accessibility": "Accessibility", - "randomizer.item.accessibility.items": "100% Inventory", - "randomizer.item.accessibility.locations": "100% Locations", - "randomizer.item.accessibility.none": "Beatable", + "randomizer.item.bombbag": "Bombbag", - "randomizer.item.sortingalgo": "Item Sorting", - "randomizer.item.sortingalgo.balanced": "Balanced", - "randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill", - "randomizer.item.sortingalgo.major_only": "Major Location Restriction", - "randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction", - "randomizer.item.sortingalgo.district": "District Restriction", - - "randomizer.item.restrict_boss_items": "Forbidden Boss Items", - "randomizer.item.restrict_boss_items.none": "None", - "randomizer.item.restrict_boss_items.mapcompass": "Map & Compass", - "randomizer.item.restrict_boss_items.dungeon": "Map & Compass & Keys", + "startinventory.usestartinventory": "Use Starting Inventory", + "custom.usecustompool": "Use Custom Item Pool", "bottom.content.worlds": "Worlds", "bottom.content.names": "Player names", diff --git a/resources/app/gui/randomize/dungeon/keysanity.json b/resources/app/gui/randomize/dungeon/keysanity.json index ffd9bc92..ec3ae380 100644 --- a/resources/app/gui/randomize/dungeon/keysanity.json +++ b/resources/app/gui/randomize/dungeon/keysanity.json @@ -1,5 +1,13 @@ { "keysanity": { + "smallkeyshuffle": { + "type": "selectbox", + "options": [ + "none", + "wild", + "universal" + ] + }, "mapshuffle": { "type": "checkbox" }, "compassshuffle": { "type": "checkbox" }, "bigkeyshuffle": { "type": "checkbox" } diff --git a/resources/app/gui/randomize/dungeon/widgets.json b/resources/app/gui/randomize/dungeon/widgets.json index e0cd2bdd..3f0abe94 100644 --- a/resources/app/gui/randomize/dungeon/widgets.json +++ b/resources/app/gui/randomize/dungeon/widgets.json @@ -1,12 +1,17 @@ { "widgets": { - "smallkeyshuffle": { + "key_logic_algorithm": { "type": "selectbox", + "default": "default", "options": [ - "none", - "wild", - "universal" - ] + "default", + "partial", + "strict" + ], + "config": { + "padx": [20,0], + "pady": [0,20] + } }, "dungeondoorshuffle": { "type": "selectbox", @@ -28,7 +33,8 @@ "random" ], "config": { - "width": 45 + "width": 45, + "padx": [20,0] } }, "door_type_mode": { @@ -41,7 +47,8 @@ "chaos" ], "config": { - "width": 45 + "width": 45, + "padx": [20,0] } }, "trap_door_mode": { @@ -54,41 +61,17 @@ "oneway" ], "config": { - "width": 30 + "width": 30, + "padx": [20,0] } }, - "key_logic_algorithm": { - "type": "selectbox", - "default": "default", - "options": [ - "default", - "partial", - "strict" - ] - }, - "decoupledoors": { "type": "checkbox" }, - "keydropshuffle": { "type": "checkbox" }, - "pottery": { - "type": "selectbox", - "default": "none", - "options": [ - "none", - "keys", - "cave", - "cavekeys", - "reduced", - "clustered", - "nonempty", - "dungeon", - "lottery" - ], + "decoupledoors": { + "type": "checkbox", "config": { - "width": 35 + "padx": [20,0], + "pady": [0,20] } }, - "colorizepots": { "type": "checkbox" }, - "dropshuffle": { "type": "checkbox" }, - "potshuffle": { "type": "checkbox" }, "experimental": { "type": "checkbox" }, "dungeon_counters": { "type": "selectbox", diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json index 41413ebd..adc16d18 100644 --- a/resources/app/gui/randomize/entrando/widgets.json +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -1,38 +1,5 @@ { "widgets": { - "openpyramid": { - "type": "selectbox", - "options": [ - "auto", - "yes", - "no" - ], - "config": { - "width": 10 - } - }, - "take_any": { - "type": "selectbox", - "options": [ - "none", - "random", - "fixed" - ] - }, - "shuffleganon": { "type": "checkbox" }, - "shufflelinks": { "type": "checkbox" }, - "shuffletavern": { "type": "checkbox" }, - "overworld_map": { - "type": "selectbox", - "options": [ - "default", - "compass", - "map" - ], - "config": { - "width": 45 - } - }, "entranceshuffle": { "type": "selectbox", "options": [ @@ -47,6 +14,47 @@ "dungeonsfull", "dungeonssimple" ] + }, + "shuffleganon": { + "type": "checkbox", + "config": { + "padx": [20,0] + } + }, + "shufflelinks": { + "type": "checkbox", + "config": { + "padx": [20,0] + } + }, + "shuffletavern": { + "type": "checkbox", + "config": { + "padx": [20,0] + } + }, + "openpyramid": { + "type": "selectbox", + "options": [ + "auto", + "yes", + "no" + ], + "config": { + "width": 6, + "pady": [20,0] + } + }, + "overworld_map": { + "type": "selectbox", + "options": [ + "default", + "compass", + "map" + ], + "config": { + "width": 45 + } } } } diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index b1ee4c84..6e377027 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,9 +4,6 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, - "print_custom_yaml": { "type": "checkbox" }, - "usestartinventory": { "type": "checkbox" }, - "usecustompool": { "type": "checkbox" }, - "race": { "type": "checkbox" } + "print_custom_yaml": { "type": "checkbox" } } } diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index a2cde0ea..9bb62bfe 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -1,12 +1,8 @@ { "checkboxes": { - "retro": { "type": "checkbox" }, - "bombbag": { "type": "checkbox" }, - "shopsanity": { "type": "checkbox" }, - "hints": { - "type": "checkbox" - }, - "pseudoboots": { "type": "checkbox" } + "hints": { "type": "checkbox" }, + "pseudoboots": { "type": "checkbox" }, + "race": { "type": "checkbox" } }, "leftItemFrame": { "worldstate": { @@ -17,7 +13,10 @@ "open", "inverted", "retro" - ] + ], + "config": { + "command": "worldstate" + } }, "logiclevel": { "type": "selectbox", @@ -63,16 +62,38 @@ "swordless", "vanilla" ] - }, - "beemizer": { - "type": "selectbox", - "options": [ - "0", "1", "2", "3", "4" - ] } }, "rightItemFrame": { - "itempool": { + "sortingalgo": { + "type": "selectbox", + "default": "balanced", + "options": [ + "balanced", + "vanilla_fill", + "major_only", + "dungeon_only", + "district" + ] + }, + "accessibility": { + "type": "selectbox", + "options": [ + "items", + "locations", + "none" + ] + }, + "restrict_boss_items": { + "type": "selectbox", + "default": "none", + "options": [ + "none", + "mapcompass", + "dungeon" + ] + }, + "itemfunction": { "type": "selectbox", "options": [ "normal", @@ -80,7 +101,78 @@ "expert" ] }, - "itemfunction": { + "timer": { + "type": "selectbox", + "options": [ + "none", + "display", + "timed", + "timed-ohko", + "ohko", + "timed-countdown" + ] + } + }, + "leftPoolHeader": { + "shopsanity": { + "type": "checkbox" + }, + "bonk_drops": { + "type": "checkbox", + "default": false + } + }, + "leftPoolFrame": { + "pottery": { + "type": "selectbox", + "default": "none", + "options": [ + "none", + "keys", + "cave", + "cavekeys", + "reduced", + "clustered", + "nonempty", + "dungeon", + "lottery" + ], + "config": { + "width": 35 + } + }, + "colorizepots": { + "type": "checkbox", + "config": { + "padx": [50,0] + } + }, + "potshuffle": { + "type": "checkbox", + "config": { + "padx": [50,0] + } + }, + "dropshuffle": { + "type": "checkbox" + }, + "keydropshuffle": { + "type": "checkbox", + "config": { + "command": "keydropshuffle" + } + }, + "take_any": { + "type": "selectbox", + "options": [ + "none", + "random", + "fixed" + ] + } + }, + "rightPoolFrame": { + "itempool": { "type": "selectbox", "options": [ "normal", @@ -104,44 +196,17 @@ "retro_silvers" ] }, - "timer": { + "beemizer": { "type": "selectbox", "options": [ - "none", - "display", - "timed", - "timed-ohko", - "ohko", - "timed-countdown" + "0", "1", "2", "3", "4" ] }, - "accessibility": { - "type": "selectbox", - "options": [ - "items", - "locations", - "none" - ] - }, - "sortingalgo": { - "type": "selectbox", - "default": "balanced", - "options": [ - "balanced", - "vanilla_fill", - "major_only", - "dungeon_only", - "district" - ] - }, - "restrict_boss_items": { - "type": "selectbox", - "default": "none", - "options": [ - "none", - "mapcompass", - "dungeon" - ] + "bombbag": { + "type": "checkbox", + "config": { + "padx": [64,0] + } } } } diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index d349cfc0..c9438deb 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -1,10 +1,5 @@ { - "topOverworldFrame": { - "bonk_drops": { - "type": "checkbox", - "default": false - } - }, + "topOverworldFrame": {}, "leftOverworldFrame": { "overworldshuffle": { "type": "selectbox", @@ -28,11 +23,17 @@ }, "mixed": { "type": "checkbox", - "default": false + "default": false, + "config": { + "padx": [79,0] + } }, "whirlpool": { "type": "checkbox", - "default": false + "default": false, + "config": { + "padx": [79,0] + } }, "overworldflute": { "type": "selectbox", @@ -41,17 +42,26 @@ "vanilla", "balanced", "random" - ] + ], + "config": { + "pady": [20,0] + } } }, "rightOverworldFrame": { "terrain": { "type": "checkbox", - "default": false + "default": false, + "config": { + "pady": [3,0] + } }, "keepsimilar": { "type": "checkbox", - "default": false + "default": false, + "config": { + "pady": [6,0] + } } } } \ No newline at end of file diff --git a/source/classes/constants.py b/source/classes/constants.py index e816f61b..19b93027 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -56,25 +56,36 @@ SETTINGSTOPROCESS = { "randomizer": { "item": { "hints": "hints", - "retro": "retro", - "bombbag": "bombbag", - "shopsanity": "shopsanity", "pseudoboots": "pseudoboots", + "race": "race", + "worldstate": "mode", "logiclevel": "logic", "goal": "goal", "crystals_gt": "crystals_gt", "crystals_ganon": "crystals_ganon", "weapons": "swords", - "itempool": "difficulty", + + "sortingalgo": "algorithm", + "accessibility": "accessibility", + "restrict_boss_items": "restrict_boss_items", "itemfunction": "item_functionality", + "timer": "timer", + + "shopsanity": "shopsanity", + "bonk_drops": "bonk_drops", + "pottery": "pottery", + "colorizepots": "colorizepots", + "potshuffle": "shufflepots", + "dropshuffle": "dropshuffle", + "keydropshuffle": "keydropshuffle", + "take_any": "take_any", + + "itempool": "difficulty", "flute_mode": "flute_mode", "bow_mode": "bow_mode", - "timer": "timer", - "accessibility": "accessibility", - "sortingalgo": "algorithm", "beemizer": "beemizer", - "restrict_boss_items": "restrict_boss_items" + "bombbag": "bombbag" }, "overworld": { "overworldshuffle": "ow_shuffle", @@ -83,17 +94,31 @@ SETTINGSTOPROCESS = { "keepsimilar": "ow_keepsimilar", "mixed": "ow_mixed", "whirlpool": "ow_whirlpool", - "bonk_drops": "bonk_drops", "overworldflute": "ow_fluteshuffle" }, "entrance": { - "openpyramid": "openpyramid", + "entranceshuffle": "shuffle", "shuffleganon": "shuffleganon", "shufflelinks": "shufflelinks", "shuffletavern": "shuffletavern", - "entranceshuffle": "shuffle", + "openpyramid": "openpyramid", "overworld_map": "overworld_map", - "take_any": "take_any", + }, + "dungeon": { + "smallkeyshuffle": "keyshuffle", + "mapshuffle": "mapshuffle", + "compassshuffle": "compassshuffle", + "bigkeyshuffle": "bigkeyshuffle", + "key_logic_algorithm": "key_logic_algorithm", + "dungeondoorshuffle": "door_shuffle", + "dungeonintensity": "intensity", + "door_type_mode": "door_type_mode", + "trap_door_mode": "trap_door_mode", + "decoupledoors": "decoupledoors", + "experimental": "experimental", + "dungeon_counters": "dungeon_counters", + "mixed_travel": "mixed_travel", + "standardize_palettes": "standardize_palettes" }, "enemizer": { "enemyshuffle": "shuffleenemies", @@ -101,27 +126,6 @@ SETTINGSTOPROCESS = { "enemydamage": "enemy_damage", "enemyhealth": "enemy_health" }, - "dungeon": { - "mapshuffle": "mapshuffle", - "compassshuffle": "compassshuffle", - "smallkeyshuffle": "keyshuffle", - "bigkeyshuffle": "bigkeyshuffle", - "dungeondoorshuffle": "door_shuffle", - "dungeonintensity": "intensity", - "door_type_mode": "door_type_mode", - "trap_door_mode": "trap_door_mode", - "key_logic_algorithm": "key_logic_algorithm", - "decoupledoors": "decoupledoors", - "keydropshuffle": "keydropshuffle", - "dropshuffle": "dropshuffle", - "pottery": "pottery", - "colorizepots": "colorizepots", - "potshuffle": "shufflepots", - "experimental": "experimental", - "dungeon_counters": "dungeon_counters", - "mixed_travel": "mixed_travel", - "standardize_palettes": "standardize_palettes" - }, "gameoptions": { "nobgm": "disablemusic", "quickswap": "quickswap", @@ -141,12 +145,15 @@ SETTINGSTOPROCESS = { "createrom": "create_rom", "calcplaythrough": "calc_playthrough", "print_custom_yaml": "print_custom_yaml", - "usestartinventory": "usestartinventory", - "usecustompool": "custom", - "race": "race", "saveonexit": "saveonexit" } }, + "startinventory": { + "usestartinventory": "usestartinventory" + }, + "custom": { + "usecustompool": "custom" + }, "bottom": { "content": { "names": "names", diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 27a22217..93976c75 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -203,13 +203,19 @@ def create_guiargs(parent): # Cycle through each page for mainpage in options: + subpage = None + _, v = next(iter(options[mainpage].items())) + if isinstance(v, str): + subpage = "" # Cycle through each subpage (in case of Item Randomizer) - for subpage in options[mainpage]: + for subpage in (options[mainpage] if subpage is None else [subpage]): # Cycle through each widget - for widget in options[mainpage][subpage]: + for widget in (options[mainpage][subpage] if subpage != "" else options[mainpage]): # Get the value and set it - arg = options[mainpage][subpage][widget] - setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get()) + 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()) # Get EnemizerCLI setting guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].widgets["enemizercli"].storageVar.get() @@ -226,7 +232,7 @@ def create_guiargs(parent): guiargs.customizer = customizer_value # Get if we're using the Custom Item Pool - guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get()) + guiargs.custom = bool(parent.pages["custom"].content.customWidgets["usecustompool"].storageVar.get()) # Get Seed ID guiargs.seed = None @@ -285,7 +291,7 @@ def create_guiargs(parent): guiargs.dropshuffle = 1 guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery - if guiargs.retro or guiargs.mode == 'retro': + 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': diff --git a/source/gui/custom/overview.py b/source/gui/custom/overview.py index c51c35be..b62bff71 100644 --- a/source/gui/custom/overview.py +++ b/source/gui/custom/overview.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y +from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y import source.gui.widgets as widgets import json import os @@ -11,10 +11,10 @@ def custom_page(top,parent): # Create uniform list columns def create_list_frame(parent, framename): - parent.frames[framename] = Frame(parent) - parent.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) - parent.frames[framename].thisRow = 0 - parent.frames[framename].thisCol = 0 + self.frames[framename] = Frame(parent) + self.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) + self.frames[framename].thisRow = 0 + self.frames[framename].thisCol = 0 # Create a vertical rule to help with splitting columns visually def create_vertical_rule(num=1): @@ -34,6 +34,8 @@ def custom_page(top,parent): # Custom Item Pool option sections self.frames = {} + self.frames["customHeader"] = Frame(self) + self.frames["customHeader"].pack(side=TOP, anchor=W) # Create 5 columns with 2 vertical rules in between each create_list_frame(self, "itemList1") create_vertical_rule(2) @@ -50,9 +52,14 @@ def custom_page(top,parent): with open(os.path.join("resources", "app", "gui", "custom", "overview", "widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) for framename,theseWidgets in myDict.items(): - dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) - for key in dictWidgets: - self.customWidgets[key] = dictWidgets[key] + if framename in self.frames: + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.customWidgets[key] = dictWidgets[key] + if framename == "customHeader": + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) + self.customWidgets[key].pack(packAttrs) # Load Custom Item Pool settings from settings file for key in CONST.CUSTOMITEMS: diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index 0130f8ad..6d358853 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -23,34 +23,41 @@ def loadcliargs(gui, args, settings=None): # Cycle through each page for mainpage in options: + subpage = None + _, v = next(iter(options[mainpage].items())) + if isinstance(v, str): + subpage = "" # Cycle through each subpage (in case of Item Randomizer) - for subpage in options[mainpage]: + for subpage in (options[mainpage] if subpage is None else [subpage]): # Cycle through each widget - for widget in options[mainpage][subpage]: - if widget in gui.pages[mainpage].pages[subpage].widgets: + for widget in (options[mainpage][subpage] if subpage != "" else options[mainpage]): + page = gui.pages[mainpage].pages[subpage] if subpage != "" else gui.pages[mainpage] + pagewidgets = page.content.customWidgets if mainpage == "custom" else page.content.startingWidgets if mainpage == "startinventory" else page.widgets + if widget in pagewidgets: thisType = "" # Get the value and set it - arg = options[mainpage][subpage][widget] + arg = options[mainpage][subpage][widget] if subpage != "" else options[mainpage][widget] if args[arg] == None: args[arg] = "" - label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) - if hasattr(gui.pages[mainpage].pages[subpage].widgets[widget],"type"): - thisType = gui.pages[mainpage].pages[subpage].widgets[widget].type + label_ref = mainpage + ('.' + subpage if subpage != "" else '') + '.' + widget + label = fish.translate("gui","gui", label_ref) + if hasattr(pagewidgets[widget],"type"): + thisType = pagewidgets[widget].type if thisType == "checkbox": - gui.pages[mainpage].pages[subpage].widgets[widget].checkbox.configure(text=label) + pagewidgets[widget].checkbox.configure(text=label) elif thisType == "selectbox": - theseOptions = gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options - gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label) + theseOptions = pagewidgets[widget].selectbox.options + pagewidgets[widget].label.configure(text=label) i = 0 for value in theseOptions["values"]: - gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + '.' + str(value)) + pagewidgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui", label_ref + '.' + str(value)) i += 1 for i in range(0, len(theseOptions["values"])): - gui.pages[mainpage].pages[subpage].widgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i]) - gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options = theseOptions + pagewidgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i]) + pagewidgets[widget].selectbox.options = theseOptions elif thisType == "spinbox": - gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label) - gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg]) + pagewidgets[widget].label.configure(text=label) + 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/dungeon.py b/source/gui/randomize/dungeon.py index 01985982..7400dbe4 100644 --- a/source/gui/randomize/dungeon.py +++ b/source/gui/randomize/dungeon.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT +from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT, TOP import source.gui.widgets as widgets import json import os @@ -16,8 +16,8 @@ def dungeon_page(parent): self.frames["keysanity"].pack(anchor=W) ## Dungeon Item Shuffle - mscbLabel = Label(self.frames["keysanity"], text="Shuffle: ") - mscbLabel.pack(side=LEFT) + mscbLabel = Label(self.frames["keysanity"], text="Dungeon Items: ") + mscbLabel.pack(side=TOP, anchor=W) # Load Dungeon Shuffle option widgets as defined by JSON file # Defns include frame name, widget type, widget options, widget placement attributes @@ -28,7 +28,9 @@ def dungeon_page(parent): dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["keysanity"]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - self.widgets[key].pack(side=LEFT) + packAttrs = {"side":LEFT} + packAttrs = widgets.add_padding_from_config(packAttrs, myDict[key]) + self.widgets[key].pack(packAttrs) # These get split left & right self.frames["widgets"] = Frame(self) @@ -39,6 +41,8 @@ def dungeon_page(parent): dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["widgets"]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - self.widgets[key].pack(anchor=W) + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, myDict[key]) + self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/randomize/entrando.py b/source/gui/randomize/entrando.py index bfc911b4..ba2bc365 100644 --- a/source/gui/randomize/entrando.py +++ b/source/gui/randomize/entrando.py @@ -26,9 +26,8 @@ def entrando_page(parent): dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - packAttrs = {"anchor":E} - if self.widgets[key].type == "checkbox" or key in ["openpyramid", "take_any"]: - packAttrs["anchor"] = W + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/randomize/item.py b/source/gui/randomize/item.py index 81c957ce..6898c75f 100644 --- a/source/gui/randomize/item.py +++ b/source/gui/randomize/item.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, E, W, LEFT, RIGHT, Label +from tkinter import ttk, font, Frame, E, W, NW, TOP, LEFT, RIGHT, Y, Label import source.gui.widgets as widgets import json import os @@ -17,13 +17,39 @@ def item_page(parent): self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) - various_options = Label(self.frames["checkboxes"], text="") + various_options = Label(self.frames["checkboxes"], text="Options: ") various_options.pack(side=LEFT) - self.frames["leftItemFrame"] = Frame(self) - self.frames["rightItemFrame"] = Frame(self) + self.frames["mainFrame"] = Frame(self) + self.frames["mainFrame"].pack(side=TOP, pady=(20,0)) + + self.frames["poolFrame"] = Frame(self) + self.frames["poolFrame"].pack(fill=Y) + + self.frames["leftItemFrame"] = Frame(self.frames["mainFrame"]) self.frames["leftItemFrame"].pack(side=LEFT) + self.frames["rightItemFrame"] = Frame(self.frames["mainFrame"]) self.frames["rightItemFrame"].pack(side=RIGHT) + + self.frames["leftPoolContainer"] = Frame(self.frames["poolFrame"]) + self.frames["leftPoolContainer"].pack(side=LEFT, padx=(0,20)) + + base_font = font.nametofont('TkTextFont').actual() + underline_font = f'"{base_font["family"]}" {base_font["size"]} underline' + various_options = Label(self.frames["leftPoolContainer"], text="Pool Expansions", font=underline_font) + various_options.pack(side=TOP, pady=(20,0)) + + self.frames["leftPoolHeader"] = Frame(self.frames["leftPoolContainer"]) + 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["rightPoolFrame"] = Frame(self.frames["poolFrame"]) + self.frames["rightPoolFrame"].pack(side=RIGHT) + + various_options = Label(self.frames["rightPoolFrame"], text="Pool Modifications", font=underline_font) + various_options.pack(side=TOP, pady=(20,0)) # Load Item Randomizer option widgets as defined by JSON file # Defns include frame name, widget type, widget options, widget placement attributes @@ -36,8 +62,15 @@ def item_page(parent): for key in dictWidgets: self.widgets[key] = dictWidgets[key] packAttrs = {"anchor":E} - if self.widgets[key].type == "checkbox": + if self.widgets[key].type == "checkbox" or framename == "leftPoolFrame": + packAttrs["anchor"] = W + if framename == "checkboxes": packAttrs["side"] = LEFT + packAttrs["padx"] = (10,0) + elif framename == "leftPoolHeader": + packAttrs["side"] = LEFT + packAttrs["padx"] = (0,20) + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/randomize/overworld.py b/source/gui/randomize/overworld.py index 190a750a..54120417 100644 --- a/source/gui/randomize/overworld.py +++ b/source/gui/randomize/overworld.py @@ -15,33 +15,24 @@ def overworld_page(parent): # Load Overworld Shuffle option widgets as defined by JSON file # Defns include frame name, widget type, widget options, widget placement attributes - self.frames["topOverworldFrame"] = Frame(self) self.frames["leftOverworldFrame"] = Frame(self) self.frames["rightOverworldFrame"] = Frame(self) - self.frames["topOverworldFrame"].pack(side=TOP, anchor=NW) self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW, fill=Y) self.frames["rightOverworldFrame"].pack(anchor=NW, fill=Y) - - shuffleLabel = Label(self.frames["topOverworldFrame"], text="Shuffle: ") - shuffleLabel.pack(side=LEFT) with open(os.path.join("resources","app","gui","randomize","overworld","widgets.json")) as overworldWidgets: myDict = json.load(overworldWidgets) for framename,theseWidgets in myDict.items(): + if not theseWidgets: + continue dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - packAttrs = {"anchor":E} - if key == "terrain": - packAttrs = {"anchor":W, "pady":(3,0)} - elif key == "keepsimilar": - packAttrs = {"anchor":W, "pady":(6,0)} - elif key == "overworldflute": - packAttrs["pady"] = (20,0) - elif key in ["mixed", "whirlpool"]: - packAttrs = {"anchor":W, "padx":(79,0)} - + packAttrs = {"anchor":W} + if self.widgets[key].type != "checkbox": + packAttrs["anchor"] = E + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/startinventory/overview.py b/source/gui/startinventory/overview.py index fce40e5f..97784521 100644 --- a/source/gui/startinventory/overview.py +++ b/source/gui/startinventory/overview.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y +from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y import source.gui.widgets as widgets import json import os @@ -11,10 +11,10 @@ def startinventory_page(top,parent): # Create uniform list columns def create_list_frame(parent, framename): - parent.frames[framename] = Frame(parent) - parent.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) - parent.frames[framename].thisRow = 0 - parent.frames[framename].thisCol = 0 + self.frames[framename] = Frame(parent) + self.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) + self.frames[framename].thisRow = 0 + self.frames[framename].thisCol = 0 # Create a vertical rule to help with splitting columns visually def create_vertical_rule(num=1): @@ -34,6 +34,8 @@ def startinventory_page(top,parent): # Starting Inventory option sections self.frames = {} + self.frames["startHeader"] = Frame(self) + self.frames["startHeader"].pack(side=TOP, anchor=W) # Create 5 columns with 2 vertical rules in between each create_list_frame(self,"itemList1") create_vertical_rule(2) @@ -55,9 +57,14 @@ def startinventory_page(top,parent): if key in myDict[thisList]: del myDict[thisList][key] for framename,theseWidgets in myDict.items(): - dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) - for key in dictWidgets: - self.startingWidgets[key] = dictWidgets[key] + if framename in self.frames: + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.startingWidgets[key] = dictWidgets[key] + if framename == "startHeader": + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) + self.startingWidgets[key].pack(packAttrs) # Load Custom Starting Inventory settings from settings file, ignoring ones to be excluded for key in CONST.CUSTOMITEMS: diff --git a/source/gui/widgets.py b/source/gui/widgets.py index aedb573d..0198a43a 100644 --- a/source/gui/widgets.py +++ b/source/gui/widgets.py @@ -1,4 +1,4 @@ -from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X +from tkinter import messagebox, Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X from source.classes.Empty import Empty # Override Spinbox to include mousewheel support for changing value @@ -16,7 +16,7 @@ class mySpinbox(Spinbox): self.invoke('buttonup') # Make a Checkbutton with a label -def make_checkbox(self, parent, label, storageVar, manager, managerAttrs): +def make_checkbox(self, parent, label, storageVar, manager, managerAttrs, config): self = Frame(parent) self.storageVar = storageVar if managerAttrs is not None and "default" in managerAttrs: @@ -25,7 +25,10 @@ def make_checkbox(self, parent, label, storageVar, manager, managerAttrs): elif managerAttrs["default"] == "false" or managerAttrs["default"] == False: self.storageVar.set(False) del managerAttrs["default"] - self.checkbox = Checkbutton(self, text=label, variable=self.storageVar) + options = {"text":label, "variable":self.storageVar} + if config and "command" in config: + options.update({"command":lambda m=config["command"]: widget_command(self, m)}) + self.checkbox = Checkbutton(self, options) if managerAttrs is not None: self.checkbox.pack(managerAttrs) else: @@ -43,7 +46,11 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt self.labelVar = StringVar() self.storageVar = storageVar - self.selectbox = OptionMenu(self, self.labelVar, *labels) + if config and "command" in config: + self.command = config["command"] + self.selectbox = OptionMenu(self, self.labelVar, *labels, command=lambda m: widget_command(self, self.command)) + else: + self.selectbox = OptionMenu(self, self.labelVar, *labels) self.selectbox.options = {} if isinstance(options,dict): @@ -96,7 +103,7 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt else: self.label.pack(side=LEFT) - self.selectbox.config(width=config['width'] if config and config['width'] else 20) + self.selectbox.config(width=config['width'] if config and 'width' in config else 20) idx = 0 default = self.selectbox.options["values"][idx] if managerAttrs is not None and "default" in managerAttrs: @@ -181,7 +188,7 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage if type == "checkbox": if thisStorageVar is None: thisStorageVar = IntVar() - widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs) + widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs, config) elif type == "selectbox": if thisStorageVar is None: thisStorageVar = StringVar() @@ -221,3 +228,51 @@ def make_widgets_from_dict(self, defns, parent): for key,defn in defns.items(): widgets[key] = make_widget_from_dict(self, defn, parent) return widgets + +# Add padding to widget +def add_padding_from_config(packAttrs, defn): + if "config" in defn: + config = defn["config"] + if 'padx' in config: + packAttrs["padx"] = config['padx'] + if 'pady' in config: + packAttrs["pady"] = config['pady'] + return packAttrs + +# Callback when a widget issues a command +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') + + 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') + + widget.storageVar.set('open') + 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"] + 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"] + temp_widget.storageVar.set(1) + text_output += f'\n {temp_widget.checkbox.cget("text")}' + + widget.storageVar.set(0) + messagebox.showinfo('', f'The following settings were changed:{text_output}') From d216cb337b3d2f71f1aedb4caf6a8c18b9717d9b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 30 Mar 2023 22:52:44 -0500 Subject: [PATCH 04/15] Adding missing bunny rule for Water D Leave --- Rules.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Rules.py b/Rules.py index 1cb91819..edaa928e 100644 --- a/Rules.py +++ b/Rules.py @@ -1214,6 +1214,7 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('Lake Hylia Northeast Water Drop', player), player) add_bunny_rule(world.get_entrance('Lake Hylia Central Water Drop', player), player) add_bunny_rule(world.get_entrance('Lake Hylia Island Water Drop', player), player) + add_bunny_rule(world.get_entrance('Lake Hylia Water D Leave', player), player) add_bunny_rule(world.get_entrance('Ice Cave SW', player), player) add_bunny_rule(world.get_entrance('Octoballoon Water Drop', player), player) add_bunny_rule(world.get_entrance('Octoballoon Waterfall Water Drop', player), player) From 3eeeda363b3531b5b87aac9692b7fefc43680f6b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 1 Apr 2023 07:59:13 -0500 Subject: [PATCH 05/15] Fixed various Flute logic issues and improved logic efficiency --- BaseClasses.py | 6 +----- ItemList.py | 17 +++++++++++++++++ OverworldShuffle.py | 2 +- Rom.py | 3 +-- Rules.py | 7 +++++++ 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 39a032fb..6ce354b1 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1299,11 +1299,7 @@ class CollectionState(object): def can_flute(self, player): if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player): return False # can't flute in rain state - if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)): - return True - lw = self.world.get_region('Kakariko Area', player) - return self.has('Ocarina (Activated)', player) or (self.has('Ocarina', player) and lw.can_reach(self) - and self.is_not_bunny(lw, player)) + return self.has('Ocarina (Activated)', player) def can_melt_things(self, player): return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.has_sword(player)) diff --git a/ItemList.py b/ItemList.py index 956b8153..3665c414 100644 --- a/ItemList.py +++ b/ItemList.py @@ -212,6 +212,20 @@ def generate_itempool(world, player): loc.locked = True loc.forced_item = loc.item + if not world.is_tile_swapped(0x18, player): + region = world.get_region('Kakariko Area',player) + + loc = Location(player, "Flute Activation", parent=region) + region.locations.append(loc) + world.dynamic_locations.append(loc) + + world.clear_location_cache() + + world.push_item(loc, ItemFactory('Ocarina (Activated)', player), False) + loc.event = True + loc.locked = True + loc.forced_item = loc.item + world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) @@ -1433,6 +1447,9 @@ def make_customizer_pool(world, player): place_item('Master Sword Pedestal', 'Triforce') guaranteed_items = alwaysitems + ['Magic Mirror', 'Moon Pearl'] + if world.is_tile_swapped(0x18, player) or world.flute_mode[player] == 'active': + guaranteed_items.remove('Ocarina') + guaranteed_items.append('Ocarina (Activated)') missing_items = [] if world.shopsanity[player]: guaranteed_items.extend(['Blue Potion', 'Green Potion', 'Red Potion']) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 4d137416..9a789b7a 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1092,7 +1092,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F region = base_world.get_region(region_name, player) for exit in region.exits: if exit.connected_region is not None: - if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], base_world.precollected_items)) and exit.spot_type == 'Flute': + if any(map(lambda i: i.name == 'Ocarina (Activated)', base_world.precollected_items)) and exit.spot_type == 'Flute': fluteregion = exit.connected_region for flutespot in fluteregion.exits: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: diff --git a/Rom.py b/Rom.py index cc784646..63544102 100644 --- a/Rom.py +++ b/Rom.py @@ -2308,10 +2308,9 @@ def write_strings(rom, world, player, team): # of how many exist. This supports many settings well. items_to_hint = RelevantItems.copy() flute_item = 'Ocarina' - if world.is_tile_swapped(0x18, player): + if world.is_tile_swapped(0x18, player) or world.flute_mode[player] == 'active': items_to_hint.remove(flute_item) flute_item = 'Ocarina (Activated)' - items_to_hint.append(flute_item) if world.owShuffle[player] != 'vanilla' or world.owMixed[player]: # Adding a guaranteed hint for the Flute in overworld shuffle. this_location = world.find_items_not_key_only(flute_item, player) diff --git a/Rules.py b/Rules.py index edaa928e..9e03ad3b 100644 --- a/Rules.py +++ b/Rules.py @@ -70,6 +70,13 @@ def set_rules(world, player): elif world.goal[player] == 'completionist': add_rule(world.get_location('Ganon', player), lambda state: state.everything(player)) + if not world.is_tile_swapped(0x18, player): + if not world.is_copied_world: + # Commented out below, this would be needed for rando implementations where Inverted requires flute activation in bunny territory + # kak_region = self.world.get_region('Kakariko Area', player) + # add_rule(world.get_location('Flute Activation', player), lambda state: state.has('Ocarina', player) and state.is_not_bunny(kak_region, player)) + add_rule(world.get_location('Flute Activation', player), lambda state: state.has('Ocarina', player)) + # if swamp and dam have not been moved we require mirror for swamp palace if not world.swamp_patch_required[player]: add_rule(world.get_entrance('Swamp Lobby Moat', player), lambda state: state.has_Mirror(player)) From 1005c7f8441ff480be28ad7ff5fb6f0e0bdbdbc1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 1 Apr 2023 08:16:27 -0500 Subject: [PATCH 06/15] Reorganized spoiler log based on UI reorg --- BaseClasses.py | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 6ce354b1..93691104 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2872,6 +2872,7 @@ class Spoiler(object): 'dungeon_counters': self.world.dungeon_counters, 'item_pool': self.world.difficulty, 'item_functionality': self.world.difficulty_adjustments, + 'beemizer': self.world.beemizer, 'gt_crystals': self.world.crystals_needed_for_gt, 'ganon_crystals': self.world.crystals_needed_for_ganon, 'open_pyramid': self.world.open_pyramid, @@ -3051,25 +3052,33 @@ class Spoiler(object): if self.world.players > 1: outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player))) outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player]) - outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player]) + outfile.write('\n') outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player]) - outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) + outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player]) outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player]) if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']: outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player]) outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player]) outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player])) outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player])) + outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) + outfile.write('\n') outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player]) outfile.write('Restricted Boss Items:'.ljust(line_width) + '%s\n' % self.metadata['restricted_boss_items'][player]) - outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player]) + outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player]) outfile.write('Flute Mode:'.ljust(line_width) + '%s\n' % self.metadata['flute_mode'][player]) outfile.write('Bow Mode:'.ljust(line_width) + '%s\n' % self.metadata['bow_mode'][player]) - outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player]) - outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player])) + outfile.write('Beemizer:'.ljust(line_width) + '%s\n' % self.metadata['beemizer'][player]) outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player])) - outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player])) + outfile.write('\n') + outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player])) + outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player])) + outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player]) + outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player])) + outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player])) + outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player]) + outfile.write('\n') outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player]) if self.metadata['ow_shuffle'][player] != 'vanilla': outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player])) @@ -3079,35 +3088,37 @@ class Spoiler(object): outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player])) outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player])) outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) - outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player])) + outfile.write('\n') outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player]) if self.metadata['shuffle'][player] != 'vanilla': outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player])) outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player])) outfile.write('Shuffle Tavern:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffletavern'][player])) + outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player]) if self.metadata['shuffle'][player] != 'vanilla' or self.metadata['ow_mixed'][player]: outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player]) - outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player]) + outfile.write('\n') + outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player])) + outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player])) + outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['keyshuffle'][player]) + outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player])) + outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player]) + outfile.write('\n') outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player]) if self.metadata['door_shuffle'][player] != 'vanilla': outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player]) outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player]) outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player]) - outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player]) outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player])) - outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) + outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player]) - outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player])) - outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player]) - outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player])) - outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player])) - outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player])) - outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['keyshuffle'][player]) - outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player])) + outfile.write('\n') outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player]) outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player]) outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player]) outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player]) + outfile.write('\n') + outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player])) outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player])) outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race'])) From 09ea7dd9a97093d3d04c8ae223f0ee9762a112e1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 1 Apr 2023 09:11:13 -0500 Subject: [PATCH 07/15] Fixed various Flute logic issues and improved logic efficiency --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 5b1a2384..8d2e5895 100644 --- a/Main.py +++ b/Main.py @@ -785,7 +785,7 @@ def create_playthrough(world): # get locations containing progress items prog_locations = [location for location in world.get_filled_locations() if location.item.advancement or world.goal[location.player] == 'completionist'] - optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile'] + optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile', 'Flute Activation'] optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs state_cache = [None] From b66b2b24b7437d41817abe19ded69d0f7a87f433 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 2 Apr 2023 06:47:11 -0500 Subject: [PATCH 08/15] Fixed dungeon revival fake world behavior in glitched --- Rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 63544102..f93abec6 100644 --- a/Rom.py +++ b/Rom.py @@ -1364,7 +1364,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180212, warningflags) # Warning flags # assorted fixes - rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world else 0x00) # remain in real dark world when dying in dark world dungeon before killing aga1 + rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world[player] else 0x00) # remain in real dark world when dying in dark world dungeon before killing aga1 rom.write_byte(0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence. if world.is_atgt_swapped(player): rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted From 034598c85ecf074663afbd06edf592eca4845915 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 2 Apr 2023 06:49:20 -0500 Subject: [PATCH 09/15] Added logic for mirror offset to Pyramid Area --- OverworldGlitchRules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index d135a9b6..dd306ac4 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -512,5 +512,5 @@ mirror_clips = [ mirror_offsets = [ (['DM Offset Mirror', 'DDM Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Courtyard Northeast', 'Pyramid Crack'], ['Pyramid Area', 'Hyrule Castle Courtyard']), - (['DM To HC Ledge Offset Mirror', 'DDM To HC Ledge Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Ledge', None], ['Pyramid Area', None]) + (['DM To HC Ledge Offset Mirror', 'DDM To Pyramid Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Ledge', 'Pyramid Area'], ['Pyramid Area', 'Hyrule Castle Area']) ] \ No newline at end of file From 7f38b31d55a606488d4d7cb4f490cc3298c25883 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 2 Apr 2023 07:42:27 -0500 Subject: [PATCH 10/15] Fixed Old Man Death causing fake world in glitched --- Rom.py | 6 +++++- data/base2current.bps | Bin 105965 -> 105975 bytes 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index f93abec6..fb0cf141 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '0a3d1d4bbec659013be5ed876c2658bd' +RANDOMIZERBASEHASH = '0dd61456cc38a4792d99d23047163660' class JsonRom(object): @@ -935,6 +935,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): old_man_house = world.get_region('Old Man House', player) if should_be_bunny(old_man_house, world.mode[player]): rom.write_bytes(0x13fff4, [0xe4, 0x00]) + + old_man_cave = world.get_entrance('Old Man Cave Exit (East)', player) + if old_man_cave.connected_region.type == RegionType.DarkWorld: + rom.write_byte(0x13fff6, 0x40) # patch doors if world.doorShuffle[player] not in ['vanilla', 'basic']: diff --git a/data/base2current.bps b/data/base2current.bps index d492f90b61065409a0fa2cfbfa8766eb30c23ef7..617b02c3657f10d281afa5b62b17ac7efbe2b7d0 100644 GIT binary patch delta 8809 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 delta 8881 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 From 2ed1edbc2006ecc3b921850fe167279eb250d7a9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 2 Apr 2023 08:28:29 -0500 Subject: [PATCH 11/15] Fixed issue with some Cliff Drop connections in Mixed + OWG --- OverworldShuffle.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 9a789b7a..44e7ab02 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1451,11 +1451,6 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'), ('Bomber Corner Pier', 'Bomber Corner Area'), # OWG In-Bounds Connections - ('Stone Bridge EC Cliff Water Drop', 'Stone Bridge Water'), #fake flipper - ('Tree Line WC Cliff Water Drop', 'Tree Line Water'), #fake flipper, - - ('Hammer Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), #fake flipper - ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water'), #fake flipper ('Ice Lake Northeast Pier Hop', 'Ice Lake Northeast Bank'), ('Ice Lake Moat Bomb Jump', 'Ice Lake Moat') ] @@ -1470,7 +1465,7 @@ default_whirlpool_connections = [ default_flute_connections = [ 0x0b, 0x16, 0x18, 0x2c, 0x2f, 0x38, 0x3b, 0x3f ] - + ow_connections = { 0x03: ([ ('West Death Mountain Teleporter', 'West Dark Death Mountain (Bottom)') @@ -1555,12 +1550,20 @@ ow_connections = { ('Stone Bridge East Ledge Drop', 'Stone Bridge North Area'), # OWG ('Hammer Bridge North Ledge Drop', 'Hammer Bridge North Area'), # OWG ('Stone Bridge Cliff Ledge Drop', 'Stone Bridge South Area'), # OWG - ('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area') # OWG + ('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG + ('Stone Bridge EC Cliff Water Drop', 'Stone Bridge Water'), # fake flipper + ('Hammer Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), # fake flipper + ('Tree Line WC Cliff Water Drop', 'Tree Line Water'), # fake flipper + ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water') # fake flipper ], [ ('Stone Bridge East Ledge Drop', 'Hammer Bridge North Area'), # OWG ('Hammer Bridge North Ledge Drop', 'Stone Bridge North Area'), # OWG ('Stone Bridge Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG - ('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge South Area') # OWG + ('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge South Area'), # OWG + ('Stone Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), # fake flipper + ('Hammer Bridge EC Cliff Water Drop', 'Stone Bridge Water'), # fake flipper + ('Tree Line WC Cliff Water Drop', 'Dark Tree Line Water'), # fake flipper + ('Dark Tree Line WC Cliff Water Drop', 'Tree Line Water') # fake flipper ]), 0x2e: ([ ('Tree Line Ledge Drop', 'Tree Line Area'), # OWG From bbf254d6f64d281ecc9238adc26c71dd82a8a501 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 2 Apr 2023 09:03:17 -0500 Subject: [PATCH 12/15] Adding missing region to Tile Regions list --- OWEdges.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OWEdges.py b/OWEdges.py index cf397f18..f5982ee2 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1182,6 +1182,7 @@ OWTileRegions = bidict({ 'East Dark Death Mountain (Top)': 0x45, 'East Dark Death Mountain (Bottom Left)': 0x45, 'East Dark Death Mountain (Bottom)': 0x45, + 'East Dark Death Mountain (Bushes)': 0x45, 'Dark Death Mountain Ledge': 0x45, 'Dark Death Mountain Isolated Ledge': 0x45, 'Dark Death Mountain Floating Island': 0x45, From 9cef1ff32ecba701b7ccec40a8cc6483625df31a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 3 Apr 2023 08:21:34 -0500 Subject: [PATCH 13/15] Temporary fix for avoiding VRAM issues in non-layout OWR modes --- Rom.py | 2 +- asm/owrando.asm | 36 +++++++++++++++++++----------------- data/base2current.bps | Bin 105975 -> 105987 bytes 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Rom.py b/Rom.py index fb0cf141..c164e845 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '0dd61456cc38a4792d99d23047163660' +RANDOMIZERBASEHASH = '92c16c60f26218c9aec838ce204c0b1e' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index a308a9fc..c4f512b0 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -860,31 +860,33 @@ OWNewDestination: { tya : sta $4202 : lda #16 : sta $4203 ;wait 8 cycles rep #$20 : txa : nop : !add $4216 : tax ;a = offset to dest record - lda.w $0006,x : sta $06 ;set coord lda.w $0008,x : sta $04 ;save dest OW slot/ID - lda.w $000a,x : sta $84 ;VRAM - + ldy $20 : lda $418 : dec #2 : bpl + : ldy $22 : + sty $06 + ;;22 e0 e2 61c 61e - X ;;20 e6 e8 618 61a - Y ;keep current position if within incoming gap lda.w $0000,x : and #$01ff : pha : lda.w $0002,x : and #$01ff : pha - ldy $20 : lda $418 : dec #2 : bpl + : ldy $22 - + tya : and #$01ff : cmp 3,s : !blt .adjustMainAxis - dec : cmp 1,s : !bge .adjustMainAxis - inc : pha : lda $06 : and #$fe00 : !add 1,s : sta $06 : pla + LDA.l OWMode : AND.w #$0007 : BEQ .noLayoutShuffle ;temporary fix until VRAM issues are solved + lda.w $0006,x : sta $06 ;set coord + lda.w $000a,x : sta $84 ;VRAM + tya : and #$01ff : cmp 3,s : !blt .adjustMainAxis + dec : cmp 1,s : !bge .adjustMainAxis + inc : pha : lda $06 : and #$fe00 : !add 1,s : sta $06 : pla - ; adjust and set other VRAM addresses - lda.w $0006,x : pha : lda $06 : !sub 1,s - jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : pha ; number of tiles - lda $418 : dec #2 : bmi + - pla : pea $0000 : bra ++ ;pla : asl #7 : pha : bra ++ ; y-axis shifts VRAM by increments of 0x80 (disabled for now) - + pla : asl : pha ; x-axis shifts VRAM by increments of 0x02 - ++ lda $84 : !add 1,s : sta $84 : pla : pla + ; adjust and set other VRAM addresses + lda.w $0006,x : pha : lda $06 : !sub 1,s + jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : pha ; number of tiles + lda $418 : dec #2 : bmi + + pla : pea $0000 : bra ++ ;pla : asl #7 : pha : bra ++ ; y-axis shifts VRAM by increments of 0x80 (disabled for now) + + pla : asl : pha ; x-axis shifts VRAM by increments of 0x02 + ++ lda $84 : !add 1,s : sta $84 : pla : pla - .adjustMainAxis - LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 ; vram - LDA $84 : SEC : SBC #$0010 : AND #$003E : LSR : STA $86 + .adjustMainAxis + LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 ; vram + LDA $84 : SEC : SBC #$0010 : AND #$003E : LSR : STA $86 + .noLayoutShuffle LDA.w $000F,X : AND.w #$00FF : STA.w $06FC ; position to walk to after transition (if non-zero) LDY.w #$0000 diff --git a/data/base2current.bps b/data/base2current.bps index 617b02c3657f10d281afa5b62b17ac7efbe2b7d0..6efe11ee423d08175e56237349ff9b06c40a44eb 100644 GIT binary patch delta 435 zcmV;k0Zjh)y9R^22C!uT1o%Y_#R1Oc3via!Az1qFd1oRpWVKLJ1iHJ9u^0c-)qw^={|9|1iLfWjb(>KJVnLaMz8 z0EGmkAgve#Iyw*nq#}d{y#N3y{{cw70+UjLlVxd@QGf>k@Nm5b0EGs<3IK(Km#aYm zC?Hg7m4Qf;H-ODB259sE0DuE%3P`1d7-Ip2glK4+T@jb?K>Tori!Hng^Gp+ARCunLjf!j#gt&12rM0dfeN2gp3%?) zOrMvWLjffLl9$3m0VD=QmVj&^M3?MC0XH2)mZe?E1keW}DwqeHMFXogLkobH0Dy@X z&;px5mq0`THxx>ArmbXMzyZ)ZrCrGc@HLaklTDY4L;)EpLJkRl!TP0L$rsQEsRNAz z2V`Af!XTG>iXngk@EqbGrHUy(@D!zrAYPZyL;)WHR}Gi(L;)NDP?rux0YL$Emrz9k d76~87Ms5y(pKW8eZ$$xsSO_8Z;054YAG+hym?Z!J delta 458 zcmV;*0X6=EyaxBX2C!uT1gJ_^wX_#R1Obtkia!Az1dEJ$fS0U40YCvqm+e0RYyrBrT0j9G0W$%B!XS$37;P3ps=WpP zg$BI{0EGm-3IK(Ky#N3y{{cw70+URFMS!Frtr!G4IuHV+BA2#70Vo~nfk>0_%`gUN z^Z)>W1853JrGyw`0fmHUXqycZm;ONkM*(J+MnVCN0(}gZ)j|O)0WOyTLjgPjbyb%? zLjfTSx|F7hr3HnGh6NxHmuy1;EE2kuV4Dam9e{xfpH!aF&;v}Am#RYnB>{(*&_e+v z20501Y#=$8`a=OX9XXbzUC9K{2O=t%2b@I%s~|%QfR_M(i5JiUn=6-2L;*KhG<2q| zWL>}k&^x7F$pr8yfPH}arCrGv z&uL6LoDF*`Z9O59QiYY(v6s3wFQ1kL;gYu|Rk0!zq7ZVrH-17x>*MFD_V2yXp*z6tzCs2B{T A2mk;8 From 9406f408df9ff769cf795767867413d95d94077d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 3 Apr 2023 08:25:14 -0500 Subject: [PATCH 14/15] Fix for HC dark rooms not getting correct lamp logic in partitioned DR --- Rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rules.py b/Rules.py index 9e03ad3b..99058d17 100644 --- a/Rules.py +++ b/Rules.py @@ -1376,9 +1376,9 @@ def add_conditional_lamps(world, player): is_dark = False if not world.sewer_light_cone[player]: is_dark = True - elif world.doorShuffle[player] != 'crossed' and not info['sewer']: + elif world.doorShuffle[player] not in ['partitioned', 'crossed'] and not info['sewer']: is_dark = True - elif world.doorShuffle[player] == 'crossed': + elif world.doorShuffle[player] in ['partitioned', 'crossed']: sewer_builder = world.dungeon_layouts[player]['Hyrule Castle'] is_dark = region not in sewer_builder.master_sector.region_set() if is_dark: From 46ed496669c3eec71901d2f7d4a63bb3339c7910 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 3 Apr 2023 08:33:28 -0500 Subject: [PATCH 15/15] Version bump 0.3.0.5 --- CHANGELOG.md | 6 ++++++ OverworldShuffle.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42ae2fb2..35763257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.0.5 +- Major reorganization to GUI Options +- Corrected various fake world behavior in glitched modes +- Fixed various issues with glitched mode logic +- Various minor logic fixes + ## 0.3.0.4 - \~Merged in DR v1.2.0.14~ - Fixed issue with enemy drops on OW enemies diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 44e7ab02..a91db720 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.0.4' +version_number = '0.3.0.5' # branch indicator is intentionally different across branches version_branch = '-u'