From 89e91a85fdacb8711936a06d5c329c6ef2ec3c52 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 21 Aug 2024 03:57:12 -0500 Subject: [PATCH] Merged in DR v1.4.5 --- .github/workflows/release-create.yml | 22 +- BaseClasses.py | 23 +- CLI.py | 3 + DoorShuffle.py | 2 +- DungeonGenerator.py | 8 +- InitialSram.py | 12 +- ItemList.py | 24 +- Items.py | 280 ++++++------- Main.py | 17 +- MultiClient.py | 2 +- OverworldShuffle.py | 2 +- Rom.py | 4 +- Rules.py | 21 +- TestSuite.py | 2 +- Text.py | 60 +-- data/base2current.bps | Bin 133611 -> 133845 bytes mystery_example.yml | 4 +- mystery_testsuite.yml | 11 +- resources/app/cli/args.json | 17 +- resources/app/cli/lang/en.json | 16 +- resources/app/gui/lang/en.json | 13 +- .../app/gui/randomize/dungeon/widgets.json | 6 +- .../app/gui/randomize/entrando/widgets.json | 20 + resources/ci/common/common.py | 2 +- resources/ci/common/prepare_release.py | 8 +- source/classes/CustomSettings.py | 4 + source/classes/constants.py | 2 + source/enemizer/EnemyLogic.py | 3 +- source/enemizer/enemy_deny.yaml | 27 +- source/overworld/EntranceShuffle2.py | 381 +++++++++++++----- source/tools/MysteryUtils.py | 2 + test/NewTestSuite.py | 2 +- test/suite/default_key_logic.yaml | 2 +- 33 files changed, 665 insertions(+), 337 deletions(-) diff --git a/.github/workflows/release-create.yml b/.github/workflows/release-create.yml index 6bcd2568..65bdb4b8 100644 --- a/.github/workflows/release-create.yml +++ b/.github/workflows/release-create.yml @@ -36,7 +36,6 @@ jobs: os-name: [ # ubuntu-latest, # ubuntu-22.04 ubuntu-22.04, - ubuntu-20.04, macos-latest, # macos-12 windows-latest # windows-2022 ] @@ -95,7 +94,6 @@ jobs: os-name: [ # ubuntu-latest, # ubuntu-22.04 ubuntu-22.04, - ubuntu-20.04, macos-latest, # macos-12 windows-latest # windows-2022 ] @@ -171,7 +169,6 @@ jobs: os-name: [ # ubuntu-latest, # ubuntu-22.04 ubuntu-22.04, - ubuntu-20.04, macos-latest, # macos-12 windows-latest # windows-2022 ] @@ -213,7 +210,6 @@ jobs: os-name: [ # ubuntu-latest, # ubuntu-22.04 ubuntu-22.04, - ubuntu-20.04, macos-latest, # macos-12 windows-latest # windows-2022 ] @@ -351,8 +347,10 @@ jobs: echo "MacOS Asset: ${{ steps.identify-macos-asset.outputs.asset_macos }}" echo "Windows Asset: ${{ steps.identify-windows-asset.outputs.asset_windows }}" - # create a release (MASTER) - - name: 📀->🚀Create a Release (MASTER) + # create a release (OverworldShuffle) + # if: contains(github.ref, 'OverworldShuffle') # branch or tag name + # if: contains(github.event.head_commit.message, 'Version bump') # commit message + - name: 📀->🚀Create a Release (OverworldShuffle) id: create_release uses: actions/create-release@v1.1.4 env: @@ -364,9 +362,9 @@ jobs: # draft: true if: contains(github.event.head_commit.message, "Merge 'OverworldShuffleDev' into OverworldShuffle") # branch/tag name and commit message - # upload linux archive asset (MASTER) + # upload linux archive asset (OverworldShuffle) #TODO: Make sure we're firing on the proper branches - - name: 🔼Upload Linux Archive Asset (MASTER) + - name: 🔼Upload Linux Archive Asset (OverworldShuffle) id: upload-linux-asset uses: actions/upload-release-asset@v1.0.2 env: @@ -378,9 +376,9 @@ jobs: asset_content_type: application/gzip if: contains(github.event.head_commit.message, "Merge 'OverworldShuffleDev' into OverworldShuffle") # branch/tag name and commit message - # upload macos archive asset (MASTER) + # upload macos archive asset (OverworldShuffle) #TODO: Make sure we're firing on the proper branches - - name: 🔼Upload MacOS Archive Asset (MASTER) + - name: 🔼Upload MacOS Archive Asset (OverworldShuffle) id: upload-macos-asset uses: actions/upload-release-asset@v1.0.2 env: @@ -392,9 +390,9 @@ jobs: asset_content_type: application/gzip if: contains(github.event.head_commit.message, "Merge 'OverworldShuffleDev' into OverworldShuffle") # branch/tag name and commit message - # upload windows archive asset (MASTER) + # upload windows archive asset (OverworldShuffle) #TODO: Make sure we're firing on the proper branches - - name: 🔼Upload Windows Archive Asset (MASTER) + - name: 🔼Upload Windows Archive Asset (OverworldShuffle) id: upload-windows-asset uses: actions/upload-release-asset@v1.0.2 env: diff --git a/BaseClasses.py b/BaseClasses.py index 9981e6fc..6abb3d12 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -642,7 +642,7 @@ class CollectionState(object): queue = deque(old_state.blocked_connections[player].items()) old_state.traverse_world(queue, rrp, bc, player) - if old_state.world.key_logic_algorithm[player] == 'default': + if old_state.world.key_logic_algorithm[player] == 'dangerous': unresolved_events = [x for y in old_state.reachable_regions[player] for x in y.locations if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) and x not in old_state.locations_checked and x.can_reach(old_state)] @@ -670,7 +670,7 @@ class CollectionState(object): queue = deque(self.blocked_connections[player].items()) self.traverse_world(queue, rrp, bc, player) - if self.world.key_logic_algorithm[player] == 'default': + if self.world.key_logic_algorithm[player] == 'dangerous': unresolved_events = [x for y in self.reachable_regions[player] for x in y.locations if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) and x not in self.locations_checked and x.can_reach(self)] @@ -2989,6 +2989,8 @@ class Spoiler(object): 'shuffleganon': self.world.shuffle_ganon, 'shufflelinks': self.world.shufflelinks, 'shuffletavern': self.world.shuffletavern, + 'skullwoods': self.world.skullwoods, + 'linked_drops': self.world.linked_drops, 'take_any': self.world.take_any, 'overworld_map': self.world.overworld_map, 'door_shuffle': self.world.doorShuffle, @@ -3242,6 +3244,9 @@ class Spoiler(object): if self.metadata['shuffle'][player] != 'vanilla': outfile.write('Shuffle Link\'s House:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player])) outfile.write('Shuffle Back of Tavern:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffletavern'][player])) + outfile.write('Skull Woods Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['skullwoods'][player]) + if self.metadata['linked_drops'] != "unset": + outfile.write('Linked Drops Override:'.ljust(line_width) + '%s\n' % self.metadata['linked_drops'][player]) outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player])) outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player]) outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player]) @@ -3641,7 +3646,11 @@ keyshuffle_mode = {'none': 0, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2} # byte 14: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo) overworld_map_mode = {'default': 0, 'compass': 1, 'map': 2} trap_door_mode = {'vanilla': 0, 'optional': 1, 'boss': 2, 'oneway': 3} -key_logic_algo = {'default': 0, 'partial': 1, 'strict': 2} +key_logic_algo = {'dangerous': 0, 'partial': 1, 'strict': 2} + +# byte 15: SSDD ???? (skullwoods, linked_drops, 4 free bytes) +skullwoods_mode = {'original': 0, 'restricted': 1, 'loose': 2, 'followlinked': 3} +linked_drops_mode = {'unset': 0, 'linked': 1, 'independent': 2} # sfx_shuffle and other adjust items does not affect settings code @@ -3698,6 +3707,8 @@ class Settings(object): ((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 5 | trap_door_mode[w.trap_door_mode[p]] << 3 | key_logic_algo[w.key_logic_algorithm[p]]), + + (skullwoods_mode[w.skullwoods[p]] << 6 | linked_drops_mode[w.linked_drops[p]] << 4), ]) return base64.b64encode(code, "+-".encode()).decode() @@ -3705,7 +3716,7 @@ class Settings(object): def adjust_args_from_code(code, player, args): settings, p = base64.b64decode(code.encode(), "+-".encode()), player - if len(settings) < 14: + if len(settings) < 15: raise Exception('Provided code is incompatible with this version') if settings[10] != settings_version: raise Exception('Provided code is incompatible with this version') @@ -3788,6 +3799,10 @@ class Settings(object): args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x18) >> 3] args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07] + if len(settings) > 15: + args.skullwoods[p] = r(skullwoods_mode)[(settings[15] & 0xc0) >> 6] + args.linked_drops[p] = r(linked_drops_mode)[(settings[15] & 0x30) >> 4] + class KeyRuleType(FastEnum): WorstCase = 0 diff --git a/CLI.py b/CLI.py index dfe2a680..70fae6e2 100644 --- a/CLI.py +++ b/CLI.py @@ -139,6 +139,7 @@ def parse_cli(argv, no_defaults=False): 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_max_difference', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern', + 'skullwoods', 'linked_drops', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', @@ -203,6 +204,8 @@ def parse_settings(): "shuffle": "vanilla", "shufflelinks": False, "shuffletavern": True, + 'skullwoods': 'original', + 'linked_drops': 'unset', "overworld_map": "default", "take_any": "none", "pseudoboots": False, diff --git a/DoorShuffle.py b/DoorShuffle.py index 074e71d4..b5a1e1dd 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -2088,7 +2088,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_ if actual_chest_keys == 0: dungeon.small_keys = [] else: - dungeon.small_keys = [ItemFactory(dungeon_keys[dungeon_name], player)] * actual_chest_keys + dungeon.small_keys = [ItemFactory(dungeon_keys[dungeon_name], player) for _ in range(actual_chest_keys)] for name, small_list in small_map.items(): used_doors.update(flatten_pair_list(small_list)) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index d5b0dafd..2e539bf1 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1850,10 +1850,8 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s sector.equations = calc_sector_equations(sector) if sector.is_entrance_sector() and not sector.destination_entrance: need_switch = True - for region in sector.get_start_regions(): - if region.crystal_switch: - need_switch = False - break + if sector.c_switch: # this relies on the fact that Mire Fishbone SE cannot be a portal + need_switch = False any_benefit = False for eq in sector.equations: if len(eq.benefit) > 0: @@ -4035,7 +4033,7 @@ def calc_door_equation(door, sector, look_for_entrance, sewers_flag=None): eq = DoorEquation(door) eq.benefit[hook_from_door(door)].append(door) eq.required = True - eq.c_switch = door.crystal == CrystalBarrier.Either + eq.c_switch = sector.c_switch # Big change - not true for mire fishbone, need to verify for others # exceptions for long entrances ??? # if door.name in ['PoD Dark Alley']: eq.entrance_flag = True diff --git a/InitialSram.py b/InitialSram.py index f3f6a5d8..001c9f9d 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -69,6 +69,7 @@ class InitialSram: starting_arrow_cap_upgrades = 30 starting_bombs = 0 starting_arrows = 0 + starting_magic = 0 startingstate = CollectionState(world) @@ -108,10 +109,10 @@ class InitialSram: if startingstate.has('Magic Upgrade (1/4)', player): equip[0x37B] = 2 - equip[0x36E] = 0x80 + starting_magic = 0x80 elif startingstate.has('Magic Upgrade (1/2)', player): equip[0x37B] = 1 - equip[0x36E] = 0x80 + starting_magic = 0x80 if startingstate.has('Return Old Man', player): self._initial_sram_bytes[0x410] |= 0x01 @@ -176,6 +177,7 @@ class InitialSram: arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10} bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10} arrows = {'Single Arrow': 1, 'Arrows (10)': 10} + magic = {'Big Magic': 0x80, 'Small Magic': 0x10} if item.name in set_table: equip[set_table[item.name][0]] = set_table[item.name][1] @@ -212,13 +214,19 @@ class InitialSram: if item.name != 'Piece of Heart' or equip[0x36B] == 0: equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0) equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0) + elif item.name in magic: + starting_magic += magic[item.name] else: raise RuntimeError(f'Unsupported item in starting equipment: {item.name}') + equip[0x36E] = min(starting_magic, 0x80) equip[0x370] = min(starting_bomb_cap_upgrades, 50) equip[0x371] = min(starting_arrow_cap_upgrades, 70) equip[0x343] = min(starting_bombs, equip[0x370]) equip[0x377] = min(starting_arrows, equip[0x371]) + + if not startingstate.has('Magic Mirror', player) and world.doorShuffle[player] != 'vanilla': + equip[0x353] = 1 # Assertion and copy equip to initial_sram_bytes assert equip[:0x340] == [0] * 0x340 diff --git a/ItemList.py b/ItemList.py index a3cf5438..17b522eb 100644 --- a/ItemList.py +++ b/ItemList.py @@ -11,7 +11,7 @@ from Tables import bonk_prize_lookup from Items import ItemFactory from source.dungeon.EnemyList import add_drop_contents -from source.overworld.EntranceShuffle2 import connect_entrance +from source.overworld.EntranceShuffle2 import exit_ids, door_addresses from source.item.FillUtil import trash_items, pot_items import source.classes.constants as CONST @@ -546,6 +546,27 @@ def set_up_take_anys(world, player, skip_adjustments=False): world.initialize_regions() +def connect_entrance(world, entrancename, exitname, player): + entrance = world.get_entrance(entrancename, player) + # check if we got an entrance or a region to connect to + try: + region = world.get_region(exitname, player) + exit = None + except RuntimeError: + exit = world.get_entrance(exitname, player) + region = exit.parent_region + + # if this was already connected somewhere, remove the backreference + if entrance.connected_region is not None: + entrance.connected_region.entrances.remove(entrance) + + target = exit_ids[exit.name][0] if exit is not None else exit_ids.get(region.name, None) + addresses = door_addresses[entrance.name][0] + + entrance.connect(region, addresses, target) + world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player) + + def create_dynamic_shop_locations(world, player): for shop in world.shops[player]: if shop.region.player == player: @@ -1462,6 +1483,7 @@ def make_customizer_pool(world, player): bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None) if not bow_found: missing_items.append('Progressive Bow') + missing_items = [i for i in missing_items if all(i != start.name or player != start.player for start in world.precollected_items)] if missing_items: logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}') diff --git a/Items.py b/Items.py index 89e07a41..c81b80d7 100644 --- a/Items.py +++ b/Items.py @@ -20,151 +20,151 @@ def ItemFactory(items, player): # Format: Name: (Advancement, Priority, Type, ItemCode, BasePrice, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) -item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'), - 'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), - 'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), +item_table = {'Bow': (True, False, None, 0x0B, 200, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'), + 'Progressive Bow': (True, False, None, 0x64, 150, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), + 'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), 'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'), 'Hammer': (True, False, None, 0x09, 250, 'Stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'), - 'Hookshot': (True, False, None, 0x0A, 250, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'), - 'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'), - 'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'), - 'Ocarina (Activated)': (True, False, None, 0x4A, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'), - 'Pegasus Boots': (True, False, None, 0x4B, 250, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'), - 'Power Glove': (True, False, None, 0x1B, 100, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the Glove'), - 'Cape': (True, False, None, 0x19, 50, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the Cape'), - 'Mushroom': (True, False, None, 0x29, 50, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'), - 'Shovel': (True, False, None, 0x13, 50, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'), - 'Lamp': (True, False, None, 0x12, 150, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'), - 'Magic Powder': (True, False, None, 0x0D, 50, 'You can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'), - 'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'), - 'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'), - 'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'), - 'Flippers': (True, False, None, 0x1E, 250, 'Fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'), - 'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'), - 'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'), - 'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'), - 'Ether': (True, False, None, 0x10, 100, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'), - 'Quake': (True, False, None, 0x11, 100, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'), - 'Bottle': (True, False, None, 0x16, 50, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'), - 'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'), - 'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'), - 'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'), - 'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'), - 'Bottle (Bee)': (True, False, None, 0x3C, 50, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'), - 'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'), - 'Master Sword': (True, False, 'Sword', 0x50, 100, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'), - 'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'), - 'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'), - 'Sword and Shield': (True, False, 'Sword', 0x00, 'An uncle\nsword rests\nhere!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'), - 'Golden Sword': (True, False, 'Sword', 0x03, 200, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'), - 'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'A better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'), - '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, 'Prize', 0x37, 250, 'A pendant that\nsome old guy\nwill never see', 'and the green pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), - 'Blue Pendant': (True, False, 'Prize', 0x39, 150, 'A pendant that you\'ll never get', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), - 'Red Pendant': (True, False, 'Prize', 0x38, 150, 'A pendant that you\'ll never get', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), - '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'), - 'Crystal 1': (True, False, 'Prize', 0xB1, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), - 'Crystal 2': (True, False, 'Prize', 0xB4, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), - 'Crystal 3': (True, False, 'Prize', 0xB6, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), - 'Crystal 4': (True, False, 'Prize', 0xB5, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), - 'Crystal 5': (True, False, 'Prize', 0xB2, 250, 'A crystal that\nunlocks a bomb', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), - 'Crystal 6': (True, False, 'Prize', 0xB0, 250, 'A crystal that\nunlocks a bomb', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), - 'Crystal 7': (True, False, 'Prize', 0xB3, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), - 'Single Arrow': (False, False, None, 0x43, 3, 'A lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'), - 'Arrows (10)': (False, False, None, 0x44, 30, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'), - 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), - 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), - 'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), - 'Arrows (5)': (False, False, None, 0xD5, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'), - 'Small Magic': (False, False, None, 0x45, 5, 'A bit of magic', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'), - 'Big Magic': (False, False, None, 0xD4, 40, 'A lot of magic', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'), + 'Hookshot': (True, False, None, 0x0A, 250, 'Hookshot!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'), + 'Magic Mirror': (True, False, None, 0x1A, 250, 'Mirror!\nTake some time to\nreflect on this moment!', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'), + 'Ocarina': (True, False, None, 0x14, 250, 'Ocarina!\nA Flute by another name', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'), + 'Ocarina (Activated)': (True, False, None, 0x4A, 250, 'Ocarina!\nA Flute by another name', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'), + 'Pegasus Boots': (True, False, None, 0x4B, 250, 'Pegasus Boots!\nGotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'), + 'Power Glove': (True, False, None, 0x1B, 100, 'Glove!\nNow you can\nlift weak stuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the Glove'), + 'Cape': (True, False, None, 0x19, 50, 'Cape!\nInvisbility cloak activate!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the Cape'), + 'Mushroom': (True, False, None, 0x29, 50, 'Mushroom!\nDon\'t eat it. Find a witch.', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'), + 'Shovel': (True, False, None, 0x13, 50, 'Shovel!\nCan you dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'), + 'Lamp': (True, False, None, 0x12, 150, 'Lamp!\nYou can see in the dark,\nand light torches.', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'), + 'Magic Powder': (True, False, None, 0x0D, 50, 'Powder!\nSprinkle it on a dancing pickle!', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'), + 'Moon Pearl': (True, False, None, 0x1F, 200, 'Moon Pearl!\nRabbit Be Gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'), + 'Cane of Somaria': (True, False, None, 0x15, 250, 'Cane of Somaria!\nMake blocks, throw blocks', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'), + 'Fire Rod': (True, False, None, 0x07, 250, 'Fire Rod!\nI\'m burning for you!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'), + 'Flippers': (True, False, None, 0x1E, 250, 'Flippers!\nFancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'), + 'Ice Rod': (True, False, None, 0x08, 250, 'Ice Rod!\nActivate Freeze Ray!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'), + 'Titans Mitts': (True, False, None, 0x1C, 200, 'Mitts!\nLift ALL the rocks!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'), + 'Bombos': (True, False, None, 0x0F, 100, 'Bombos!\nExplosions! Fire!\nBurn it all!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'), + 'Ether': (True, False, None, 0x10, 100, 'Ether!\nLet\'s cool things down!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'), + 'Quake': (True, False, None, 0x11, 100, 'Quake!\nLet\'s shake the ground!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'), + 'Bottle': (True, False, None, 0x16, 50, 'Bottle!\nStore all manner of things!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'), + 'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Red Potion!\nFill your hearts!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'), + 'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Green Potion!\nRestore your magic!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'), + 'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Blue Potion!\nHeal and restore!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'), + 'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Fairy Bottle!\nI\'ll save your life!', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'), + 'Bottle (Bee)': (True, False, None, 0x3C, 50, 'Bee Bottle!\nI\'ll sting your enemies a bit!', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'), + 'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'Good Bee Bottle!\nI\'ll sting your enemies\na whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'), + 'Master Sword': (True, False, 'Sword', 0x50, 100, 'Master Sword!\nEvil\'s bane!', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'), + 'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'Tempered Sword!\nMore slashy!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'), + 'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'Fighter Sword!\nStarter level slashy!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'), + 'Sword and Shield': (True, False, 'Sword', 0x00, 'Sword and Shield!\nUncle sword ahoy!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'), + 'Golden Sword': (True, False, 'Sword', 0x03, 200, 'Golden Sword!\nBest slashy!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'), + 'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'Sword!\nA better sword for your time!', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'), + 'Progressive Glove': (True, False, None, 0x61, 150, 'Glove!\nLift more than you can now!', '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, 'Silver Arrows!\nDefeat Ganon faster!', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Arrows'), + 'Green Pendant': (True, False, 'Prize', 0x37, 250, 'Green Pendant! Too\nbad Sahasrahla\nwill never see it!', 'and the green pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), + 'Blue Pendant': (True, False, 'Prize', 0x39, 150, 'Blue Pendant!\nBut how?!', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), + 'Red Pendant': (True, False, 'Prize', 0x38, 150, 'Red Pendant!\nBut how?!', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), + 'Triforce': (True, False, None, 0x6A, 777, 'Triforce!\nGrab me to 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, 'Power Star!\nA small step\ntowards 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, 'Triforce Piece!\nA small step\ntowards victory!', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce piece'), + 'Crystal 1': (True, False, 'Prize', 0xB1, 200, 'Crystal 1! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 2': (True, False, 'Prize', 0xB4, 200, 'Crystal 2! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 3': (True, False, 'Prize', 0xB6, 200, 'Crystal 3! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 4': (True, False, 'Prize', 0xB5, 200, 'Crystal 4! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 5': (True, False, 'Prize', 0xB2, 250, 'Crystal 5! It unlocks a bomb!', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 6': (True, False, 'Prize', 0xB0, 250, 'Crystal 6! It unlocks a bomb!', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 7': (True, False, 'Prize', 0xB3, 200, 'Crystal 7! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Single Arrow': (False, False, None, 0x43, 3, 'Single Arrow!\nDestiny awaits!', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'), + 'Arrows (10)': (False, False, None, 0x44, 30, '10 Arrows!\nAn archer\'s bailout!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'), + 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, '10 Arrow Upgrade!\nGain 10 more arrow capacity!', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), + 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, '5 Arrow Upgrade!\nGain 5 more arrow capacity!', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), + 'Single Bomb': (False, False, None, 0x27, 5, '1 Bomb!\nBoom boom pow!', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), + 'Arrows (5)': (False, False, None, 0xD5, 15, '5 Arrows!\nAn archer\'s bailout!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'), + 'Small Magic': (False, False, None, 0x45, 5, 'Magic Decanter!\nJust a little one', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'), + 'Big Magic': (False, False, None, 0xD4, 40, 'Magic Decanter!\nFull refill', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'), 'Chicken': (False, False, None, 0xD3, 5, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'), - 'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'), - 'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'), - 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), - 'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), - 'Blue Mail': (False, True, None, 0x22, 50, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'), - 'Red Mail': (False, True, None, 0x23, 100, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'), - 'Progressive Armor': (False, True, None, 0x60, 50, 'Time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'), - 'Blue Boomerang': (True, False, None, 0x0C, 50, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'), - 'Red Boomerang': (True, False, None, 0x2A, 50, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'), - 'Blue Shield': (False, True, None, 0x04, 50, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a Blue Shield'), - 'Red Shield': (False, True, None, 0x05, 500, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'a Red Shield'), - 'Mirror Shield': (True, False, None, 0x06, 200, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'), - 'Progressive Shield': (True, False, None, 0x5F, 50, 'Have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'), - 'Bug Catching Net': (True, False, None, 0x21, 50, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'), - 'Cane of Byrna': (True, False, None, 0x18, 50, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'), - 'Boss Heart Container': (False, True, None, 0x3E, 40, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), - 'Sanctuary Heart Container': (False, True, None, 0x3F, 50, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), - 'Piece of Heart': (False, False, None, 0x17, 10, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'), - 'Rupee (1)': (False, False, None, 0x34, 0, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'), - 'Rupees (5)': (False, False, None, 0x35, 2, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'), - 'Rupees (20)': (False, False, None, 0x36, 10, 'Just couch\ncash. Move\nright along.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'destitute boy has lunch again', 'a red rupee'), - 'Rupees (50)': (False, False, None, 0x41, 25, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'), - 'Rupees (100)': (False, False, None, 0x40, 50, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'), - 'Rupees (300)': (False, False, None, 0x46, 150, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'), - 'Rupoor': (False, False, None, 0x59, 0, 'A debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'), - 'Red Clock': (False, True, None, 0x5B, 0, 'A waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'), - 'Blue Clock': (False, True, None, 0x5C, 50, 'A bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'), - 'Green Clock': (False, True, None, 0x5D, 200, 'A lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'), + 'Bombs (3)': (False, False, None, 0x28, 15, '3 Bombs!\nExplosions!\nIn a handy 3-pack', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'), + 'Bombs (10)': (False, False, None, 0x31, 50, '10 Bombs!\nExplosions!\nIn a thrifty 10-pack', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'), + 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, '10 Bomb Upgrade!\nGain 10 more bomb capacity!', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, '5 Bomb Upgrade!\nGain 5 more bomb capacity!', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Blue Mail': (False, True, None, 0x22, 50, 'Blue Mail!\nLess damage activate!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'), + 'Red Mail': (False, True, None, 0x23, 100, 'Red Mail!\nEven less damage!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'), + 'Progressive Armor': (False, True, None, 0x60, 50, 'Upgrade Mail!\nNot the kind you read!', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'), + 'Blue Boomerang': (True, False, None, 0x0C, 50, 'Blue Boomerang!\nAlways comes back!', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'), + 'Red Boomerang': (True, False, None, 0x2A, 50, 'Red Boomerang!\nAlways comes back!', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'), + 'Blue Shield': (False, True, None, 0x04, 50, 'Blue Shield!\nBlock rocks!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a blue shield'), + 'Red Shield': (False, True, None, 0x05, 500, 'Red Shield!\nBlock fireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'the Red Shield'), + 'Mirror Shield': (True, False, None, 0x06, 200, 'Mirror Shield!\nBlock lasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'), + 'Progressive Shield': (True, False, None, 0x5F, 50, 'Shield!\nA better shield for your time!', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'), + 'Bug Catching Net': (True, False, None, 0x21, 50, 'Bug Net!\nCatch all manner\nof things!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'), + 'Cane of Byrna': (True, False, None, 0x18, 50, 'Cane of Byrna!\nSwirly protection!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'), + 'Boss Heart Container': (False, True, None, 0x3E, 40, 'Heart Container!\nHealth Increased!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), + 'Sanctuary Heart Container': (False, True, None, 0x3F, 50, 'Heart Container!\nHealth Increased!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), + 'Piece of Heart': (False, False, None, 0x17, 10, 'Heart Piece!\nOne step closer\nto more health!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'), + 'Rupee (1)': (False, False, None, 0x34, 0, 'Rupees!\nJust pocket\nchange.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'), + 'Rupees (5)': (False, False, None, 0x35, 2, 'Rupees!\nJust pocket\nchange.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'), + 'Rupees (20)': (False, False, None, 0x36, 10, 'Rupees!\nJust couch\ncash.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'destitute boy has lunch again', 'a red rupee'), + 'Rupees (50)': (False, False, None, 0x41, 25, 'Rupees!\nA big pile!', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'), + 'Rupees (100)': (False, False, None, 0x40, 50, 'Rupees!\nA big stash!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'), + 'Rupees (300)': (False, False, None, 0x46, 150, 'Rupees!\nA big hoard!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'), + 'Rupoor': (False, False, None, 0x59, 0, 'Rupoor!\nI\'ll take your rupees!', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'), + 'Red Clock': (False, True, None, 0x5B, 0, 'Red Clock!\nA waste of time.', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'), + 'Blue Clock': (False, True, None, 0x5C, 50, 'Blue Clock!\nA bit of time!', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'), + 'Green Clock': (False, True, None, 0x5D, 200, 'Green Clock!\nTons of time!', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'), 'Single RNG': (False, True, None, 0x62, 300, 'Something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'), 'Multi RNG': (False, True, None, 0x63, 100, 'Something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'), - 'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance - 'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance - 'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Eastern Palace'), - 'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 60, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Eastern Palace'), - 'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 10, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Eastern Palace'), - 'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Eastern Palace'), - 'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 40, 'A small key to the desert', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Desert Palace'), - 'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 60, 'A big key to the desert', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Desert Palace'), - 'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 10, 'Now you can find Lanmolas!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Desert Palace'), - 'Map (Desert Palace)': (False, True, 'Map', 0x7C, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Desert Palace'), - 'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 40, 'A small key to Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Tower of Hera'), - 'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 60, 'A big key to Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Tower of Hera'), - 'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 10, 'Now you can find Moldorm!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Tower of Hera'), - 'Map (Tower of Hera)': (False, True, 'Map', 0x75, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Tower of Hera'), - 'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 40, 'A small key to the castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Hyrule Castle'), - 'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 60, 'A big key to the castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Hyrule Castle'), - 'Compass (Escape)': (False, True, 'Compass', 0x8F, 10, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a Compass to Hyrule Castle'), - 'Map (Escape)': (False, True, 'Map', 0x7F, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Hyrule Castle'), - 'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 40, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Castle Tower'), - 'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 60, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Castle Tower'), - 'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 10, 'Now you can find Aga1!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a Compass to Castle Tower'), - 'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Castle Tower'), - 'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 40, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Palace of Darkness'), - 'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 60, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Palace of Darkness'), - 'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 10, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Palace of Darkness'), - 'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Palace of Darkness'), - 'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 40, 'A small key to thievery', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Thieves\' Town'), - 'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 60, 'A big key to thievery', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Thieves\' Town'), - 'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 10, 'Now you can find Blind!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Thieves\' Town'), - 'Map (Thieves Town)': (False, True, 'Map', 0x74, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Thieves\' Town'), - 'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 40, 'A small key to the woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Skull Woods'), - 'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 60, 'A big key to the woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Skull Woods'), - 'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 10, 'Now you can find Mothula!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Skull Woods'), - 'Map (Skull Woods)': (False, True, 'Map', 0x77, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Skull Woods'), - 'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 40, 'A small key to the swamp', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Swamp Palace'), - 'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 60, 'A big key to the swamp', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Swamp Palace'), - 'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 10, 'Now you can find Arrghus!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Swamp Palace'), - 'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Swamp Palace'), - 'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 40, 'A small key to the iceberg', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Ice Palace'), - 'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 60, 'A big key to the iceberg', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Ice Palace'), - 'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 10, 'Now you can find Kholdstare!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Ice Palace'), - 'Map (Ice Palace)': (False, True, 'Map', 0x76, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Ice Palace'), - 'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 40, 'A small key to the mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Misery Mire'), - 'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 60, 'A big key to the mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Misery Mire'), - 'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 10, 'Now you can find Vitreous!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Misery Mire'), - 'Map (Misery Mire)': (False, True, 'Map', 0x78, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Misery Mire'), - 'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 40, 'A small key to the pipe maze', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Turtle Rock'), - 'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 60, 'A big key to the pipe maze', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Turtle Rock'), - 'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 10, 'Now you can find Trinexx!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Turtle Rock'), - 'Map (Turtle Rock)': (False, True, 'Map', 0x73, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Turtle Rock'), - 'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 40, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Ganon\'s Tower'), - 'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 60, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Ganon\'s Tower'), - 'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 10, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Ganon\'s Tower'), + 'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Half Magic!\nDouble your magic power!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance + 'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Quarter Magic!\nMagic is basically free now!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance + 'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key for\nEastern Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Eastern Palace'), + 'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 60, 'A big key for\nEastern Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Eastern Palace'), + 'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 10, 'A compass for\nEastern Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Eastern Palace'), + 'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Eastern Palace'), + 'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 40, 'A small key for\nDesert Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Desert Palace'), + 'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 60, 'A big key for\nDesert Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Desert Palace'), + 'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 10, 'A compass for\nDesert Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Desert Palace'), + 'Map (Desert Palace)': (False, True, 'Map', 0x7C, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Desert Palace'), + 'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 40, 'A small key for\nTower of Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Tower of Hera'), + 'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 60, 'A big key for\nTower of Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Tower of Hera'), + 'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 10, 'A compass for\nTower of Hera', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Tower of Hera'), + 'Map (Tower of Hera)': (False, True, 'Map', 0x75, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Tower of Hera'), + 'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 40, 'A small key for\nHyrule Castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Hyrule Castle'), + 'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 60, 'A big key for\nHyrule Castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Hyrule Castle'), + 'Compass (Escape)': (False, True, 'Compass', 0x8F, 10, 'A compass for\nHyrule Castle', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Hyrule Castle'), + 'Map (Escape)': (False, True, 'Map', 0x7F, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Hyrule Castle'), + 'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 40, 'A small key for\nAgahnim\'s Tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'), + 'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 60, 'A big key for\nAgahnim\'s Tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'), + 'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 10, 'A compass for\nAgahnim\'s Tower', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'), + 'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'), + 'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 40, 'A small key for\nDark Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'), + 'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 60, 'A big key for\nDark Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'), + 'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 10, 'A compass for\nDark Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'), + 'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Palace of Darkness'), + 'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 40, 'A small key for\nThieves Town', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Thieves\' Town'), + 'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 60, 'A big key for\nThieves Town', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Thieves\' Town'), + 'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 10, 'A compass for\nThieves Town', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Thieves\' Town'), + 'Map (Thieves Town)': (False, True, 'Map', 0x74, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Thieves\' Town'), + 'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 40, 'A small key for\nSkull Woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Skull Woods'), + 'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 60, 'A big key for\nSkull Woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Skull Woods'), + 'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 10, 'A compass for\nSkull Woods', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Skull Woods'), + 'Map (Skull Woods)': (False, True, 'Map', 0x77, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Skull Woods'), + 'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 40, 'A small key for\nSwamp Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Swamp Palace'), + 'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 60, 'A big key for\nSwamp Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Swamp Palace'), + 'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 10, 'A compass for\nSwamp Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Swamp Palace'), + 'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Swamp Palace'), + 'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 40, 'A small key for\nIce Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ice Palace'), + 'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 60, 'A big key for\nIce Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ice Palace'), + 'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 10, 'A compass for\nIce Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ice Palace'), + 'Map (Ice Palace)': (False, True, 'Map', 0x76, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ice Palace'), + 'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 40, 'A small key for\nMisery Mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Misery Mire'), + 'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 60, 'A big key for\nMisery Mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Misery Mire'), + 'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 10, 'A compass for\nMisery Mire', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Misery Mire'), + 'Map (Misery Mire)': (False, True, 'Map', 0x78, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Misery Mire'), + 'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 40, 'A small key for\nTurtle Rock', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Turtle Rock'), + 'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 60, 'A big key for\nTurtle Rock', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Turtle Rock'), + 'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 10, 'A compass for\nTurtle Rock', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Turtle Rock'), + 'Map (Turtle Rock)': (False, True, 'Map', 0x73, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Turtle Rock'), + 'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 40, 'A small key for\nGanon\'s Tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ganon\'s Tower'), + 'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 60, 'A big key for\nGanon\'s Tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ganon\'s Tower'), + 'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 10, 'A compass for\nGanon\'s Tower', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ganon\'s Tower'), 'Map (Ganons Tower)': (False, True, 'Map', 0x72, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Ganon\'s Tower'), 'Small Key (Universal)': (False, True, None, 0xAF, 100, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key'), 'Nothing': (False, False, None, 0x5A, 1, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'), diff --git a/Main.py b/Main.py index e2fcc7d1..91826269 100644 --- a/Main.py +++ b/Main.py @@ -40,7 +40,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.12' +version_number = '1.4.5' version_branch = '-u' __version__ = f'{version_number}{version_branch}' @@ -495,6 +495,8 @@ def init_world(args, fish): world.standardize_palettes = args.standardize_palettes.copy() world.shufflelinks = args.shufflelinks.copy() world.shuffletavern = args.shuffletavern.copy() + world.skullwoods = args.skullwoods.copy() + world.linked_drops = args.linked_drops.copy() world.pseudoboots = args.pseudoboots.copy() world.overworld_map = args.overworld_map.copy() world.take_any = args.take_any.copy() @@ -522,7 +524,18 @@ def set_starting_inventory(world, args): if inv_list: for inv_item in inv_list: name = inv_item.strip() - name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' + if inv_item == 'RandomWeapon': + name = random.choice(['Progressive Bow', 'Hammer', 'Progressive Sword', 'Cane of Somaria', 'Cane of Byrna', 'Fire Rod']) + extra = [] + if name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']: + extra.append('Big Magic') + if name == 'Progressive Bow': + extra.extend(['Arrows (10)'] * 3) + for e in extra: + item = ItemFactory(e, p) + if item: + world.push_precollected(item) + name = name if name != 'Ocarina' or world.flute_mode[p] != 'active' else 'Ocarina (Activated)' item = ItemFactory(name, p) if item: world.push_precollected(item) diff --git a/MultiClient.py b/MultiClient.py index 5859f731..0d238392 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -1097,7 +1097,7 @@ async def game_watcher(ctx : Context): async def main(): parser = argparse.ArgumentParser() - parser.add_argument('--snes', default='localhost:8080', help='Address of the QUsb2snes server.') + parser.add_argument('--snes', default='localhost:23074', help='Address of the QUsb2snes/SNI server.') parser.add_argument('--connect', default=None, help='Address of the multiworld host.') parser.add_argument('--password', default=None, help='Password of the multiworld host.') parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical']) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index fd1b3932..a8628757 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1151,7 +1151,7 @@ def define_tile_groups(world, do_grouped, player): merge_groups([[0x13, 0x14, 0x1b]]) # sanctuary and grave connector - if world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']: + if world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'district']: merge_groups([[0x13, 0x14]]) # cross-screen connector diff --git a/Rom.py b/Rom.py index f8bb2cfc..8abaa922 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '23be89a3b206df54fcc5df664b0ca5a3' +RANDOMIZERBASEHASH = 'f3d6ac83d5d2ee0e3fb96f7ef5128e61' class JsonRom(object): @@ -889,7 +889,7 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_byte(cr_pc+0x1e, 0xEE) # slash rom.write_byte(cr_pc+0x1f, thousands_bot) # modify stat config - stat_address = 0x23978C + stat_address = 0x23983E stat_pc = snes_to_pc(stat_address) rom.write_byte(stat_pc, 0xa9) # change to pos 21 (from b1) rom.write_byte(stat_pc+2, 0xc0) # change to 12 bits (from a0) diff --git a/Rules.py b/Rules.py index 549e3d30..61c918fa 100644 --- a/Rules.py +++ b/Rules.py @@ -557,11 +557,12 @@ def global_rules(world, player): set_rule(world.get_entrance('Mire Post-Gap Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Falling Bridge Hook Path', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Falling Bridge Hook Only Path', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.has_sword(player) or - (state.has('Fire Rod', player) and (state.can_use_bombs(player) or state.can_extend_magic(player, 9))) or # 9 fr shots or 8 with some bombs - (state.has('Ice Rod', player) and state.can_use_bombs(player)) or # freeze popo and throw, bomb to finish - state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ... - # byrna could work with sufficient magic + # Note: new enemy logic doesn't account for Fire Rod + Bombs or Ice Rod + Bombs yet + # set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.has_sword(player) or + # (state.has('Fire Rod', player) and (state.can_use_bombs(player) or state.can_extend_magic(player, 9))) or # 9 fr shots or 8 with some bombs + # (state.has('Ice Rod', player) and state.can_use_bombs(player)) or # freeze popo and throw, bomb to finish + # state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ... + # byrna could work with sufficient magic set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) loc = world.get_location('Misery Mire - Spikes Pot Key', player) if loc.pot: @@ -641,10 +642,6 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Ice Armos NE', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state)) set_rule(world.get_entrance('GT Ice Armos WS', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state)) - set_rule(world.get_entrance('GT Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player)) - set_rule(world.get_entrance('GT Mimics 1 ES', player), lambda state: state.can_shoot_arrows(player)) - set_rule(world.get_entrance('GT Mimics 2 WS', player), lambda state: state.can_shoot_arrows(player)) - set_rule(world.get_entrance('GT Mimics 2 NE', player), lambda state: state.can_shoot_arrows(player)) # consider access to refill room - interior doors would need a change set_rule(world.get_entrance('GT Cannonball Bridge SE', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) @@ -1531,6 +1528,10 @@ std_kill_rooms = { (['GT Petting Zoo SE'], [], 0x7d, [4, 5, 6, 7, 8, 10]), 'GT DMs Room': # Four red stalfos (['GT DMs Room SW'], [], 0x7b, [2, 3, 4, 5, 8, 9, 10]), + 'GT Mimics 1': # two red mimics + (['GT Mimics 1 NW', 'GT Mimics 1 ES'], [], 0x006b, [5, 6, 7, 8, 9]), + 'GT Mimics 2': # two red mimics + (['GT Mimics 2 NE', 'GT Mimics 2 WS'], [], 0x006b, [10, 11, 12, 13]), 'GT Gauntlet 1': # Stalfos/zazaks (['GT Gauntlet 1 WN'], [], 0x5d, [3, 4, 5, 6]), 'GT Gauntlet 2': # Red stalfos @@ -2017,7 +2018,7 @@ def add_key_logic_rules(world, player): eval_func = eval_small_key_door if world.key_logic_algorithm[player] == 'strict' and world.keyshuffle[player] == 'wild': eval_func = eval_small_key_door_strict - elif world.key_logic_algorithm[player] != 'default': + elif world.key_logic_algorithm[player] != 'dangerous': eval_func = eval_small_key_door_partial for d_name, d_logic in key_logic.items(): for door_name, rule in d_logic.door_rules.items(): diff --git a/TestSuite.py b/TestSuite.py index a1b18ab3..ca734581 100644 --- a/TestSuite.py +++ b/TestSuite.py @@ -29,7 +29,7 @@ def main(args=None): ['Std ', ' --mode standard'], ['Inv ', ' --mode inverted']]: - basecommand = f"python3.8 DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler" + basecommand = f"python DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --spoiler none" def gen_seed(): taskcommand = basecommand + " " + command + mode[1] diff --git a/Text.py b/Text.py index 08756f8a..7518c54d 100644 --- a/Text.py +++ b/Text.py @@ -1668,50 +1668,50 @@ class TextTable(object): text['potion_shop_no_empty_bottles'] = CompressedTextMapper.convert("Whoa, bucko!\nNo empty bottles.") text['item_get_lamp'] = CompressedTextMapper.convert("Lamp! You can see in the dark, and light torches.") text['item_get_boomerang'] = CompressedTextMapper.convert("Boomerang! Press START to select it.") - text['item_get_bow'] = CompressedTextMapper.convert("You're in bow mode now!") - text['item_get_shovel'] = CompressedTextMapper.convert("This is my new mop. My friend George, he gave me this mop. It's a pretty good mop. It's not as good as my old mop. I miss my old mop. But it's still a good mop.") - text['item_get_magic_cape'] = CompressedTextMapper.convert("Finally! we get to play Invisble Man!") - text['item_get_powder'] = CompressedTextMapper.convert("It's the powder. Let's cause some mischief!") - text['item_get_flippers'] = CompressedTextMapper.convert("Splish! Splash! Let's go take a bath!") - text['item_get_power_gloves'] = CompressedTextMapper.convert("Feel the power! You can now lift light rocks! Rock on!") + text['item_get_bow'] = CompressedTextMapper.convert("Bow! Join the archer class!") + text['item_get_shovel'] = CompressedTextMapper.convert("Shovel! Can you dig it?") + text['item_get_magic_cape'] = CompressedTextMapper.convert("Cape! Invisbility cloak activate!") + text['item_get_powder'] = CompressedTextMapper.convert("Powder! Sprinkle it on a dancing pickle!") + text['item_get_flippers'] = CompressedTextMapper.convert("Flippers! Time to swim!") + text['item_get_power_gloves'] = CompressedTextMapper.convert("Gloves! Lift up those rocks!") text['item_get_pendant_courage'] = CompressedTextMapper.convert("We have the Pendant of Courage! How brave!") text['item_get_pendant_power'] = CompressedTextMapper.convert("We have the Pendant of Power! How robust!") text['item_get_pendant_wisdom'] = CompressedTextMapper.convert("We have the Pendant of Wisdom! How astute!") - text['item_get_mushroom'] = CompressedTextMapper.convert("A Mushroom! Don't eat it. Find a witch.") - text['item_get_book'] = CompressedTextMapper.convert("It book! U R now litterit!") - text['item_get_moonpearl'] = CompressedTextMapper.convert("I found a shiny marble! No more hops!") + text['item_get_mushroom'] = CompressedTextMapper.convert("Mushroom! Don't eat it. Find a witch.") + text['item_get_book'] = CompressedTextMapper.convert("Book! Are you well read?") + text['item_get_moonpearl'] = CompressedTextMapper.convert("Moon Pearl! Rabbit Be Gone!") text['item_get_compass'] = CompressedTextMapper.convert("A compass! I can now find the boss.") # 60 text['item_get_map'] = CompressedTextMapper.convert("Yo! You found a MAP! Press X to see it.") - text['item_get_ice_rod'] = CompressedTextMapper.convert("It's the Ice Rod! Freeze Ray time.") - text['item_get_fire_rod'] = CompressedTextMapper.convert("A Rod that shoots fire? Let's burn all the things!") - text['item_get_ether'] = CompressedTextMapper.convert("We can chill out with this!") - text['item_get_bombos'] = CompressedTextMapper.convert("Let's set everything on fire, and melt things!") - text['item_get_quake'] = CompressedTextMapper.convert("Time to make the earth shake, rattle, and roll!") + text['item_get_ice_rod'] = CompressedTextMapper.convert("Ice Rod! Time to chill out!") + text['item_get_fire_rod'] = CompressedTextMapper.convert("Fire Rod! I'm burning for you!") + text['item_get_ether'] = CompressedTextMapper.convert("Ether! Let's cool things down!") + text['item_get_bombos'] = CompressedTextMapper.convert("Bombos! Explosions, fire, burn it all!") + text['item_get_quake'] = CompressedTextMapper.convert("Quake! Let's shake the ground!") text['item_get_hammer'] = CompressedTextMapper.convert("STOP!\n\nHammer Time!") # 66 - text['item_get_ocarina'] = CompressedTextMapper.convert("Finally! We can play the Song of Time!") - text['item_get_cane_of_somaria'] = CompressedTextMapper.convert("Make blocks!\nThrow blocks!\nsplode Blocks!") - text['item_get_hookshot'] = CompressedTextMapper.convert("BOING!!!\nBOING!!!\nSay no more…") + text['item_get_ocarina'] = CompressedTextMapper.convert("Ocarina! A Flute by another name") + text['item_get_cane_of_somaria'] = CompressedTextMapper.convert("Somaria! Make blocks, throw blocks") + text['item_get_hookshot'] = CompressedTextMapper.convert("Hookshot! Grab all the things!") text['item_get_bombs'] = CompressedTextMapper.convert("BOMBS! Use A to pick 'em up, throw 'em, get hurt!") - text['item_get_bottle'] = CompressedTextMapper.convert("It's a terrarium. I hope we find a lizard!") + text['item_get_bottle'] = CompressedTextMapper.convert("Bottle! Store all manner of things") text['item_get_big_key'] = CompressedTextMapper.convert("Yo! You got a Big Key!") - text['item_get_titans_mitts'] = CompressedTextMapper.convert("So, like, you can now lift anything.\nANYTHING!") - text['item_get_magic_mirror'] = CompressedTextMapper.convert("We could stare at this all day or, you know, beat Ganon…") + text['item_get_titans_mitts'] = CompressedTextMapper.convert("Mitts! Lift ALL the rocks!") + text['item_get_magic_mirror'] = CompressedTextMapper.convert("Mirror! Take some time to reflect on this moment!") text['item_get_fake_mastersword'] = CompressedTextMapper.convert("It's the Master Sword! …or not…\n\n FOOL!") # 70 text['post_item_get_mastersword'] = CompressedTextMapper.convert("{NOBORDER}\n{SPEED6}\n@, you got the sword!\n{CHANGEMUSIC}\nNow let's go beat up Agahnim!") - text['item_get_red_potion'] = CompressedTextMapper.convert("Red goo to go! Nice!") - text['item_get_green_potion'] = CompressedTextMapper.convert("Green goo to go! Nice!") - text['item_get_blue_potion'] = CompressedTextMapper.convert("Blue goo to go! Nice!") - text['item_get_bug_net'] = CompressedTextMapper.convert("Surprise Net! Let's catch stuff!") - text['item_get_blue_mail'] = CompressedTextMapper.convert("Blue threads? Less damage activated!") - text['item_get_red_mail'] = CompressedTextMapper.convert("You feel the power of the eggplant on your head.") - text['item_get_temperedsword'] = CompressedTextMapper.convert("Nice… I now have a craving for Cheetos.") - text['item_get_mirror_shield'] = CompressedTextMapper.convert("Pit would be proud!") - text['item_get_cane_of_byrna'] = CompressedTextMapper.convert("It's the Blue Cane. You can now protect yourself with lag!") + text['item_get_red_potion'] = CompressedTextMapper.convert("Red Potion! Heal yourself") + text['item_get_green_potion'] = CompressedTextMapper.convert("Green Potion! Magic refill!") + text['item_get_blue_potion'] = CompressedTextMapper.convert("Blue Potion! Heal and restore!") + text['item_get_bug_net'] = CompressedTextMapper.convert("Bug Net! Let's catch stuff!") + text['item_get_blue_mail'] = CompressedTextMapper.convert("Blue Mail! Less damage activated!") + text['item_get_red_mail'] = CompressedTextMapper.convert("Red Mail! Even less damage!") + text['item_get_temperedsword'] = CompressedTextMapper.convert("Tempered Sword! Even more slashy!") + text['item_get_mirror_shield'] = CompressedTextMapper.convert("Mirror Shield! Time to reflect") + text['item_get_cane_of_byrna'] = CompressedTextMapper.convert("Byrna! Swirly protection!") text['missing_big_key'] = CompressedTextMapper.convert("Something is missing…\nThe Big Key?") text['missing_magic'] = CompressedTextMapper.convert("Something is missing…\nMagic meter?") - text['item_get_pegasus_boots'] = CompressedTextMapper.convert("Finally, it's bonking time!\nHold A to dash") + text['item_get_pegasus_boots'] = CompressedTextMapper.convert("Pegasus Boots! Finally, it's bonking time!\nHold A to dash") text['talking_tree_info_start'] = CompressedTextMapper.convert("Whoa! I can talk again!") text['talking_tree_info_1'] = CompressedTextMapper.convert("Yank on the pitchfork in the center of town, ya heard it here.") text['talking_tree_info_2'] = CompressedTextMapper.convert("Ganon is such a dingus, no one likes him, ya heard it here.") diff --git a/data/base2current.bps b/data/base2current.bps index 93182c430c36120fb62335c7d06dd17530952a10..d98852bc6112aaec13fd80eafb50fcc4c8ae1736 100644 GIT binary patch delta 22844 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 axITm!iQi(m%^}$=UiizQWJT$Pi~kRE^299w delta 22363 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
Lok-#-y3FL z8~?;~b6jc-;SHt4Juzz)F;A64R>*YNst=SAb`l!n1Q}dRB8x!@&AWp zD;acN|D8NlL%RQN4M|mY$aYmthf(&hn_|9prXg3hRxCxi!pRI zQE6aNV7f=!1m2Imi^tyK*_5}$Wn^6P0@F70GWlEs3$_&P31S)qH74gC<9^AQSYx6b zvlzEa#>HxMe|S@<9Kw{I@sQT|@@x8!05QD35%*k!OUqVLD9bQCRr_II()k++mJne=%m>@Qxk_?86#NN-%b6njqSA~gwWp` z7r{eZ3#s7Im*XGiCjCGsRD<~5GoDjd-jL?>p2tB`On@EmNcG%6kgyi!-pXcejs0; zg4mSG(Jmk>Ma)GEie$C~y`OFAun&UZBq3e*oHMtT;iZ?2Vx#yrls4OjhsEc%U74fj zUYR#cGqVQxyT}?HNTG}z(ZK?Gj*xWgU^z(1Z5{YW7H}+GMSl8LtAmG}1$+ZWPT1o< zu2Tbxj>)OfUUR~lsV>nDBCbb3;7BORtcQr0+JDM!_N#bKcBX@aCu7v5PKO^GQT*3w z1UrXE8DF->jlt7)(q0dXgPBoAe1)s$w+3*Gi_WEL~b{Lh+7uNko=WorUB;rm9K2J_-wDnc_rRn z-Tdb4`rH?)Wh6xpg%Q4s)w)R>4Wny0^MxGA%=>>;p4YZ>B=0rJ_Y7M(JcN#yamb_| zuDYIh^@cf=m<+gc^QFXQ05M%qO70n;mga6C2OE)1QnZ>KYy>$#6Peot5exj+-0c#q zZ!<}lqG<<@B*$@?j{LN~-$M33X_bwn1>bF6cR+0toX!;X%^e9LhnheKMa?Ey0_(_9 z6Ii^mc6YZrT=ELk{aSfX#9sC%6R2r=g$F5afnbkyc@2g%CJ>#Vz47eX7U0tH{`+x&R4neds4HSPIIDAPuuOMMzKeKs>&W zB?66d7V_!XaPsslq|#I*Np6J$9*3iv4C}C>m&md*_T5(aSisx8%oM3e+r-xO!k`y- z`I^4p#ar0r?}9sL?)hWxKO0g5qlFpg*XN!;?L`6IPPGq3r`~1rFM%8Fle^+%_r^UG zZwAx5kY$#xWXmo?9fvz_NsVzK&GN27G+z*QpjQ(zBQocfvPm{r?c$o7cd^TtRZhSK znp=`{o9qf@SJ)weGi!TBug1Nqt8s@$;nnOj=Wkw_=WqjDU*FkFma@7VkOW>iF{2$n&ODm=89$R<3FO9?a9EhV{Cm^P%$GC9 z8R9Kqv%i9Ed~Sj3yHEGL%3iz${sOPD1N*I}V^o?nO=>Azdz1a{SKQp)&%f$XPhL|E zN<8XWkNYsf^W;pYjgp_Tqd7Yvj3=;*Q!j0H!TR!Zj|+?SwxVWc&>Tr&v%+kR0>L* s)aG5Rs|!8C@hRke(7l|hr1J5405wvX%i4If3y1&wt?p-!Q!n-Y7qxrV5C8xG diff --git a/mystery_example.yml b/mystery_example.yml index 45d82ecd..79f9815d 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -47,8 +47,8 @@ boss: 0 oneway: 0 key_logic_algorithm: - default: 1 - partial: 0 + dangerous: 0 + partial: 1 strict: 0 decoupledoors: off door_self_loops: diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index 08110a27..955f6cd6 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -25,7 +25,7 @@ trap_door_mode: boss: 1 oneway: 1 key_logic_algorithm: - default: 1 + dangerous: 1 partial: 0 strict: 0 decoupledoors: @@ -71,6 +71,15 @@ shufflelinks: shuffletavern: on: 1 off: 1 +skullwoods: + original: 1 + restricted: 1 + loose: 1 + followlinked: 1 +linked_drops: + unset: 1 + linked: 1 + independent: 1 world_state: standard: 1 open: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index eef2cccc..7d2de9da 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -254,7 +254,7 @@ }, "key_logic_algorithm": { "choices": [ - "default", + "dangerous", "partial", "strict" ] @@ -516,6 +516,21 @@ "action": "store_true", "type": "bool" }, + "skullwoods": { + "choices": [ + "original", + "restricted", + "loose", + "followlinked" + ] + }, + "linked_drops": { + "choices": [ + "unset", + "linked", + "independent" + ] + }, "overworld_map": { "choices": [ "default", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 80f479e8..0ddb3c48 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -305,7 +305,7 @@ ], "key_logic_algorithm": [ "Key Logic Algorithm (default: %(default)s)", - "default: Balance between safety and randomization", + "dangerous: Key usage must follow logic for safety", "partial: Partial protection when using certain minor glitches", "strict: Ensure small keys are available" ], @@ -431,6 +431,20 @@ "shuffletavern": [ "Include the back of the tavern in the entrance shuffle pool. (default: %(default)s)" ], + "skullwoods": [ + "Select how to shuffle skull woods (default: %(default)s)", + "Original: Skull Woods is shuffled amongst itself", + "Restricted: Drops are vanilla. Entrances stay in skull woods", + "Loose: Drops are vanilla. Entrances go in the main pool", + "Followlinked: If drops are linked, then pinball/left side will be vanilla", + " with other drops paired with an entrance. Otherwise, all go into the main pool" + ], + "linked_drops": [ + "Select how drops are treated in entrance shuffle. (default: %(default)s)", + "Unset: The shuffle mode determines the setting.", + "Linked: Dropdowns will be linked with entrance caves", + "Independent: Dropdowns will not be linked" + ], "overworld_map": [ "Control if and how the overworld map indicators show the locations of dungeons (default: %(default)s)" ], diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index a9541a0b..68fba490 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -92,7 +92,7 @@ "randomizer.dungeon.trap_door_mode.oneway": "Remove All Annoying Traps", "randomizer.dungeon.key_logic_algorithm": "Key Logic Algorithm", - "randomizer.dungeon.key_logic_algorithm.default": "Default", + "randomizer.dungeon.key_logic_algorithm.dangerous": "Dangerous", "randomizer.dungeon.key_logic_algorithm.partial": "Partial Protection", "randomizer.dungeon.key_logic_algorithm.strict": "Strict", @@ -195,6 +195,17 @@ "randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full", "randomizer.entrance.entranceshuffle.dungeonssimple": "Dungeons + Simple", + "randomizer.entrance.skullwoods": "Skull Woods Shuffle", + "randomizer.entrance.skullwoods.original": "Original", + "randomizer.entrance.skullwoods.restricted": "Vanilla Drops, Entrances Restricted", + "randomizer.entrance.skullwoods.loose": "Vanilla Drops, Entrances use Shuffle", + "randomizer.entrance.skullwoods.followlinked": "Follow Linked Drops Setting", + + "randomizer.entrance.linked_drops": "Linked Drops Override", + "randomizer.entrance.linked_drops.unset": "Determined by Shuffle", + "randomizer.entrance.linked_drops.linked": "Always Linked", + "randomizer.entrance.linked_drops.independent": "Independent", + "randomizer.gameoptions.nobgm": "Disable Music & MSU-1", "randomizer.gameoptions.quickswap": "L/R Quickswapping", "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", diff --git a/resources/app/gui/randomize/dungeon/widgets.json b/resources/app/gui/randomize/dungeon/widgets.json index 268fee35..89addb90 100644 --- a/resources/app/gui/randomize/dungeon/widgets.json +++ b/resources/app/gui/randomize/dungeon/widgets.json @@ -14,11 +14,11 @@ }, "key_logic_algorithm": { "type": "selectbox", - "default": "default", + "default": "partial", "options": [ - "default", "partial", - "strict" + "strict", + "dangerous" ], "config": { "padx": [20,0], diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json index 809dbac5..61b8609f 100644 --- a/resources/app/gui/randomize/entrando/widgets.json +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -35,6 +35,26 @@ "padx": [20,0] } }, + "skullwoods": { + "type": "selectbox", + "options": [ + "original", + "restricted", + "loose", + "followlinked" + ], + "config": { + "width": 30 + } + }, + "linked_drops": { + "type": "selectbox", + "options": [ + "unset", + "linked", + "independent" + ] + }, "openpyramid": { "type": "selectbox", "options": [ diff --git a/resources/ci/common/common.py b/resources/ci/common/common.py index 5329c768..17b37434 100644 --- a/resources/ci/common/common.py +++ b/resources/ci/common/common.py @@ -19,7 +19,7 @@ global FILESIZE_CHECK # windows: 2022, 2019 # macos: 14, 13, 12, 11 DEFAULT_EVENT = "event" -DEFAULT_REPO_SLUG = "miketrethewey/ALttPDoorRandomizer" +DEFAULT_REPO_SLUG = "codemann8/ALttPDoorRandomizer" FILENAME_CHECKS = [ "DungeonRandomizer", "Gui", diff --git a/resources/ci/common/prepare_release.py b/resources/ci/common/prepare_release.py index 8bab9de4..c4b4bce3 100644 --- a/resources/ci/common/prepare_release.py +++ b/resources/ci/common/prepare_release.py @@ -71,7 +71,7 @@ if len(BUILD_FILENAMES) > 0: # clean the git slate git_clean() - # mv dirs from source code + # mv dirs from source code dirs = [ os.path.join(".",".git"), os.path.join(".",".github"), @@ -98,8 +98,8 @@ if len(BUILD_FILENAMES) > 0: if "linux" in env["OS_NAME"] or "ubuntu" in env["OS_NAME"] or "mac" in env["OS_NAME"] or "osx" in env["OS_NAME"]: os.chmod(os.path.join(".",BUILD_FILENAME),0o755) - # .zip if windows - # .tar.gz otherwise + # .zip if windows + # .tar.gz otherwise if len(BUILD_FILENAMES) > 1: # ZIP_FILENAME = os.path.join("..","deploy",env["REPO_NAME"]) ZIP_FILENAME = os.path.join("..","deploy","ALttPOverworldRandomizer") @@ -112,7 +112,7 @@ if len(BUILD_FILENAMES) > 0: make_archive(ZIP_FILENAME,"gztar") ZIP_FILENAME += ".tar.gz" - # mv dirs back + # mv dirs back for dir in dirs: if os.path.isdir(os.path.join("..","build",dir)): move( diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 33fd8f87..51c852d7 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -160,6 +160,8 @@ class CustomSettings(object): args.bombbag[p] = get_setting(settings['bombbag'], args.bombbag[p]) args.shufflelinks[p] = get_setting(settings['shufflelinks'], args.shufflelinks[p]) args.shuffletavern[p] = get_setting(settings['shuffletavern'], args.shuffletavern[p]) + args.skullwoods[p] = get_setting(settings['skullwoods'], args.skullwoods[p]) + args.linked_drops[p] = get_setting(settings['linked_drops'], args.linked_drops[p]) args.restrict_boss_items[p] = get_setting(settings['restrict_boss_items'], args.restrict_boss_items[p]) args.overworld_map[p] = get_setting(settings['overworld_map'], args.overworld_map[p]) args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p]) @@ -335,6 +337,8 @@ class CustomSettings(object): settings_dict[p]['bombbag'] = world.bombbag[p] settings_dict[p]['shufflelinks'] = world.shufflelinks[p] settings_dict[p]['shuffletavern'] = world.shuffletavern[p] + settings_dict[p]['skullwoods'] = world.skullwoods[p] + settings_dict[p]['linked_drops'] = world.linked_drops[p] settings_dict[p]['overworld_map'] = world.overworld_map[p] settings_dict[p]['pseudoboots'] = world.pseudoboots[p] settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p] diff --git a/source/classes/constants.py b/source/classes/constants.py index 4d170746..14739fb8 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -103,6 +103,8 @@ SETTINGSTOPROCESS = { "shuffleganon": "shuffleganon", "shufflelinks": "shufflelinks", "shuffletavern": "shuffletavern", + "skullwoods": "skullwoods", + "linked_drops": "linked_drops", "openpyramid": "openpyramid", "overworld_map": "overworld_map", }, diff --git a/source/enemizer/EnemyLogic.py b/source/enemizer/EnemyLogic.py index 67f6eb38..df8a21c5 100644 --- a/source/enemizer/EnemyLogic.py +++ b/source/enemizer/EnemyLogic.py @@ -201,8 +201,7 @@ def find_possible_rules(vln_list, used_resources, world, player): flag = flag if flag < 0 else (hits + used_resources['Magic'] * 7 / 8) optional_clears[vln_sub_list].append((byrna_rule(world, player, flag), resources)) elif damage_type == 'FireRod' and hits + used_resources['Magic'] <= 160: - flag = min(vln[damage_type] for vln in vln_list.values()) - flag = flag if flag < 0 else (hits + used_resources['Magic']) + flag = hits + used_resources['Magic'] optional_clears[vln_sub_list].append((fire_rod_rule(world, player, flag), resources)) elif damage_type == 'IceRod' and hits + used_resources['Magic'] <= 160: flag = min(vln[damage_type] for vln in vln_list.values()) diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml index 65a96ab1..04be92fa 100644 --- a/source/enemizer/enemy_deny.yaml +++ b/source/enemizer/enemy_deny.yaml @@ -34,8 +34,9 @@ UwGeneralDeny: - [ 0x001e, 4, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Red Bari 4" - [ 0x001e, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 1" - [ 0x001e, 6, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 2" - - [ 0x001f, 0, [ "RollerHorizontalRight" ] ] #"Ice Palace - Big Key View - Pengator 1" + - [0x001f, 0, ["RollerHorizontalRight", "RollerHorizontalLeft"]] #"Ice Palace - Big Key View - Pengator 1" - [ 0x001f, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [0x001f, 4, ["RollerVerticalDown", "RollerVerticalUp"]] # too large, hits the door - [ 0x0021, 3, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 2" - [ 0x0021, 4, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 3" - [ 0x0024, 6, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots @@ -54,10 +55,12 @@ UwGeneralDeny: - [0x0028, 2, ["Raven", "Poe", "GreenZirro", "BlueZirro", "Swamola", "Zora"]] - [0x0028, 3, ["Raven", "Poe", "GreenZirro", "BlueZirro", "Swamola", "Zora"]] - [0x0028, 4, ["Raven", "Poe", "GreenZirro", "BlueZirro", "Swamola", "Zora", "RollerVerticalUp"]] #"Swamp Palace - Entrance Ledge - Spike Trap" - - [ 0x002a, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] #"Palace of Darkness - Arena Main - Hardhat Beetle 1" - - [ 0x002a, 3, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 2" + - [0x002a, 1, ["RollerVerticalUp", "RollerVerticalDown"]] + - [0x002a, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Chainchomp"]] #"Palace of Darkness - Arena Main - Hardhat Beetle 1" + - [0x002a, 3, ["Statue", "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper", "Chainchomp"]] #"Palace of Darkness - Arena Main - Hardhat Beetle 2" - [ 0x002a, 4, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper", "RollerHorizontalRight", "RollerHorizontalLeft"]] - [ 0x002a, 6, [ "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 5" + - [0x002a, 7, ["RollerVerticalUp", "RollerVerticalDown", "Chainchomp"]] - [ 0x002b, 5, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Fairies - Red Bari 2" - [ 0x002e, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 1" - [ 0x002e, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 2" @@ -80,6 +83,7 @@ UwGeneralDeny: - [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" ]] + - [ 0x003b, 4, ["RollerVerticalUp", "RollerVerticalDown"]] - [ 0x003c, 0, ["BigSpike"]] - [ 0x003c, 1, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hookshot Cave - Blue Bari 1" - [ 0x003c, 2, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hookshot Cave - Blue Bari 2" @@ -118,7 +122,7 @@ UwGeneralDeny: - [ 0x0049, 5, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 2" - [ 0x0049, 7, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 4" - [ 0x0049, 8, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Skull Woods - Bari Pits - Gibdo 5" - - [ 0x004b, 0, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Palace of Darkness - Mimics 1 - Red Eyegore" + - [0x004b, 0, ["Beamos", "AntiFairyCircle", "Bumper", "BigSpike"]] #"Palace of Darkness - Mimics 1 - Red Eyegore" - [ 0x004b, 1, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Warp Hint - Antifairy 1" - [ 0x004b, 5, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 1" - [ 0x004b, 6, [ "AntiFairyCircle", "BigSpike" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 2" @@ -129,6 +133,7 @@ UwGeneralDeny: - [ 0x0050, 0, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Guard" - [ 0x0050, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 1" - [ 0x0050, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 2" + - [0x0051, 2, ["Zoro"]] # Zoro clips off and doesn't return - [ 0x0052, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Guard" - [ 0x0052, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 1" - [ 0x0052, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 2" @@ -151,7 +156,7 @@ UwGeneralDeny: - [ 0x0057, 13, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue", "BigSpike"]] #"Skull Woods - Big Key Room - Blue Bari 1" - [ 0x0057, 14, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue", "BigSpike"]] #"Skull Woods - Big Key Room - Blue Bari 2" - [ 0x0058, 0, ["Statue"]] - - [ 0x0058, 1, ["Statue"]] + - [0x0058, 1, ["Statue", "RollerHorizontalRight", "RollerHorizontalLeft"]] - [ 0x0058, 2, ["Statue"]] - [ 0x0058, 3, ["Statue"]] - [ 0x0058, 4, ["Statue"]] @@ -205,7 +210,7 @@ UwGeneralDeny: - [ 0x007b, 7, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - DMs Room - Hardhat Beetle" - [ 0x007c, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Fire Bar (Counterclockwise)" - [ 0x007c, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Spike Trap" - - [ 0x007c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue"]] #"Ganon's Tower - Randomizer Room - Fire Bar (Clockwise)" + - [ 0x007c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue", "SpikeBlock"]] #"Ganon's Tower - Randomizer Room - Fire Bar (Clockwise)" - [ 0x007c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Hardhat Beetle" - [ 0x007d, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 1" - [ 0x007d, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 2" @@ -267,6 +272,7 @@ UwGeneralDeny: - [ 0x009c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 3" - [ 0x009c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 4" - [ 0x009c, 5, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 5" + - [0x009d, 2, ["AntiFairyCircle"]] - [ 0x009d, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Compass Room - Gibdo 2" - [ 0x009d, 6, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Ganon's Tower - Compass Room - Blue Bari 1" - [ 0x009d, 7, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Compass Room - Blue Bari 2" @@ -288,7 +294,7 @@ UwGeneralDeny: - [ 0x00ab, 7, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Thieves' Town - Spike Dodge - Spike Trap 6" - [ 0x00ae, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 1" - [ 0x00ae, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 2" - - [ 0x00af, 0, [ "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Ice Clock - Fire Bar (Clockwise)" + - [0x00af, 0, ["RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Lynel"]] #"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" @@ -332,6 +338,7 @@ UwGeneralDeny: - [ 0x00cb, 9, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] - [ 0x00cb, 10, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] - [ 0x00cb, 11, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [0x00cc, 6, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper"]] # prevent access around - [ 0x00cc, 8, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) - [ 0x00cc, 12, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) - [ 0x00ce, 0, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCCW", "Bumper", "Chainchomp"]] #"Ice Palace - Over Boss - top - Red Bari 1" @@ -389,7 +396,7 @@ UwGeneralDeny: - [ 0x00e7, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 6" - [ 0x00e7, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 7" - [ 0x00e8, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 1" - - [ 0x00e8, 1, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 2" + - [ 0x00e8, 2, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 2" - [ 0x00ee, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 1" - [ 0x00ee, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 2" - [ 0x00ee, 2, [ "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 3" @@ -402,7 +409,7 @@ UwGeneralDeny: - [ 0x00f1, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 4" - [ 0x00f1, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 5" - [ 0x00f1, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 6" - - [ 0x00fd, 0, [ "Bumper" ] ] + - [0x00fd, 0, ["Bumper", "AntiFairyCircle"]] - [ 0x0107, 1, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] - [ 0x0107, 2, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] - [0x010b, 6, ["RollerHorizontalRight"]] @@ -449,6 +456,8 @@ OwGeneralDeny: - [0x5e, 18, ["Gibo"]] # kiki eating Gibo - [0x5e, 19, ["Gibo"]] # kiki eating Gibo - [0x5e, 20, ["Gibo"]] # kiki eating Gibo + - [0x62, 1, ["RollerVerticalUp", "RollerVerticalDown"]] # hard to avoid roller around hammer pegs + - [0x62, 3, ["RollerVerticalUp", "RollerVerticalDown"]] # hard to avoid roller around hammer pegs - [0x77, 1, ["Bumper"]] # soft-lock potential near ladder - [0x7f, 1, ["Bumper"]] # soft-lock potential near ladder UwEnemyDrop: diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 37ea8404..52bbf9d5 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -26,6 +26,7 @@ class EntrancePool(object): self.decoupled_exits = [] self.original_entrances = set() self.original_exits = set() + self.same_world_restricted = {} self.world = world self.player = player @@ -59,7 +60,6 @@ def link_entrances_new(world, player): avail_pool.entrances = set(i_drop_map.keys()).union(i_entrance_map.keys()).union(i_single_ent_map.keys()) avail_pool.exits = set(i_entrance_map.values()).union(i_drop_map.values()).union(i_single_ent_map.values()) avail_pool.inverted = world.mode[player] == 'inverted' - avail_pool.assumed_loose_caves = world.shuffle[player] == 'district' inverted_substitution(avail_pool, avail_pool.entrances, True, True) inverted_substitution(avail_pool, avail_pool.exits, False, True) avail_pool.original_entrances.update(avail_pool.entrances) @@ -99,8 +99,13 @@ def link_entrances_new(world, player): if mode not in modes: raise RuntimeError(f'Shuffle mode {mode} is not yet supported') mode_cfg = copy.deepcopy(modes[mode]) + + if world.linked_drops[player] != 'unset': + mode_cfg['keep_drops_together'] = 'on' if world.linked_drops[player] == 'linked' else 'off' + avail_pool.swapped = mode_cfg['undefined'] == 'swap' avail_pool.keep_drops_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True + avail_pool.assumed_loose_caves = not avail_pool.keep_drops_together and world.shuffle[player] == 'district' avail_pool.coupled = mode_cfg['decoupled'] != 'on' if 'decoupled' in mode_cfg else True if avail_pool.is_standard(): do_standard_connections(avail_pool) @@ -108,11 +113,7 @@ def link_entrances_new(world, player): for pool_name, pool in pool_list.items(): special_shuffle = pool['special'] if 'special' in pool else None if special_shuffle == 'drops': - holes, targets = find_entrances_and_targets_drops(avail_pool, pool['entrances']) - if avail_pool.swapped: - connect_swapped(holes, targets, avail_pool) - else: - connect_random(holes, targets, avail_pool) + handle_skull_woods_drops(avail_pool, pool['entrances'], mode_cfg) elif special_shuffle == 'normal_drops': cross_world = mode_cfg['cross_world'] == 'on' if 'cross_world' in mode_cfg else False do_holes_and_linked_drops(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, cross_world) @@ -143,20 +144,7 @@ def link_entrances_new(world, player): entrances, exits = find_entrances_and_exits(avail_pool, entrances+drops) do_main_shuffle(entrances, exits, avail_pool, mode_cfg) elif special_shuffle == 'skull': - entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) - rem_ent = None - if avail_pool.world.shuffle[avail_pool.player] in ['dungeonssimple', 'simple', 'restricted'] \ - and not avail_pool.world.is_tile_swapped(0x00, avail_pool.player): - rem_ent = random.choice(['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']) - entrances.remove(rem_ent) - exits.remove('Skull Woods First Section Exit') - connect_random(entrances, exits, avail_pool, True) - entrances, exits = [rem_ent], ['Skull Woods First Section Exit'] - if avail_pool.swapped: - connect_swapped(entrances, exits, avail_pool, True) - else: - connect_random(entrances, exits, avail_pool, True) - avail_pool.skull_handled = True + handle_skull_woods_entrances(avail_pool, pool['entrances']) else: entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) do_main_shuffle(entrances, exits, avail_pool, mode_cfg) @@ -253,12 +241,21 @@ def do_main_shuffle(entrances, exits, avail, mode_def): # mandatory exits rem_entrances, rem_exits = set(), set() if not cross_world: + determine_dungeon_restrictions(avail) mand_exits = figure_out_must_exits_same_world(entrances, exits, avail) must_exit_lw, must_exit_dw, lw_entrances, dw_entrances, multi_exit_caves = mand_exits + + def do_world_mandatory(world_entrances, world_must_exits, restriction): + nonlocal multi_exit_caves + candidates = filter_restricted_caves(multi_exit_caves, restriction, avail) + other_candidates = [x for x in multi_exit_caves if x not in candidates] # remember those not passed in + do_mandatory_connections(avail, world_entrances, candidates, world_must_exits) + multi_exit_caves = (other_candidates + candidates) if other_candidates else candidates # rebuild list from the candidates and those not passed + if not avail.inverted: - do_mandatory_connections(avail, lw_entrances, multi_exit_caves, must_exit_lw) + do_world_mandatory(lw_entrances, must_exit_lw, 'LightWorld') else: - do_mandatory_connections(avail, dw_entrances, multi_exit_caves, must_exit_dw) + do_world_mandatory(dw_entrances, must_exit_dw, 'DarkWorld') new_mec = [] for cave_option in multi_exit_caves: @@ -272,9 +269,9 @@ def do_main_shuffle(entrances, exits, avail, mode_def): multi_exit_caves = new_mec if not avail.inverted: - do_mandatory_connections(avail, dw_entrances, multi_exit_caves, must_exit_dw) + do_world_mandatory(dw_entrances, must_exit_dw, 'DarkWorld') else: - do_mandatory_connections(avail, lw_entrances, multi_exit_caves, must_exit_lw) + do_world_mandatory(lw_entrances, must_exit_lw, 'LightWorld') rem_entrances.update(lw_entrances) rem_entrances.update(dw_entrances) else: @@ -367,6 +364,10 @@ def do_main_shuffle(entrances, exits, avail, mode_def): if bonk_fairy_exception(x): lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x) do_same_world_connectors(lw_entrances, dw_entrances, multi_exit_caves, avail) + if avail.world.doorShuffle[avail.player] != 'vanilla': + determine_dungeon_restrictions(avail) + possibles = figure_out_possible_exits(rem_exits) + do_same_world_possible_connectors(lw_entrances, dw_entrances, possibles, avail) unused_entrances.update(lw_entrances) unused_entrances.update(dw_entrances) else: @@ -519,12 +520,16 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world): if not avail.keep_drops_together: targets = [avail.one_way_map[x] for x in holes_to_shuffle] - connect_random(holes_to_shuffle, targets, avail) + if avail.swapped: + connect_swapped(holes_to_shuffle, targets, avail) + else: + connect_random(holes_to_shuffle, targets, avail) remove_from_list(entrances, holes_to_shuffle) remove_from_list(exits, targets) return # we're done here hole_entrances, hole_targets = [], [] + leftover_hole_entrances, leftover_hole_targets = [], [] for hole in drop_map: if hole in avail.original_entrances and hole in linked_drop_map: linked_entrance = linked_drop_map[hole] @@ -534,39 +539,44 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world): target_drop = avail.one_way_map[hole] if target_exit in exits and target_drop in exits: hole_targets.append((target_exit, target_drop)) + else: + if hole in avail.original_entrances and hole in entrances: + leftover_hole_entrances.append(hole) + if drop_map[hole] in exits: + leftover_hole_targets.append(drop_map[hole]) random.shuffle(hole_entrances) - if not cross_world and 'Sanctuary Grave' in holes_to_shuffle: - hc = avail.world.get_entrance('Hyrule Castle Exit (South)', avail.player) - is_hc_in_dw = avail.world.mode[avail.player] == 'inverted' - if hc.connected_region: - is_hc_in_dw = hc.connected_region.type == RegionType.DarkWorld - chosen_entrance = None - if is_hc_in_dw: - if avail.swapped: - chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances and e[0] != 'Sanctuary') + if not cross_world: + if 'Sanctuary Grave' in holes_to_shuffle: + hc = avail.world.get_entrance('Hyrule Castle Exit (South)', avail.player) + is_hc_in_opp_world = avail.inverted + if hc.connected_region: + opp_world = RegionType.LightWorld if avail.inverted else RegionType.DarkWorld + is_hc_in_opp_world = hc.connected_region.type == opp_world + start_world_entrances = DW_Entrances if avail.inverted else LW_Entrances + opp_world_entrances = LW_Entrances if avail.inverted else DW_Entrances + chosen_entrance = None + if is_hc_in_opp_world: + if avail.swapped: + chosen_entrance = next(e for e in hole_entrances if e[0] in opp_world_entrances and e[0] != 'Sanctuary') + if not chosen_entrance: + chosen_entrance = next((e for e in hole_entrances if e[0] in opp_world_entrances), None) if not chosen_entrance: - chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances) - if not chosen_entrance: - if avail.swapped: - chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances and e[0] != 'Sanctuary') - if not chosen_entrance: - chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances) - if chosen_entrance: - hole_entrances.remove(chosen_entrance) - sanc_interior = next(target for target in hole_targets if target[0] == 'Sanctuary Exit') - hole_targets.remove(sanc_interior) - connect_two_way(chosen_entrance[0], sanc_interior[0], avail) # two-way exit - connect_entrance(chosen_entrance[1], sanc_interior[1], avail) # hole - remove_from_list(entrances, [chosen_entrance[0], chosen_entrance[1]]) - remove_from_list(exits, [sanc_interior[0], sanc_interior[1]]) - if avail.swapped and drop_map[chosen_entrance[1]] != sanc_interior[1]: - swap_ent, swap_ext = connect_swap(chosen_entrance[0], sanc_interior[0], avail) - swap_drop, swap_tgt = connect_swap(chosen_entrance[1], sanc_interior[1], avail) - hole_entrances.remove((swap_ent, swap_drop)) - hole_targets.remove((swap_ext, swap_tgt)) - remove_from_list(entrances, [swap_ent, swap_drop]) - remove_from_list(exits, [swap_ext, swap_tgt]) + if avail.swapped: + chosen_entrance = next(e for e in hole_entrances if e[0] in start_world_entrances and e[0] != 'Sanctuary') + if not chosen_entrance: + chosen_entrance = next(e for e in hole_entrances if e[0] in start_world_entrances) + + if chosen_entrance: + connect_hole_via_interior(chosen_entrance, 'Sanctuary Exit', hole_entrances, hole_targets, entrances, exits, avail) + + sw_world_entrances = DW_Entrances if not avail.world.is_tile_swapped(0x00, avail.player) else LW_Entrances + if 'Skull Woods First Section Hole (North)' in holes_to_shuffle: + chosen_entrance = next(e for e in hole_entrances if e[0] in sw_world_entrances) + connect_hole_via_interior(chosen_entrance, 'Skull Woods First Section Exit', hole_entrances, hole_targets, entrances, exits, avail) + if 'Skull Woods Second Section Hole' in holes_to_shuffle: + chosen_entrance = next(e for e in hole_entrances if e[0] in sw_world_entrances) + connect_hole_via_interior(chosen_entrance, 'Skull Woods Second Section Exit (East)', hole_entrances, hole_targets, entrances, exits, avail) random.shuffle(hole_targets) while len(hole_entrances): @@ -588,6 +598,31 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world): remove_from_list(entrances, [swap_ent, swap_drop]) remove_from_list(exits, [swap_ext, swap_tgt]) + if leftover_hole_entrances and leftover_hole_targets: + remove_from_list(entrances, leftover_hole_entrances) + remove_from_list(exits, leftover_hole_targets) + if avail.swapped: + connect_swapped(leftover_hole_entrances, leftover_hole_targets, avail) + else: + connect_random(leftover_hole_entrances, leftover_hole_targets, avail) + + +def connect_hole_via_interior(chosen_entrance, interior, hole_entrances, hole_targets, entrances, exits, avail): + hole_entrances.remove(chosen_entrance) + interior = next(target for target in hole_targets if target[0] == interior) + hole_targets.remove(interior) + connect_two_way(chosen_entrance[0], interior[0], avail) + connect_entrance(chosen_entrance[1], interior[1], avail) + remove_from_list(entrances, [chosen_entrance[0], chosen_entrance[1]]) + remove_from_list(exits, [interior[0], interior[1]]) + if avail.swapped and drop_map[chosen_entrance[1]] != interior[1]: + swap_ent, swap_ext = connect_swap(chosen_entrance[0], interior[0], avail) + swap_drop, swap_tgt = connect_swap(chosen_entrance[1], interior[1], avail) + hole_entrances.remove((swap_ent, swap_drop)) + hole_targets.remove((swap_ext, swap_tgt)) + remove_from_list(entrances, [swap_ent, swap_drop]) + remove_from_list(exits, [swap_ext, swap_tgt]) + def do_dark_sanc(entrances, exits, avail): if avail.world.is_dark_chapel_start(avail.player): @@ -867,14 +902,15 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo return found_entrances -def figure_out_connectors(exits, avail): +def figure_out_connectors(exits, avail, cross_world=True): multi_exit_caves = [] cave_list = list(Connector_List) - if avail.assumed_loose_caves: - sw_list = ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'] - random.shuffle(sw_list) - cave_list.extend([sw_list]) - cave_list.extend([[entrance_map[e]] for e in linked_drop_map.values() if 'Inverted ' not in e]) + if avail.assumed_loose_caves or (not avail.skull_handled and (cross_world or not avail.world.is_tile_swapped(0x00, avail.player))): + skull_connector = [x for x in ['Skull Woods Second Section Exit (West)', 'Skull Woods Second Section Exit (East)'] if x in exits] + cave_list.extend(skull_connector) + if avail.assumed_loose_caves or not avail.keep_drops_together: + cave_list.extend([[entrance_map[e]] for e in linked_drop_map.values() if 'Inverted ' not in e and 'Skull Woods ' not in e]) + for item in cave_list: if all(x in exits for x in item): remove_from_list(exits, item) @@ -1055,12 +1091,49 @@ def must_exits_helper(avail): return must_exit_lw, must_exit_dw +def figure_out_possible_exits(exits): + possible_multi_exit_caves = [] + for item in doors_possible_connectors: + if item in exits: + remove_from_list(exits, item) + possible_multi_exit_caves.append(item) + return possible_multi_exit_caves + + +def determine_dungeon_restrictions(avail): + check_for_hc = (avail.is_standard() or avail.world.doorShuffle[avail.player] != 'vanilla') + for check in dungeon_restriction_checks: + dungeon_exits, drop_regions = check + if check_for_hc and any('Hyrule Castle' in x for x in dungeon_exits): + avail.same_world_restricted.update({x: 'LightWorld' for x in dungeon_exits}) + else: + restriction = None + for x in dungeon_exits: + ent = avail.world.get_entrance(x, avail.player) + if ent.connected_region: + if ent.connected_region.type == RegionType.LightWorld: + restriction = 'LightWorld' + elif ent.connected_region.type == RegionType.DarkWorld: + restriction = 'DarkWorld' + # Holes only restrict + for x in drop_regions: + region = avail.world.get_region(x, avail.player) + ent = next((ent for ent in region.entrances if ent.parent_region and ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]), None) + if ent: + if ent.parent_region.type == RegionType.LightWorld and not avail.inverted: + restriction = 'LightWorld' + elif ent.parent_region.type == RegionType.DarkWorld and avail.inverted: + restriction = 'DarkWorld' + if restriction: + avail.same_world_restricted.update({x: restriction for x in dungeon_exits}) + + def figure_out_must_exits_same_world(entrances, exits, avail): lw_entrances, dw_entrances = [], [] for x in entrances: lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x) - multi_exit_caves = figure_out_connectors(exits, avail) + multi_exit_caves = figure_out_connectors(exits, avail, False) must_exit_lw, must_exit_dw = must_exits_helper(avail) must_exit_lw = must_exit_filter(avail, must_exit_lw, lw_entrances) @@ -1069,8 +1142,26 @@ def figure_out_must_exits_same_world(entrances, exits, avail): return must_exit_lw, must_exit_dw, lw_entrances, dw_entrances, multi_exit_caves +def filter_restricted_caves(multi_exit_caves, restriction, avail): + candidates = [] + for cave in multi_exit_caves: + if all(x not in avail.same_world_restricted or avail.same_world_restricted[x] == restriction for x in cave): + candidates.append(cave) + return candidates + + +def flatten(list_to_flatten): + ret = [] + for item in list_to_flatten: + if isinstance(item, tuple): + ret.extend(item) + else: + ret.append(item) + return ret + + def figure_out_must_exits_cross_world(entrances, exits, avail): - multi_exit_caves = figure_out_connectors(exits, avail) + multi_exit_caves = figure_out_connectors(exits, avail, True) must_exit_lw, must_exit_dw = must_exits_helper(avail) must_exit = must_exit_filter(avail, must_exit_lw + must_exit_dw, entrances) @@ -1083,7 +1174,7 @@ def do_same_world_connectors(lw_entrances, dw_entrances, caves, avail): random.shuffle(dw_entrances) random.shuffle(caves) while caves: - # connect highest exit count caves first, prevent issue where we have 2 or 3 exits across worlds left to fill + # connect highest-exit-count caves first, prevent issue where we have 2 or 3 exits across worlds left to fill cave_candidate = (None, 0) for i, cave in enumerate(caves): if isinstance(cave, str): @@ -1092,12 +1183,19 @@ def do_same_world_connectors(lw_entrances, dw_entrances, caves, avail): cave_candidate = (i, len(cave)) cave = caves.pop(cave_candidate[0]) - target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances if isinstance(cave, str): cave = (cave,) + target, restriction = None, None + if any(x in avail.same_world_restricted for x in cave): + restriction = next(avail.same_world_restricted[x] for x in cave if x in avail.same_world_restricted) + target = lw_entrances if restriction == 'LightWorld' else dw_entrances + if target is None: + target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances # check if we can still fit the cave into our target group if len(target) < len(cave): + if restriction: + raise Exception('Not enough entrances for restricted cave, algorithm needs revision (main)') # need to use other set target = lw_entrances if target is dw_entrances else dw_entrances @@ -1111,6 +1209,18 @@ def do_same_world_connectors(lw_entrances, dw_entrances, caves, avail): connect_two_way(target.pop(), ext, avail) +def do_same_world_possible_connectors(lw_entrances, dw_entrances, possibles, avail): + random.shuffle(possibles) + while possibles: + possible = possibles.pop() + target = None + if possible in avail.same_world_restricted: + target = lw_entrances if avail.same_world_restricted[possible] == 'LightWorld' else dw_entrances + if target is None: + target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances + connect_two_way(target.pop(), possible, avail) + determine_dungeon_restrictions(avail) + def do_cross_world_connectors(entrances, caves, avail): random.shuffle(entrances) random.shuffle(caves) @@ -1158,6 +1268,44 @@ def do_cross_world_connectors(entrances, caves, avail): break +def handle_skull_woods_drops(avail, pool, mode_cfg): + skull_woods = avail.world.skullwoods[avail.player] + if skull_woods in ['restricted', 'loose']: + for drop in pool: + target = drop_map[drop] + connect_entrance(drop, target, avail) + elif skull_woods == 'original': + holes, targets = find_entrances_and_targets_drops(avail, pool) + if avail.swapped: + connect_swapped(holes, targets, avail) + else: + connect_random(holes, targets, avail) + elif skull_woods == 'followlinked': + keep_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True + if keep_together: + for drop in ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)']: + target = drop_map[drop] + connect_entrance(drop, target, avail) + + +def handle_skull_woods_entrances(avail, pool): + skull_woods = avail.world.skullwoods[avail.player] + if skull_woods in ['restricted', 'original']: + entrances, exits = find_entrances_and_exits(avail, pool) + if avail.world.shuffle[avail.player] in ['dungeonssimple', 'simple', 'restricted'] \ + and not avail.world.is_tile_swapped(0x00, avail.player): + rem_ent = random.choice(['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']) + entrances.remove(rem_ent) + exits.remove('Skull Woods First Section Exit') + connect_random(entrances, exits, avail, True) + entrances, exits = [rem_ent], ['Skull Woods First Section Exit'] + if avail.swapped: + connect_swapped(entrances, exits, avail, True) + else: + connect_random(entrances, exits, avail, True) + avail.skull_handled = True + + def do_fixed_shuffle(avail, entrance_list): max_size = 0 options = {} @@ -1214,7 +1362,6 @@ def do_same_world_shuffle(avail, pool_def): multi_exit = pool_def['connectors'] # complete_entrance_set = set() lw_entrances, dw_entrances, multi_exits_caves, other_exits = [], [], [], [] - hyrule_forced = None single_entrances, single_exits = find_entrances_and_exits(avail, single_exit) other_exits.extend(single_exits) @@ -1224,11 +1371,8 @@ def do_same_world_shuffle(avail, pool_def): for option in multi_exit: multi_entrances, multi_exits = find_entrances_and_exits(avail, option) # complete_entrance_set.update(multi_entrances) - if avail.is_sanc_forced_in_hc() and any(x in multi_entrances for x in ['Hyrule Castle Entrance (South)', - 'Hyrule Castle Entrance (East)', - 'Hyrule Castle Entrance (West)']): - hyrule_forced = [multi_exits] - multi_exits_caves.append(multi_exits) + if multi_exits: + multi_exits_caves.append(multi_exits) for x in multi_entrances: (dw_entrances, lw_entrances)[x in LW_Entrances].append(x) @@ -1236,13 +1380,16 @@ def do_same_world_shuffle(avail, pool_def): must_exit_lw = must_exit_filter(avail, must_exit_lw, lw_entrances) must_exit_dw = must_exit_filter(avail, must_exit_dw, dw_entrances) - do_mandatory_connections(avail, lw_entrances, multi_exits_caves, must_exit_lw) - if hyrule_forced and hyrule_forced[0] in multi_exits_caves: - remove_from_list(multi_exits_caves, hyrule_forced) - do_mandatory_connections(avail, dw_entrances, multi_exits_caves, must_exit_dw) - multi_exits_caves.append(hyrule_forced[0]) - else: - do_mandatory_connections(avail, dw_entrances, multi_exits_caves, must_exit_dw) + def do_world_mandatory(world_entrances, world_must_exits, restriction): + nonlocal multi_exits_caves + candidates = filter_restricted_caves(multi_exits_caves, restriction, avail) + other_candidates = [x for x in multi_exits_caves if x not in candidates] # remember those not passed in + do_mandatory_connections(avail, world_entrances, candidates, world_must_exits) + multi_exits_caves = (other_candidates + candidates) if other_candidates else candidates # rebuild list from the candidates and those not passed + + determine_dungeon_restrictions(avail) + do_world_mandatory(lw_entrances, must_exit_lw, 'LightWorld') + do_world_mandatory(dw_entrances, must_exit_dw, 'DarkWorld') # connect caves random.shuffle(lw_entrances) @@ -1250,19 +1397,20 @@ def do_same_world_shuffle(avail, pool_def): random.shuffle(multi_exits_caves) while multi_exits_caves: cave_candidate = (None, 0) - if hyrule_forced and hyrule_forced[0] in multi_exits_caves: - multi_exits_caves.remove(hyrule_forced[0]) - cave = hyrule_forced[0] - hyrule_forced = None - target = lw_entrances - else: - for i, cave in enumerate(multi_exits_caves): - if len(cave) > cave_candidate[1]: - cave_candidate = (i, len(cave)) - cave = multi_exits_caves.pop(cave_candidate[0]) - target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances + for i, cave in enumerate(multi_exits_caves): + if len(cave) > cave_candidate[1]: + cave_candidate = (i, len(cave)) + cave = multi_exits_caves.pop(cave_candidate[0]) + target, restriction = None, None + if any(x in avail.same_world_restricted for x in cave): + restriction = next(avail.same_world_restricted[x] for x in cave if x in avail.same_world_restricted) + target = lw_entrances if restriction == 'LightWorld' else dw_entrances + if target is None: + target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances if len(target) < len(cave): # swap because we ran out of entrances in that world + if restriction: + raise Exception('Not enough entrances for restricted cave, algorithm needs revision (dungeonsfull)') target = lw_entrances if target is dw_entrances else dw_entrances for ext in cave: @@ -1670,7 +1818,7 @@ def connect_swap(entrance, exit, avail): swap_entrance = next(e for e, x in avail.combine_map.items() if x == exit) if swap_entrance in ['Pyramid Entrance', 'Pyramid Hole'] and avail.world.is_tile_swapped(0x1b, avail.player): swap_entrance = 'Inverted ' + swap_entrance - if entrance in entrance_map: + if swap_exit in entrance_map.values(): connect_two_way(swap_entrance, swap_exit, avail) else: connect_entrance(swap_entrance, swap_exit, avail) @@ -1816,6 +1964,12 @@ modes = { 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] }, + 'skull_layout': { + 'special': 'vanilla', + 'condition': '', + 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)'] + }, 'single_entrance_dungeon': { 'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace', 'Ganons Tower'] @@ -1849,13 +2003,15 @@ modes = { 'sanc_flag': 'light_world', # always light world flag 'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Agahnims Tower', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace', - 'Ganons Tower'], + 'Ganons Tower', 'Desert Palace Entrance (North)', 'Dark Death Mountain Ledge (East)'], 'connectors': [['Hyrule Castle Entrance (South)', 'Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)'], ['Desert Palace Entrance (South)', 'Desert Palace Entrance (East)', - 'Desert Palace Entrance (West)', 'Desert Palace Entrance (North)'], + 'Desert Palace Entrance (West)'], ['Turtle Rock', 'Turtle Rock Isolated Ledge Entrance', - 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)']] + 'Dark Death Mountain Ledge (West)'], + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', + 'Skull Woods First Section Door']] }, } }, @@ -1878,7 +2034,8 @@ modes = { 'special': 'normal_drops', 'entrances': ['Hyrule Castle Secret Entrance Drop', 'Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', - 'Sanctuary Grave', 'Pyramid Hole'] + 'Sanctuary Grave', 'Pyramid Hole', + 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] }, 'fixed_non_items': { 'special': 'vanilla', @@ -1990,7 +2147,8 @@ modes = { 'special': 'normal_drops', 'entrances': ['Hyrule Castle Secret Entrance Drop', 'Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', - 'Sanctuary Grave', 'Pyramid Hole'] + 'Sanctuary Grave', 'Pyramid Hole', + 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] }, 'fixed_non_items': { 'special': 'vanilla', @@ -2081,6 +2239,12 @@ modes = { 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] }, + 'skull_layout': { + 'special': 'vanilla', + 'condition': '', + 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)'] + }, 'single_entrance_dungeon': { 'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace', 'Ganons Tower'] @@ -2156,6 +2320,12 @@ modes = { 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] }, + 'skull_layout': { + 'special': 'vanilla', + 'condition': '', + 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)'] + }, 'single_entrance_dungeon': { 'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace', 'Ganons Tower'] @@ -2408,11 +2578,14 @@ linked_drop_map = { 'Lumberjack Tree Tree': 'Lumberjack Tree Cave', 'Sanctuary Grave': 'Sanctuary', 'Pyramid Hole': 'Pyramid Entrance', - 'Inverted Pyramid Hole': 'Inverted Pyramid Entrance' + 'Inverted Pyramid Hole': 'Inverted Pyramid Entrance', + + 'Skull Woods First Section Hole (North)': 'Skull Woods First Section Door', + 'Skull Woods Second Section Hole': 'Skull Woods Second Section Door (East)', } sw_linked_drop_map = { - 'Skull Woods Second Section Hole': 'Skull Woods Second Section Door (West)', + 'Skull Woods Second Section Hole': 'Skull Woods Second Section Door (East)', 'Skull Woods First Section Hole (North)': 'Skull Woods First Section Door', 'Skull Woods First Section Hole (West)': 'Skull Woods First Section Door', 'Skull Woods First Section Hole (East)': 'Skull Woods First Section Door' @@ -2490,7 +2663,6 @@ entrance_map = { 'Paradox Cave (Top)': 'Paradox Cave Exit (Top)' } - single_entrance_map = { 'Mimic Cave': 'Mimic Cave', 'Dark Death Mountain Fairy': 'Dark Death Mountain Healer Fairy', 'Dark Death Mountain Shop': 'Dark Death Mountain Shop', 'Spike Cave': 'Spike Cave', @@ -2601,6 +2773,19 @@ Dungeon_Exit_Set = { 'Turtle Rock Exit (Front)', 'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)' } +dungeon_restriction_checks = [ + (['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Sanctuary Exit'], ['Sewer Drop']), + (['Desert Palace Exit (South)', 'Desert Palace Exit (East)', 'Desert Palace Exit (West)', 'Desert Palace Exit (North)'], []), + (['Turtle Rock Exit (Front)', 'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)', 'Turtle Rock Ledge Exit (East)'], []), + (['Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', 'Skull Woods Final Section Exit'], + ['Skull Pinball', 'Skull Left Drop', 'Skull Pot Circle', 'Skull Back Drop']) + ] + +doors_possible_connectors = [ + 'Sanctuary Exit', 'Desert Palace Exit (North)', 'Skull Woods First Section Exit', + 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', 'Skull Woods Final Section Exit' +] + # Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups Must_Exit_Invalid_Connections = defaultdict(set) diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 77f6dd2e..e7da45df 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -173,6 +173,8 @@ def roll_settings(weights): ret.shuffleganon = get_choice_bool('shuffleganon') ret.shufflelinks = get_choice_bool('shufflelinks') ret.shuffletavern = get_choice_bool('shuffletavern') + ret.skullwoods = get_choice('skullwoods') + ret.linked_drops = get_choice('linked_drops') ret.crystals_gt = get_choice('tower_open') ret.crystals_ganon = get_choice('ganon_open') diff --git a/test/NewTestSuite.py b/test/NewTestSuite.py index cb4739e0..c5beba4f 100644 --- a/test/NewTestSuite.py +++ b/test/NewTestSuite.py @@ -136,7 +136,7 @@ if __name__ == "__main__": error[2] = error[2].split("\n") results["errors"].append(error) - with open("new-test-suite-success.txt", "w") as stream: + with open(os.path.join(LOGPATH, "new-test-suite-success.txt"), 'w') as stream: stream.write(str.join("\n", successes)) results["success"] = successes diff --git a/test/suite/default_key_logic.yaml b/test/suite/default_key_logic.yaml index e9f3e94b..39b5e43c 100644 --- a/test/suite/default_key_logic.yaml +++ b/test/suite/default_key_logic.yaml @@ -6,7 +6,7 @@ meta: players: 1 settings: 1: - key_logic_algorithm: default + key_logic_algorithm: dangerous keysanity: True crystals_needed_for_gt: 0 # to skip trash fill placements: