From 329950eadce3f5d9027699fd6fd1c9f77c1394ec Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 7 Aug 2023 13:19:03 -0600 Subject: [PATCH 01/12] CI Update ubuntu --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5845e58b..68aa8738 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: # os & python versions strategy: matrix: - os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ] + os-name: [ ubuntu-latest, ubuntu-20.04, macOS-latest, windows-latest ] python-version: [ 3.9 ] # needs: [ install-test ] steps: From 4dce2762f15a2de7afb50b19bbd49c5a50edda80 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 7 Aug 2023 13:24:36 -0600 Subject: [PATCH 02/12] CI Update ubuntu --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68aa8738..ab6c8163 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,7 +110,7 @@ jobs: strategy: matrix: # install/release on not bionic - os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ] + os-name: [ ubuntu-latest, ubuntu-20.04, macOS-latest, windows-latest ] python-version: [ 3.9 ] needs: [ install-build ] From 14c0448fe7be5093038ac37753f0247525247bea Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 13 Nov 2023 15:43:27 -0700 Subject: [PATCH 03/12] fix(key logic): typo fix(bunny logic): multiple paths considered --- KeyDoorShuffle.py | 2 +- Main.py | 2 +- RELEASENOTES.md | 4 + Rules.py | 215 ++++++++++++++++++++++++---------------------- 4 files changed, 118 insertions(+), 105 deletions(-) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index b600f814..1d90017a 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -1729,7 +1729,7 @@ def imp_locations_factory(world, player): imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] if world.mode[player] == 'standard': imp_locations.append('Zelda Pickup') - imp_locations.append('Zelda Dropoff') + imp_locations.append('Zelda Drop Off') return imp_locations diff --git a/Main.py b/Main.py index 4f49cc48..48414afd 100644 --- a/Main.py +++ b/Main.py @@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.tools.BPS import create_bps_from_data -__version__ = '1.1.6-dev' +__version__ = '1.1.7-dev' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b3734893..a4ff5718 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -181,6 +181,10 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o # Bug Fixes and Notes +* 1.1.7 + * Fixed logic issues: + * Self-locking key not allowed in Sanctuary in standard (typo fixed) + * More advanced bunny-walking logic in dungeons (multiple paths considred) * 1.1.6 * Minor issue with dungeon counter hud interfering with timer * 1.1.5 diff --git a/Rules.py b/Rules.py index 397b4054..4e788287 100644 --- a/Rules.py +++ b/Rules.py @@ -98,16 +98,20 @@ def mirrorless_path_to_castle_courtyard(world, player): else: queue.append((entrance.connected_region, new_path)) + def set_rule(spot, rule): spot.access_rule = rule + def set_defeat_dungeon_boss_rule(location): # Lambda required to defer evaluation of dungeon.boss since it will change later if boos shuffle is used set_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state)) + def set_always_allow(spot, rule): spot.always_allow = rule + def add_rule(spot, rule, combine='and'): old_rule = spot.access_rule if combine == 'or': @@ -128,22 +132,26 @@ def forbid_item(location, item, player): old_rule = location.item_rule location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i) + def add_item_rule(location, rule): old_rule = location.item_rule location.item_rule = lambda item: rule(item) and old_rule(item) + def item_in_locations(state, item, player, locations): for location in locations: if item_name(state, location[0], location[1]) == (item, player): return True return False + def item_name(state, location, player): location = state.world.get_location(location, player) if location.item is None: return None return (location.item.name, location.item.player) + def global_rules(world, player): # ganon can only carry triforce add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) @@ -161,7 +169,7 @@ def global_rules(world, player): set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) - set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith + set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player)) set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player)) set_rule(world.get_location('Sick Kid', player), lambda state: state.has_bottle(player)) @@ -220,7 +228,6 @@ def global_rules(world, player): set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) - set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player)) @@ -478,7 +485,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Swamp Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier', player), player)) set_rule(world.get_entrance('Swamp Crystal Switch Inner to Crystal', player), lambda state: state.can_hit_crystal(player)) - set_rule(world.get_entrance('Swamp Crystal Switch Outer to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_beam_sword(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('Swamp Crystal Switch Outer', player), player))) # It is the length of the sword, not the beam itself that allows this + set_rule(world.get_entrance('Swamp Crystal Switch Outer to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_beam_sword(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('Swamp Crystal Switch Outer', player), player))) # It is the length of the sword, not the beam itself that allows this set_rule(world.get_entrance('Swamp Crystal Switch Outer to Inner Bypass', player), lambda state: state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) set_rule(world.get_entrance('Swamp Crystal Switch Inner to Outer Bypass', player), lambda state: state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) @@ -521,8 +528,8 @@ def global_rules(world, player): set_rule(world.get_entrance('Mire Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Left', player), player)) set_rule(world.get_entrance('Mire Conveyor to Crystal', player), lambda state: state.can_hit_crystal(player)) - set_rule(world.get_entrance('Mire Tall Dark and Roomy to Ranged Crystal', player), lambda state: True) # Can always throw pots - set_rule(world.get_entrance('Mire Fishbone Blue Barrier Bypass', player), lambda state: False) # (state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) and state.can_tastate.can_use_bombs(player) // Easy to do but obscure. Should it be in logic? + set_rule(world.get_entrance('Mire Tall Dark and Roomy to Ranged Crystal', player), lambda state: True) # Can always throw pots + set_rule(world.get_entrance('Mire Fishbone Blue Barrier Bypass', player), lambda state: False) # (state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) and state.can_tastate.can_use_bombs(player) // Easy to do but obscure. Should it be in logic? set_rule(world.get_location('Turtle Rock - Chain Chomps', player), lambda state: state.can_reach('TR Chain Chomps Top', 'Region', player) and state.can_hit_crystal_through_barrier(player)) set_rule(world.get_entrance('TR Chain Chomps Top to Bottom Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Chain Chomps Top', player), player)) @@ -544,14 +551,14 @@ def global_rules(world, player): set_rule(world.get_entrance('TR Pokey 2 Top to Crystal', player), lambda state: state.can_hit_crystal(player)) set_rule(world.get_entrance('TR Crystaroller Top to Crystal', player), lambda state: state.can_hit_crystal(player)) set_rule(world.get_entrance('TR Crystal Maze Start to Crystal', player), lambda state: state.can_hit_crystal(player)) - set_rule(world.get_entrance('TR Chain Chomps Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Chain Chomps Bottom', player), player))) # or state.has_beam_sword(player) - set_rule(world.get_entrance('TR Pokey 2 Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('TR Pokey 2 Bottom', player), player))) # or state.has_beam_sword(player) - set_rule(world.get_entrance('TR Crystaroller Bottom to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Bottom', player), player))) # or state.has_beam_sword(player) - set_rule(world.get_entrance('TR Crystaroller Middle to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Chain Chomps Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Chain Chomps Bottom', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Pokey 2 Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('TR Pokey 2 Bottom', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Crystaroller Bottom to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Bottom', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Crystaroller Middle to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player))) # or state.has_beam_sword(player) set_rule(world.get_entrance('TR Crystaroller Middle to Bottom Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Blue Boomerang', player)) - set_rule(world.get_entrance('TR Crystal Maze End to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player)) # or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) // These work by clipping the rang through the two stone blocks, which works sometimes. - set_rule(world.get_entrance('TR Crystal Maze Interior to End Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # Beam sword does NOT work - set_rule(world.get_entrance('TR Crystal Maze Interior to Start Bypass', player), lambda state: True) # Can always grab a pot from the interior and walk it to the start region and throw it there + set_rule(world.get_entrance('TR Crystal Maze End to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player)) # or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) // These work by clipping the rang through the two stone blocks, which works sometimes. + set_rule(world.get_entrance('TR Crystal Maze Interior to End Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # Beam sword does NOT work + set_rule(world.get_entrance('TR Crystal Maze Interior to Start Bypass', player), lambda state: True) # Can always grab a pot from the interior and walk it to the start region and throw it there set_rule(world.get_entrance('GT Hookshot Platform Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Platform', player), player)) set_rule(world.get_entrance('GT Hookshot Entry Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Entry', player), player)) @@ -655,7 +662,7 @@ def bomb_rules(world, player): ('GT Petting Zoo SE', False), # Dont make anyone do this room with bombs and/or pots. ('GT DMs Room SW', False) # Four red stalfos ] - for killdoor,bombable in easy_kill_rooms: + for killdoor, bombable in easy_kill_rooms: if bombable: add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player))) else: @@ -663,47 +670,47 @@ def bomb_rules(world, player): add_rule(world.get_entrance('Ice Stalfos Hint SE', player), lambda state: state.can_use_bombs(player)) # Need bombs for big stalfos knights add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_use_bombs(player) and state.has('Magic Powder'), player) - enemy_kill_drops = [ # Location, bool-bombable + enemy_kill_drops = [ # Location, bool-bombable ('Hyrule Castle - Map Guard Key Drop', True), ('Hyrule Castle - Boomerang Guard Key Drop', True), ('Hyrule Castle - Key Rat Key Drop', True), -# ('Hyrule Castle - Big Key Drop', True), # Pots are available -# ('Eastern Palace - Dark Eyegore Key Drop', True), # Pots are available + # ('Hyrule Castle - Big Key Drop', True), # Pots are available + # ('Eastern Palace - Dark Eyegore Key Drop', True), # Pots are available ('Castle Tower - Dark Archer Key Drop', True), -# ('Castle Tower - Circle of Pots Key Drop', True), # Pots are available -# ('Skull Woods - Spike Corner Key Drop', True), # Pots are available + # ('Castle Tower - Circle of Pots Key Drop', True), # Pots are available + # ('Skull Woods - Spike Corner Key Drop', True), # Pots are available ('Ice Palace - Jelly Key Drop', True), ('Ice Palace - Conveyor Key Drop', True), ('Misery Mire - Conveyor Crystal Key Drop', True), ('Turtle Rock - Pokey 1 Key Drop', True), ('Turtle Rock - Pokey 2 Key Drop', True), -# ('Ganons Tower - Mini Helmasaur Key Drop', True) # Pots are available - ('Castle Tower - Room 03', True), # Two spring soliders - ('Ice Palace - Compass Chest', True) # Pengators + # ('Ganons Tower - Mini Helmasaur Key Drop', True) # Pots are available + ('Castle Tower - Room 03', True), # Two spring soliders + ('Ice Palace - Compass Chest', True) # Pengators ] - for location,bombable in enemy_kill_drops: + for location, bombable in enemy_kill_drops: if bombable: - add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.can_kill_most_things(player)) + add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.can_kill_most_things(player)) else: add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player)) - add_rule(world.get_location('Attic Cracked Floor', player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_location('Attic Cracked Floor', player), lambda state: state.can_use_bombs(player)) bombable_floors = ['PoD Pit Room Bomb Hole', 'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole', 'GT Bob\'s Room Hole'] for entrance in bombable_floors: - add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) if world.doorShuffle[player] == 'vanilla': - add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. - add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player)) # Is this the same as above? + add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. + add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player)) # Is this the same as above? dungeon_bonkable = ['Sewers Rat Path WS', 'Sewers Rat Path WN', 'PoD Warp Hint SE', 'PoD Jelly Hall NW', 'PoD Jelly Hall NE', 'PoD Mimics 1 SW', 'Thieves Ambush E', 'Thieves Rail Ledge W', 'TR Dash Room NW', 'TR Crystaroller SW', 'TR Dash Room ES', - 'GT Four Torches NW','GT Fairy Abyss SW' + 'GT Four Torches NW', 'GT Fairy Abyss SW' ] dungeon_bombable = ['PoD Map Balcony WS', 'PoD Arena Ledge ES', 'PoD Dark Maze E', 'PoD Big Chest Balcony W', - 'Swamp Pot Row WN','Swamp Map Ledge EN', 'Swamp Hammer Switch WN', 'Swamp Hub Dead Ledge EN', 'Swamp Waterway N', 'Swamp I S', + 'Swamp Pot Row WN', 'Swamp Map Ledge EN', 'Swamp Hammer Switch WN', 'Swamp Hub Dead Ledge EN', 'Swamp Waterway N', 'Swamp I S', 'Skull Pot Circle WN', 'Skull Pull Switch EN', 'Skull Big Key EN', 'Skull Lone Pot WN', 'Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW', 'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole', @@ -711,9 +718,9 @@ def bomb_rules(world, player): 'GT Warp Maze (Rails) WS', 'GT Bob\'s Room Hole', 'GT Randomizer Room ES', 'GT Bomb Conveyor SW', 'GT Crystal Circles NW', 'GT Cannonball Bridge SE', 'GT Refill NE' ] for entrance in dungeon_bonkable: - add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) + add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) for entrance in dungeon_bombable: - add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) else: doors_to_bomb_check = [x for x in world.doors if x.player == player and x.type in [DoorType.Normal, DoorType.Interior]] for door in doors_to_bomb_check: @@ -768,7 +775,6 @@ def pot_rules(world, player): add_rule(l, lambda state: state.can_hit_crystal(player)) - def default_rules(world, player): # overworld requirements set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player)) @@ -789,9 +795,9 @@ def default_rules(world, player): set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.has('Ocarina', player)) set_rule(world.get_entrance('Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.has('Ocarina', player) and state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer - set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer - set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) # bunny cannot lift bushes + set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) # bunny cannot lift bushes set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player)) set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player)) @@ -799,7 +805,7 @@ def default_rules(world, player): set_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Flippers', player)) # to leave via fake flippers, you'd need pearl and have waterwalk or swimming state, so just require flippers set_rule(world.get_entrance('Zora Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player)) # will get automatic moon pearl requirement + set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player)) # will get automatic moon pearl requirement set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player)) set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Desert Ledge Return Rocks', player), lambda state: state.can_lift_rocks(player)) # should we decide to place something that is not a dungeon end up there at some point @@ -824,22 +830,22 @@ def default_rules(world, player): set_rule(world.get_entrance('South Dark World Bridge', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) set_rule(world.get_entrance('Bonk Fairy (Dark)', player), lambda state: state.has_Pearl(player) and state.has_Boots(player)) set_rule(world.get_entrance('West Dark World Gap', player), lambda state: state.has_Pearl(player) and state.has('Hookshot', player)) - set_rule(world.get_entrance('Palace of Darkness', player), lambda state: state.has_Pearl(player)) # kiki needs pearl + set_rule(world.get_entrance('Palace of Darkness', player), lambda state: state.has_Pearl(player)) # kiki needs pearl set_rule(world.get_entrance('Hyrule Castle Ledge Mirror Spot', player), lambda state: state.has_Mirror(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_entrance('Dark Lake Hylia Drop (East)', player), lambda state: (state.has_Pearl(player) and state.has('Flippers', player) or state.has_Mirror(player))) # Overworld Bunny Revival set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) set_rule(world.get_entrance('Dark Lake Hylia Drop (South)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # ToDo any fake flipper set up? - set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: state.has_Pearl(player)) # bomb required + set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: state.has_Pearl(player)) # bomb required set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) # Fake Flippers set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.has_Pearl(player) and state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Hype Cave', player), lambda state: state.has_Pearl(player)) # bomb required - set_rule(world.get_entrance('Brewery', player), lambda state: state.has_Pearl(player)) # bomb required - set_rule(world.get_entrance('Thieves Town', player), lambda state: state.has_Pearl(player)) # bunny cannot pull - set_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), lambda state: state.has_Pearl(player)) # bunny cannot lift bush - set_rule(world.get_entrance('Skull Woods Second Section Hole', player), lambda state: state.has_Pearl(player)) # bunny cannot lift bush + set_rule(world.get_entrance('Hype Cave', player), lambda state: state.has_Pearl(player)) # bomb required + set_rule(world.get_entrance('Brewery', player), lambda state: state.has_Pearl(player)) # bomb required + set_rule(world.get_entrance('Thieves Town', player), lambda state: state.has_Pearl(player)) # bunny cannot pull + set_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), lambda state: state.has_Pearl(player)) # bunny cannot lift bush + set_rule(world.get_entrance('Skull Woods Second Section Hole', player), lambda state: state.has_Pearl(player)) # bunny cannot lift bush set_rule(world.get_entrance('Maze Race Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Cave 45 Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bombos Tablet Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -859,7 +865,7 @@ def default_rules(world, player): set_rule(world.get_entrance('Bumper Cave Bottom to Top', player), lambda state: state.has('Cape', player)) set_rule(world.get_entrance('Bumper Cave Top To Bottom', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) - set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player) and state.has_Pearl(player)) # bunny cannot use fire rod + set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player) and state.has_Pearl(player)) # bunny cannot use fire rod set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) set_rule(world.get_entrance('Desert Ledge (Northeast) Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -920,8 +926,8 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.can_flute(player) and state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('East Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer - set_rule(world.get_entrance('South Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('East Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('South Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('West Dark World Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player) and state.has_Pearl(player)) @@ -931,13 +937,13 @@ def inverted_rules(world, player): set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player) and (state.has_Pearl(player) or state.has('Beat Agahnim 1', player)) or (state.can_reach('Light World', 'Region', player) and state.has_Mirror(player))) # Need LW access using Mirror or Portal - set_rule(world.get_location('Mushroom', player), lambda state: state.has_Pearl(player)) # need pearl to pick up bushes + set_rule(world.get_location('Mushroom', player), lambda state: state.has_Pearl(player)) # need pearl to pick up bushes set_rule(world.get_entrance('Bush Covered Lawn Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bush Covered Lawn Inner Bushes', player), lambda state: state.has_Pearl(player)) set_rule(world.get_entrance('Bush Covered Lawn Outer Bushes', player), lambda state: state.has_Pearl(player)) set_rule(world.get_entrance('Bomb Hut Inner Bushes', player), lambda state: state.has_Pearl(player)) set_rule(world.get_entrance('Bomb Hut Outer Bushes', player), lambda state: state.has_Pearl(player)) - set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: state.has_Pearl(player)) # need bomb + set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: state.has_Pearl(player)) # need bomb set_rule(world.get_entrance('North Fairy Cave Drop', player), lambda state: state.has_Pearl(player)) set_rule(world.get_entrance('Lost Woods Hideout Drop', player), lambda state: state.has_Pearl(player)) set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and (state.can_reach('Potion Shop Area', 'Region', player))) # new inverted region, need pearl for bushes or access to potion shop door/waterfall fairy @@ -996,7 +1002,7 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Dark Death Mountain Ledge Mirror Spot (West)', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Laser Bridge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) # new inverted spots set_rule(world.get_entrance('Post Aga Teleporter', player), lambda state: state.has('Beat Agahnim 1', player)) @@ -1098,17 +1104,20 @@ def forbid_bomb_jump_requirements(world, player): add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) + # Light cones in standard depend on which world we actually are in, not which one the location would normally be # We add Lamp requirements only to those locations which lie in the dark world (or everything if open DW_Entrances = ['Bumper Cave (Bottom)', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)', 'Hookshot Cave', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Thieves Town', 'Skull Woods Final Section', 'Ice Palace', 'Misery Mire', 'Palace of Darkness', 'Swamp Palace', 'Turtle Rock', 'Dark Death Mountain Ledge (West)'] + def check_is_dark_world(region): for entrance in region.entrances: if entrance.name in DW_Entrances: return True return False + def add_conditional_lamps(world, player): def add_conditional_lamp(spot, region, spottype='Location'): if spottype == 'Location': @@ -1180,6 +1189,7 @@ def add_conditional_lamps(world, player): add_conditional_lamp('Old Man House Front to Back', 'Old Man House', 'Entrance') + def open_rules(world, player): # softlock protection as you can reach the sewers small key door with a guard drop key set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player)) @@ -1187,7 +1197,6 @@ def open_rules(world, player): def swordless_rules(world, player): - set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) @@ -1199,46 +1208,47 @@ def swordless_rules(world, player): if world.mode[player] != 'inverted': set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle - set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player) and state.has_Mirror(player)) else: # only need ddm access for aga tower in inverted - set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) std_kill_rooms = { - 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard - 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard - 'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], # Can use pots - 'Desert Compass Room': ['Desert Compass NW'], # Three popos - 'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'], # Four popos - 'Hera Beetles': ['Hera Beetles WS'], # Three blue beetles and only two pots, and bombs don't work. - 'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'], # Two ball and chain + 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard + 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard + 'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], # Can use pots + 'Desert Compass Room': ['Desert Compass NW'], # Three popos + 'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'], # Four popos + 'Hera Beetles': ['Hera Beetles WS'], # Three blue beetles and only two pots, and bombs don't work. + 'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'], # Two ball and chain 'Tower Dark Archers': ['Tower Dark Archers WN'], # Not a kill room 'Tower Red Spears': ['Tower Red Spears WN'], # Two spear soldiers - 'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'], # Two usain bolts - 'Tower Circle of Pots': ['Tower Circle of Pots NW'], # Two spear soldiers. Plenty of pots. + 'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'], # Two usain bolts + 'Tower Circle of Pots': ['Tower Circle of Pots NW'], # Two spear soldiers. Plenty of pots. 'PoD Turtle Party': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], # Lots of turtles. - 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. - 'Ice Stalfos Hint': ['Ice Stalfos Hint SE'], # Need bombs for big stalfos knights - 'Ice Pengator Trap': ['Ice Pengator Trap NE'], # Five pengators. Bomb-doable? - 'Mire 2': ['Mire 2 NE'], # Wizzrobes. Bombs dont work. - 'Mire Cross': ['Mire Cross ES'], # 4 Sluggulas. Bombs don't work - 'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'], # Two pokeys - 'GT Petting Zoo': ['GT Petting Zoo SE'], # Dont make anyone do this room with bombs. - 'GT DMs Room': ['GT DMs Room SW'], # Four red stalfos - 'GT Gauntlet 1': ['GT Gauntlet 1 WN'], # Stalfos/zazaks - 'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'], # Red stalfos - 'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'], # Blue zazaks - 'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'], # Red zazaks - 'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'], # Stalfos and zazak - 'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'], # Wizzrobes. Bombs don't work - 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work + 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. + 'Ice Stalfos Hint': ['Ice Stalfos Hint SE'], # Need bombs for big stalfos knights + 'Ice Pengator Trap': ['Ice Pengator Trap NE'], # Five pengators. Bomb-doable? + 'Mire 2': ['Mire 2 NE'], # Wizzrobes. Bombs dont work. + 'Mire Cross': ['Mire Cross ES'], # 4 Sluggulas. Bombs don't work + 'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'], # Two pokeys + 'GT Petting Zoo': ['GT Petting Zoo SE'], # Dont make anyone do this room with bombs. + 'GT DMs Room': ['GT DMs Room SW'], # Four red stalfos + 'GT Gauntlet 1': ['GT Gauntlet 1 WN'], # Stalfos/zazaks + 'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'], # Red stalfos + 'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'], # Blue zazaks + 'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'], # Red zazaks + 'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'], # Stalfos and zazak + 'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'], # Wizzrobes. Bombs don't work + 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work } # all trap rooms? + def add_connection(parent_name, target_name, entrance_name, world, player): parent = world.get_region(parent_name, player) target = world.get_region(target_name, player) @@ -1276,7 +1286,7 @@ def standard_rules(world, player): return loc.item and loc.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)'] def standard_escape_rule(state): - return state.can_kill_most_things(player) or bomb_escape_rule() + return state.can_kill_most_things(player) or bomb_escape_rule() add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule) @@ -1300,6 +1310,7 @@ def standard_rules(world, player): def check_rule_list(state, r_list): return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:]) + rule_list, debug_path = find_rules_for_zelda_delivery(world, player) set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) @@ -1473,7 +1484,7 @@ def set_big_bomb_rules(world, player): set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Dark World', 'Region', player) and state.can_reach('Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player)) - #crossing peg bridge starting from the southern dark world + # crossing peg bridge starting from the southern dark world def cross_peg_bridge(state): return state.has('Hammer', player) and state.has_Pearl(player) @@ -1495,28 +1506,28 @@ def set_big_bomb_rules(world, player): # G = Glove if bombshop_entrance.name in Normal_LW_entrances: - #1. basic routes - #2. Can reach Eastern dark world some other way, mirror, get bomb, return to mirror spot, walk to pyramid: Needs mirror + # 1. basic routes + # 2. Can reach Eastern dark world some other way, mirror, get bomb, return to mirror spot, walk to pyramid: Needs mirror # -> M or BR add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: basic_routes(state) or state.has_Mirror(player)) elif bombshop_entrance.name in LW_walkable_entrances: - #1. Mirror then basic routes + # 1. Mirror then basic routes # -> M and BR add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and basic_routes(state)) elif bombshop_entrance.name in Northern_DW_entrances: - #1. Mirror and basic routes - #2. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl + # 1. Mirror and basic routes + # 2. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl # -> (Mitts and CPB) or (M and BR) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (state.has_Mirror(player) and basic_routes(state))) elif bombshop_entrance.name == 'Bumper Cave (Bottom)': - #1. Mirror and Lift rock and basic_routes - #2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) - #3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl + # 1. Mirror and Lift rock and basic_routes + # 2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) + # 3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl # -> (Mitts and CPB) or (((G or Flute) and M) and BR)) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (((state.can_lift_rocks(player) or state.has('Ocarina', player)) and state.has_Mirror(player)) and basic_routes(state))) elif bombshop_entrance.name in Southern_DW_entrances: - #1. Mirror and enter via gate: Need mirror and Aga1 - #2. cross peg bridge: Need hammer and moon pearl + # 1. Mirror and enter via gate: Need mirror and Aga1 + # 2. cross peg bridge: Need hammer and moon pearl # -> CPB or (M and A) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player))) elif bombshop_entrance.name in Isolated_DW_entrances: @@ -1780,7 +1791,6 @@ def set_inverted_big_bomb_rules(world, player): def set_bunny_rules(world, player, inverted): - # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', @@ -1819,13 +1829,14 @@ def set_bunny_rules(world, player, inverted): return region.is_light_world else: return region.is_dark_world + def is_link(region): if inverted: return region.is_dark_world else: return region.is_light_world - def get_rule_to_add(region, location = None, connecting_entrance = None): + def get_rule_to_add(region, location=None, connecting_entrance=None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. if world.logic[player] == 'owglitches': @@ -1848,16 +1859,15 @@ def set_bunny_rules(world, player, inverted): # for each such entrance a new option is added that consist of: # a) being able to reach it, and # b) being able to access all entrances from there to `region` - seen = {region} - queue = deque([(region, [])]) + queue = deque([(region, [], {region})]) while queue: - (current, path) = queue.popleft() + (current, path, seen) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: continue new_path = path + [entrance.access_rule] - seen.add(new_region) + new_seen = seen.union({new_region}) if not is_link(new_region): if world.logic[player] == 'owglitches': if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: @@ -1894,7 +1904,7 @@ def set_bunny_rules(world, player, inverted): else: continue if is_bunny(new_region): - queue.append((new_region, new_path)) + queue.append((new_region, new_path, new_seen)) else: # we have reached pure light world, so we have a new possible option possible_options.append(path_to_access_rule(new_path, entrance)) @@ -1941,7 +1951,6 @@ drop_dungeon_entrances = { "Skull Back Drop" } - bunny_revivable_entrances = { "Sewers Pull Switch", "TR Dash Room", "Swamp Boss", "Hera Boss", "Tower Agahnim 1", "Ice Lobby", "Sewers Rat Path", "PoD Falling Bridge", @@ -1989,7 +1998,7 @@ bunny_impassible_doors = { 'Eastern Darkness S', 'Eastern Darkness NE', 'Eastern Darkness Up Stairs', 'Eastern Attic Start WS', 'Eastern Single Eyegore NE', 'Eastern Duo Eyegores NE', 'Desert Main Lobby Left Path', 'Desert Main Lobby Right Path', 'Desert Left Alcove Path', 'Desert Right Alcove Path', 'Desert Compass NW', - 'Desert West Lobby NW', 'Desert Back Lobby NW', 'Desert Four Statues NW', 'Desert Four Statues ES', + 'Desert West Lobby NW', 'Desert Back Lobby NW', 'Desert Four Statues NW', 'Desert Four Statues ES', 'Desert Beamos Hall WS', 'Desert Beamos Hall NE', 'Desert Wall Slide NW', 'Hera Lobby to Front Barrier - Blue', 'Hera Front to Lobby Barrier - Blue', 'Hera Front to Down Stairs Barrier - Blue', 'Hera Down Stairs to Front Barrier - Blue', 'Hera Tile Room EN', 'Hera Tridorm SE', 'Hera Beetles WS', @@ -2001,14 +2010,14 @@ bunny_impassible_doors = { 'PoD Arena Landing Bonk Path', 'PoD Sexy Statue NW', 'PoD Map Balcony Drop Down', 'PoD Mimics 1 NW', 'PoD Falling Bridge Path N', 'PoD Falling Bridge Path S', 'PoD Mimics 2 NW', 'PoD Bow Statue Down Ladder', 'PoD Dark Pegs Landing to Right', - 'PoD Dark Pegs Left to Middle Barrier - Blue', 'PoD Dark Pegs Left to Ranged Crystal', + 'PoD Dark Pegs Left to Middle Barrier - Blue', 'PoD Dark Pegs Left to Ranged Crystal', 'PoD Turtle Party ES', 'PoD Turtle Party NW', 'PoD Callback Warp', 'Swamp Lobby Moat', 'Swamp Entrance Moat', 'Swamp Trench 1 Approach Swim Depart', 'Swamp Trench 1 Approach Key', 'Swamp Trench 1 Key Approach', 'Swamp Trench 1 Key Ledge Depart', 'Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Departure Key', 'Swamp Hub Hook Path', 'Swamp Shortcut Blue Barrier', 'Swamp Trench 2 Pots Blue Barrier', 'Swamp Trench 2 Pots Wet', 'Swamp Trench 2 Departure Wet', 'Swamp West Ledge Hook Path', 'Swamp Barrier Ledge Hook Path', 'Swamp Attic Left Pit', 'Swamp Attic Right Pit', 'Swamp Push Statue NW', 'Swamp Push Statue NE', - 'Swamp Drain Right Switch', 'Swamp Waterway NE', 'Swamp Waterway N', 'Swamp Waterway NW', + 'Swamp Drain Right Switch', 'Swamp Waterway NE', 'Swamp Waterway N', 'Swamp Waterway NW', 'Skull Pot Circle WN', 'Skull Pot Circle Star Path', 'Skull Pull Switch S', 'Skull Big Chest N', 'Skull Big Chest Hookpath', 'Skull 2 East Lobby NW', 'Skull Back Drop Star Path', 'Skull 2 West Lobby NW', 'Skull 3 Lobby EN', 'Skull Star Pits SW', 'Skull Star Pits ES', 'Skull Torch Room WN', 'Skull Vines NW', @@ -2043,7 +2052,7 @@ bunny_impassible_doors = { 'GT Double Switch Exit to Blue Barrier', 'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Ice Armos NE', 'GT Ice Armos WS', 'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS', 'GT Mimics 2 NE', 'GT Hidden Spikes EN', 'GT Cannonball Bridge SE', 'GT Gauntlet 1 WN', 'GT Gauntlet 2 EN', - 'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', + 'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', 'GT Gauntlet 5 NW', 'GT Gauntlet 5 WS', 'GT Lanmolas 2 ES', 'GT Lanmolas 2 NW', 'GT Wizzrobes 1 SW', 'GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE', 'GT Torch Cross ES', 'GT Falling Torches NE', 'GT Moldorm Gap', 'GT Validation Block Path' @@ -2091,7 +2100,7 @@ def eval_small_key_door_main(state, door_name, dungeon, player): door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) elif ruleType == KeyRuleType.AllowSmall: if (door_rule.small_location.item and door_rule.small_location.item.name == key_logic.small_key_name - and door_rule.small_location.item.player == player): + and door_rule.small_location.item.player == player): return True # always okay if allow small is on elif isinstance(ruleType, tuple): lock, lock_item = ruleType @@ -2126,7 +2135,7 @@ def create_key_rule(small_key_name, player, keys): def create_key_rule_allow_small(small_key_name, player, keys, location): loc = location.name - return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) + return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1)) def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs): @@ -2137,7 +2146,7 @@ def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_ def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs): loc = location.name chest_names = [x.name for x in bk_locs] - return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys)) + return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys)) def create_advanced_key_rule(key_logic, player, rule): From dd942865e5d4798f80582a631acaf805f4f02b7f Mon Sep 17 00:00:00 2001 From: Christopher Heuer Date: Thu, 1 Feb 2024 11:41:03 -0700 Subject: [PATCH 04/12] Update main & glitched tourney winners --- Text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Text.py b/Text.py index 27d27f1d..af22c84d 100644 --- a/Text.py +++ b/Text.py @@ -1782,7 +1782,7 @@ class TextTable(object): text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.") text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.") text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.") - text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2021~~~\nDaaanty\n\n ~~~2019~~~\nJet082\n\n ~~~2018~~~\nAndy\n\n ~~~2017~~~\nA: ajneb174\nS: ajneb174") + text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2023~~~\nGanonsGoneWild\n\n\n ~~~2022~~~\nObscure\n\n\n ~~~2021~~~\nDaaanty\n\n ~~~2019~~~\nJet082\n\n ~~~2018~~~\nAndy\n\n ~~~2017~~~\nA: ajneb174\nS: ajneb174") text['caught_a_bee'] = CompressedTextMapper.convert("Caught a Bee\n ≥ Keep\n Release\n{CHOICE}") text['caught_a_fairy'] = CompressedTextMapper.convert("Caught Fairy!\n ≥ Keep\n Release\n{CHOICE}") text['no_empty_bottles'] = CompressedTextMapper.convert("Whoa, bucko!\nNo empty bottles.") @@ -2010,7 +2010,7 @@ class TextTable(object): text['ganon_fall_in_alt'] = CompressedTextMapper.convert("You think you are ready to face me?\n\nI will not die unless you complete your goals. Dingus!") text['ganon_phase_3_alt'] = CompressedTextMapper.convert("Got wax in your ears? I cannot die!") # 190 - text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n~~~HMG 2021~~~\nKrithel\n\n~~~OWG 2019~~~\nGlan\n\n~~~OWG 2018~~~\nChristosOwen\nthe numpty") + text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n~~~HMG 2023~~~\ntam\n\n~~~No Logic 2022~~~\nChexhuman\n\n~~~HMG 2021~~~\nKrithel\n\n~~~OWG 2019~~~\nGlan\n\n~~~OWG 2018~~~\nChristosOwen\nthe numpty") text['fish_money'] = CompressedTextMapper.convert("It's a secret to everyone.") text['sign_ganons_tower'] = CompressedTextMapper.convert("You need all 7 crystals to enter.") text['sign_ganon'] = CompressedTextMapper.convert("You need all 7 crystals to beat Ganon.") From 6c1f6730865fee19985f826f63b0f02f84ffeba9 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sun, 17 Mar 2024 10:26:25 +0100 Subject: [PATCH 05/12] fix: Make pendants consistent between in-game, spoiler and memory --- Items.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Items.py b/Items.py index 73c9d196..8a55f2f1 100644 --- a/Items.py +++ b/Items.py @@ -61,8 +61,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Progressive Glove': (True, False, None, 0x61, 150, 'A way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'), 'Silver Arrows': (True, False, None, 0x58, 100, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the silver arrows'), 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x37, 0x08], 999, None, None, None, None, None, None, None), - 'Red Pendant': (True, False, 'Crystal', [0x02, 0x32, 0x60, 0x00, 0x69, 0x38, 0x09], 999, None, None, None, None, None, None, None), - 'Blue Pendant': (True, False, 'Crystal', [0x01, 0x34, 0x60, 0x00, 0x69, 0x39, 0x0a], 999, None, None, None, None, None, None, None), + 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x39, 0x09], 999, None, None, None, None, None, None, None), + 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x38, 0x0a], 999, None, None, None, None, None, None, None), 'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'), 'Power Star': (True, False, None, 0x6B, 100, 'A small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'), 'Triforce Piece': (True, False, None, 0x6C, 100, 'A small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce piece'), From b55727f0bf56d13493aacc16a55a4c9bcd3c2cde Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Mar 2024 09:51:29 -0600 Subject: [PATCH 06/12] chore: updated tournament winners --- Main.py | 2 +- RELEASENOTES.md | 2 ++ Text.py | 20 +++++++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Main.py b/Main.py index 48414afd..4006bb08 100644 --- a/Main.py +++ b/Main.py @@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.tools.BPS import create_bps_from_data -__version__ = '1.1.7-dev' +__version__ = '1.1.8-dev' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a4ff5718..0a62d5b9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -181,6 +181,8 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o # Bug Fixes and Notes +* 1.1.8 + * Updated tournament winners * 1.1.7 * Fixed logic issues: * Self-locking key not allowed in Sanctuary in standard (typo fixed) diff --git a/Text.py b/Text.py index af22c84d..4e84898d 100644 --- a/Text.py +++ b/Text.py @@ -1772,7 +1772,10 @@ class TextTable(object): text['telepathic_tile_misery_mire'] = CompressedTextMapper.convert("{NOBORDER}\nLighting 4 torches will open your way forward!") text['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^") text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.") - text['telepathic_tile_under_ganon'] = CompressedTextMapper.convert("Doors Async League winners\n{HARP}\n ~~~2022~~~\nAndy\n\n ~~~2021~~~\nprdwong") + text['telepathic_tile_under_ganon'] = CompressedTextMapper.convert("Doors Async League winners\n{HARP}\n" + " ~~~2023~~~\nEriror\n\n" + " ~~~2022~~~\nAndy\n\n" + " ~~~2021~~~\nprdwong") text['telepathic_tile_palace_of_darkness'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a funny looking Enemizer") # C0 text['telepathic_tile_desert_bonk_torch_room'] = CompressedTextMapper.convert("{NOBORDER}\nThings can be knocked down, if you fancy yourself a dashing dude.") @@ -1782,7 +1785,13 @@ class TextTable(object): text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.") text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.") text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.") - text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2023~~~\nGanonsGoneWild\n\n\n ~~~2022~~~\nObscure\n\n\n ~~~2021~~~\nDaaanty\n\n ~~~2019~~~\nJet082\n\n ~~~2018~~~\nAndy\n\n ~~~2017~~~\nA: ajneb174\nS: ajneb174") + text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n" + " ~~~2023~~~\nnGanonsGoneWild\n\n" + " ~~~2022~~~\nObscure\n\n" + " ~~~2021~~~\nDaaanty\n\n" + " ~~~2019~~~\nJet082\n\n" + " ~~~2018~~~\nAndy\n\n" + " ~~~2017~~~\nA: ajneb174\nS: ajneb174") text['caught_a_bee'] = CompressedTextMapper.convert("Caught a Bee\n ≥ Keep\n Release\n{CHOICE}") text['caught_a_fairy'] = CompressedTextMapper.convert("Caught Fairy!\n ≥ Keep\n Release\n{CHOICE}") text['no_empty_bottles'] = CompressedTextMapper.convert("Whoa, bucko!\nNo empty bottles.") @@ -2010,7 +2019,12 @@ class TextTable(object): text['ganon_fall_in_alt'] = CompressedTextMapper.convert("You think you are ready to face me?\n\nI will not die unless you complete your goals. Dingus!") text['ganon_phase_3_alt'] = CompressedTextMapper.convert("Got wax in your ears? I cannot die!") # 190 - text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n~~~HMG 2023~~~\ntam\n\n~~~No Logic 2022~~~\nChexhuman\n\n~~~HMG 2021~~~\nKrithel\n\n~~~OWG 2019~~~\nGlan\n\n~~~OWG 2018~~~\nChristosOwen\nthe numpty") + text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n" + "~~~HMG 2023~~~\ntam\n\n" + "~~~No Logic 2022~~~\nChexhuman\n\n" + "~~~HMG 2021~~~\nKrithel\n\n" + "~~~OWG 2019~~~\nGlan\n\n" + "~~~OWG 2018~~~\nChristosOwen\nthe numpty") text['fish_money'] = CompressedTextMapper.convert("It's a secret to everyone.") text['sign_ganons_tower'] = CompressedTextMapper.convert("You need all 7 crystals to enter.") text['sign_ganon'] = CompressedTextMapper.convert("You need all 7 crystals to beat Ganon.") From 2f0e27b8e2b06520a19fd5cf5d69429bcf4ef09e Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Mar 2024 13:15:58 -0600 Subject: [PATCH 07/12] fix: dungeon indicator changes from rom --- RELEASENOTES.md | 6 +++++- Rom.py | 5 ++--- data/base2current.bps | Bin 117491 -> 117467 bytes 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index db0682ed..7b843e09 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -142,7 +142,11 @@ These are now independent of retro mode and have three options: None, Random, an # Patch Notes * 1.4.1.9u - * todo + * Enemy Drop Underworld: Changed enemy drop indicator to not require compass + * Experimental: Moved dark world bunny spawns out of experimental. (It is now always on) + * Fix: Red/Blue pendants were swapped for autotracking. (Thanks Muffins!) + * Fix: Red square sometimes wasn't blinking + * Updated tournament winner * 1.4.1.8u * HUD: New dungeon indicators based on common abbreviations * OWG+HMG: EG is allowed to be armed diff --git a/Rom.py b/Rom.py index fba37983..59470684 100644 --- a/Rom.py +++ b/Rom.py @@ -42,7 +42,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '67b9ede4928dac94a650e9e9396ef44a' +RANDOMIZERBASEHASH = 'deda3f2e279062c296181393dba6c51c' class JsonRom(object): @@ -569,8 +569,7 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_bytes(0xfb1fc, [0xc8, 0x9d, 0x69, 0xb4, 0xac, 0x5d]) if world.standardize_palettes[player] == 'original': dr_flags |= DROptions.OriginalPalettes - if world.experimental[player]: - dr_flags |= DROptions.DarkWorld_Spawns + dr_flags |= DROptions.DarkWorld_Spawns # no longer experimental if world.logic[player] not in ['owglitches', 'hybridglitches', 'nologic']: dr_flags |= DROptions.Fix_EG if world.door_type_mode[player] in ['big', 'all', 'chaos']: diff --git a/data/base2current.bps b/data/base2current.bps index 2c405d81b1fbf77c1277b1196a8966a11e127452..ea0dde9614bb5a511efad354d0fee752e1f510bf 100644 GIT binary patch delta 1459 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 From ce0c86bcf361f477cc270ee6f3b53c1c68238b35 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Mar 2024 13:29:24 -0600 Subject: [PATCH 08/12] fix: terrorpins in hellway can clip out --- RELEASENOTES.md | 3 ++- source/enemizer/enemy_deny.yaml | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7b843e09..303430eb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -146,7 +146,8 @@ These are now independent of retro mode and have three options: None, Random, an * Experimental: Moved dark world bunny spawns out of experimental. (It is now always on) * Fix: Red/Blue pendants were swapped for autotracking. (Thanks Muffins!) * Fix: Red square sometimes wasn't blinking - * Updated tournament winner + * Updated tournament winners + * Enemizer: Enemy bans * 1.4.1.8u * HUD: New dungeon indicators based on common abbreviations * OWG+HMG: EG is allowed to be armed diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml index b3d94127..455d59af 100644 --- a/source/enemizer/enemy_deny.yaml +++ b/source/enemizer/enemy_deny.yaml @@ -680,11 +680,11 @@ UwEnemyDrop: - [0x00a5, 4, ["GreenMimic", "RedMimic"]] - [0x00a5, 5, ["GreenMimic", "RedMimic"]] - [0x00a5, 6, ["GreenMimic", "RedMimic"]] - - [0x00bb, 1, ["GreenMimic", "RedMimic"]] - - [0x00bb, 4, ["GreenMimic", "RedMimic"]] - - [0x00bb, 5, ["GreenMimic", "RedMimic"]] - - [0x00bb, 6, ["GreenMimic", "RedMimic"]] - - [0x00bb, 7, ["GreenMimic", "RedMimic"]] + - [0x00bb, 1, ["GreenMimic", "RedMimic", "Terrorpin"]] + - [0x00bb, 4, ["GreenMimic", "RedMimic", "Terrorpin"]] + - [0x00bb, 5, ["GreenMimic", "RedMimic", "Terrorpin"]] + - [0x00bb, 6, ["GreenMimic", "RedMimic", "Terrorpin"]] + - [0x00bb, 7, ["GreenMimic", "RedMimic", "Terrorpin"]] - [0x00bb, 8, ["GreenMimic", "RedMimic"]] - [0x00bb, 9, ["GreenMimic", "RedMimic"]] - [0x00bb, 10, ["GreenMimic", "RedMimic"]] From d8199742db4e19d682a6e029246c40bd6088d834 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 12 Apr 2024 16:24:11 -0600 Subject: [PATCH 09/12] chore(rom): code cleanup fix(rom): chest turns for MW items --- Main.py | 2 +- RELEASENOTES.md | 4 ++++ Rom.py | 2 +- data/base2current.bps | Bin 117467 -> 117405 bytes 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Main.py b/Main.py index f4bedf43..2ebc4611 100644 --- a/Main.py +++ b/Main.py @@ -38,7 +38,7 @@ from source.enemizer.DamageTables import DamageTable from source.enemizer.Enemizer import randomize_enemies from source.rom.DataTables import init_data_tables -version_number = '1.4.1.9' +version_number = '1.4.1.10' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 303430eb..3b65289d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -141,6 +141,10 @@ These are now independent of retro mode and have three options: None, Random, an # Patch Notes +* 1.4.1.10u + * ? + * Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody) + * Rom: Code prettification and fixing byte designations by Codemann * 1.4.1.9u * Enemy Drop Underworld: Changed enemy drop indicator to not require compass * Experimental: Moved dark world bunny spawns out of experimental. (It is now always on) diff --git a/Rom.py b/Rom.py index 59470684..d5106162 100644 --- a/Rom.py +++ b/Rom.py @@ -42,7 +42,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'deda3f2e279062c296181393dba6c51c' +RANDOMIZERBASEHASH = '9eadc8aa7df986d90bbb3779e700aa77' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index ea0dde9614bb5a511efad354d0fee752e1f510bf..63c3f0d2aa0f167229ab815a4b53899bc13cc818 100644 GIT binary patch delta 15874 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 NT>I=)$wbGG{|`{fi=Y4i delta 15472 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 From ebd86e80d79675a526854edd04f795b925568ff8 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 12 Apr 2024 17:32:35 -0600 Subject: [PATCH 10/12] fix(logic): exception for vanilla mire key door layout, crystal switches can be reached with two keys, just can't tell which one was chosen --- BaseClasses.py | 4 +++- DoorShuffle.py | 23 +++++++++++++++++++++-- RELEASENOTES.md | 2 +- Rules.py | 24 ++++++++++++++++++++---- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ab4f4158..64b33852 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -695,7 +695,7 @@ class CollectionState(object): @staticmethod def valid_crystal(door, new_crystal_state): return (not door.crystal or door.crystal == CrystalBarrier.Either or new_crystal_state == CrystalBarrier.Either - or new_crystal_state == door.crystal) + or new_crystal_state == door.crystal or door.alternative_crystal_rule) def check_key_doors_in_dungeons(self, rrp, player): for dungeon_name, checklist in self.dungeons_to_check[player].items(): @@ -1828,6 +1828,7 @@ class Door(object): self.bigKey = False # There's a big key door on this side self.ugly = False # Indicates that it can't be seen from the front (e.g. back of a big key door) self.crystal = CrystalBarrier.Null # How your crystal state changes if you use this door + self.alternative_crystal_rule = False self.req_event = None # if a dungeon event is required for this door - swamp palace mostly self.controller = None self.dependents = [] @@ -3209,3 +3210,4 @@ class KeyRuleType(FastEnum): WorstCase = 0 AllowSmall = 1 Lock = 2 + CrystalAlternative = 3 diff --git a/DoorShuffle.py b/DoorShuffle.py index c961dee7..e6fd8baa 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -20,7 +20,7 @@ from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, sim from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations from DungeonGenerator import valid_region_to_explore from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock -from KeyDoorShuffle import validate_bk_layout +from KeyDoorShuffle import validate_bk_layout, DoorRules from Utils import ncr, kth_combination @@ -263,7 +263,7 @@ def vanilla_key_logic(world, player): log_key_logic(builder.name, key_layout.key_logic) # special adjustments for vanilla if world.keyshuffle[player] != 'universal': - if world.mode[player] != 'standard' and world.dropshuffle[player] == 'none' : + if world.mode[player] != 'standard' and world.dropshuffle[player] == 'none': # adjust hc doors def adjust_hc_door(door_rule): if door_rule.new_rules[KeyRuleType.WorstCase] == 3: @@ -279,9 +279,28 @@ def vanilla_key_logic(world, player): if pod_front.new_rules[KeyRuleType.WorstCase] == 6: pod_front.new_rules[KeyRuleType.WorstCase] = 1 pod_front.small_key_num = 1 + # adjust mire key logic - this currently cannot be done dynamically + create_alternative_door_rules('Mire Hub Upper Blue Barrier', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Hub Lower Blue Barrier', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Hub Right Blue Barrier', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Hub Top Blue Barrier', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Hub Switch Blue Barrier N', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Hub Switch Blue Barrier S', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Map Spot Blue Barrier', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Map Spike Side Blue Barrier', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Crystal Dead End Left Barrier', 2, 'Misery Mire', world, player) + create_alternative_door_rules('Mire Crystal Dead End Right Barrier', 2, 'Misery Mire', world, player) # gt logic? I'm unsure it needs adjusting +def create_alternative_door_rules(door, amount, dungeon, world, player): + rules = DoorRules(0, True) + world.key_logic[player][dungeon].door_rules[door] = rules + rules.new_rules[KeyRuleType.CrystalAlternative] = amount + world.get_door(door, player).alternative_crystal_rule = True + + + def validate_vanilla_reservation(dungeon, world, player): return validate_key_layout(world.key_layout[player][dungeon.name], world, player) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3b65289d..795bad41 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -142,7 +142,7 @@ These are now independent of retro mode and have three options: None, Random, an # Patch Notes * 1.4.1.10u - * ? + * Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access is only locked by 2 keys thanks to that being the minimun in Mire to reach one of two crystal switches. * Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody) * Rom: Code prettification and fixing byte designations by Codemann * 1.4.1.9u diff --git a/Rules.py b/Rules.py index b2eb91f1..ce6eb08a 100644 --- a/Rules.py +++ b/Rules.py @@ -2332,10 +2332,13 @@ def add_key_logic_rules(world, player): 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) - add_rule(door_entrance, eval_func(door_name, d_name, player)) - if door_entrance.door.dependents: - for dep in door_entrance.door.dependents: - add_rule(dep.entrance, eval_func(door_name, d_name, player)) + if not door_entrance.door.smallKey and door_entrance.door.crystal == CrystalBarrier.Blue: + add_rule(door_entrance, eval_alternative_crystal(door_name, d_name, player), 'or') + else: + add_rule(door_entrance, eval_func(door_name, d_name, player)) + if door_entrance.door.dependents: + for dep in door_entrance.door.dependents: + add_rule(dep.entrance, eval_func(door_name, d_name, player)) for location in d_logic.bk_restricted: if not location.forced_item: forbid_item(location, d_logic.bk_name, player) @@ -2427,6 +2430,15 @@ def eval_small_key_door_strict_main(state, door_name, dungeon, player): return state.has_sm_key_strict(key_layout.key_logic.small_key_name, player, number) +def eval_alternative_crystal_main(state, door_name, dungeon, player): + key_logic = state.world.key_logic[player][dungeon] + door_rule = key_logic.door_rules[door_name] + for ruleType, number in door_rule.new_rules.items(): + if ruleType == KeyRuleType.CrystalAlternative: + return state.has_sm_key(key_logic.small_key_name, player, number) + return False + + def eval_small_key_door(door_name, dungeon, player): return lambda state: eval_small_key_door_main(state, door_name, dungeon, player) @@ -2439,6 +2451,10 @@ def eval_small_key_door_strict(door_name, dungeon, player): return lambda state: eval_small_key_door_strict_main(state, door_name, dungeon, player) +def eval_alternative_crystal(door_name, dungeon, player): + return lambda state: eval_alternative_crystal_main(state, door_name, dungeon, player) + + def allow_big_key_in_big_chest(bk_name, player): return lambda state, item: item.name == bk_name and item.player == player From 2cf2383c16ed764dfd7e091c3911b7f978bfd76f Mon Sep 17 00:00:00 2001 From: S Date: Sat, 13 Apr 2024 10:44:18 -0400 Subject: [PATCH 11/12] allow BPS to be encoded into the jsonout --- Main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Main.py b/Main.py index 2ebc4611..8e49e3f0 100644 --- a/Main.py +++ b/Main.py @@ -7,6 +7,7 @@ import RaceRandom as random import string import time import zlib +import base64 from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance, Settings from Bosses import place_bosses @@ -369,6 +370,10 @@ def main(args, seed=None, fish=None): if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches + if args.bps: + localRom = LocalRom.fromJsonRom(rom, args.rom) + patch = create_bps_from_data(LocalRom(args.rom, patch=False).buffer, localRom.buffer) + jsonout[f'bps_t{team}_p{player}'] = base64.b64encode(patch.binary_ba).decode() else: outfilepname = f'_T{team+1}' if world.teams > 1 else '' if world.players > 1: From 22a9f76c77f4c0c159781a25a2a670db690793c9 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 15 Apr 2024 12:39:33 -0600 Subject: [PATCH 12/12] fix(enemizer): various enemy bans --- RELEASENOTES.md | 6 ++++-- source/enemizer/enemy_deny.yaml | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 795bad41..d37a2a0a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -142,9 +142,11 @@ These are now independent of retro mode and have three options: None, Random, an # Patch Notes * 1.4.1.10u - * Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access is only locked by 2 keys thanks to that being the minimun in Mire to reach one of two crystal switches. - * Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody) + * Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access are only locked by 2 keys thanks to that being the minimum in Mire to reach one of two crystal switches. + * Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody) + * Enemizer: Enemy bans * Rom: Code prettification and fixing byte designations by Codemann + * Support added for BPS patches via jsonout setting (Thanks Veetorp!) * 1.4.1.9u * Enemy Drop Underworld: Changed enemy drop indicator to not require compass * Experimental: Moved dark world bunny spawns out of experimental. (It is now always on) diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml index 455d59af..d6dc6564 100644 --- a/source/enemizer/enemy_deny.yaml +++ b/source/enemizer/enemy_deny.yaml @@ -77,7 +77,7 @@ UwGeneralDeny: - [ 0x0038, 4, [ "RollerHorizontalRight" ] ] #"Swamp Palace - Long Hall - Kyameron 2" - [ 0x0039, 3, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Mini Helmasaur" - [ 0x0039, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 1" - - [ 0x0039, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Hardhat Beetle" + - [0x0039, 5, ["RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Bumper"]] #"Skull Woods - Play Pen - Hardhat Beetle" - [ 0x0039, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 2" - [ 0x003b, 1, [ "Bumper" ]] - [ 0x003c, 0, ["BigSpike"]] @@ -289,6 +289,8 @@ UwGeneralDeny: - [ 0x00ae, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 1" - [ 0x00ae, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 2" - [ 0x00af, 0, [ "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Ice Clock - Fire Bar (Clockwise)" + - [0x00b0, 7, [ "StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay + - [0x00b0, 8, [ "StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay - [ 0x00b1, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 1" - [ 0x00b1, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 2" - [ 0x00b1, 4, ["Bumper", "BigSpike", "AntiFairyCircle" ]] @@ -445,6 +447,7 @@ OwGeneralDeny: - [0x5e, 19, ["Gibo"]] # kiki eating Gibo - [0x5e, 20, ["Gibo"]] # kiki eating Gibo - [0x77, 1, ["Bumper"]] # soft-lock potential near ladder + - [0x7f, 1, ["Bumper"]] # soft-lock potential near ladder UwEnemyDrop: - [0x0085, 9, ["Babasu"]] # ran off the edge and didn't return - [0x00cb, 3, ["Zoro"]] # layer issues @@ -457,8 +460,6 @@ UwEnemyDrop: - [0x0077, 5, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] - [0x008D, 10, ["StalfosKnight", "Geldman", "Blob", "Stal"]] - [0x008D, 12, ["StalfosKnight", "Geldman", "Blob", "Stal"]] - - [0x00b0, 7, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay - - [0x00b0, 8, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay # the following are not allowed at certain pits (or on conveyors near pits) # because they despawned or clipped away or immediately fell, etc - [0x003d, 9, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", "Hover",