diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b12176d7..261dc125 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,15 +27,15 @@ jobs: strategy: matrix: os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ] - python-version: [ 3.8 ] + python-version: [ 3.9 ] # needs: [ install-test ] steps: # checkout commit - name: Checkout commit - uses: actions/checkout@v1 + uses: actions/checkout@v2 # install python - name: Install python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: "x64" @@ -48,6 +48,28 @@ jobs: run: | python ./resources/ci/common/install.py pip install pyinstaller + # get parent directory + - name: Get Repo Name + uses: mad9000/actions-find-and-replace-string@1 + id: repoName + with: + source: ${{ github.repository }} + find: '${{ github.repository_owner }}/' + replace: '' + - name: Get Parent Directory Path (!Windows) + uses: mad9000/actions-find-and-replace-string@1 + id: parentDirNotWin + with: + source: ${{ github.workspace }} + find: '${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}' + replace: ${{ steps.repoName.outputs.value }} + - name: Get Parent Directory Path (Windows) + uses: mad9000/actions-find-and-replace-string@1 + id: parentDir + with: + source: ${{ steps.parentDirNotWin.outputs.value }} + find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}' + replace: ${{ steps.repoName.outputs.value }} # try to get UPX - name: Get UPX env: @@ -70,10 +92,10 @@ jobs: python ./resources/ci/common/prepare_binary.py # upload binary artifacts for later step - name: Upload Binary Artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: binaries-${{ matrix.os-name }} - path: ../artifact + path: ${{ steps.parentDir.outputs.value }}/artifact # Install & Preparing Release # Set up environment @@ -87,18 +109,18 @@ jobs: # os & python versions strategy: matrix: - # install/release on not xenial + # install/release on not bionic os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ] - python-version: [ 3.8 ] + python-version: [ 3.9 ] needs: [ install-build ] steps: # checkout commit - name: Checkout commit - uses: actions/checkout@v1 + uses: actions/checkout@v2 # install python - name: Install Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: "x64" @@ -110,9 +132,31 @@ jobs: OS_NAME: ${{ matrix.os-name }} run: | python ./resources/ci/common/install.py + # get parent directory + - name: Get Repo Name + uses: mad9000/actions-find-and-replace-string@1 + id: repoName + with: + source: ${{ github.repository }} + find: '${{ github.repository_owner }}/' + replace: '' + - name: Get Parent Directory Path (!Windows) + uses: mad9000/actions-find-and-replace-string@1 + id: parentDirNotWin + with: + source: ${{ github.workspace }} + find: '${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}' + replace: ${{ steps.repoName.outputs.value }} + - name: Get Parent Directory Path (Windows) + uses: mad9000/actions-find-and-replace-string@1 + id: parentDir + with: + source: ${{ steps.parentDirNotWin.outputs.value }} + find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}' + replace: ${{ steps.repoName.outputs.value }} # download binary artifact - name: Download Binary Artifact - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: name: binaries-${{ matrix.os-name }} path: ./ @@ -126,22 +170,22 @@ jobs: python ./resources/ci/common/prepare_release.py # upload appversion artifact for later step - name: Upload AppVersion Artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: appversion-${{ matrix.os-name }} path: ./resources/app/meta/manifests/app_version.txt # upload archive artifact for later step - name: Upload Archive Artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: archive-${{ matrix.os-name }} - path: ../deploy + path: ${{ steps.parentDir.outputs.value }}/deploy # Deploy to GitHub Releases # Release Name: ALttPDoorRandomizer v${GITHUB_TAG} # Release Body: Inline content of RELEASENOTES.md # Release Body: Fallback to URL to RELEASENOTES.md - # Release Files: ../deploy + # Release Files: ${{ steps.parentDir.outputs.value }}/deploy deploy-release: name: Deploy GHReleases runs-on: ${{ matrix.os-name }} @@ -150,42 +194,64 @@ jobs: # os & python versions strategy: matrix: - # release only on focal/bionic + # release only on focal os-name: [ ubuntu-latest ] - python-version: [ 3.8 ] + python-version: [ 3.9 ] needs: [ install-prepare-release ] steps: # checkout commit - name: Checkout commit - uses: actions/checkout@v1 + uses: actions/checkout@v2 + # get parent directory + - name: Get Repo Name + uses: mad9000/actions-find-and-replace-string@1 + id: repoName + with: + source: ${{ github.repository }} + find: '${{ github.repository_owner }}/' + replace: '' + - name: Get Parent Directory Path (!Windows) + uses: mad9000/actions-find-and-replace-string@1 + id: parentDirNotWin + with: + source: ${{ github.workspace }} + find: '${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}' + replace: ${{ steps.repoName.outputs.value }} + - name: Get Parent Directory Path (Windows) + uses: mad9000/actions-find-and-replace-string@1 + id: parentDir + with: + source: ${{ steps.parentDirNotWin.outputs.value }} + find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}' + replace: ${{ steps.repoName.outputs.value }} - name: Install Dependencies via pip run: | python -m pip install pytz requests # download appversion artifact - name: Download AppVersion Artifact - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: name: appversion-${{ matrix.os-name }} - path: ../build + path: ${{ steps.parentDir.outputs.value }}/build # download ubuntu archive artifact - name: Download Ubuntu Archive Artifact - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: name: archive-ubuntu-latest - path: ../deploy/linux + path: ${{ steps.parentDir.outputs.value }}/deploy/linux # download macos archive artifact - name: Download MacOS Archive Artifact - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: name: archive-macOS-latest - path: ../deploy/macos + path: ${{ steps.parentDir.outputs.value }}/deploy/macos # download windows archive artifact - name: Download Windows Archive Artifact - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: name: archive-windows-latest - path: ../deploy/windows + path: ${{ steps.parentDir.outputs.value }}/deploy/windows # debug info - name: Debug Info id: debug_info @@ -199,44 +265,35 @@ jobs: RELEASE_NAME="ALttPDoorRandomizer ${GITHUB_TAG}" echo "Release Name: ${RELEASE_NAME}" echo "Git Tag: ${GITHUB_TAG}" - # read releasenotes - - name: Read RELEASENOTES - id: release_notes - run: | - body="$(cat RELEASENOTES.md)" - body="${body//'%'/'%25'}" - body="${body//$'\n'/'%0A'}" - body="${body//$'\r'/'%0D'}" - echo "::set-output name=body::$body" # create a pre/release - name: Create a Pre/Release id: create_release - uses: actions/create-release@master + uses: actions/create-release@v1.1.4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: v${{ steps.debug_info.outputs.github_tag }} release_name: ALttPDoorRandomizer v${{ steps.debug_info.outputs.github_tag }} - body: ${{ steps.release_notes.outputs.body }} + body_path: RELEASENOTES.md draft: true prerelease: true if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') # upload linux archive asset - name: Upload Linux Archive Asset id: upload-linux-asset - uses: actions/upload-release-asset@v1.0.1 + uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ../deploy/linux/ALttPDoorRandomizer.tar.gz - asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-bionic.tar.gz + asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-focal.tar.gz asset_content_type: application/gzip if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') # upload macos archive asset - name: Upload MacOS Archive Asset id: upload-macos-asset - uses: actions/upload-release-asset@v1.0.1 + uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -248,7 +305,7 @@ jobs: # upload windows archive asset - name: Upload Windows Archive Asset id: upload-windows-asset - uses: actions/upload-release-asset@v1.0.1 + uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.gitignore b/.gitignore index 7bb60709..bc027507 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ weights/ /MultiMystery/ /Players/ /QUsb2Snes/ +/output/ + +base2current.json resources/user/* !resources/user/.gitkeep @@ -35,3 +38,4 @@ get-pip.py venv test *.code-workspace +*.zspr diff --git a/BaseClasses.py b/BaseClasses.py index 15a820f4..d5791d47 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -131,6 +131,7 @@ class World(object): set_player_attr('open_pyramid', False) set_player_attr('treasure_hunt_icon', 'Triforce Piece') set_player_attr('treasure_hunt_count', 0) + set_player_attr('treasure_hunt_total', 0) set_player_attr('potshuffle', False) set_player_attr('pot_contents', None) @@ -623,7 +624,8 @@ class CollectionState(object): for event in reachable_events: if event.name in flooded_keys.keys(): flood_location = self.world.get_location(flooded_keys[event.name], event.player) - if flood_location.item and flood_location not in self.locations_checked: + if (flood_location.item and flood_location not in self.locations_checked + and self.location_can_be_flooded(flood_location)): adjusted_checks.remove(event) if len(adjusted_checks) < len(reachable_events): return adjusted_checks @@ -634,9 +636,14 @@ class CollectionState(object): flood_location = world.get_location(flooded_keys[location.name], location.player) item = flood_location.item item_is_important = False if not item else item.advancement or item.bigkey or item.smallkey - return flood_location in self.locations_checked or not item_is_important + return (flood_location in self.locations_checked or not item_is_important + or not self.location_can_be_flooded(flood_location)) return True + @staticmethod + def location_can_be_flooded(location): + return location.parent_region.name in ['Swamp Trench 1 Alcove', 'Swamp Trench 2 Alcove'] + def has(self, item, player, count=1): if count == 1: return (item, player) in self.prog_items @@ -814,14 +821,17 @@ class CollectionState(object): elif 'Shield' in item.name: if self.has('Mirror Shield', item.player): pass - elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3: + elif self.has('Shield Level', item.player, 2) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3: self.prog_items['Mirror Shield', item.player] += 1 + self.prog_items['Shield Level', item.player] += 1 changed = True - elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2: + elif self.has('Shield Level', item.player, 1) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2: self.prog_items['Red Shield', item.player] += 1 + self.prog_items['Shield Level', item.player] += 1 changed = True elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1: self.prog_items['Blue Shield', item.player] += 1 + self.prog_items['Shield Level', item.player] += 1 changed = True elif 'Bow' in item.name: if self.has('Silver Arrows', item.player): @@ -1414,6 +1424,11 @@ class Door(object): else: self.passage = False + def kind(self, world): + if self.roomIndex != -1 and self.doorListPos != -1: + return world.get_room(self.roomIndex, self.player).kind(self) + return None + def __eq__(self, other): return isinstance(other, self.__class__) and self.name == other.name @@ -2064,6 +2079,8 @@ class Spoiler(object): 'experimental': self.world.experimental, 'keydropshuffle': self.world.keydropshuffle, 'shopsanity': self.world.shopsanity, + 'triforcegoal': self.world.treasure_hunt_count, + 'triforcepool': self.world.treasure_hunt_total, 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} } @@ -2108,6 +2125,9 @@ class Spoiler(object): outfile.write('Retro: %s\n' % ('Yes' if self.metadata['retro'][player] else 'No')) outfile.write('Swords: %s\n' % self.metadata['weapons'][player]) outfile.write('Goal: %s\n' % self.metadata['goal'][player]) + if self.metadata['goal'][player] == 'triforcehunt': + outfile.write('Triforce Pieces Required: %s\n' % self.metadata['triforcegoal'][player]) + outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player]) outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) outfile.write('Overworld Shuffle: %s\n' % self.metadata['ow_shuffle'][player]) @@ -2295,8 +2315,8 @@ counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} access_mode = {"items": 0, "locations": 1, "none": 2} # byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies) -boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3} -enemy_mode = {"none": 0, "shuffled": 1, "random": 2} +boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3} +enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2} # byte 7: HHHD DP?? (enemy_health, enemy_dmg, potshuffle, ?) e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4} diff --git a/Bosses.py b/Bosses.py index dd90bb6e..de04cbef 100644 --- a/Bosses.py +++ b/Bosses.py @@ -39,6 +39,7 @@ def MoldormDefeatRule(state, player): return state.has_blunt_weapon(player) def HelmasaurKingDefeatRule(state, player): + # TODO: technically possible with the hammer return state.has_sword(player) or state.can_shoot_arrows(player) def ArrghusDefeatRule(state, player): diff --git a/CLI.py b/CLI.py index e35fe44c..cc78b7b3 100644 --- a/CLI.py +++ b/CLI.py @@ -6,7 +6,6 @@ import textwrap import shlex import sys -import source.classes.constants as CONST from source.classes.BabelFish import BabelFish from Utils import update_deprecated_args @@ -17,6 +16,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): def _get_help_string(self, action): return textwrap.dedent(action.help) + def parse_cli(argv, no_defaults=False): def defval(value): return value if not no_defaults else None @@ -28,50 +28,57 @@ def parse_cli(argv, no_defaults=False): fish = BabelFish(lang=lang) # we need to know how many players we have first + # also if we're loading our own settings file, we should do that now parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--settingsfile', help="input json file of settings", type=str) parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) multiargs, _ = parser.parse_known_args(argv) + if multiargs.settingsfile: + settings = apply_settings_file(settings, multiargs.settingsfile) + parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) # get args args = [] - with open(os.path.join("resources","app","cli","args.json")) as argsFile: - args = json.load(argsFile) - for arg in args: - argdata = args[arg] - argname = "--" + arg - argatts = {} - argatts["help"] = "(default: %(default)s)" - if "action" in argdata: - argatts["action"] = argdata["action"] - if "choices" in argdata: - argatts["choices"] = argdata["choices"] - argatts["const"] = argdata["choices"][0] - argatts["default"] = argdata["choices"][0] - argatts["nargs"] = "?" - if arg in settings: - default = settings[arg] - if "type" in argdata and argdata["type"] == "bool": - default = settings[arg] != 0 - argatts["default"] = defval(default) - arghelp = fish.translate("cli","help",arg) - if "help" in argdata and argdata["help"] == "suppress": - argatts["help"] = argparse.SUPPRESS - elif not isinstance(arghelp,str): - argatts["help"] = '\n'.join(arghelp).replace("\\'","'") - else: - argatts["help"] = arghelp + " " + argatts["help"] - parser.add_argument(argname,**argatts) + with open(os.path.join("resources", "app", "cli", "args.json")) as argsFile: + args = json.load(argsFile) + for arg in args: + argdata = args[arg] + argname = "--" + arg + argatts = {} + argatts["help"] = "(default: %(default)s)" + if "action" in argdata: + argatts["action"] = argdata["action"] + if "choices" in argdata: + argatts["choices"] = argdata["choices"] + argatts["const"] = argdata["choices"][0] + argatts["default"] = argdata["choices"][0] + argatts["nargs"] = "?" + if arg in settings: + default = settings[arg] + if "type" in argdata and argdata["type"] == "bool": + default = settings[arg] != 0 + argatts["default"] = defval(default) + arghelp = fish.translate("cli", "help", arg) + if "help" in argdata and argdata["help"] == "suppress": + argatts["help"] = argparse.SUPPRESS + elif not isinstance(arghelp, str): + argatts["help"] = '\n'.join(arghelp).replace("\\'", "'") + else: + argatts["help"] = arghelp + " " + argatts["help"] + parser.add_argument(argname, **argatts) - parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli","help","seed")), type=int) - parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli","help","count")), type=int) + parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli", "help", "seed")), type=int) + parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli", "help", "count")), type=int) parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS) # included for backwards compatibility parser.add_argument('--beemizer', default=defval(settings["beemizer"]), type=lambda value: min(max(int(value), 0), 4)) parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) + parser.add_argument('--securerandom', default=defval(settings["securerandom"]), action='store_true') parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) + parser.add_argument('--settingsfile', dest="filename", help="input json file of settings", type=str) if multiargs.multi: for player in range(1, multiargs.multi + 1): @@ -85,11 +92,13 @@ def parse_cli(argv, no_defaults=False): if multiargs.multi: defaults = copy.deepcopy(ret) for player in range(1, multiargs.multi + 1): - playerargs = parse_cli(shlex.split(getattr(ret,f"p{player}")), True) + playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', + 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', + 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', @@ -103,6 +112,15 @@ def parse_cli(argv, no_defaults=False): return ret +def apply_settings_file(settings, settings_path): + if os.path.exists(settings_path): + with open(settings_path) as json_file: + data = json.load(json_file) + for k, v in data.items(): + settings[k] = v + return settings + + def parse_settings(): # set default settings settings = { @@ -147,10 +165,19 @@ def parse_settings(): "dungeon_counters": "default", "mixed_travel": "prevent", "standardize_palettes": "standardize", + + "triforce_pool": 30, + "triforce_goal": 20, + "triforce_pool_min": 0, + "triforce_pool_max": 0, + "triforce_goal_min": 0, + "triforce_goal_max": 0, + "triforce_min_difference": 10, "code": "", "multi": 1, "names": "", + "securerandom": False, # Hints default to TRUE "hints": True, @@ -159,15 +186,15 @@ def parse_settings(): "quickswap": False, "heartcolor": "red", "heartbeep": "normal", - "sprite": os.path.join(".","data","sprites","official","001.link.1.zspr"), + "sprite": os.path.join(".", "data", "sprites", "official", "001.link.1.zspr"), "fastmenu": "normal", "ow_palettes": "default", "uw_palettes": "default", - # Spoiler defaults to FALSE + # Spoiler defaults to TRUE # Playthrough defaults to TRUE # ROM defaults to TRUE - "create_spoiler": False, + "create_spoiler": True, "calc_playthrough": True, "create_rom": True, "usestartinventory": False, @@ -265,17 +292,15 @@ def parse_settings(): # read saved settings file if it exists and set these settings_path = os.path.join(".", "resources", "user", "settings.json") - if os.path.exists(settings_path): - with(open(settings_path)) as json_file: - data = json.load(json_file) - for k, v in data.items(): - settings[k] = v + settings = apply_settings_file(settings, settings_path) return settings # Priority fallback is: # 1: CLI -# 2: Settings file +# 2: Settings file(s) # 3: Canned defaults + + def get_args_priority(settings_args, gui_args, cli_args): args = {} args["settings"] = parse_settings() if settings_args is None else settings_args @@ -302,7 +327,7 @@ def get_args_priority(settings_args, gui_args, cli_args): for k in vars(args["cli"]): load_doesnt_have_key = k not in args["load"] cli_val = cli[k] - if isinstance(cli_val,dict) and 1 in cli_val: + if isinstance(cli_val, dict) and 1 in cli_val: cli_val = cli_val[1] different_val = (k in args["load"] and k in cli) and (str(args["load"][k]) != str(cli_val)) cli_has_empty_dict = k in cli and isinstance(cli_val, dict) and len(cli_val) == 0 @@ -311,9 +336,9 @@ def get_args_priority(settings_args, gui_args, cli_args): args["load"][k] = cli_val newArgs = {} - for key in [ "settings", "gui", "cli", "load" ]: + for key in ["settings", "gui", "cli", "load"]: if args[key]: - if isinstance(args[key],dict): + if isinstance(args[key], dict): newArgs[key] = argparse.Namespace(**args[key]) else: newArgs[key] = args[key] diff --git a/DoorShuffle.py b/DoorShuffle.py index a9bbd529..bf987618 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -42,6 +42,7 @@ def link_doors(world, player): disconnect_portal(portal, world, player) reset_portals(world, player) reset_rooms(world, player) + world.get_door("Skull Pinball WS", player).no_exit() def link_doors_main(world, player): @@ -67,7 +68,7 @@ def link_doors_main(world, player): for entrance, ext in ladders: connect_two_way(world, entrance, ext, player) - if world.intensity[player] < 3 or world.doorShuffle == 'vanilla': + if world.intensity[player] < 3 or world.doorShuffle[player] == 'vanilla': mirror_route = world.get_entrance('Sanctuary Mirror Route', player) mr_door = mirror_route.door sanctuary = mirror_route.parent_region @@ -397,7 +398,10 @@ def choose_portals(world, player): master_door_list = [x for x in world.doors if x.player == player and x.portalAble] portal_assignment = defaultdict(list) - for dungeon, info in info_map.items(): + shuffled_info = list(info_map.items()) + if cross_flag: + random.shuffle(shuffled_info) + for dungeon, info in shuffled_info: outstanding_portals = list(dungeon_portals[dungeon]) hc_flag = std_flag and dungeon == 'Hyrule Castle' if hc_flag: @@ -543,7 +547,7 @@ def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allo bk_shuffle=False, standard=False): ret = [x for x in door_list if bk_shuffle or not x.bk_shuffle_req] if crossed: - ret = [x for x in ret if not x.dungeonLink or x.entrance.parent_region.dungeon.name == dungeon] + ret = [x for x in ret if not x.dungeonLink or x.dungeonLink == dungeon or x.dungeonLink.startswith('link')] else: ret = [x for x in ret if x.entrance.parent_region.dungeon.name == dungeon] if need_passage: diff --git a/Doors.py b/Doors.py index a3c8d8c6..73806dd3 100644 --- a/Doors.py +++ b/Doors.py @@ -206,7 +206,7 @@ def create_doors(world, player): create_door(player, 'Desert East Wing Key Door EN', Intr).dir(Ea, 0x85, Top, High).small_key().pos(1), create_door(player, 'Desert Compass Key Door WN', Intr).dir(We, 0x85, Top, High).small_key().pos(1), create_door(player, 'Desert Compass NW', Nrml).dir(No, 0x85, Right, High).trap(0x4).pos(0), - create_door(player, 'Desert Cannonball S', Nrml).dir(So, 0x75, Right, High).pos(1).portal(Z, 0x02), + create_door(player, 'Desert Cannonball S', Nrml).dir(So, 0x75, Right, High).pos(1).portal(X, 0x02), create_door(player, 'Desert Arrow Pot Corner S Edge', Open).dir(So, 0x75, None, High).edge(6, Z, 0x20), create_door(player, 'Desert Arrow Pot Corner W Edge', Open).dir(We, 0x75, None, High).edge(2, Z, 0x20), create_door(player, 'Desert Arrow Pot Corner NW', Intr).dir(No, 0x75, Left, High).pos(0), @@ -1312,12 +1312,7 @@ def create_doors(world, player): # can't unlink from skull woods right now world.get_door('Skull 2 West Lobby S', player).dungeonLink = 'Skull Woods' - world.get_door('Ice Spike Cross SE', player).dungeonLink = 'linkIceFalls' - world.get_door('Ice Tall Hint SE', player).dungeonLink = 'linkIceFalls' - world.get_door('Ice Switch Room SE', player).dungeonLink = 'linkIceFalls' - - world.get_door('Ice Cross Bottom SE', player).dungeonLink = 'linkIceFalls2' - world.get_door('Ice Conveyor SW', player).dungeonLink = 'linkIceFalls2' + set_special_dungeon_links(world, player) def create_portals(world, player): @@ -1351,10 +1346,21 @@ def create_portals(world, player): world.dungeon_portals[player] += dungeon_portals +def set_special_dungeon_links(world, player): + world.get_door('Ice Spike Cross SE', player).dungeonLink = 'linkIceFalls' + world.get_door('Ice Tall Hint SE', player).dungeonLink = 'linkIceFalls' + world.get_door('Ice Switch Room SE', player).dungeonLink = 'linkIceFalls' + + world.get_door('Ice Cross Bottom SE', player).dungeonLink = 'linkIceFalls2' + world.get_door('Ice Conveyor SW', player).dungeonLink = 'linkIceFalls2' + + def reset_portals(world, player): world.dungeon_portals[player].clear() world._portal_cache.clear() create_portals(world, player) + set_special_dungeon_links(world, player) + def create_paired_doors(world, player): world.paired_doors[player] = [ diff --git a/EntranceShuffle.py b/EntranceShuffle.py index b9b9425f..b2cec0a6 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1,6 +1,7 @@ import random # ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. +from collections import defaultdict def link_entrances(world, player): @@ -1117,11 +1118,9 @@ def link_inverted_entrances(world, player): # randomize which desert ledge door is a must-exit if random.randint(0, 1) == 0: lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)') - dp_must_exit = 'Desert Palace Entrance (North)' lw_entrances.append('Desert Palace Entrance (West)') else: lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (West)') - dp_must_exit = 'Desert Palace Entrance (West)' lw_entrances.append('Desert Palace Entrance (North)') dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) @@ -1157,13 +1156,10 @@ def link_inverted_entrances(world, player): connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player) dungeon_exits.remove('Inverted Agahnims Tower Exit') - - all_dungeon_entrances = dw_entrances + lw_entrances - connect_mandatory_exits(world, all_dungeon_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player, dp_must_exit) - - remaining_dw_entrances = [i for i in all_dungeon_entrances if i in dw_entrances] - remaining_lw_entrances = [i for i in all_dungeon_entrances if i in lw_entrances] - connect_caves(world, remaining_lw_entrances, remaining_dw_entrances, dungeon_exits, player) + + connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) + + connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) elif world.shuffle[player] == 'simple': simple_shuffle_dungeons(world, player) @@ -1271,7 +1267,7 @@ def link_inverted_entrances(world, player): caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House) single_doors = list(Single_Cave_Doors) bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) door_targets = list(Inverted_Single_Cave_Targets) # place links house @@ -1354,18 +1350,16 @@ def link_inverted_entrances(world, player): old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera']) caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) door_targets = list(Inverted_Single_Cave_Targets) old_man_house = list(Old_Man_House) # randomize which desert ledge door is a must-exit if random.randint(0, 1) == 0: lw_must_exits.append('Desert Palace Entrance (North)') - dp_must_exit = 'Desert Palace Entrance (North)' lw_entrances.append('Desert Palace Entrance (West)') else: lw_must_exits.append('Desert Palace Entrance (West)') - dp_must_exit = 'Desert Palace Entrance (West)' lw_entrances.append('Desert Palace Entrance (North)') # tavern back door cannot be shuffled yet @@ -1428,7 +1422,7 @@ def link_inverted_entrances(world, player): # no dw must exits in inverted, but we randomize whether cave is in light or dark world if random.randint(0, 1) == 0: caves += old_man_house - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player, dp_must_exit) + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) try: caves.remove(old_man_house[0]) except ValueError: @@ -1437,7 +1431,7 @@ def link_inverted_entrances(world, player): connect_caves(world, lw_entrances, [], old_man_house, player) else: connect_caves(world, dw_entrances, [], old_man_house, player) - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player, dp_must_exit) + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) # place old man, has limited options # exit has to come from specific set of doors, the entrance is free to move about @@ -1506,17 +1500,15 @@ def link_inverted_entrances(world, player): old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera']) caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) door_targets = list(Inverted_Single_Cave_Targets) # randomize which desert ledge door is a must-exit if random.randint(0, 1) == 0: must_exits.append('Desert Palace Entrance (North)') - dp_must_exit = 'Desert Palace Entrance (North)' entrances.append('Desert Palace Entrance (West)') else: must_exits.append('Desert Palace Entrance (West)') - dp_must_exit = 'Desert Palace Entrance (West)' entrances.append('Desert Palace Entrance (North)') caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) @@ -1567,7 +1559,7 @@ def link_inverted_entrances(world, player): #place must-exit caves - connect_mandatory_exits(world, entrances, caves, must_exits, player, dp_must_exit) + connect_mandatory_exits(world, entrances, caves, must_exits, player) # place old man, has limited options @@ -1630,8 +1622,8 @@ def link_inverted_entrances(world, player): # and rentering to find bomb shop. However appended list here is all those that we currently have # bomb shop logic for. # Specifically we could potentially add: 'Dark Death Mountain Ledge (East)' and doors associated with pits - bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Turtle Rock Isolated Ledge Entrance', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance']) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Turtle Rock Isolated Ledge Entrance', 'Hookshot Cave Back Entrance']) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) door_targets = list(Inverted_Single_Cave_Targets) random.shuffle(doors) @@ -1940,17 +1932,33 @@ def connect_random(world, exitlist, targetlist, player, two_way=False): connect_entrance(world, exit, target, player) -def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, dp_must_exit=None): +def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): """This works inplace""" random.shuffle(entrances) random.shuffle(caves) + # Keeps track of entrances that cannot be used to access each exit / cave + if world.mode == 'inverted': + invalid_connections = Inverted_Must_Exit_Invalid_Connections.copy() + else: + invalid_connections = Must_Exit_Invalid_Connections.copy() + invalid_cave_connections = defaultdict(set) + + # Handle inverted Aga Tower - if it depends on connections, then so does Hyrule Castle Ledge + if world.mode == 'inverted': + for entrance in invalid_connections: + if world.get_entrance(entrance, player).connected_region == world.get_region('Inverted Agahnims Tower', player): + for exit in invalid_connections[entrance]: + invalid_connections[exit] = invalid_connections[exit].union({'Inverted Ganons Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'}) + break + used_caves = [] + required_entrances = 0 # Number of entrances reserved for used_caves while must_be_exits: exit = must_be_exits.pop() # find multi exit cave cave = None for candidate in caves: - if not isinstance(candidate, str): + if not isinstance(candidate, str) and (candidate in used_caves or len(candidate) < len(entrances) - required_entrances - 1): cave = candidate break @@ -1959,30 +1967,47 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, dp_m # all caves are sorted so that the last exit is always reachable connect_two_way(world, exit, cave[-1], player) - if len(cave) == 2: - entrance = entrances.pop() - # ToDo Better solution, this is a hot fix. Do not connect both sides of trock/desert ledge only to each other - if world.mode[player] != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)': - new_entrance = entrances.pop() - entrances.append(entrance) - entrance = new_entrance - if world.mode[player] == 'inverted' and entrance == dp_must_exit: - new_entrance = entrances.pop() - entrances.append(entrance) - entrance = new_entrance + if len(cave) == 2: + entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in invalid_cave_connections[tuple(cave)]) + entrances.remove(entrance) connect_two_way(world, entrance, cave[0], player) + if cave in used_caves: + required_entrances -= 2 + used_caves.remove(cave) + if entrance in invalid_connections: + for exit2 in invalid_connections[entrance]: + invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]).union(invalid_cave_connections[tuple(cave)]) elif cave[-1] == 'Spectacle Rock Cave Exit': #Spectacle rock only has one exit - for exit in cave[:-1]: - connect_two_way(world,entrances.pop(),exit, player) + cave_entrances = [] + for cave_exit in cave[:-1]: + entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit]) + cave_entrances.append(entrance) + entrances.remove(entrance) + connect_two_way(world,entrance,cave_exit, player) + if entrance not in invalid_connections: + invalid_connections[exit] = set() + if all(entrance in invalid_connections for entrance in cave_entrances): + new_invalid_connections = invalid_connections[cave_entrances[0]].intersection(invalid_connections[cave_entrances[1]]) + for exit2 in new_invalid_connections: + invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]) else:#save for later so we can connect to multiple exits + if cave in used_caves: + required_entrances -= 1 + used_caves.remove(cave) + else: + required_entrances += len(cave)-1 caves.append(cave[0:-1]) random.shuffle(caves) used_caves.append(cave[0:-1]) + invalid_cave_connections[tuple(cave[0:-1])] = invalid_cave_connections[tuple(cave)].union(invalid_connections[exit]) caves.remove(cave) for cave in used_caves: if cave in caves: #check if we placed multiple entrances from this 3 or 4 exit - for exit in cave: - connect_two_way(world, entrances.pop(), exit, player) + for cave_exit in cave: + entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]) + invalid_cave_connections[tuple(cave)] = set() + entrances.remove(entrance) + connect_two_way(world, entrance, cave_exit, player) caves.remove(cave) @@ -2685,6 +2710,8 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (North)'] +Inverted_Blacksmith_Multi_Cave_Doors = Blacksmith_Multi_Cave_Doors # same as non-inverted + Inverted_LW_Single_Cave_Doors = LW_Single_Cave_Doors + ['Inverted Big Bomb Shop'] Inverted_DW_Single_Cave_Doors = ['Bonk Fairy (Dark)', @@ -2755,6 +2782,37 @@ Inverted_Bomb_Shop_Single_Cave_Doors = ['Waterfall of Wishing', 'Inverted Links House', 'Inverted Big Bomb Shop'] +Inverted_Blacksmith_Single_Cave_Doors = ['Blinds Hideout', + 'Lake Hylia Fairy', + 'Light Hype Fairy', + 'Desert Fairy', + 'Chicken House', + 'Aginahs Cave', + 'Sahasrahlas Hut', + 'Cave Shop (Lake Hylia)', + 'Blacksmiths Hut', + 'Sick Kids House', + 'Lost Woods Gamble', + 'Fortune Teller (Light)', + 'Snitch Lady (East)', + 'Snitch Lady (West)', + 'Bush Covered House', + 'Tavern (Front)', + 'Light World Bomb Hut', + 'Kakariko Shop', + 'Mini Moldorm Cave', + 'Long Fairy Cave', + 'Good Bee Cave', + '20 Rupee Cave', + '50 Rupee Cave', + 'Ice Rod Cave', + 'Library', + 'Potion Shop', + 'Dam', + 'Lumberjack House', + 'Lake Hylia Fortune Teller', + 'Kakariko Gamble Game', + 'Inverted Big Bomb Shop'] Inverted_Single_Cave_Targets = ['Blinds Hideout', 'Bonk Fairy (Light)', @@ -2844,6 +2902,27 @@ Isolated_LH_Doors = ['Kings Grave', 'Dark World Hammer Peg Cave', 'Turtle Rock Isolated Ledge Entrance'] +# Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups +Must_Exit_Invalid_Connections = defaultdict(set, { + 'Dark Death Mountain Ledge (East)': {'Dark Death Mountain Ledge (West)', 'Mimic Cave'}, + 'Dark Death Mountain Ledge (West)': {'Dark Death Mountain Ledge (East)', 'Mimic Cave'}, + 'Mimic Cave': {'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)'}, + 'Bumper Cave (Top)': {'Death Mountain Return Cave (West)'}, + 'Death Mountain Return Cave (West)': {'Bumper Cave (Top)'}, + 'Skull Woods Second Section Door (West)': {'Skull Woods Final Section'}, + 'Skull Woods Final Section': {'Skull Woods Second Section Door (West)'}, +}) +Inverted_Must_Exit_Invalid_Connections = defaultdict(set, { + 'Bumper Cave (Top)': {'Death Mountain Return Cave (West)'}, + 'Death Mountain Return Cave (West)': {'Bumper Cave (Top)'}, + 'Desert Palace Entrance (North)': {'Desert Palace Entrance (West)'}, + 'Desert Palace Entrance (West)': {'Desert Palace Entrance (North)'}, + 'Inverted Ganons Tower': {'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'}, + 'Hyrule Castle Entrance (West)': {'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower'}, + 'Hyrule Castle Entrance (East)': {'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower'}, +}) + + # these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions mandatory_connections = [('Links House S&Q', 'Links House'), ('Sanctuary S&Q', 'Sanctuary'), @@ -2879,8 +2958,10 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Old Man S&Q', 'Old Man House'), ('Castle Ledge S&Q', 'Hyrule Castle Ledge'), ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), - ('Lake Hylia Island', 'Lake Hylia Island'), - ('Zoras River', 'Zoras River'), + ('Lake Hylia Island Pier', 'Lake Hylia Island'), + ('Lake Hylia Warp', 'Northeast Light World'), + ('Northeast Light World Warp', 'Light World'), + ('Zoras River', 'Zoras River'), ('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Inner Rocks', 'Light World'), ('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'), diff --git a/Fill.py b/Fill.py index 08b54450..5f48a08b 100644 --- a/Fill.py +++ b/Fill.py @@ -377,6 +377,19 @@ def flood_items(world): break +def lock_shop_locations(world, player): + for shop, loc_names in shop_to_location_table.items(): + for loc in loc_names: + world.get_location(loc, player).event = True + world.get_location(loc, player).locked = True + # I don't believe these locations exist in non-shopsanity + # if world.retro[player]: + # for shop, loc_names in retro_shops.items(): + # for loc in loc_names: + # world.get_location(loc, player).event = True + # world.get_location(loc, player).locked = True + + def sell_potions(world, player): loc_choices = [] for shop in world.shops[player]: diff --git a/InvertedRegions.py b/InvertedRegions.py index 4ae0bbdb..53a33f43 100644 --- a/InvertedRegions.py +++ b/InvertedRegions.py @@ -11,7 +11,7 @@ def create_inverted_regions(world, player): ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam', 'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump', - 'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', 'Lake Hylia Island', + 'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', 'Lake Hylia Island Pier', 'Lake Hylia Warp', 'Bonk Rock Cave', 'Library', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow', 'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Light World River Drop', 'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)', @@ -30,7 +30,7 @@ def create_inverted_regions(world, player): "Blind\'s Hideout - Right", "Blind\'s Hideout - Far Left", "Blind\'s Hideout - Far Right"]), - create_lw_region(player, 'Northeast Light World', None, ['Zoras River', 'Waterfall of Wishing', 'Potion Shop Outer Rock', 'Northeast Dark World Mirror Spot']), + create_lw_region(player, 'Northeast Light World', None, ['Zoras River', 'Waterfall of Wishing', 'Potion Shop Outer Rock', 'Northeast Dark World Mirror Spot', 'Northeast Light World Warp']), create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop', 'Potion Shop Inner Bushes', 'Potion Shop Inner Rock', 'Potion Shop Mirror Spot', 'Potion Shop River Drop']), create_lw_region(player, 'Graveyard Cave Area', None, ['Graveyard Cave', 'Graveyard Cave Inner Bushes', 'Graveyard Cave Mirror Spot']), create_lw_region(player, 'River', None, ['Light World Pier', 'Potion Shop Pier']), diff --git a/ItemList.py b/ItemList.py index 3fb995d7..c85ba3e7 100644 --- a/ItemList.py +++ b/ItemList.py @@ -37,7 +37,7 @@ Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', - 'triforcehunt', 'triforce_pieces_required', 'retro', + 'retro', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', 'progressive_armor_limit', 'progressive_bottle_limit', 'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit']) @@ -60,8 +60,6 @@ difficulties = { basicbow = ['Bow', 'Silver Arrows'], timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - triforcehunt = ['Triforce Piece'] * 30, - triforce_pieces_required = 20, retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 4, @@ -87,8 +85,6 @@ difficulties = { basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - triforcehunt = ['Triforce Piece'] * 30, - triforce_pieces_required = 20, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 3, @@ -114,8 +110,6 @@ difficulties = { basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - triforcehunt = ['Triforce Piece'] * 30, - triforce_pieces_required = 20, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 2, @@ -198,7 +192,6 @@ def generate_itempool(world, player): region = world.get_region('Hyrule Castle Courtyard',player) loc = Location(player, "Murahdahla", parent=region) - loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= state.world.treasure_hunt_count[player] region.locations.append(loc) world.dynamic_locations.append(loc) @@ -262,7 +255,7 @@ def generate_itempool(world, player): (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray) world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999) else: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player]) + (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player]) if player in world.pool_adjustment.keys(): amt = world.pool_adjustment[player] @@ -286,7 +279,7 @@ def generate_itempool(world, player): if not found_sword and world.swords[player] != 'swordless': found_sword = True possible_weapons.append(item) - if item in ['Progressive Bow', 'Bow'] and not found_bow: + if item in ['Progressive Bow', 'Bow'] and not found_bow and not world.retro[player]: found_bow = True possible_weapons.append(item) if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: @@ -320,10 +313,14 @@ def generate_itempool(world, player): if clock_mode is not None: world.clock_mode = clock_mode - if treasure_hunt_count is not None: - world.treasure_hunt_count[player] = treasure_hunt_count - if treasure_hunt_icon is not None: - world.treasure_hunt_icon[player] = treasure_hunt_icon + if world.goal[player] == 'triforcehunt': + if world.treasure_hunt_count[player] == 0: + world.treasure_hunt_count[player] = 20 + if world.treasure_hunt_total[player] == 0: + world.treasure_hunt_total[player] = 30 + world.treasure_hunt_icon[player] = 'Triforce Piece' + if world.custom: + world.treasure_hunt_count[player] = treasure_hunt_count world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player and ((item.smallkey and world.keyshuffle[player]) @@ -511,6 +508,8 @@ def set_up_shops(world, player): shop.add_inventory(1, 'Small Key (Universal)', 100) shop.add_inventory(2, 'Bombs (10)', 50) rss.locked = True + cap_shop = world.get_region('Capacity Upgrade', player).shop + cap_shop.inventory[1] = None # remove arrow capacity upgrades in retro def customize_shops(world, player): @@ -576,7 +575,6 @@ def customize_shops(world, player): loc.item = upgrade upgrade.location = loc change_shop_items_to_rupees(world, player, shops_to_customize) - todays_discounts(world, player) def randomize_price(price): @@ -598,7 +596,7 @@ def randomize_price(price): def change_shop_items_to_rupees(world, player, shops): locations = world.get_filled_locations(player) for location in locations: - if location.item.name in shop_transfer.keys() and location.parent_region.name not in shops: + if location.item.name in shop_transfer.keys() and (location.parent_region.name not in shops or location.name == 'Potion Shop'): new_item = ItemFactory(shop_transfer[location.item.name], location.item.player) location.item = new_item if location.parent_region.name == 'Capacity Upgrade' and location.item.name in cap_blacklist: @@ -609,19 +607,6 @@ def change_shop_items_to_rupees(world, player, shops): shop.add_inventory(slot, new_item.name, randomize_price(new_item.price), 1, player=new_item.player) -def todays_discounts(world, player): - locs = [] - for shop, locations in shop_to_location_table.items(): - for slot, loc in enumerate(locations): - locs.append((world.get_location(loc, player), shop, slot)) - discount_number = random.randint(4, 7) - chosen_locations = random.choices(locs, k=discount_number) - for location, shop_name, slot in chosen_locations: - shop = world.get_region(shop_name, player).shop - orig = location.item.price - shop.inventory[slot]['price'] = randomize_price(orig // 5) - - repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart', 'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion'] @@ -631,20 +616,22 @@ cap_replacements = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)'] cap_blacklist = ['Green Potion', 'Red Potion', 'Blue Potion'] -shop_transfer = {'Red Potion': 'Rupees (100)', 'Bee': 'Rupees (5)', 'Blue Potion': 'Rupees (100)', +shop_transfer = {'Red Potion': 'Rupees (50)', 'Bee': 'Rupees (5)', 'Blue Potion': 'Rupees (50)', 'Green Potion': 'Rupees (50)', # money seems a bit too generous with these on # 'Blue Shield': 'Rupees (50)', 'Red Shield': 'Rupees (300)', } -def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, door_shuffle): +def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, door_shuffle): pool = [] placed_items = {} precollected_items = [] clock_mode = None - treasure_hunt_count = None - treasure_hunt_icon = None + if goal == 'triforcehunt': + if treasure_hunt_total == 0: + treasure_hunt_total = 30 + triforcepool = ['Triforce Piece'] * int(treasure_hunt_total) pool.extend(alwaysitems) @@ -738,13 +725,13 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r extraitems -= len(diff.timedohko) clock_mode = 'countdown-ohko' if goal == 'triforcehunt': - pool.extend(diff.triforcehunt) - extraitems -= len(diff.triforcehunt) - treasure_hunt_count = diff.triforce_pieces_required - treasure_hunt_icon = 'Triforce Piece' + pool.extend(triforcepool) + extraitems -= len(triforcepool) for extra in diff.extras: if extraitems > 0: + if len(extra) > extraitems: + extra = random.choices(extra, k=extraitems) pool.extend(extra) extraitems -= len(extra) @@ -769,7 +756,7 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r pool.extend(['Small Key (Universal)']) else: pool.extend(['Small Key (Universal)']) - return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) + return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray): if isinstance(customitemarray,dict) and 1 in customitemarray: diff --git a/Items.py b/Items.py index d280d6e5..59b8bdc6 100644 --- a/Items.py +++ b/Items.py @@ -65,8 +65,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], 999, None, None, None, None, None, None, None), 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], 999, None, None, None, None, None, None, None), 'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'), - 'Power Star': (True, False, None, 0x6B, 50, '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, 50, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'), + '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, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], 999, None, None, None, None, None, None, None), 'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], 999, None, None, None, None, None, None, None), 'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], 999, None, None, None, None, None, None, None), @@ -111,56 +111,56 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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, 30, '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, 50, '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'), + '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 comapss 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'), diff --git a/Main.py b/Main.py index f643ca11..6ae85496 100644 --- a/Main.py +++ b/Main.py @@ -24,11 +24,11 @@ from RoomData import create_rooms from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items -from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression +from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.3.1.0-u' +__version__ = '0.3.1.7-u' class EnemizerError(RuntimeError): @@ -42,6 +42,9 @@ def main(args, seed=None, fish=None): start = time.perf_counter() + # if args.securerandom: + # random.use_secure() + # initialize the world if args.code: for player, code in args.code.items(): @@ -58,6 +61,9 @@ def main(args, seed=None, fish=None): world.seed = int(seed) random.seed(world.seed) + if args.securerandom: + world.seed = None + world.remote_items = args.remote_items.copy() world.mapshuffle = args.mapshuffle.copy() world.compassshuffle = args.compassshuffle.copy() @@ -82,6 +88,8 @@ def main(args, seed=None, fish=None): world.keydropshuffle = args.keydropshuffle.copy() world.mixed_travel = args.mixed_travel.copy() world.standardize_palettes = args.standardize_palettes.copy() + world.treasure_hunt_count = args.triforce_goal.copy() + world.treasure_hunt_total = args.triforce_pool.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} @@ -167,6 +175,9 @@ def main(args, seed=None, fish=None): sell_potions(world, player) if world.retro[player]: sell_keys(world, player) + else: + lock_shop_locations(world, player) + logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes")) diff --git a/Mystery.py b/Mystery.py index 1225eef2..d14deb07 100644 --- a/Mystery.py +++ b/Mystery.py @@ -126,6 +126,12 @@ def roll_settings(weights): return None return random.choices(list(root[option].keys()), weights=list(map(int,root[option].values())))[0] + def get_choice_default(option, root=weights, default=None): + choice = get_choice(option, root) + if choice is None and default is not None: + return default + return choice + ret = argparse.Namespace() glitches_required = get_choice('glitches_required') @@ -175,7 +181,15 @@ def roll_settings(weights): ret.crystals_gt = get_choice('tower_open') ret.crystals_ganon = get_choice('ganon_open') - + + if ret.goal == 'triforcehunt': + goal_min = get_choice_default('triforce_goal_min', default=20) + goal_max = get_choice_default('triforce_goal_max', default=20) + pool_min = get_choice_default('triforce_pool_min', default=30) + pool_max = get_choice_default('triforce_pool_max', default=30) + ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) + min_diff = get_choice_default('triforce_min_difference', default=10) + ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) ret.mode = get_choice('world_state') if ret.mode == 'retro': ret.mode = 'open' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index dd5ec356..9b677253 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -51,6 +51,7 @@ All prices range approx. from half the base price to the base price in increment | Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-250 | | Moon Pearl | 200 | 100-200 | | Lamp, Progressive Bows, Gloves, & Swords | 150 | 75-150 +| | Triforce Piece | 100 | 50-100 | Medallions | Bombos, Ether, Quake | 100 | 50-100 | Safety/Fetch | Cape, Mushroom, Shovel, Powder, Bug Net, Byrna, Progressive Armor & Shields, Half Magic | 50 | 25-50 | Bottles | Empty Bottle or Bee Bottle | 50 | 25-50 @@ -60,8 +61,8 @@ All prices range approx. from half the base price to the base price in increment | Health | Heart Container | 40 | 20-40 | | Sanctuary Heart | 50 | 25-50 | | Piece of Heart | 10 | 5-10 -| Dungeon | Big Keys | 50 | 25-50 -| | Small Keys | 30 | 15-30 +| Dungeon | Big Keys | 60 | 30-60 +| | Small Keys | 40 | 20-40 | | Info Maps | 20 | 10-20 | | Other Maps & Compasses | 10 | 5-10 | Rupees | Green | Free | Free @@ -74,7 +75,7 @@ All prices range approx. from half the base price to the base price in increment | | Single Arrow | 3 | 3 | Original Shop Items | Other Ammo, Refills, Non-Progressive Shields, Capacity Upgrades, Small Hearts, Retro Quiver, Universal Key | Original | Could be Discounted as Above -In addition, 4-7 items are steeply discounted at random. +~~In addition, 4-7 items are steeply discounted at random.~~ Sales are over. #### Rupee Balancing Algorithm @@ -122,12 +123,62 @@ Added to CLI only now. With more testing, this will be added to the GUI to be ab The Mystery.py file has been updated for those who like to use that for generating games. Supports keydropshuffle, shopsanity, and other settings that have been added. +## Triforce Hunt Options + +Thanks to deathFouton! + +--triforce_pool and --triforce_goal added to the CLI. + +Also, to the Mystery.py he added the following options: +* triforce_goal_min +* triforce_goal_max +* triforce_pool_min +* triforce_pool_max +* triforce_min_difference + +See the example yaml file for demonstrated usage. + ## Experimental Item Counter New item counter modified to show total # Bug Fixes and Notes. +* 0.3.1.7-u + * TFH counter off in modes where it should be off + * Fixed Big Bomb logic for inverted (bad merge) + * Updated pip requirements for MultiClient + * Updated local_install.py and instructions (Thanks MikeTrethewey) +* 0.3.1.6-u + * Fix for inverted. AT or GT vanilla lobby in intensity 3 should not softlock on exit in non-ER modes. + * Backward compatibility for "chaos" enemizer flags. (Thanks krebel) + * Fix for potshuffle and swamp trench generation errors (Thanks StructuralMike) + * Fix for TFH playthrough (Thanks StructuralMike) + * Fix for Standard+Retro (Thanks StructuralMike) + * New options for TFH in CLI and Mystery (Thanks deathFouton) + * A few price adjustments for Shopsanity + * Fixed a subtle problem with Progressive Shields introduced by Shopsanity +* 0.3.1.5-u + * Ganon hints fixed for shops + * Added support for a settings file so SahasrahBot and the main website can use it easier. (Thanks Synack) + * Fix for Skull Pinball during re-generation attempts (thank compiling) + * Fix for multiworld progression balancing and shopsanity +* 0.3.1.4-u + * Fix for Blind when shuffled to TT and another dungeon + * Remove use of RaceRandom + * Minor update to GameType field +* 0.3.1.3-u + * Fix for the Rom field on the GUI +* 0.3.1.2-u + * Include base ER updates + * Goal sign should now indicate whether Aga 2 is required or not + * Inverted logic fixes + * Potion shop (powder) item now properly replaced with rupees + * Removed Arrow Capacity upgrades in retro (non-shopsanity) + * Intensity level 3 fixes courtesy of Catobat + * Scrolling issue in Desert Cannonball +* 0.3.1.1-u + * Potion shop crash in non-shopsanity * 0.3.1.0-u * Shopsanity introduced * Blind sequence restored when Blind is in Theives Town in boss shuffle diff --git a/RaceRandom.py b/RaceRandom.py new file mode 100644 index 00000000..127d966d --- /dev/null +++ b/RaceRandom.py @@ -0,0 +1,44 @@ +import random as _random +from functools import update_wrapper + +__all__ = ["use_secure"] + +_prng_inst = _random.Random() +_cprng_inst = _random.SystemRandom() + +_mode = "prng" + +def use_secure(secure=True): + # pylint: disable=global-statement + global _mode + _mode = "cprng" if secure else "prng" + +def _wrap(name): + def somefunc(*args, **kwargs): + return getattr(_cprng_inst if _mode == "cprng" else _prng_inst, name)(*args, **kwargs) + update_wrapper(somefunc, getattr(_prng_inst, name)) + + return somefunc + +# These are for intellisense purposes only, and will be overwritten below +choice = _prng_inst.choice +gauss = _prng_inst.gauss +getrandbits = _prng_inst.getrandbits +randint = _prng_inst.randint +random = _prng_inst.random +randrange = _prng_inst.randrange +sample = _prng_inst.sample +seed = _prng_inst.seed +shuffle = _prng_inst.shuffle +uniform = _prng_inst.uniform + +for func_name in dir(_random): + if not callable(getattr(_random, func_name)): + continue + if not callable(getattr(_prng_inst, func_name, None)): + continue + if func_name.startswith('_'): + continue + + globals()[func_name] = _wrap(func_name) + __all__.append(func_name) diff --git a/Rom.py b/Rom.py index 310d1a2c..92f0bfc9 100644 --- a/Rom.py +++ b/Rom.py @@ -16,7 +16,7 @@ from BaseClasses import CollectionState, ShopType, Region, Location, OWEdge, Doo from DoorShuffle import compass_data, DROptions, boss_indicator from Dungeons import dungeon_music_addresses from KeyDoorShuffle import count_locations_exclude_logic -from Regions import location_table +from Regions import location_table, shop_to_location_table from RoomData import DoorKind from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts @@ -312,8 +312,10 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_ if world.get_dungeon("Thieves Town", player).boss.enemizer_name == "Blind": rom.write_byte(0x04DE81, 0x6) # maiden spawn - # restore blind spawn code - rom.write_bytes(0xEA081, [0xaf, 0xcc, 0xf3, 0x7e, 0xc9, 0x6, 0xf0, 0x24, + # restore blind spawn code - necessary because the old enemizer clobbers this stuff + # this line could be commented out if ijwu's enemizer is used exclusively + # if keeping this line, note the jump to the dr_baserom's enemizer section + rom.write_bytes(0xEA081, [0x5c, 0x00, 0x80, 0xb7, 0xc9, 0x6, 0xf0, 0x24, 0xad, 0x3, 0x4, 0x29, 0x20, 0xf0, 0x1d]) rom.write_byte(0x200101, 0) # Do not close boss room door on entry. @@ -736,6 +738,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): aga_portal = world.get_portal('Agahnims Tower', player) gt_portal = world.get_portal('Ganons Tower', player) aga_portal.exit_offset, gt_portal.exit_offset = gt_portal.exit_offset, aga_portal.exit_offset + aga_portal.default = False + gt_portal.default = False for portal in world.dungeon_portals[player]: if not portal.default: @@ -1122,17 +1126,18 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # set up goals for treasure hunt rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28]) - rom.write_byte(0x180167, world.treasure_hunt_count[player] % 256) - rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) + if world.goal[player] == 'triforcehunt': + rom.write_byte(0x180167, int(world.treasure_hunt_count[player]) % 256) + rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) - rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed + rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed gametype = 0x04 # item - if world.shuffle[player] != 'vanilla': - gametype |= 0x02 # entrance + if world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]: + gametype |= 0x02 # entrance/door if enemized: - gametype |= 0x01 # enemizer - rom.write_byte(0x180211, gametype) # Game type + gametype |= 0x01 # enemizer + rom.write_byte(0x180211, gametype) # Game type # assorted fixes rom.write_byte(0x1800A2, 0x01) # remain in real dark world when dying in dark world dungeon before killing aga1 @@ -1351,6 +1356,14 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x13f0f6+x, room.position(portal.door).value) rom.write_byte(0x13f0f7+x, room.kind(portal.door).value) + # Bitfield - enable text box to show with free roaming items + # + # ---o bmcs + # o - enabled for outside dungeon items + # b - enabled for inside big keys + # m - enabled for inside maps + # c - enabled for inside compasses + # s - enabled for inside small keys rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle[player] else 0x00) | (0x02 if world.compassshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] else 0x00) @@ -1367,6 +1380,14 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): else: rom.write_byte(0x18003C, 0x00) + # Bitfield - enable free items to show up in menu + # + # ----dcba + # d - Compass + # c - Map + # b - Big Key + # a - Small Key + # rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] else 0x00) @@ -1468,7 +1489,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) # allow smith into multi-entrance caves in appropriate shuffles - if world.shuffle[player] in ['restricted', 'full', 'crossed', 'insanity']: + if world.shuffle[player] in ['restricted', 'full', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item @@ -1559,7 +1580,10 @@ def write_custom_shops(rom, world, player): break if world.shopsanity[player] or shop.type == ShopType.TakeAny: rom.write_byte(0x186560 + shop.sram_address + index, 1) - item_id = ItemFactory(item['item'], player).code + loc_item = world.get_location(shop_to_location_table[shop.region.name][index], player).item + if not loc_item: + loc_item = ItemFactory(item['item'], player) + item_id = loc_item.code price = int16_as_bytes(item['price']) replace = ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF replace_price = int16_as_bytes(item['replacement_price']) @@ -2007,16 +2031,16 @@ def write_strings(rom, world, player, team): prog_bow_locs = world.find_items('Progressive Bow', player) distinguished_prog_bow_loc = next((location for location in prog_bow_locs if location.item.code == 0x65), None) + progressive_silvers = world.difficulty_requirements[player].progressive_bow_limit >= 2 or world.swords[player] == 'swordless' if distinguished_prog_bow_loc: prog_bow_locs.remove(distinguished_prog_bow_loc) - silverarrow_hint = (' %s?' % hint_text(distinguished_prog_bow_loc).replace('Ganon\'s', 'my')) + silverarrow_hint = (' %s?' % hint_text(distinguished_prog_bow_loc).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint if any(prog_bow_locs): - silverarrow_hint = (' %s?' % hint_text(random.choice(prog_bow_locs)).replace('Ganon\'s', 'my')) + silverarrow_hint = (' %s?' % hint_text(random.choice(prog_bow_locs)).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint - crystal5 = world.find_items('Crystal 5', player)[0] crystal6 = world.find_items('Crystal 6', player)[0] tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5.hint_text, crystal6.hint_text) @@ -2025,7 +2049,15 @@ def write_strings(rom, world, player, team): tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[player] == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt[player] - tt['sign_ganon'] = ('You need %d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else 'You need %d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player] + + ganon_crystals_singular = 'You need %d crystal to beat Ganon.' + ganon_crystals_plural = 'You need %d crystals to beat Ganon.' + + if world.goal[player] == 'ganon': + ganon_crystals_singular = 'To beat Ganon you must collect %d crystal and defeat his minion at the top of his tower.' + ganon_crystals_plural = 'To beat Ganon you must collect %d crystals and defeat his minion at the top of his tower.' + + tt['sign_ganon'] = (ganon_crystals_singular if world.crystals_needed_for_ganon[player] == 1 else ganon_crystals_plural) % world.crystals_needed_for_ganon[player] if world.goal[player] in ['dungeons']: tt['sign_ganon'] = 'You need to complete all the dungeons.' @@ -2042,7 +2074,7 @@ def write_strings(rom, world, player, team): tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' - tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % world.treasure_hunt_count[player] + tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) elif world.goal[player] in ['pedestal']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' diff --git a/Rules.py b/Rules.py index 602a51a4..dfc11f19 100644 --- a/Rules.py +++ b/Rules.py @@ -41,6 +41,8 @@ def set_rules(world, player): elif world.goal[player] == 'ganon': # require aga2 to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) + elif world.goal[player] == 'triforcehunt': + add_rule(world.get_location('Murahdahla', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) @@ -689,7 +691,9 @@ def inverted_rules(world, player): set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player) and state.has_Pearl(player)) set_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Flippers', player) and state.has_Pearl(player)) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo - set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player) or (state.can_reach('Light World', 'Region', player) and state.has_Mirror(player))) + set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player) and + (state.has_Pearl(player) or state.has('Beat Agahnim 1', player)) or (state.can_reach('Light World', 'Region', player) + and state.has_Mirror(player))) # Need LW access using Mirror or Portal set_rule(world.get_location('Mushroom', player), lambda state: state.has_Pearl(player)) # need pearl to pick up bushes set_rule(world.get_entrance('Bush Covered Lawn Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bush Covered Lawn Inner Bushes', player), lambda state: state.has_Pearl(player)) @@ -728,7 +732,6 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('East Dark World Bridge', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Flippers', player)) # ToDo any fake flipper set up? (Qirn Jump) set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hammer Peg Area Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -807,7 +810,9 @@ def no_glitches_rules(world, player): else: add_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player))) add_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to - add_rule(world.get_entrance('Lake Hylia Island', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + add_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to + set_rule(world.get_entrance('Lake Hylia Warp', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to + set_rule(world.get_entrance('Northeast Light World Warp', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to add_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) add_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has('Flippers', player)) add_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) @@ -1308,6 +1313,7 @@ def set_big_bomb_rules(world, player): # means you need an escape route of either Flippers or Flute add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player)) and (basic_routes(state) or state.has_Mirror(player))) + def set_inverted_big_bomb_rules(world, player): bombshop_entrance = world.get_region('Inverted Big Bomb Shop', player).entrances[0] Normal_LW_entrances = ['Blinds Hideout', @@ -1358,33 +1364,35 @@ def set_inverted_big_bomb_rules(world, player): 'Cave 45', 'Checkerboard Cave', 'Inverted Big Bomb Shop'] - LW_DM_entrances = ['Old Man Cave (East)', - 'Old Man House (Bottom)', - 'Old Man House (Top)', - 'Death Mountain Return Cave (East)', - 'Spectacle Rock Cave Peak', - 'Tower of Hera', - 'Death Mountain Return Cave (West)', - 'Paradox Cave (Top)', - 'Fairy Ascension Cave (Top)', - 'Spiral Cave', - 'Paradox Cave (Bottom)', - 'Paradox Cave (Middle)', - 'Hookshot Fairy', - 'Spiral Cave (Bottom)', - 'Mimic Cave', - 'Fairy Ascension Cave (Bottom)', - 'Desert Palace Entrance (West)', - 'Desert Palace Entrance (North)', - 'Desert Palace Entrance (South)'] + Isolated_LW_entrances = ['Old Man Cave (East)', + 'Old Man House (Bottom)', + 'Old Man House (Top)', + 'Death Mountain Return Cave (East)', + 'Spectacle Rock Cave Peak', + 'Tower of Hera', + 'Death Mountain Return Cave (West)', + 'Paradox Cave (Top)', + 'Fairy Ascension Cave (Top)', + 'Spiral Cave', + 'Paradox Cave (Bottom)', + 'Paradox Cave (Middle)', + 'Hookshot Fairy', + 'Spiral Cave (Bottom)', + 'Mimic Cave', + 'Fairy Ascension Cave (Bottom)', + 'Desert Palace Entrance (West)', + 'Desert Palace Entrance (North)', + 'Desert Palace Entrance (South)'] + Eastern_DW_entrances = ['Palace of Darkness', + 'Palace of Darkness Hint', + 'Dark Lake Hylia Fairy', + 'East Dark World Hint'] Northern_DW_entrances = ['Brewery', 'C-Shaped House', 'Chest Game', 'Dark World Hammer Peg Cave', - 'Red Shield Shop', 'Inverted Dark Sanctuary', 'Fortune Teller (Dark)', - 'Dark World Shop', 'Dark World Lumberjack Shop', 'Thieves Town', 'Skull Woods First Section Door', @@ -1409,19 +1417,26 @@ def set_inverted_big_bomb_rules(world, player): 'Hookshot Cave', 'Turtle Rock Isolated Ledge Entrance', 'Hookshot Cave Back Entrance', - 'Inverted Agahnims Tower', - 'Dark Lake Hylia Ledge Fairy', + 'Inverted Agahnims Tower',] + LW_walkable_entrances = ['Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Hint', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy', - 'Misery Mire'] + 'Misery Mire', + 'Red Shield Shop'] LW_bush_entrances = ['Bush Covered House', 'Light World Bomb Hut', 'Graveyard Cave'] + LW_inaccessible_entrances = ['Desert Palace Entrance (East)', + 'Spectacle Rock Cave', + 'Spectacle Rock Cave (Bottom)'] - set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Dark World', 'Region', player) and state.can_reach('Inverted Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player)) + set_rule(world.get_entrance('Pyramid Fairy', player), + lambda state: state.can_reach('East Dark World', 'Region', player) + and state.can_reach('Inverted Big Bomb Shop', 'Region', player) + and state.has('Crystal 5', player) and state.has('Crystal 6', player)) # crossing peg bridge starting from the southern dark world def cross_peg_bridge(state): @@ -1433,25 +1448,38 @@ def set_inverted_big_bomb_rules(world, player): # H = hammer # M = Mirror # G = Glove - if bombshop_entrance.name in Normal_LW_entrances: + if bombshop_entrance.name in Eastern_DW_entrances: + # Just walk to the pyramid + pass + elif bombshop_entrance.name in Normal_LW_entrances: # Just walk to the castle and mirror. add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player)) - elif bombshop_entrance.name in LW_DM_entrances: + elif bombshop_entrance.name in Isolated_LW_entrances: # For these entrances, you cannot walk to the castle/pyramid and thus must use Mirror and then Flute. add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and state.has_Mirror(player)) elif bombshop_entrance.name in Northern_DW_entrances: # You can just fly with the Flute, you can take a long walk with Mitts and Hammer, # or you can leave a Mirror portal nearby and then walk to the castle to Mirror again. - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute or (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (state.has_Mirror(player) and state.can_reach('Light World', 'Region', player))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute or + (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or + (state.has_Mirror(player) and state.can_reach('Light World', 'Region', player))) elif bombshop_entrance.name in Southern_DW_entrances: # This is the same as north DW without the Mitts rock present. add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or state.can_flute(player) or (state.has_Mirror(player) and state.can_reach('Light World', 'Region', player))) elif bombshop_entrance.name in Isolated_DW_entrances: # There's just no way to escape these places with the bomb and no Flute. add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player)) + elif bombshop_entrance.name in LW_walkable_entrances: + # You can fly with the flute, or leave a mirror portal and walk through the light world + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) or + (state.has_Mirror(player) and state.can_reach('Light World', 'Region', player))) elif bombshop_entrance.name in LW_bush_entrances: # These entrances are behind bushes in LW so you need either Pearl or the tools to solve NDW bomb shop locations. add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and (state.can_flute(player) or state.has_Pearl(player) or (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)))) + #todo: I stopped here - + elif bombshop_entrance.name == 'Dark World Shop': + # This is mostly the same as NDW but the Mirror path requires the Pearl, or using the Hammer + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute or (state.can_lift_heavy_rocks(player) and state.has('Hammer', player)) or (state.has_Mirror(player) and state.can_reach('Light World', 'Region', player) and (state.has_Pearl(player) or state.has('Hammer', player)))) elif bombshop_entrance.name == 'Bumper Cave (Bottom)': # This is mostly the same as NDW but the Mirror path requires being able to lift a rock. add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute or (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (state.has_Mirror(player) and state.can_lift_rocks(player) and state.can_reach('Light World', 'Region', player))) @@ -1476,6 +1504,14 @@ def set_inverted_big_bomb_rules(world, player): elif bombshop_entrance.name == 'Capacity Upgrade': # You must Mirror but then can use either Ice Palace return path. add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player)) and state.has_Mirror(player)) + elif bombshop_entrance.name in LW_inaccessible_entrances: + # You can't get to the pyramid from these entrances without bomb duping. + raise Exception('No valid path to open Pyramid Fairy. (Could not route from %s)' % bombshop_entrance.name) + elif bombshop_entrance.name == 'Pyramid Fairy': + # Self locking. The shuffles don't put the bomb shop here, but doesn't lock anything important. + set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) + else: + raise Exception('No logic found for routing from %s to the pyramid.' % bombshop_entrance.name) def set_bunny_rules(world, player): diff --git a/Utils.py b/Utils.py index 229573f2..06ccca62 100644 --- a/Utils.py +++ b/Utils.py @@ -6,19 +6,24 @@ import sys import xml.etree.ElementTree as ET from collections import defaultdict + def int16_as_bytes(value): value = value & 0xFFFF return [value & 0xFF, (value >> 8) & 0xFF] + def int32_as_bytes(value): value = value & 0xFFFFFFFF return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF] + def pc_to_snes(value): - return ((value<<1) & 0x7F0000)|(value & 0x7FFF)|0x8000 + return ((value << 1) & 0x7F0000) | (value & 0x7FFF) | 0x8000 + def snes_to_pc(value): - return ((value & 0x7F0000)>>1)|(value & 0x7FFF) + return ((value & 0x7F0000) >> 1) | (value & 0x7FFF) + def parse_player_names(names, players, teams): names = [n for n in re.split(r'[, ]', names) if n] @@ -32,9 +37,11 @@ def parse_player_names(names, players, teams): names = names[players:] return ret + def is_bundled(): return getattr(sys, 'frozen', False) + def local_path(path): # just do stuff here and bail return os.path.join(".", path) @@ -44,51 +51,26 @@ def local_path(path): if is_bundled(): # we are running in a bundle - local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member + local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member else: # we are running in a normal Python environment local_path.cached_path = os.path.dirname(os.path.abspath(__file__)) return os.path.join(local_path.cached_path, path) + local_path.cached_path = None + def output_path(path): - # just do stuff here and bail - return os.path.join(".", path) - - if output_path.cached_path is not None: - return os.path.join(output_path.cached_path, path) - - if not is_bundled(): + if output_path.cached_path is None: output_path.cached_path = '.' - return os.path.join(output_path.cached_path, path) - else: - # has been packaged, so cannot use CWD for output. - if sys.platform == 'win32': - #windows - documents = os.path.join(os.path.expanduser("~"),"Documents") - elif sys.platform == 'darwin': - from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error - # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains - NSDocumentDirectory = 9 - NSUserDomainMask = 1 - # True for expanding the tilde into a fully qualified path - documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0] - elif sys.platform.find("linux") or sys.platform.find("ubuntu") or sys.platform.find("unix"): - documents = os.path.join(os.path.expanduser("~"),"Documents") - else: - raise NotImplementedError('Not supported yet') + return os.path.join(output_path.cached_path, path) - output_path.cached_path = os.path.join(documents, 'ALttPDoorRandomizer') - if not os.path.exists(output_path.cached_path): - os.makedirs(output_path.cached_path) - if not os.path.join(output_path.cached_path, path): - os.makedirs(os.path.join(output_path.cached_path, path)) - return os.path.join(output_path.cached_path, path) output_path.cached_path = None + def open_file(filename): if sys.platform == 'win32': os.startfile(filename) @@ -96,15 +78,17 @@ def open_file(filename): open_command = 'open' if sys.platform == 'darwin' else 'xdg-open' subprocess.call([open_command, filename]) + def close_console(): if sys.platform == 'win32': - #windows + # windows import ctypes.wintypes try: ctypes.windll.kernel32.FreeConsole() except Exception: pass + def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', new_rom='working.sfc'): from collections import OrderedDict import json @@ -125,7 +109,7 @@ def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Jap if offset - 1 in out_data: out_data[offset-1].extend(out_data.pop(offset)) with open('data/base2current.json', 'wt') as outfile: - json.dump([{key:value} for key, value in out_data.items()], outfile, separators=(",", ":")) + json.dump([{key: value} for key, value in out_data.items()], outfile, separators=(",", ":")) basemd5 = hashlib.md5() basemd5.update(new_rom_data) @@ -241,7 +225,6 @@ def room_palette_data(old_rom): print(f'{hex(header_offset)}: {[hex(x) for x in rooms]}') - # Palette notes: # HC: 0 # Sewer/Dungeon: 1 @@ -303,21 +286,22 @@ def print_wiki_doors_by_region(d_regions, world, player): toprint += ('| '+'
'.join(strs_to_print)) toprint += "\n" toprint += ('|}') + "\n" - with open(os.path.join(".","resources", "user", "regions-" + d + ".txt"),"w+") as f: + with open(os.path.join(".", "resources", "user", "regions-" + d + ".txt"), "w+") as f: f.write(toprint) + def update_deprecated_args(args): if args: argVars = vars(args) - truthy = [ 1, True, "True", "true" ] + truthy = [1, True, "True", "true"] # Hints default to TRUE # Don't do: Yes # Do: No if "no_hints" in argVars: src = "no_hints" - if isinstance(argVars["hints"],dict): + if isinstance(argVars["hints"], dict): tmp = {} - for idx in range(1,len(argVars["hints"]) + 1): + for idx in range(1, len(argVars["hints"]) + 1): tmp[idx] = argVars[src] not in truthy # tmp = !src args.hints = tmp # dest = tmp else: @@ -326,23 +310,23 @@ def update_deprecated_args(args): # Do: Yes if "hints" in argVars: src = "hints" - if isinstance(argVars["hints"],dict): + if isinstance(argVars["hints"], dict): tmp = {} - for idx in range(1,len(argVars["hints"]) + 1): + for idx in range(1, len(argVars["hints"]) + 1): tmp[idx] = argVars[src] not in truthy # tmp = !src args.no_hints = tmp # dest = tmp else: args.no_hints = args.hints not in truthy # dest = !src - # Spoiler defaults to FALSE - # Don't do: No - # Do: Yes - if "create_spoiler" in argVars: - args.suppress_spoiler = not args.create_spoiler in truthy + # Spoiler defaults to TRUE # Don't do: Yes # Do: No if "suppress_spoiler" in argVars: args.create_spoiler = not args.suppress_spoiler in truthy + # Don't do: No + # Do: Yes + if "create_spoiler" in argVars: + args.suppress_spoiler = not args.create_spoiler in truthy # ROM defaults to TRUE # Don't do: Yes @@ -376,6 +360,7 @@ def update_deprecated_args(args): return args + def print_wiki_doors_by_room(d_regions, world, player): for d, region_list in d_regions.items(): tile_map = {} @@ -407,14 +392,15 @@ def print_wiki_doors_by_room(d_regions, world, player): toprint += ('|-') + "\n" toprint += ('! Door !! Room Side !! Requirement') + "\n" for ext in region.exits: - ext_part = ext.name.replace(region.name,'') + ext_part = ext.name.replace(region.name, '') ext_part = ext_part.strip() toprint += ('{{DungeonRoomDoorList/Row|{{ROOTPAGENAME}}|{{SUBPAGENAME}}|' + ext_part + '|Side|}}') + "\n" toprint += ('|}') + "\n" toprint += ('') + "\n" - with open(os.path.join(".","resources", "user", "rooms-" + d + ".txt"),"w+") as f: + with open(os.path.join(".", "resources", "user", "rooms-" + d + ".txt"), "w+") as f: f.write(toprint) + def print_xml_doors(d_regions, world, player): root = ET.Element('root') for d, region_list in d_regions.items(): @@ -459,7 +445,8 @@ def extract_data_from_us_rom(rom): with open(rom, 'rb') as stream: rom_data = bytearray(stream.read()) - rooms = [0x9a, 0x69, 0x78, 0x79, 0x7a, 0x88, 0x8a, 0xad] + rooms = [0x1c, 0x1d, 0x4e] + # rooms = [0x9a, 0x69, 0x78, 0x79, 0x7a, 0x88, 0x8a, 0xad] for room in rooms: b2idx = room*2 b3idx = room*3 @@ -470,11 +457,20 @@ def extract_data_from_us_rom(rom): header.append(rom_data[headerloc+i]) objectptr = 0xF8000 + b3idx objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000 - objectloc -= 0x100000 + bank = rom_data[objectptr+2] + even = bank % 2 == 0 + adjustment = ((bank // 2 if even else bank // 2 + 1) << 16) + (0x8000 if even else 0) + objectloc -= adjustment stop, idx = False, 0 ffcnt = 0 mode = 0 - while ffcnt < 2: + # first two bytes + b1 = rom_data[objectloc+idx] + b2 = rom_data[objectloc+idx+1] + objectdata.append(b1) + objectdata.append(b2) + idx += 2 + while ffcnt < 3: b1 = rom_data[objectloc+idx] b2 = rom_data[objectloc+idx+1] b3 = rom_data[objectloc+idx+2] @@ -483,9 +479,11 @@ def extract_data_from_us_rom(rom): if b1 == 0xff and b2 == 0xff: ffcnt += 1 mode = 0 - if b1 == 0xf0 and b2 == 0xff: + idx += 2 + elif b1 == 0xf0 and b2 == 0xff: mode = 1 - if not mode and ffcnt < 2: + idx += 2 + elif not mode and ffcnt < 3: objectdata.append(b3) idx += 3 else: @@ -573,8 +571,9 @@ def extract_data_from_jp_rom(rom): with open(rom, 'rb') as stream: rom_data = bytearray(stream.read()) + rooms = [0x1c, 0x1d, 0x4e] # rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d] - rooms = [0x1a, 0x2a, 0xd1] + # rooms = [0x1a, 0x2a, 0xd1] for room in rooms: b2idx = room*2 b3idx = room*3 @@ -585,11 +584,20 @@ def extract_data_from_jp_rom(rom): header.append(rom_data[headerloc+i]) objectptr = 0xF8000 + b3idx objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000 - objectloc -= 0x100000 + bank = rom_data[objectptr+2] + even = bank % 2 == 0 + adjustment = ((bank // 2 if even else bank // 2 + 1) << 16) + (0x8000 if even else 0) + objectloc -= adjustment stop, idx = False, 0 ffcnt = 0 mode = 0 - while ffcnt < 2: + # first two bytes + b1 = rom_data[objectloc+idx] + b2 = rom_data[objectloc+idx+1] + objectdata.append(b1) + objectdata.append(b2) + idx += 2 + while ffcnt < 3: b1 = rom_data[objectloc+idx] b2 = rom_data[objectloc+idx+1] b3 = rom_data[objectloc+idx+2] @@ -598,9 +606,11 @@ def extract_data_from_jp_rom(rom): if b1 == 0xff and b2 == 0xff: ffcnt += 1 mode = 0 - if b1 == 0xf0 and b2 == 0xff: + idx += 2 + elif b1 == 0xf0 and b2 == 0xff: mode = 1 - if not mode and ffcnt < 2: + idx += 2 + elif not mode and ffcnt < 3: objectdata.append(b3) idx += 3 else: diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..c7418817 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md index fd0233f0..298230ed 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -1,13 +1,19 @@ # Running from source -1. Get [python](http://python.org/downloads) -1. Get the [Door Randomizer Unstable source code](https://github.com/Aerinon/ALttPDoorRandomizer/archive/DoorDevUnstable.zip) -1. Install Platform-specific dependencies -1. Run `DoorRandomizer.py` for command-line script -1. Run `Gui.py` for user interface +|Instruction|Image| +|-----------|-----| +|Get [python](http://python.org/downloads)|![Get python](https://raw.githubusercontent.com/miketrethewey/ALttPDoorRandomizer/DoorDevUnstable/docs/images/python.png) +|Get the [Door Randomizer Unstable source code](https://github.com/Aerinon/ALttPDoorRandomizer/archive/DoorDevUnstable.zip)|![Get source code](https://raw.githubusercontent.com/miketrethewey/ALttPDoorRandomizer/DoorDevUnstable/docs/images/sourcecode.png) +|Install Platform-specific dependencies (see below)|![Command line](https://raw.githubusercontent.com/miketrethewey/ALttPDoorRandomizer/DoorDevUnstable/docs/images/cmd.png) +|Run `DungeonRandomizer.py` for command-line script|![DungeonRandomizer.py](https://raw.githubusercontent.com/miketrethewey/ALttPDoorRandomizer/DoorDevUnstable/docs/images/py-dungeonrandomizer.png) +|Run `Gui.py` for user interface|![Gui.py](https://raw.githubusercontent.com/miketrethewey/ALttPDoorRandomizer/DoorDevUnstable/docs/images/py-gui.png) ## Platform-specific dependencies -### Windows - -* Run `resources/ci/common/local_install.py` +|Platform|Command line|Image| +| :----: |------------|-----| +|Windows |`resources/ci/common/local_install.py`|![Windows](https://raw.githubusercontent.com/miketrethewey/ALttPDoorRandomizer/DoorDevUnstable/docs/images/cli-windows.png) +|`py` Launcher: 3.9 |`resources/ci/common/local_install.py --py 3.9`| +|`py` Launcher: 3.8 |`resources/ci/common/local_install.py --py 3.8`| +|`py` Launcher: 3.7 |`resources/ci/common/local_install.py --py 3.7`| +|`py` Launcher: 3.6 |`resources/ci/common/local_install.py --py 3.6`| diff --git a/docs/images/cli-windows.png b/docs/images/cli-windows.png new file mode 100644 index 00000000..2cb7a092 Binary files /dev/null and b/docs/images/cli-windows.png differ diff --git a/docs/images/cmd.png b/docs/images/cmd.png new file mode 100644 index 00000000..cb2b6973 Binary files /dev/null and b/docs/images/cmd.png differ diff --git a/docs/images/py-dungeonrandomizer.png b/docs/images/py-dungeonrandomizer.png new file mode 100644 index 00000000..f3d8de6a Binary files /dev/null and b/docs/images/py-dungeonrandomizer.png differ diff --git a/docs/images/py-gui.png b/docs/images/py-gui.png new file mode 100644 index 00000000..6d7b3cc9 Binary files /dev/null and b/docs/images/py-gui.png differ diff --git a/docs/images/python.png b/docs/images/python.png new file mode 100644 index 00000000..87bd1a61 Binary files /dev/null and b/docs/images/python.png differ diff --git a/docs/images/sourcecode.png b/docs/images/sourcecode.png new file mode 100644 index 00000000..07acbf7b Binary files /dev/null and b/docs/images/sourcecode.png differ diff --git a/mystery_example.yml b/mystery_example.yml index cb075090..bd19e6f5 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -39,11 +39,20 @@ dungeons: 1 pedestal: 2 triforce-hunt: 0 + triforce_goal_min: 10 + triforce_goal_max: 30 + triforce_pool_min: 20 + triforce_pool_max: 40 + triforce_min_difference: 10 dungeon_items: standard: 10 mc: 3 mcs: 2 full: 5 + dungeon_counters: + on: 5 + off: 0 + default: 5 experimental: on: 1 off: 0 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index bbbae5a6..e1b8ad65 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -5,13 +5,13 @@ "type": "bool" }, "create_spoiler": { - "action": "store_true", - "type": "bool" + "action": "store_false", + "dest": "suppress_spoiler", + "type": "bool", + "help": "suppress" }, "suppress_spoiler": { - "action": "store_false", - "dest": "create_spoiler", - "help": "suppress" + "action": "store_true" }, "logic": { "choices": [ @@ -226,6 +226,13 @@ "usestartinventory": { "type": "bool" }, + "triforce_pool": {}, + "triforce_goal": {}, + "triforce_pool_min": {}, + "triforce_pool_max": {}, + "triforce_goal_min": {}, + "triforce_goal_max": {}, + "triforce_min_difference": {}, "custom": { "type": "bool", "help": "suppress" diff --git a/resources/app/meta/manifests/pip_requirements.txt b/resources/app/meta/manifests/pip_requirements.txt index f192a6b3..595691d2 100644 --- a/resources/app/meta/manifests/pip_requirements.txt +++ b/resources/app/meta/manifests/pip_requirements.txt @@ -1,3 +1,6 @@ aenum fast-enum -python-bps-continued \ No newline at end of file +python-bps-continued +colorama +aioconsole +websockets \ No newline at end of file diff --git a/resources/ci/common/common.py b/resources/ci/common/common.py index f2288fe2..b89a266b 100644 --- a/resources/ci/common/common.py +++ b/resources/ci/common/common.py @@ -1,5 +1,7 @@ import os # for env vars import stat # file statistics +import sys # default system info +from my_path import get_py_path global UBUNTU_VERSIONS global DEFAULT_EVENT @@ -44,6 +46,8 @@ def prepare_env(): APP_VERSION = f.readlines()[0].strip() # ci data env["CI_SYSTEM"] = os.getenv("CI_SYSTEM","") + # py data + (env["PYTHON_EXE_PATH"],env["PY_EXE_PATH"],env["PIP_EXE_PATH"]) = get_py_path() # git data env["BRANCH"] = os.getenv("TRAVIS_BRANCH","") env["GITHUB_ACTOR"] = os.getenv("GITHUB_ACTOR","MegaMan.EXE") @@ -75,10 +79,19 @@ def prepare_env(): env["BUILD_NUMBER"] = os.getenv("TRAVIS_BUILD_NUMBER",env["GITHUB_RUN_NUMBER"]) GITHUB_TAG = os.getenv("TRAVIS_TAG",os.getenv("GITHUB_TAG","")) - OS_NAME = os.getenv("TRAVIS_OS_NAME",os.getenv("OS_NAME","")).replace("macOS","osx") + OS_NAME = os.getenv("TRAVIS_OS_NAME",os.getenv("OS_NAME",sys.platform)).replace("macOS","osx") OS_DIST = os.getenv("TRAVIS_DIST","notset") OS_VERSION = "" + if "win32" in OS_NAME or \ + "cygwin" in OS_NAME or \ + "msys" in OS_NAME: + OS_NAME = "windows" + elif "darwin" in OS_NAME: + OS_NAME = "osx" + elif "linux2" in OS_NAME: + OS_NAME = "linux" + if '-' in OS_NAME: OS_VERSION = OS_NAME[OS_NAME.find('-')+1:] OS_NAME = OS_NAME[:OS_NAME.find('-')] diff --git a/resources/ci/common/get_get_pip.py b/resources/ci/common/get_get_pip.py index 9b69930d..a0e127ba 100644 --- a/resources/ci/common/get_get_pip.py +++ b/resources/ci/common/get_get_pip.py @@ -1,39 +1,63 @@ import common +import argparse +import os import urllib.request, ssl import subprocess # do stuff at the shell level env = common.prepare_env() -def get_get_pip(): - print("Getting pip getter!") - #make the request! - url = "https://bootstrap.pypa.io/get-pip.py" - context = ssl._create_unverified_context() - req = urllib.request.urlopen(url, context=context) - got_pip = req.read().decode("utf-8") +def get_get_pip(PY_VERSION): + try: + import pip + except ImportError: + print("Getting pip getter!") + #make the request! + url = "https://bootstrap.pypa.io/get-pip.py" + context = ssl._create_unverified_context() + req = urllib.request.urlopen(url, context=context) + got_pip = req.read().decode("utf-8") - with open("get-pip.py", "w") as g: - req = urllib.request.Request( - url, - data=None, - headers={ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" - } - ) - req = urllib.request.urlopen(req, context=context) - data = req.read().decode("utf-8") - g.write(data) + with open("get-pip.py", "w") as g: + req = urllib.request.Request( + url, + data=None, + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" + } + ) + req = urllib.request.urlopen(req, context=context) + data = req.read().decode("utf-8") + g.write(data) - # get executables - # python - # linux/windows: python - # macosx: python3 - PYTHON_EXECUTABLE = "python3" if "osx" in env["OS_NAME"] else "python" - print("Getting pip!") - subprocess.check_call([PYTHON_EXECUTABLE,"get-pip.py"]) + # get executables + # python + # linux/windows: python + # macosx: python3 + PYTHON_EXECUTABLE = "python3" if "osx" in env["OS_NAME"] else "python" + if PY_VERSION == None: + PY_VERSION = 0 + + if float(PY_VERSION) > 0: + PYTHON_EXECUTABLE = "py" + + print("Getting pip!") + args = [ + env["PYTHON_EXE_PATH"] + PYTHON_EXECUTABLE, + '-' + str(PY_VERSION), + "get-pip.py" + ] + if PY_VERSION == 0: + del args[1] + subprocess.check_call(args) if __name__ == "__main__": - try: - import pip - except ImportError: - get_get_pip() + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--py', default=0) + command_line_args = parser.parse_args() + PY_VERSION = vars(command_line_args)["py"] + + try: + import pip + print("pip is installed") + except ImportError: + get_get_pip(PY_VERSION) diff --git a/resources/ci/common/install.py b/resources/ci/common/install.py index 70d10202..5874d0dd 100644 --- a/resources/ci/common/install.py +++ b/resources/ci/common/install.py @@ -1,9 +1,14 @@ import common +import argparse +import os +import platform import subprocess # do stuff at the shell level env = common.prepare_env() -def run_install(): +pip_requirements = os.path.join(".","resources","app","meta","manifests","pip_requirements.txt") + +def run_install(PY_VERSION,USER): # get executables # python # linux/windows: python @@ -11,20 +16,79 @@ def run_install(): # pip # linux/macosx: pip3 # windows: pip + PYTHON_PATH = env["PYTHON_EXE_PATH"] PYTHON_EXECUTABLE = "python3" if "osx" in env["OS_NAME"] else "python" + PIP_PATH = env["PIP_EXE_PATH"] PIP_EXECUTABLE = "pip" if "windows" in env["OS_NAME"] else "pip3" PIP_EXECUTABLE = "pip" if "osx" in env["OS_NAME"] and "actions" in env["CI_SYSTEM"] else PIP_EXECUTABLE - # upgrade pip - subprocess.check_call([PYTHON_EXECUTABLE,"-m","pip","install","--upgrade","pip"]) + if PY_VERSION == None: + PY_VERSION = 0 + if USER == None: + USER = False + + if float(PY_VERSION) > 0: + PYTHON_EXECUTABLE = "py" + PYTHON_PATH = env["PY_EXE_PATH"] + print("Installing to Python %.1f via Py Launcher" % float(PY_VERSION)) + else: + print("Installing to Python %s" % platform.python_version()) + print("Installing packages at %s level" % ("User" if USER else "Global")) + + print() + print("Upgrading pip-") + # upgrade pip + args = [ + PYTHON_PATH + PYTHON_EXECUTABLE, + '-' + str(PY_VERSION), + "-m", + "pip", + "install", + "--upgrade", + "--user", + "pip" + ] + if not USER: + args.remove("--user") + if PY_VERSION == 0: + del args[1] + subprocess.check_call(args) - # pip version - subprocess.check_call([PIP_EXECUTABLE,"--version"]) # if pip3, install wheel if PIP_EXECUTABLE == "pip3": - subprocess.check_call([PIP_EXECUTABLE,"install","-U","wheel"]) + print("Installing Wheel!") + args = [ + PIP_PATH + PIP_EXECUTABLE, + "install", + "--user", + "-U", + "wheel" + ] + if not USER: + args.remove("--user") + subprocess.check_call(args) + + print() # install listed dependencies - subprocess.check_call([PIP_EXECUTABLE,"install","-r","./resources/app/meta/manifests/pip_requirements.txt"]) + print("Installing dependencies") + print("-----------------------") + args = [ + PIP_PATH + PIP_EXECUTABLE, + "install", + "--user", + "-r", + pip_requirements + ] + if not USER: + args.remove("--user") + subprocess.check_call(args) if __name__ == "__main__": - run_install() + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--py', default=0) + parser.add_argument('--user', default=False, action="store_true") + command_line_args = parser.parse_args() + PY_VERSION = vars(command_line_args)["py"] + USER = vars(command_line_args)["user"] + + run_install(PY_VERSION,USER) diff --git a/resources/ci/common/local_install.py b/resources/ci/common/local_install.py index 8cde8348..02cc25a5 100644 --- a/resources/ci/common/local_install.py +++ b/resources/ci/common/local_install.py @@ -1,8 +1,17 @@ import install import get_get_pip +import argparse + +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument('--py', default=0) +parser.add_argument('--user', default=False, action="store_true") +command_line_args = parser.parse_args() +PY_VERSION = vars(command_line_args)["py"] +USER = vars(command_line_args)["user"] + # get & install pip -get_get_pip.get_get_pip() +get_get_pip.get_get_pip(PY_VERSION) # run installer -install.run_install() +install.run_install(PY_VERSION,USER) diff --git a/resources/ci/common/my_path.py b/resources/ci/common/my_path.py new file mode 100644 index 00000000..7d91f15e --- /dev/null +++ b/resources/ci/common/my_path.py @@ -0,0 +1,33 @@ +import os +import sys + +def get_py_path(): + user_paths = os.environ["PATH"].split(os.pathsep) + (python,py) = ("","") + + for path in user_paths: + parts = path.split(os.sep) + part = parts[len(parts) - 1].lower() + if ("python" in part) and ('.' not in part): + path.replace(os.sep,os.sep + os.sep) + if path not in user_paths: + py = path + + for path in sys.path: + parts = path.split(os.sep) + part = parts[len(parts) - 1].lower() + if ("python" in part) and ('.' not in part): + path.replace(os.sep,os.sep + os.sep) + if path not in user_paths: + python = path + + paths = ( + os.path.join(python,"") if python != "" else "", + os.path.join(py,"") if py != "" else "", + os.path.join(python,"Scripts","") if python != "" else "" + ) + # print(paths) + return paths + +if __name__ == "__main__": + get_py_path() diff --git a/source/classes/constants.py b/source/classes/constants.py index 2c59ed9f..5e614db0 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -34,7 +34,7 @@ CUSTOMITEMLABELS = [ "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Bottle", "Cane of Somaria", - "Cane of Byrna", "Magic Cape", "Magic Mirror", "Pegasus Boots", "Power Glove", + "Cane of Byrna", "Cape", "Magic Mirror", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Moon Pearl", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Fighter Sword", "Master Sword", "Tempered Sword", diff --git a/source/gui/adjust/overview.py b/source/gui/adjust/overview.py index 4f7fe543..b741f63e 100644 --- a/source/gui/adjust/overview.py +++ b/source/gui/adjust/overview.py @@ -7,6 +7,8 @@ import json import logging import os +from Utils import output_path + def adjust_page(top, parent, settings): # Adjust page self = ttk.Frame(parent) @@ -90,6 +92,8 @@ def adjust_page(top, parent, settings): # These are the options to Adjust def adjustRom(): + if output_path.cached_path is None: + output_path.cached_path = top.settings["outputpath"] options = { "heartbeep": "heartbeep", "heartcolor": "heartcolor", diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 70bf9bd9..d69ab5cb 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -146,10 +146,13 @@ def bottom_frame(self, parent, args=None): self.widgets[widget].pieces["button"].pack(side=LEFT) def open_output(): - if args and args.outputpath: - open_file(output_path(args.outputpath)) - else: - open_file(output_path(parent.settings["outputpath"])) + if output_path.cached_path is None: + if args and args.outputpath: + output_path.cached_path = args.outputpath + else: + output_path.cached_path = parent.settings["outputpath"] + + open_file(output_path('.')) ## Output Button # widget ID diff --git a/source/gui/randomize/generation.py b/source/gui/randomize/generation.py index 4bcb868c..ea09767f 100644 --- a/source/gui/randomize/generation.py +++ b/source/gui/randomize/generation.py @@ -66,7 +66,7 @@ def generation_page(parent,settings): # FIXME: Translate these def RomSelect(): rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")], initialdir=os.path.join(".")) - self.widgets[widget].storageVar.set(rom) + self.widgets["rom"].storageVar.set(rom) # dialog button self.widgets[widget].pieces["button"] = Button(self.widgets[widget].pieces["frame"], text='Select Rom', command=RomSelect) diff --git a/test/TestDeathMountain.py b/test/TestDeathMountain.py new file mode 100644 index 00000000..089ae52d --- /dev/null +++ b/test/TestDeathMountain.py @@ -0,0 +1,53 @@ +from BaseClasses import World +from Dungeons import create_dungeons +from EntranceShuffle import link_entrances +from ItemList import difficulties +from Regions import create_regions +from Rules import set_rules +from test.TestVanilla import TestVanilla + + +class TestDeathMountain(TestVanilla): + def setUp(self): + self.world = World(1, 'vanilla', 'noglitches', 'open', 'random', 'normal', 'normal', 'none', 'on', 'ganon', 'balanced', + True, False, False, False, False, False, False, False, False, None, + 'none', False) + self.world.difficulty_requirements = difficulties['normal'] + create_regions(self.world, 1) + create_dungeons(self.world, 1) + link_entrances(self.world, 1) + set_rules(self.world, 1) + + def testWestDeathMountain(self): + self.run_tests([ + ["Ether Tablet", False, []], + ["Ether Tablet", False, [], ['Progressive Glove', 'Ocarina']], + ["Ether Tablet", False, [], ['Lamp', 'Ocarina']], + ["Ether Tablet", False, [], ['Magic Mirror', 'Hookshot']], + ["Ether Tablet", False, [], ['Magic Mirror', 'Hammer']], + ["Ether Tablet", False, ['Progressive Sword'], ['Progressive Sword']], + ["Ether Tablet", False, [], ['Book of Mudora']], + ["Ether Tablet", True, ['Ocarina', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Ocarina', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + + ["Old Man", False, []], + ["Old Man", False, [], ['Progressive Glove', 'Ocarina']], + ["Old Man", False, [], ['Lamp']], + ["Old Man", True, ['Ocarina', 'Lamp']], + ["Old Man", True, ['Progressive Glove', 'Lamp']], + + ["Spectacle Rock Cave", False, []], + ["Spectacle Rock Cave", False, [], ['Progressive Glove', 'Ocarina']], + ["Spectacle Rock Cave", False, [], ['Lamp', 'Ocarina']], + ["Spectacle Rock Cave", True, ['Ocarina']], + ["Spectacle Rock Cave", True, ['Progressive Glove', 'Lamp']], + + ["Spectacle Rock", False, []], + ["Spectacle Rock", False, [], ['Progressive Glove', 'Ocarina']], + ["Spectacle Rock", False, [], ['Lamp', 'Ocarina']], + ["Spectacle Rock", False, [], ['Magic Mirror']], + ["Spectacle Rock", True, ['Ocarina', 'Magic Mirror']], + ["Spectacle Rock", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], + ]) diff --git a/test/TestVanilla.py b/test/TestVanilla.py new file mode 100644 index 00000000..3bf4a67e --- /dev/null +++ b/test/TestVanilla.py @@ -0,0 +1,41 @@ +import unittest + +from BaseClasses import World, CollectionState +from Dungeons import create_dungeons, get_dungeon_item_pool +from EntranceShuffle import link_entrances +from InvertedRegions import mark_dark_world_regions +from ItemList import difficulties +from Items import ItemFactory +from Regions import create_regions +from Rules import set_rules + + +class TestVanilla(unittest.TestCase): + def setUp(self): + self.world = World(1, 'vanilla', 'noglitches', 'open', 'random', 'normal', 'normal', 'none', 'on', 'ganon', 'balanced', + True, False, False, False, False, False, False, False, False, None, + 'none', False) + self.world.difficulty_requirements = difficulties['normal'] + create_regions(self.world, 1) + create_dungeons(self.world, 1) + link_entrances(self.world, 1) + mark_dark_world_regions(self.world) + set_rules(self.world, 1) + + def run_tests(self, access_pool): + for location, access, *item_pool in access_pool: + items = item_pool[0] + all_except = item_pool[1] if len(item_pool) > 1 else None + with self.subTest(location=location, access=access, items=items, all_except=all_except): + if all_except and len(all_except) > 0: + items = self.world.itempool[:] + items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] + items.extend(ItemFactory(item_pool[0], 1)) + else: + items = ItemFactory(items, 1) + state = CollectionState(self.world) + for item in items: + item.advancement = True + state.collect(item) + + self.assertEqual(self.world.get_location(location, 1).can_reach(state), access) \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/dungeons/TestAgahnimsTower.py b/test/dungeons/TestAgahnimsTower.py new file mode 100644 index 00000000..1c71d51b --- /dev/null +++ b/test/dungeons/TestAgahnimsTower.py @@ -0,0 +1,24 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestAgahnimsTower(TestDungeon): + + def testTower(self): + self.starting_regions = ['Agahnims Tower'] + self.run_tests([ + ["Castle Tower - Room 03", False, []], + ["Castle Tower - Room 03", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], + ["Castle Tower - Room 03", True, ['Progressive Sword']], + + ["Castle Tower - Dark Maze", False, []], + ["Castle Tower - Dark Maze", False, [], ['Small Key (Agahnims Tower)']], + ["Castle Tower - Dark Maze", False, [], ['Lamp']], + ["Castle Tower - Dark Maze", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], + ["Castle Tower - Dark Maze", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Lamp']], + + ["Agahnim 1", False, []], + ["Agahnim 1", False, ['Small Key (Agahnims Tower)'], ['Small Key (Agahnims Tower)']], + ["Agahnim 1", False, [], ['Progressive Sword']], + ["Agahnim 1", False, [], ['Lamp']], + ["Agahnim 1", True, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp', 'Progressive Sword']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestDarkPalace.py b/test/dungeons/TestDarkPalace.py new file mode 100644 index 00000000..48c1928e --- /dev/null +++ b/test/dungeons/TestDarkPalace.py @@ -0,0 +1,80 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestDarkPalace(TestDungeon): + + def testDarkPalace(self): + self.starting_regions = ['Palace of Darkness (Entrance)'] + key = 'Small Key (Palace of Darkness)' + self.run_tests([ + ["Palace of Darkness - Shooter Room", True, []], + + ["Palace of Darkness - The Arena - Ledge", False, []], + ["Palace of Darkness - The Arena - Ledge", False, [], ['Progressive Bow']], + ["Palace of Darkness - The Arena - Ledge", True, ['Progressive Bow']], + + ["Palace of Darkness - Map Chest", False, []], + ["Palace of Darkness - Map Chest", False, [], ['Progressive Bow']], + ["Palace of Darkness - Map Chest", True, ['Progressive Bow']], + + #Lower requirement for self-locking key + #No lower requirement when bow/hammer is out of logic + ["Palace of Darkness - Big Key Chest", False, []], + ["Palace of Darkness - Big Key Chest", False, [key]*5, [key]], + ["Palace of Darkness - Big Key Chest", True, [key]*6], + + ["Palace of Darkness - The Arena - Bridge", False, []], + ["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Progressive Bow']], + ["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Hammer']], + ["Palace of Darkness - The Arena - Bridge", True, [key]], + ["Palace of Darkness - The Arena - Bridge", True, ['Progressive Bow', 'Hammer']], + + ["Palace of Darkness - Stalfos Basement", False, []], + ["Palace of Darkness - Stalfos Basement", False, [], [key, 'Progressive Bow']], + ["Palace of Darkness - Stalfos Basement", False, [], [key, 'Hammer']], + ["Palace of Darkness - Stalfos Basement", True, [key]], + ["Palace of Darkness - Stalfos Basement", True, ['Progressive Bow', 'Hammer']], + + ["Palace of Darkness - Compass Chest", False, []], + ["Palace of Darkness - Compass Chest", False, [key]*3, [key]], + ["Palace of Darkness - Compass Chest", True, [key]*4], + + #@todo: Advanced? + ["Palace of Darkness - Dark Basement - Left", False, []], + ["Palace of Darkness - Dark Basement - Left", False, [], ['Lamp']], + ["Palace of Darkness - Dark Basement - Left", False, [key]*3, [key]], + ["Palace of Darkness - Dark Basement - Left", True, ['Lamp'] + [key]*4], + + ["Palace of Darkness - Dark Basement - Right", False, []], + ["Palace of Darkness - Dark Basement - Right", False, [], ['Lamp']], + ["Palace of Darkness - Dark Basement - Right", False, [key] * 3, [key]], + ["Palace of Darkness - Dark Basement - Right", True, ['Lamp'] + [key] * 4], + + ["Palace of Darkness - Harmless Hellway", False, []], + ["Palace of Darkness - Harmless Hellway", False, [key]*5, [key]], + ["Palace of Darkness - Harmless Hellway", True, [key]*6], + + ["Palace of Darkness - Dark Maze - Top", False, []], + ["Palace of Darkness - Dark Maze - Top", False, [], ['Lamp']], + ["Palace of Darkness - Dark Maze - Top", False, [key]*5, [key]], + ["Palace of Darkness - Dark Maze - Top", True, ['Lamp'] + [key]*6], + + ["Palace of Darkness - Dark Maze - Bottom", False, []], + ["Palace of Darkness - Dark Maze - Bottom", False, [], ['Lamp']], + ["Palace of Darkness - Dark Maze - Bottom", False, [key]*5, [key]], + ["Palace of Darkness - Dark Maze - Bottom", True, ['Lamp'] + [key]*6], + + ["Palace of Darkness - Big Chest", False, []], + ["Palace of Darkness - Big Chest", False, [], ['Lamp']], + ["Palace of Darkness - Big Chest", False, [], ['Big Key (Palace of Darkness)']], + ["Palace of Darkness - Big Chest", False, [key]*5, [key]], + ["Palace of Darkness - Big Chest", True, ['Lamp', 'Big Key (Palace of Darkness)'] + [key]*6], + + ["Palace of Darkness - Boss", False, []], + ["Palace of Darkness - Boss", False, [], ['Lamp']], + ["Palace of Darkness - Boss", False, [], ['Hammer']], + ["Palace of Darkness - Boss", False, [], ['Progressive Bow']], + ["Palace of Darkness - Boss", False, [], ['Big Key (Palace of Darkness)']], + ["Palace of Darkness - Boss", False, [key]*5, [key]], + ["Palace of Darkness - Boss", True, ['Lamp', 'Hammer', 'Progressive Bow', 'Big Key (Palace of Darkness)'] + [key]*6], + ]) \ No newline at end of file diff --git a/test/dungeons/TestDesertPalace.py b/test/dungeons/TestDesertPalace.py new file mode 100644 index 00000000..82255146 --- /dev/null +++ b/test/dungeons/TestDesertPalace.py @@ -0,0 +1,39 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestDesertPalace(TestDungeon): + + def testDesertPalace(self): + self.starting_regions = ['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)'] + self.run_tests([ + ["Desert Palace - Map Chest", True, []], + + ["Desert Palace - Big Chest", False, []], + ["Desert Palace - Big Chest", False, [], ['Big Key (Desert Palace)']], + ["Desert Palace - Big Chest", True, ['Big Key (Desert Palace)']], + + ["Desert Palace - Torch", False, []], + ["Desert Palace - Torch", False, [], ['Pegasus Boots']], + ["Desert Palace - Torch", True, ['Pegasus Boots']], + + ["Desert Palace - Compass Chest", False, []], + ["Desert Palace - Compass Chest", False, [], ['Small Key (Desert Palace)']], + ["Desert Palace - Compass Chest", True, ['Small Key (Desert Palace)']], + + #@todo: Require a real weapon for enemizer? + ["Desert Palace - Big Key Chest", False, []], + ["Desert Palace - Big Key Chest", False, [], ['Small Key (Desert Palace)']], + ["Desert Palace - Big Key Chest", True, ['Small Key (Desert Palace)']], + + ["Desert Palace - Boss", False, []], + ["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']], + ["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']], + ["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']], + ["Desert Palace - Boss", False, [], ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Fire Rod']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Progressive Sword']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Hammer']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Ice Rod']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Somaria']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestDungeon.py b/test/dungeons/TestDungeon.py new file mode 100644 index 00000000..1eb3cc7b --- /dev/null +++ b/test/dungeons/TestDungeon.py @@ -0,0 +1,49 @@ +import unittest + +from BaseClasses import World, CollectionState +from Dungeons import create_dungeons, get_dungeon_item_pool +from EntranceShuffle import mandatory_connections, connect_simple +from ItemList import difficulties, generate_itempool +from Items import ItemFactory +from Regions import create_regions +from Rules import set_rules + + +class TestDungeon(unittest.TestCase): + def setUp(self): + self.world = World(1, 'vanilla', 'noglitches', 'open', 'random', 'normal', 'normal', 'none', 'on', 'ganon', 'balanced', + True, False, False, False, False, False, False, False, False, None, + 'none', False) + self.starting_regions = [] + self.world.difficulty_requirements = difficulties['normal'] + create_regions(self.world, 1) + create_dungeons(self.world, 1) + for exitname, regionname in mandatory_connections: + connect_simple(self.world, exitname, regionname, 1) + connect_simple(self.world, self.world.get_entrance('Big Bomb Shop', 1), self.world.get_region('Big Bomb Shop', 1), 1) + self.world.swamp_patch_required[1] = True + set_rules(self.world, 1) + generate_itempool(self.world, 1) + self.world.itempool.extend(get_dungeon_item_pool(self.world)) + self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + + def run_tests(self, access_pool): + for region in self.starting_regions: + self.world.get_region(region, 1).can_reach_private = lambda _: True + + for location, access, *item_pool in access_pool: + items = item_pool[0] + all_except = item_pool[1] if len(item_pool) > 1 else None + with self.subTest(location=location, access=access, items=items, all_except=all_except): + if all_except and len(all_except) > 0: + items = self.world.itempool[:] + items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] + items.extend(ItemFactory(item_pool[0], 1)) + else: + items = ItemFactory(items, 1) + state = CollectionState(self.world) + for item in items: + item.advancement = True + state.collect(item) + + self.assertEqual(self.world.get_location(location, 1).can_reach(state), access) \ No newline at end of file diff --git a/test/dungeons/TestEasternPalace.py b/test/dungeons/TestEasternPalace.py new file mode 100644 index 00000000..775fa8a1 --- /dev/null +++ b/test/dungeons/TestEasternPalace.py @@ -0,0 +1,29 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestEasternPalace(TestDungeon): + + def testEastern(self): + self.starting_regions = ["Eastern Palace"] + self.run_tests([ + ["Eastern Palace - Compass Chest", True, []], + + ["Eastern Palace - Cannonball Chest", True, []], + + ["Eastern Palace - Big Chest", False, []], + ["Eastern Palace - Big Chest", False, [], ['Big Key (Eastern Palace)']], + ["Eastern Palace - Big Chest", True, ['Big Key (Eastern Palace)']], + + ["Eastern Palace - Map Chest", True, []], + + ["Eastern Palace - Big Key Chest", False, []], + ["Eastern Palace - Big Key Chest", False, [], ['Lamp']], + ["Eastern Palace - Big Key Chest", True, ['Lamp']], + + #@todo: Advanced? + ["Eastern Palace - Boss", False, []], + ["Eastern Palace - Boss", False, [], ['Lamp']], + ["Eastern Palace - Boss", False, [], ['Progressive Bow']], + ["Eastern Palace - Boss", False, [], ['Big Key (Eastern Palace)']], + ["Eastern Palace - Boss", True, ['Lamp', 'Progressive Bow', 'Big Key (Eastern Palace)']] + ]) diff --git a/test/dungeons/TestGanonsTower.py b/test/dungeons/TestGanonsTower.py new file mode 100644 index 00000000..1517dc25 --- /dev/null +++ b/test/dungeons/TestGanonsTower.py @@ -0,0 +1,144 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestGanonsTower(TestDungeon): + + def testGanonsTower(self): + self.starting_regions = ['Ganons Tower (Entrance)'] + self.run_tests([ + ["Ganons Tower - Bob's Torch", False, []], + ["Ganons Tower - Bob's Torch", False, [], ['Pegasus Boots']], + ["Ganons Tower - Bob's Torch", True, ['Pegasus Boots']], + + ["Ganons Tower - DMs Room - Top Left", False, []], + ["Ganons Tower - DMs Room - Top Left", False, [], ['Hammer']], + ["Ganons Tower - DMs Room - Top Left", False, [], ['Hookshot']], + ["Ganons Tower - DMs Room - Top Left", True, ['Hookshot', 'Hammer']], + + ["Ganons Tower - DMs Room - Top Right", False, []], + ["Ganons Tower - DMs Room - Top Right", False, [], ['Hammer']], + ["Ganons Tower - DMs Room - Top Right", False, [], ['Hookshot']], + ["Ganons Tower - DMs Room - Top Right", True, ['Hookshot', 'Hammer']], + + ["Ganons Tower - DMs Room - Bottom Left", False, []], + ["Ganons Tower - DMs Room - Bottom Left", False, [], ['Hammer']], + ["Ganons Tower - DMs Room - Bottom Left", False, [], ['Hookshot']], + ["Ganons Tower - DMs Room - Bottom Left", True, ['Hookshot', 'Hammer']], + + ["Ganons Tower - DMs Room - Bottom Right", False, []], + ["Ganons Tower - DMs Room - Bottom Right", False, [], ['Hammer']], + ["Ganons Tower - DMs Room - Bottom Right", False, [], ['Hookshot']], + ["Ganons Tower - DMs Room - Bottom Right", True, ['Hookshot', 'Hammer']], + + ["Ganons Tower - Randomizer Room - Top Left", False, []], + ["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hammer']], + ["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hookshot']], + ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Randomizer Room - Top Right", False, []], + ["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hammer']], + ["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hookshot']], + ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Randomizer Room - Bottom Left", False, []], + ["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hookshot']], + ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Randomizer Room - Bottom Right", False, []], + ["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hookshot']], + ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Firesnake Room", False, []], + ["Ganons Tower - Firesnake Room", False, [], ['Hammer']], + ["Ganons Tower - Firesnake Room", False, [], ['Hookshot']], + ["Ganons Tower - Firesnake Room", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Map Chest", False, []], + ["Ganons Tower - Map Chest", False, [], ['Hammer']], + ["Ganons Tower - Map Chest", False, [], ['Hookshot', 'Pegasus Boots']], + ["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hammer', 'Pegasus Boots']], + + ["Ganons Tower - Big Chest", False, []], + ["Ganons Tower - Big Chest", False, [], ['Big Key (Ganons Tower)']], + ["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Hope Room - Left", True, []], + + ["Ganons Tower - Hope Room - Right", True, []], + + ["Ganons Tower - Bob's Chest", False, []], + ["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Tile Room", False, []], + ["Ganons Tower - Tile Room", False, [], ['Cane of Somaria']], + ["Ganons Tower - Tile Room", True, ['Cane of Somaria']], + + ["Ganons Tower - Compass Room - Top Left", False, []], + ["Ganons Tower - Compass Room - Top Left", False, [], ['Cane of Somaria']], + ["Ganons Tower - Compass Room - Top Left", False, [], ['Fire Rod']], + ["Ganons Tower - Compass Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + + ["Ganons Tower - Compass Room - Top Right", False, []], + ["Ganons Tower - Compass Room - Top Right", False, [], ['Cane of Somaria']], + ["Ganons Tower - Compass Room - Top Right", False, [], ['Fire Rod']], + ["Ganons Tower - Compass Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + + ["Ganons Tower - Compass Room - Bottom Left", False, []], + ["Ganons Tower - Compass Room - Bottom Left", False, [], ['Cane of Somaria']], + ["Ganons Tower - Compass Room - Bottom Left", False, [], ['Fire Rod']], + ["Ganons Tower - Compass Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + + ["Ganons Tower - Compass Room - Bottom Right", False, []], + ["Ganons Tower - Compass Room - Bottom Right", False, [], ['Cane of Somaria']], + ["Ganons Tower - Compass Room - Bottom Right", False, [], ['Fire Rod']], + ["Ganons Tower - Compass Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + + ["Ganons Tower - Big Key Chest", False, []], + ["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Big Key Room - Left", False, []], + ["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Big Key Room - Right", False, []], + ["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + + ["Ganons Tower - Mini Helmasaur Room - Left", False, []], + ["Ganons Tower - Mini Helmasaur Room - Left", False, [], ['Progressive Bow']], + ["Ganons Tower - Mini Helmasaur Room - Left", False, [], ['Big Key (Ganons Tower)']], + ["Ganons Tower - Mini Helmasaur Room - Left", False, [], ['Lamp', 'Fire Rod']], + ["Ganons Tower - Mini Helmasaur Room - Left", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']], + ["Ganons Tower - Mini Helmasaur Room - Left", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']], + + ["Ganons Tower - Mini Helmasaur Room - Right", False, []], + ["Ganons Tower - Mini Helmasaur Room - Right", False, [], ['Progressive Bow']], + ["Ganons Tower - Mini Helmasaur Room - Right", False, [], ['Big Key (Ganons Tower)']], + ["Ganons Tower - Mini Helmasaur Room - Right", False, [], ['Lamp', 'Fire Rod']], + ["Ganons Tower - Mini Helmasaur Room - Right", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']], + ["Ganons Tower - Mini Helmasaur Room - Right", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']], + + ["Ganons Tower - Pre-Moldorm Chest", False, []], + ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Progressive Bow']], + ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Big Key (Ganons Tower)']], + ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Lamp', 'Fire Rod']], + ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']], + ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']], + + ["Ganons Tower - Validation Chest", False, []], + ["Ganons Tower - Validation Chest", False, [], ['Hookshot']], + ["Ganons Tower - Validation Chest", False, [], ['Progressive Bow']], + ["Ganons Tower - Validation Chest", False, [], ['Big Key (Ganons Tower)']], + ["Ganons Tower - Validation Chest", False, [], ['Lamp', 'Fire Rod']], + ["Ganons Tower - Validation Chest", False, [], ['Progressive Sword', 'Hammer']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestIcePalace.py b/test/dungeons/TestIcePalace.py new file mode 100644 index 00000000..e415c5d0 --- /dev/null +++ b/test/dungeons/TestIcePalace.py @@ -0,0 +1,79 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestIcePalace(TestDungeon): + + def testIcePalace(self): + self.starting_regions = ['Ice Palace (Entrance)'] + self.run_tests([ + ["Ice Palace - Big Key Chest", False, []], + ["Ice Palace - Big Key Chest", False, [], ['Hammer']], + ["Ice Palace - Big Key Chest", False, [], ['Progressive Glove']], + ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + #@todo: Change from item randomizer - Right side key door is only in logic if big key is in there + #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cape', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cape', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + + ["Ice Palace - Compass Chest", False, []], + ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']], + + ["Ice Palace - Map Chest", False, []], + ["Ice Palace - Map Chest", False, [], ['Hammer']], + ["Ice Palace - Map Chest", False, [], ['Progressive Glove']], + ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Map Chest", True, ['Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Map Chest", True, ['Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cape', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cape', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + + ["Ice Palace - Spike Room", False, []], + ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Spike Room", True, ['Fire Rod', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Spike Room", True, ['Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)']], + #["Ice Palace - Spike Room", True, ['Cape', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Spike Room", True, ['Cape', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Spike Room", True, ['Cane of Byrna', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + #["Ice Palace - Spike Room", True, ['Cane of Byrna', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + + ["Ice Palace - Freezor Chest", False, []], + ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Freezor Chest", True, ['Fire Rod']], + ["Ice Palace - Freezor Chest", True, ['Bombos', 'Progressive Sword']], + + ["Ice Palace - Iced T Room", False, []], + ["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Iced T Room", True, ['Fire Rod']], + ["Ice Palace - Iced T Room", True, ['Bombos', 'Progressive Sword']], + + ["Ice Palace - Big Chest", False, []], + ["Ice Palace - Big Chest", False, [], ['Big Key (Ice Palace)']], + ["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Big Chest", True, ['Big Key (Ice Palace)', 'Fire Rod']], + ["Ice Palace - Big Chest", True, ['Big Key (Ice Palace)', 'Bombos', 'Progressive Sword']], + + ["Ice Palace - Boss", False, []], + ["Ice Palace - Boss", False, [], ['Hammer']], + ["Ice Palace - Boss", False, [], ['Progressive Glove']], + ["Ice Palace - Boss", False, [], ['Big Key (Ice Palace)']], + ["Ice Palace - Boss", False, [], ['Fire Rod', 'Bombos']], + ["Ice Palace - Boss", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)']], + ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestMiseryMire.py b/test/dungeons/TestMiseryMire.py new file mode 100644 index 00000000..5f0a354c --- /dev/null +++ b/test/dungeons/TestMiseryMire.py @@ -0,0 +1,84 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestMiseryMire(TestDungeon): + + def testMiseryMire(self): + self.starting_regions = ['Misery Mire (Entrance)'] + self.run_tests([ + ["Misery Mire - Bridge Chest", False, []], + ["Misery Mire - Bridge Chest", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Bridge Chest", False, [], ['Progressive Sword', 'Hammer', 'Fire Rod', 'Cane of Somaria', 'Progressive Bow', 'Ice Rod']], #Ice Rod works! + ["Misery Mire - Bridge Chest", True, ['Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Bridge Chest", True, ['Progressive Sword', 'Hookshot']], + ["Misery Mire - Bridge Chest", True, ['Hammer', 'Pegasus Boots']], + ["Misery Mire - Bridge Chest", True, ['Hammer', 'Hookshot']], + ["Misery Mire - Bridge Chest", True, ['Fire Rod', 'Pegasus Boots']], + ["Misery Mire - Bridge Chest", True, ['Fire Rod', 'Hookshot']], + ["Misery Mire - Bridge Chest", True, ['Cane of Somaria', 'Pegasus Boots']], + ["Misery Mire - Bridge Chest", True, ['Cane of Somaria', 'Hookshot']], + ["Misery Mire - Bridge Chest", True, ['Progressive Bow', 'Pegasus Boots']], + ["Misery Mire - Bridge Chest", True, ['Progressive Bow', 'Hookshot']], + ["Misery Mire - Bridge Chest", True, ['Ice Rod', 'Pegasus Boots']], + ["Misery Mire - Bridge Chest", True, ['Ice Rod', 'Hookshot']], + + ["Misery Mire - Big Chest", False, []], + ["Misery Mire - Big Chest", False, [], ['Big Key (Misery Mire)']], + ["Misery Mire - Big Chest", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Big Chest", False, [], ['Progressive Sword', 'Hammer', 'Fire Rod', 'Cane of Somaria', 'Progressive Bow', 'Ice Rod']], + ["Misery Mire - Big Chest", True, ['Big Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']], + ["Misery Mire - Big Chest", True, ['Big Key (Misery Mire)', 'Hookshot', 'Progressive Sword']], + + ["Misery Mire - Main Lobby", False, []], + ["Misery Mire - Main Lobby", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Main Lobby", False, [], ['Small Key (Misery Mire)', 'Big Key (Misery Mire)']], + ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']], + ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']], + ["Misery Mire - Main Lobby", True, ['Big Key (Misery Mire)', 'Hookshot', 'Progressive Sword']], + ["Misery Mire - Main Lobby", True, ['Big Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']], + + ["Misery Mire - Big Key Chest", False, []], + ["Misery Mire - Big Key Chest", False, [], ['Fire Rod', 'Lamp']], + ["Misery Mire - Big Key Chest", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Big Key Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)'], ['Small Key (Misery Mire)']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']], + + ["Misery Mire - Compass Chest", False, []], + ["Misery Mire - Compass Chest", False, [], ['Fire Rod', 'Lamp']], + ["Misery Mire - Compass Chest", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Compass Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)'], ['Small Key (Misery Mire)']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']], + + ["Misery Mire - Map Chest", False, []], + ["Misery Mire - Map Chest", False, [], ['Small Key (Misery Mire)', 'Big Key (Misery Mire)']], + ["Misery Mire - Map Chest", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Map Chest", True, ['Big Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Map Chest", True, ['Big Key (Misery Mire)', 'Progressive Sword', 'Hookshot']], + + ["Misery Mire - Spike Chest", False, []], + ["Misery Mire - Spike Chest", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Spike Chest", True, ['Progressive Sword', 'Pegasus Boots', 'Cape']], + ["Misery Mire - Spike Chest", True, ['Progressive Sword', 'Hookshot', 'Cape']], + ["Misery Mire - Spike Chest", True, ['Progressive Sword', 'Pegasus Boots', 'Cane of Byrna']], + ["Misery Mire - Spike Chest", True, ['Progressive Sword', 'Hookshot', 'Cane of Byrna']], + ["Misery Mire - Spike Chest", True, ['Progressive Sword', 'Pegasus Boots', 'Boss Heart Container']], + ["Misery Mire - Spike Chest", True, ['Progressive Sword', 'Hookshot', 'Boss Heart Container']], + + ["Misery Mire - Boss", False, []], + ["Misery Mire - Boss", False, [], ['Lamp']], + ["Misery Mire - Boss", False, [], ['Cane of Somaria']], + ["Misery Mire - Boss", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow']], + ["Misery Mire - Boss", False, [], ['Big Key (Misery Mire)']], + ["Misery Mire - Boss", False, [], ['Pegasus Boots', 'Hookshot']], + ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']], + ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestSkullWoods.py b/test/dungeons/TestSkullWoods.py new file mode 100644 index 00000000..01ab4b58 --- /dev/null +++ b/test/dungeons/TestSkullWoods.py @@ -0,0 +1,96 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestSkullWoods(TestDungeon): + + def testSkullWoodsFrontAllEntrances(self): + self.starting_regions = ['Skull Woods First Section', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'] + self.run_tests([ + ["Skull Woods - Big Chest", False, []], + ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']], + ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']], + + ["Skull Woods - Compass Chest", True, []], + + ["Skull Woods - Map Chest", True, []], + + ["Skull Woods - Pot Prison", True, []], + + ["Skull Woods - Pinball Room", True, []] + ]) + + def testSkullWoodsFrontOnly(self): + self.starting_regions = ['Skull Woods First Section'] + self.run_tests([ + ["Skull Woods - Big Chest", False, []], + ["Skull Woods - Big Chest", False, [], ['Never in logic']], + + ["Skull Woods - Compass Chest", False, []], + ["Skull Woods - Compass Chest", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], + ["Skull Woods - Compass Chest", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']], + + ["Skull Woods - Map Chest", True, []], + + ["Skull Woods - Pot Prison", False, []], + ["Skull Woods - Pot Prison", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], + ["Skull Woods - Pot Prison", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']], + + ["Skull Woods - Pinball Room", False, []], + ["Skull Woods - Pinball Room", False, [], ['Small Key (Skull Woods)']], + ["Skull Woods - Pinball Room", True, ['Small Key (Skull Woods)']] + ]) + + def testSkullWoodsLeftOnly(self): + self.starting_regions = ['Skull Woods First Section (Left)'] + self.run_tests([ + ["Skull Woods - Big Chest", False, []], + ["Skull Woods - Big Chest", False, [], ['Never in logic']], + + ["Skull Woods - Compass Chest", True, []], + + ["Skull Woods - Map Chest", False, []], + ["Skull Woods - Map Chest", False, [], ['Small Key (Skull Woods)']], + ["Skull Woods - Map Chest", True, ['Small Key (Skull Woods)']], + + ["Skull Woods - Pot Prison", True, []], + + ["Skull Woods - Pinball Room", True, []] + ]) + + def testSkullWoodsBackOnly(self): + self.starting_regions = ['Skull Woods First Section (Top)'] + self.run_tests([ + ["Skull Woods - Big Chest", False, []], + ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']], + ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']], + + ["Skull Woods - Compass Chest", False, []], + ["Skull Woods - Compass Chest", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], + ["Skull Woods - Compass Chest", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']], + + ["Skull Woods - Map Chest", True, []], + + ["Skull Woods - Pot Prison", False, []], + ["Skull Woods - Pot Prison", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], + ["Skull Woods - Pot Prison", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']], + + ["Skull Woods - Pinball Room", False, []], + ["Skull Woods - Pinball Room", False, [], ['Small Key (Skull Woods)']], + ["Skull Woods - Pinball Room", True, ['Small Key (Skull Woods)']] + ]) + + def testSkullWoodsMiddle(self): + self.starting_regions = ['Skull Woods Second Section'] + self.run_tests([["Skull Woods - Big Key Chest", True, []]]) + + def testSkullWoodsBack(self): + self.starting_regions = ['Skull Woods Final Section (Entrance)'] + self.run_tests([ + ["Skull Woods - Bridge Room", True, []], + + ["Skull Woods - Boss", False, []], + ["Skull Woods - Boss", False, [], ['Fire Rod']], + ["Skull Woods - Boss", False, [], ['Progressive Sword']], + ["Skull Woods - Boss", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], + ["Skull Woods - Boss", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestSwampPalace.py b/test/dungeons/TestSwampPalace.py new file mode 100644 index 00000000..534939c9 --- /dev/null +++ b/test/dungeons/TestSwampPalace.py @@ -0,0 +1,80 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestSwampPalace(TestDungeon): + + def testSwampPalace(self): + self.starting_regions = ['Swamp Palace (Entrance)'] + self.run_tests([ + ["Swamp Palace - Entrance", False, []], + ["Swamp Palace - Entrance", False, [], ['Flippers']], + ["Swamp Palace - Entrance", False, [], ['Open Floodgate']], + ["Swamp Palace - Entrance", True, ['Open Floodgate', 'Flippers']], + + ["Swamp Palace - Big Chest", False, []], + ["Swamp Palace - Big Chest", False, [], ['Flippers']], + ["Swamp Palace - Big Chest", False, [], ['Open Floodgate']], + ["Swamp Palace - Big Chest", False, [], ['Hammer']], + ["Swamp Palace - Big Chest", False, [], ['Big Key (Swamp Palace)']], + ["Swamp Palace - Big Chest", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Big Chest", True, ['Open Floodgate', 'Big Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + + ["Swamp Palace - Big Key Chest", False, []], + ["Swamp Palace - Big Key Chest", False, [], ['Flippers']], + ["Swamp Palace - Big Key Chest", False, [], ['Open Floodgate']], + ["Swamp Palace - Big Key Chest", False, [], ['Hammer']], + ["Swamp Palace - Big Key Chest", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + + ["Swamp Palace - Map Chest", False, []], + ["Swamp Palace - Map Chest", False, [], ['Flippers']], + ["Swamp Palace - Map Chest", False, [], ['Open Floodgate']], + ["Swamp Palace - Map Chest", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Map Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers']], + + ["Swamp Palace - West Chest", False, []], + ["Swamp Palace - West Chest", False, [], ['Flippers']], + ["Swamp Palace - West Chest", False, [], ['Open Floodgate']], + ["Swamp Palace - West Chest", False, [], ['Hammer']], + ["Swamp Palace - West Chest", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + + ["Swamp Palace - Compass Chest", False, []], + ["Swamp Palace - Compass Chest", False, [], ['Flippers']], + ["Swamp Palace - Compass Chest", False, [], ['Open Floodgate']], + ["Swamp Palace - Compass Chest", False, [], ['Hammer']], + ["Swamp Palace - Compass Chest", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Compass Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + + ["Swamp Palace - Flooded Room - Left", False, []], + ["Swamp Palace - Flooded Room - Left", False, [], ['Flippers']], + ["Swamp Palace - Flooded Room - Left", False, [], ['Open Floodgate']], + ["Swamp Palace - Flooded Room - Left", False, [], ['Hammer']], + ["Swamp Palace - Flooded Room - Left", False, [], ['Hookshot']], + ["Swamp Palace - Flooded Room - Left", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Flooded Room - Left", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + + ["Swamp Palace - Flooded Room - Right", False, []], + ["Swamp Palace - Flooded Room - Right", False, [], ['Flippers']], + ["Swamp Palace - Flooded Room - Right", False, [], ['Open Floodgate']], + ["Swamp Palace - Flooded Room - Right", False, [], ['Hammer']], + ["Swamp Palace - Flooded Room - Right", False, [], ['Hookshot']], + ["Swamp Palace - Flooded Room - Right", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Flooded Room - Right", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + + ["Swamp Palace - Waterfall Room", False, []], + ["Swamp Palace - Waterfall Room", False, [], ['Flippers']], + ["Swamp Palace - Waterfall Room", False, [], ['Open Floodgate']], + ["Swamp Palace - Waterfall Room", False, [], ['Hammer']], + ["Swamp Palace - Waterfall Room", False, [], ['Hookshot']], + ["Swamp Palace - Waterfall Room", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Waterfall Room", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + + ["Swamp Palace - Boss", False, []], + ["Swamp Palace - Boss", False, [], ['Flippers']], + ["Swamp Palace - Boss", False, [], ['Open Floodgate']], + ["Swamp Palace - Boss", False, [], ['Hammer']], + ["Swamp Palace - Boss", False, [], ['Hookshot']], + ["Swamp Palace - Boss", False, [], ['Small Key (Swamp Palace)']], + ["Swamp Palace - Boss", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestThievesTown.py b/test/dungeons/TestThievesTown.py new file mode 100644 index 00000000..5fdf9037 --- /dev/null +++ b/test/dungeons/TestThievesTown.py @@ -0,0 +1,40 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestThievesTown(TestDungeon): + + def testThievesTown(self): + self.starting_regions = ['Thieves Town (Entrance)'] + self.run_tests([ + ["Thieves' Town - Attic", False, []], + ["Thieves' Town - Attic", False, [], ['Big Key (Thieves Town)']], + ["Thieves' Town - Attic", False, [], ['Small Key (Thieves Town)']], + ["Thieves' Town - Attic", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']], + + ["Thieves' Town - Big Key Chest", True, []], + + ["Thieves' Town - Map Chest", True, []], + + ["Thieves' Town - Compass Chest", True, []], + + ["Thieves' Town - Ambush Chest", True, []], + + ["Thieves' Town - Big Chest", False, []], + ["Thieves' Town - Big Chest", False, [], ['Big Key (Thieves Town)']], + ["Thieves' Town - Big Chest", False, [], ['Small Key (Thieves Town)']], + ["Thieves' Town - Big Chest", False, [], ['Hammer']], + ["Thieves' Town - Big Chest", True, ['Hammer', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)']], + + ["Thieves' Town - Blind's Cell", False, []], + ["Thieves' Town - Blind's Cell", False, [], ['Big Key (Thieves Town)']], + ["Thieves' Town - Blind's Cell", True, ['Big Key (Thieves Town)']], + + ["Thieves' Town - Boss", False, []], + ["Thieves' Town - Boss", False, [], ['Big Key (Thieves Town)']], + ["Thieves' Town - Boss", False, [], ['Small Key (Thieves Town)']], + ["Thieves' Town - Boss", False, [], ['Hammer', 'Progressive Sword', 'Cane of Somaria', 'Cane of Byrna']], + ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']], + ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']], + ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']], + ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']], + ]) \ No newline at end of file diff --git a/test/dungeons/TestTowerOfHera.py b/test/dungeons/TestTowerOfHera.py new file mode 100644 index 00000000..9762d208 --- /dev/null +++ b/test/dungeons/TestTowerOfHera.py @@ -0,0 +1,32 @@ +from test.dungeons.TestDungeon import TestDungeon + + +class TestTowerOfHera(TestDungeon): + + def testTowerOfHera(self): + self.starting_regions = ['Tower of Hera (Bottom)'] + self.run_tests([ + ["Tower of Hera - Big Key Chest", False, []], + ["Tower of Hera - Big Key Chest", False, [], ['Small Key (Tower of Hera)']], + ["Tower of Hera - Big Key Chest", False, [], ['Lamp', 'Fire Rod']], + ["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Lamp']], + ["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Fire Rod']], + + ["Tower of Hera - Basement Cage", True, []], + + ["Tower of Hera - Map Chest", True, []], + + ["Tower of Hera - Compass Chest", False, []], + ["Tower of Hera - Compass Chest", False, [], ['Big Key (Tower of Hera)']], + ["Tower of Hera - Compass Chest", True, ['Big Key (Tower of Hera)']], + + ["Tower of Hera - Big Chest", False, []], + ["Tower of Hera - Big Chest", False, [], ['Big Key (Tower of Hera)']], + ["Tower of Hera - Big Chest", True, ['Big Key (Tower of Hera)']], + + ["Tower of Hera - Boss", False, []], + ["Tower of Hera - Boss", False, [], ['Big Key (Tower of Hera)']], + ["Tower of Hera - Boss", False, [], ['Progressive Sword', 'Hammer']], + ["Tower of Hera - Boss", True, ['Progressive Sword', 'Big Key (Tower of Hera)']], + ["Tower of Hera - Boss", True, ['Hammer', 'Big Key (Tower of Hera)']], + ]) \ No newline at end of file diff --git a/test/dungeons/__init__.py b/test/dungeons/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/inverted/TestInverted.py b/test/inverted/TestInverted.py new file mode 100644 index 00000000..82e1c0f0 --- /dev/null +++ b/test/inverted/TestInverted.py @@ -0,0 +1,28 @@ +from BaseClasses import World +from Dungeons import create_dungeons, get_dungeon_item_pool +from EntranceShuffle import link_inverted_entrances +from InvertedRegions import create_inverted_regions +from ItemList import generate_itempool, difficulties +from Items import ItemFactory +from Regions import mark_light_world_regions +from Rules import set_rules +from test.TestVanilla import TestVanilla + + +class TestInverted(TestVanilla): + def setUp(self): + self.world = World(1, 'vanilla', 'noglitches', 'inverted', 'random', 'normal', 'normal', 'none', 'on', 'ganon', 'balanced', + True, False, False, False, False, False, False, False, False, None, + 'none', False) + self.world.difficulty_requirements = difficulties['normal'] + create_inverted_regions(self.world, 1) + create_dungeons(self.world, 1) + link_inverted_entrances(self.world, 1) + generate_itempool(self.world, 1) + self.world.required_medallions[1] = ['Ether', 'Quake'] + self.world.itempool.extend(get_dungeon_item_pool(self.world)) + self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.world.get_location('Agahnim 1', 1).item = None + self.world.get_location('Agahnim 2', 1).item = None + mark_light_world_regions(self.world) + set_rules(self.world, 1) diff --git a/test/inverted/TestInvertedBombRules.py b/test/inverted/TestInvertedBombRules.py new file mode 100644 index 00000000..4914d2e3 --- /dev/null +++ b/test/inverted/TestInvertedBombRules.py @@ -0,0 +1,47 @@ +import unittest + +from BaseClasses import World +from Dungeons import create_dungeons +from EntranceShuffle import connect_entrance, Inverted_LW_Entrances, Inverted_LW_Dungeon_Entrances, Inverted_LW_Single_Cave_Doors, Inverted_Old_Man_Entrances, Inverted_DW_Entrances, Inverted_DW_Dungeon_Entrances, Inverted_DW_Single_Cave_Doors, \ + Inverted_LW_Entrances_Must_Exit, Inverted_LW_Dungeon_Entrances_Must_Exit, Inverted_Bomb_Shop_Multi_Cave_Doors, Inverted_Bomb_Shop_Single_Cave_Doors, Inverted_Blacksmith_Single_Cave_Doors, Inverted_Blacksmith_Multi_Cave_Doors +from InvertedRegions import create_inverted_regions +from ItemList import difficulties +from Rules import set_inverted_big_bomb_rules + + +class TestInvertedBombRules(unittest.TestCase): + + def setUp(self): + self.world = World(1, 'vanilla', 'noglitches', 'inverted', 'random', 'normal', 'normal', 'none', 'on', 'ganon', 'balanced', + True, False, False, False, False, False, False, False, False, None, + 'none', False) + self.world.difficulty_requirements = difficulties['normal'] + create_inverted_regions(self.world, 1) + create_dungeons(self.world, 1) + + #TODO: Just making sure I haven't missed an entrance. It would be good to test the rules make sense as well. + def testInvertedBombRulesAreComplete(self): + entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors) + must_exits = list(Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit) + for entrance_name in (entrances + must_exits): + if entrance_name not in ['Desert Palace Entrance (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']: + entrance = self.world.get_entrance(entrance_name, 1) + connect_entrance(self.world, entrance, 'Inverted Big Bomb Shop', 1) + set_inverted_big_bomb_rules(self.world, 1) + entrance.connected_region.entrances.remove(entrance) + entrance.connected_region = None + + def testInvalidEntrancesAreNotUsed(self): + entrances = list(Inverted_Blacksmith_Multi_Cave_Doors + Inverted_Blacksmith_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + Inverted_Bomb_Shop_Single_Cave_Doors) + invalid_entrances = ['Desert Palace Entrance (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)', 'Pyramid Fairy'] + for invalid_entrance in invalid_entrances: + self.assertNotIn(invalid_entrance, entrances) + + def testInvalidEntrances(self): + for entrance_name in ['Desert Palace Entrance (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']: + entrance = self.world.get_entrance(entrance_name, 1) + connect_entrance(self.world, entrance, 'Inverted Big Bomb Shop', 1) + with self.assertRaises(Exception): + set_inverted_big_bomb_rules(self.world, 1) + entrance.connected_region.entrances.remove(entrance) + entrance.connected_region = None diff --git a/test/inverted/TestInvertedDarkWorld.py b/test/inverted/TestInvertedDarkWorld.py new file mode 100644 index 00000000..c2249c7e --- /dev/null +++ b/test/inverted/TestInvertedDarkWorld.py @@ -0,0 +1,116 @@ +from test.inverted.TestInverted import TestInverted + + +class TestInvertedDeathMountain(TestInverted): + + def testNorthWest(self): + self.run_tests([ + ["Brewery", True, []], + + ["C-Shaped House", True, []], + + ["Chest Game", True, []], + + ["Peg Cave", False, []], + ["Peg Cave", False, [], ['Hammer']], + ["Peg Cave", False, [], ['Progressive Glove', 'Magic Mirror']], + ["Peg Cave", True, ['Hammer', 'Progressive Glove', 'Progressive Glove']], + ["Peg Cave", True, ['Hammer', 'Progressive Glove', 'Magic Mirror', 'Moon Pearl']], + ["Peg Cave", True, ['Hammer', 'Beat Agahnim 1', 'Magic Mirror']], + + ["Bumper Cave Ledge", False, []], + ["Bumper Cave Ledge", False, [], ['Moon Pearl']], + ["Bumper Cave Ledge", False, [], ['Cape']], + ["Bumper Cave Ledge", False, [], ['Progressive Glove']], + ["Bumper Cave Ledge", False, [], ['Magic Mirror']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Bumper Cave Ledge", True, ['Moon Pearl', 'Cape', 'Magic Mirror', 'Progressive Glove', 'Beat Agahnim 1']], + + ["Blacksmith", False, []], + ["Blacksmith", False, [], ['Progressive Glove', 'Magic Mirror']], + ["Blacksmith", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + #@todo: Can get this without moon pearl + #["Blacksmith", True, ['Beat Agahnim 1', 'Magic Mirror']], + ["Blacksmith", True, ['Beat Agahnim 1', 'Magic Mirror', 'Moon Pearl']], + ["Blacksmith", True, ['Progressive Glove', 'Hammer', 'Magic Mirror', 'Moon Pearl']], + + ["Purple Chest", False, []], + ["Purple Chest", False, [], ['Progressive Glove', 'Magic Mirror']], + ["Purple Chest", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + # @todo: Can get this without moon pearl + #["Purple Chest", True, ['Beat Agahnim 1', 'Magic Mirror']], + ["Purple Chest", True, ['Beat Agahnim 1', 'Magic Mirror', 'Moon Pearl']], + ["Purple Chest", True, ['Progressive Glove', 'Hammer', 'Magic Mirror', 'Moon Pearl']], + ]) + + def testNorthEast(self): + self.run_tests([ + ["Catfish", False, []], + ["Catfish", False, [], ['Progressive Glove', 'Flippers']], + ["Catfish", False, [], ['Progressive Glove', 'Magic Mirror']], + ["Catfish", False, [], ['Progressive Glove', 'Moon Pearl']], + ["Catfish", True, ['Beat Agahnim 1', 'Magic Mirror', 'Progressive Glove']], + ["Catfish", True, ['Beat Agahnim 1', 'Moon Pearl', 'Magic Mirror', 'Flippers']], + ["Catfish", True, ['Progressive Glove', 'Hammer']], + ["Catfish", True, ['Progressive Glove', 'Flippers']], + ["Catfish", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Moon Pearl']], + + ["Pyramid", False, []], + ["Pyramid", True, ['Beat Agahnim 1', 'Magic Mirror']], + ["Pyramid", True, ['Hammer']], + ["Pyramid", True, ['Flippers', 'Progressive Glove']], + ["Pyramid", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Moon Pearl']], + + ["Pyramid Fairy - Left", False, []], + ["Pyramid Fairy - Left", False, [], ['Magic Mirror']], + ["Pyramid Fairy - Left", False, [], ['Crystal 5']], + ["Pyramid Fairy - Left", False, [], ['Crystal 6']], + ["Pyramid Fairy - Left", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Hammer', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Left", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Left", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Beat Agahnim 1']], + + ["Pyramid Fairy - Right", False, []], + ["Pyramid Fairy - Right", False, [], ['Magic Mirror']], + ["Pyramid Fairy - Right", False, [], ['Crystal 5']], + ["Pyramid Fairy - Right", False, [], ['Crystal 6']], + ["Pyramid Fairy - Right", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Hammer', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Right", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Pyramid Fairy - Right", True, ['Crystal 5', 'Crystal 6', 'Magic Mirror', 'Beat Agahnim 1']], + ]) + + def testSouth(self): + self.run_tests([ + ["Hype Cave - Top", True, []], + + ["Hype Cave - Middle Right", True, []], + + ["Hype Cave - Middle Left", True, []], + + ["Hype Cave - Bottom", True, []], + + ["Hype Cave - Generous Guy", True, []], + + ["Stumpy", True, []], + + ["Digging Game", True, []], + + ["Link's House", True, []], + ]) + + def testMireArea(self): + self.run_tests([ + ["Mire Shed - Left", False, []], + ["Mire Shed - Left", False, [], ['Ocarina', 'Magic Mirror']], + ["Mire Shed - Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove']], + ["Mire Shed - Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Hammer']], + ["Mire Shed - Left", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1']], + ["Mire Shed - Left", True, ['Magic Mirror', 'Beat Agahnim 1']], + + ["Mire Shed - Right", False, []], + ["Mire Shed - Right", False, [], ['Ocarina', 'Magic Mirror']], + ["Mire Shed - Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove']], + ["Mire Shed - Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Hammer']], + ["Mire Shed - Right", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1']], + ["Mire Shed - Right", True, ['Magic Mirror', 'Beat Agahnim 1']], + ]) \ No newline at end of file diff --git a/test/inverted/TestInvertedDeathMountain.py b/test/inverted/TestInvertedDeathMountain.py new file mode 100644 index 00000000..fbd75ddd --- /dev/null +++ b/test/inverted/TestInvertedDeathMountain.py @@ -0,0 +1,229 @@ +from test.inverted.TestInverted import TestInverted + + +class TestInvertedDeathMountain(TestInverted): + + def testWestDeathMountain(self): + self.run_tests([ + ["Old Man", False, []], + ["Old Man", False, [], ['Progressive Glove', 'Ocarina']], + ["Old Man", False, [], ['Lamp']], + ["Old Man", True, ['Progressive Glove', 'Lamp']], + ["Old Man", False, ['Ocarina', 'Lamp']], + + ["Spectacle Rock Cave", False, []], + ["Spectacle Rock Cave", False, [], ['Progressive Glove', 'Ocarina']], + ["Spectacle Rock Cave", False, [], ['Lamp', 'Ocarina']], + ["Spectacle Rock Cave", False, ['Ocarina', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock Cave", False, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock Cave", False, ['Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Spectacle Rock Cave", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock Cave", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Spectacle Rock Cave", True, ['Progressive Glove', 'Lamp']], + ]) + + def testEastDeathMountain(self): + self.run_tests([ + ["Spiral Cave", False, []], + ["Spiral Cave", False, [], ['Moon Pearl']], + ["Spiral Cave", False, [], ['Progressive Glove', 'Ocarina']], + ["Spiral Cave", False, [], ['Lamp', 'Ocarina']], + ["Spiral Cave", False, ['Progressive Glove'], ['Hookshot', 'Progressive Glove']], + ["Spiral Cave", False, ['Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Spiral Cave", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Spiral Cave", False, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Spiral Cave", False, ['Ocarina', 'Hookshot', 'Moon Pearl']], + ["Spiral Cave", True, ['Ocarina', 'Hookshot', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Spiral Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hookshot']], + ["Spiral Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Spiral Cave", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Far Left", False, []], + ["Paradox Cave Lower - Far Left", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Far Left", False, [], ['Progressive Glove', 'Ocarina']], + ["Paradox Cave Lower - Far Left", False, [], ['Lamp', 'Ocarina']], + ["Paradox Cave Lower - Far Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", False, ['Ocarina', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Ocarina', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Left", False, []], + ["Paradox Cave Lower - Left", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Left", False, [], ['Progressive Glove', 'Ocarina']], + ["Paradox Cave Lower - Left", False, [], ['Lamp', 'Ocarina']], + ["Paradox Cave Lower - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", False, ['Ocarina', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Ocarina', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Middle", False, []], + ["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Middle", False, [], ['Progressive Glove', 'Ocarina']], + ["Paradox Cave Lower - Middle", False, [], ['Lamp', 'Ocarina']], + ["Paradox Cave Lower - Middle", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", False, ['Ocarina', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Ocarina', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Right", False, []], + ["Paradox Cave Lower - Right", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Right", False, [], ['Progressive Glove', 'Ocarina']], + ["Paradox Cave Lower - Right", False, [], ['Lamp', 'Ocarina']], + ["Paradox Cave Lower - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", False, ['Ocarina', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Ocarina', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Lower - Far Right", False, []], + ["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']], + ["Paradox Cave Lower - Far Right", False, [], ['Progressive Glove', 'Ocarina']], + ["Paradox Cave Lower - Far Right", False, [], ['Lamp', 'Ocarina']], + ["Paradox Cave Lower - Far Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", False, ['Ocarina', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Ocarina', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Upper - Left", False, []], + ["Paradox Cave Upper - Left", False, [], ['Moon Pearl']], + ["Paradox Cave Upper - Left", False, [], ['Progressive Glove', 'Ocarina']], + ["Paradox Cave Upper - Left", False, [], ['Lamp', 'Ocarina']], + ["Paradox Cave Upper - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", False, ['Ocarina', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Ocarina', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Paradox Cave Upper - Right", False, []], + ["Paradox Cave Upper - Right", False, [], ['Moon Pearl']], + ["Paradox Cave Upper - Right", False, [], ['Progressive Glove', 'Ocarina']], + ["Paradox Cave Upper - Right", False, [], ['Lamp', 'Ocarina']], + ["Paradox Cave Upper - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", False, ['Ocarina', 'Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Ocarina', 'Progressive Glove', 'Hammer', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Ocarina', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Mimic Cave", False, []], + ["Mimic Cave", False, [], ['Moon Pearl']], + ["Mimic Cave", False, [], ['Hammer']], + ["Mimic Cave", False, [], ['Progressive Glove', 'Ocarina']], + ["Mimic Cave", False, [], ['Lamp', 'Ocarina']], + ["Mimic Cave", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Mimic Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], + + ["Ether Tablet", False, []], + ["Ether Tablet", False, [], ['Moon Pearl']], + ["Ether Tablet", False, [], ['Progressive Glove', 'Ocarina']], + ["Ether Tablet", False, [], ['Lamp', 'Ocarina']], + ["Ether Tablet", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Ether Tablet", False, [], ['Hammer']], + ["Ether Tablet", False, ['Progressive Sword'], ['Progressive Sword']], + ["Ether Tablet", False, [], ['Book of Mudora']], + ["Ether Tablet", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Ether Tablet", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + + ["Spectacle Rock", False, []], + ["Spectacle Rock", False, [], ['Moon Pearl']], + ["Spectacle Rock", False, [], ['Progressive Glove', 'Ocarina']], + ["Spectacle Rock", False, [], ['Lamp', 'Ocarina']], + ["Spectacle Rock", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], + ["Spectacle Rock", False, [], ['Hammer']], + ["Spectacle Rock", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Hammer', 'Hookshot']], + ["Spectacle Rock", True, ['Ocarina', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Spectacle Rock", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Spectacle Rock", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], + ]) + + def testEastDarkWorldDeathMountain(self): + self.run_tests([ + ["Superbunny Cave - Top", False, []], + ["Superbunny Cave - Top", False, [], ['Progressive Glove', 'Ocarina']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Lamp']], + ["Superbunny Cave - Top", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Ocarina']], + ["Superbunny Cave - Top", True, ['Hammer', 'Progressive Glove', 'Moon Pearl', 'Ocarina']], + + ["Superbunny Cave - Bottom", False, []], + ["Superbunny Cave - Bottom", False, [], ['Progressive Glove', 'Ocarina']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Lamp']], + ["Superbunny Cave - Bottom", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Ocarina']], + ["Superbunny Cave - Bottom", True, ['Hammer', 'Progressive Glove', 'Moon Pearl', 'Ocarina']], + + ["Hookshot Cave - Bottom Right", False, []], + ["Hookshot Cave - Bottom Right", False, [], ['Progressive Glove', 'Ocarina']], + ["Hookshot Cave - Bottom Right", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Lamp', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Ocarina', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Ocarina', 'Pegasus Boots']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Ocarina', 'Hookshot']], + ["Hookshot Cave - Bottom Right", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Ocarina', 'Hookshot']], + + ["Hookshot Cave - Bottom Left", False, []], + ["Hookshot Cave - Bottom Left", False, [], ['Progressive Glove', 'Ocarina']], + ["Hookshot Cave - Bottom Left", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Ocarina', 'Hookshot']], + ["Hookshot Cave - Bottom Left", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Ocarina', 'Hookshot']], + + ["Hookshot Cave - Top Left", False, []], + ["Hookshot Cave - Top Left", False, [], ['Progressive Glove', 'Ocarina']], + ["Hookshot Cave - Top Left", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Ocarina', 'Hookshot']], + ["Hookshot Cave - Top Left", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Ocarina', 'Hookshot']], + + ["Hookshot Cave - Top Right", False, []], + ["Hookshot Cave - Top Right", False, [], ['Progressive Glove', 'Ocarina']], + ["Hookshot Cave - Top Right", False, [], ['Pegasus Boots', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Ocarina', 'Hookshot']], + ["Hookshot Cave - Top Right", True, ['Progressive Glove', 'Hammer', 'Moon Pearl', 'Ocarina', 'Hookshot']], + ]) + + def testWestDarkWorldDeathMountain(self): + self.run_tests([ + ["Spike Cave", False, []], + ["Spike Cave", False, [], ['Progressive Glove']], + ["Spike Cave", False, [], ['Hammer']], + ["Spike Cave", False, [], ['Cape', 'Cane of Byrna']], + ["Spike Cave", False, [], ['Cane of Byrna', 'AnyBottle', 'Magic Upgrade (1/2)']], + ["Spike Cave", False, [], ['AnyBottle', 'Magic Upgrade (1/2)', 'Pegasus Boots', 'Boss Heart Container', 'Piece of Heart', 'Sanctuary Heart Container']], + ["Spike Cave", False, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Cape']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Ocarina', 'Moon Pearl', 'Cape']], + ["Spike Cave", False, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Bottle', 'Hammer', 'Progressive Glove', 'Ocarina', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Lamp', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Ocarina', 'Moon Pearl', 'Cape']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Magic Upgrade (1/2)', 'Hammer', 'Progressive Glove', 'Ocarina', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Pegasus Boots', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Pegasus Boots', 'Hammer', 'Progressive Glove', 'Ocarina', 'Moon Pearl', 'Cane of Byrna']], + ["Spike Cave", True, ['Boss Heart Container', 'Hammer', 'Progressive Glove', 'Lamp', 'Cane of Byrna']], + ["Spike Cave", True, ['Boss Heart Container', 'Hammer', 'Progressive Glove', 'Ocarina', 'Moon Pearl', 'Cane of Byrna']], + ]) + diff --git a/test/inverted/TestInvertedLightWorld.py b/test/inverted/TestInvertedLightWorld.py new file mode 100644 index 00000000..6a1e5baf --- /dev/null +++ b/test/inverted/TestInvertedLightWorld.py @@ -0,0 +1,380 @@ +from test.inverted.TestInverted import TestInverted + + +class TestInvertedLightWorld(TestInverted): + def setUp(self): + super().setUp() + + def testLostWoods(self): + self.run_tests([ + ["Master Sword Pedestal", False, []], + ["Master Sword Pedestal", False, [], ['Green Pendant']], + ["Master Sword Pedestal", False, [], ['Red Pendant']], + ["Master Sword Pedestal", False, [], ['Blue Pendant']], + # @todo: Can get this without moon pearl + # ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1']], + ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Moon Pearl']], + ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Master Sword Pedestal", True, ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mushroom", False, []], + ["Mushroom", False, [], ['Moon Pearl']], + ["Mushroom", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mushroom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mushroom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Lost Woods Hideout", False, []], + ["Lost Woods Hideout", False, [], ['Moon Pearl']], + ["Lost Woods Hideout", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Lost Woods Hideout", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Lost Woods Hideout", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Lumberjack Tree", False, []], + ["Lumberjack Tree", False, [], ['Pegasus Boots']], + ["Lumberjack Tree", False, [], ['Beat Agahnim 1']], + ["Lumberjack Tree", False, [], ['Moon Pearl']], + ["Lumberjack Tree", True, ['Pegasus Boots', 'Beat Agahnim 1', 'Moon Pearl']], + ]) + + def testKakariko(self): + self.run_tests([ + ["Kakariko Tavern", False, []], + ["Kakariko Tavern", False, [], ['Moon Pearl']], + ["Kakariko Tavern", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Tavern", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Tavern", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Chicken House", False, []], + ["Chicken House", False, [], ['Moon Pearl']], + ["Chicken House", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Top", False, []], + ["Kakariko Well - Top", False, [], ['Moon Pearl']], + ["Kakariko Well - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Left", False, []], + ["Kakariko Well - Left", False, [], ['Moon Pearl']], + ["Kakariko Well - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Middle", False, []], + ["Kakariko Well - Middle", False, [], ['Moon Pearl']], + ["Kakariko Well - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Right", False, []], + ["Kakariko Well - Right", False, [], ['Moon Pearl']], + ["Kakariko Well - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Kakariko Well - Bottom", False, []], + ["Kakariko Well - Bottom", False, [], ['Moon Pearl']], + ["Kakariko Well - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Top", False, []], + ["Blind's Hideout - Top", False, [], ['Moon Pearl']], + ["Blind's Hideout - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Left", False, []], + ["Blind's Hideout - Left", False, [], ['Moon Pearl']], + ["Blind's Hideout - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Right", False, []], + ["Blind's Hideout - Right", False, [], ['Moon Pearl']], + ["Blind's Hideout - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Far Left", False, []], + ["Blind's Hideout - Far Left", False, [], ['Moon Pearl']], + ["Blind's Hideout - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Blind's Hideout - Far Right", False, []], + ["Blind's Hideout - Far Right", False, [], ['Moon Pearl']], + ["Blind's Hideout - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Bottle Merchant", False, []], + #@todo: Can get this without moon pearl + #["Bottle Merchant", True, ['Beat Agahnim 1']], + ["Bottle Merchant", True, ['Beat Agahnim 1', 'Moon Pearl']], + ["Bottle Merchant", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Bottle Merchant", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Sick Kid", False, []], + ["Sick Kid", False, [], ['AnyBottle']], + ["Sick Kid", False, ['Bottle (Bee)']], + ["Sick Kid", False, ['Bottle (Fairy)']], + ["Sick Kid", False, ['Bottle (Red Potion)']], + ["Sick Kid", False, ['Bottle (Green Potion)']], + ["Sick Kid", False, ['Bottle (Blue Potion)']], + ["Sick Kid", False, ['Bottle']], + ["Sick Kid", False, ['Bottle (Good Bee)']], + ["Sick Kid", True, ['Bottle (Bee)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Bee)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Bee)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Fairy)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Fairy)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Fairy)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Red Potion)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Red Potion)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Red Potion)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Green Potion)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Green Potion)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Green Potion)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Blue Potion)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Blue Potion)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Blue Potion)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sick Kid", True, ['Bottle (Good Bee)', 'Beat Agahnim 1']], + ["Sick Kid", True, ['Bottle (Good Bee)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sick Kid", True, ['Bottle (Good Bee)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Magic Bat", False, []], + ["Magic Bat", False, [], ['Magic Powder']], + ["Magic Bat", False, [], ['Hammer']], + ["Magic Bat", False, [], ['Moon Pearl']], + ["Magic Bat", False, ['Magic Powder', 'Hammer', 'Moon Pearl']], + ["Magic Bat", True, ['Magic Powder', 'Hammer', 'Moon Pearl', 'Beat Agahnim 1']], + ["Magic Bat", True, ['Magic Powder', 'Hammer', 'Moon Pearl', 'Progressive Glove']], + + ["Library", False, []], + ["Library", False, [], ['Pegasus Boots']], + ["Library", False, [], ['Moon Pearl']], + ["Library", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], + ["Library", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Library", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Maze Race", False, []], + ["Maze Race", False, [], ['Moon Pearl']], + ["Maze Race", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ]) + + def testSouthLightWorld(self): + self.run_tests([ + ["Desert Ledge", False, []], + ["Desert Ledge", False, [], ['Book of Mudora']], + ["Desert Ledge", False, [], ['Moon Pearl']], + ["Desert Ledge", True, ['Book of Mudora', 'Moon Pearl', 'Beat Agahnim 1']], + ["Desert Ledge", True, ['Book of Mudora', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Desert Ledge", True, ['Book of Mudora', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Checkerboard Cave", False, []], + ["Checkerboard Cave", False, [], ['Progressive Glove']], + ["Checkerboard Cave", False, [], ['Moon Pearl']], + ["Checkerboard Cave", True, ['Progressive Glove', 'Beat Agahnim 1', 'Moon Pearl']], + ["Checkerboard Cave", True, ['Progressive Glove', 'Hammer', 'Moon Pearl']], + ["Checkerboard Cave", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Aginah's Cave", False, []], + ["Aginah's Cave", False, [], ['Moon Pearl']], + ["Aginah's Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Bombos Tablet", False, []], + ["Bombos Tablet", False, ['Progressive Sword'], ['Progressive Sword']], + ["Bombos Tablet", False, [], ['Book of Mudora']], + ["Bombos Tablet", False, [], ['Moon Pearl', 'Beat Agahnim 1']], + ["Bombos Tablet", True, ['Beat Agahnim 1', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Hammer', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Beat Agahnim 1', 'Progressive Sword', 'Progressive Sword']], + + ["Floodgate Chest", False, []], + ["Floodgate Chest", False, [], ['Moon Pearl']], + ["Floodgate Chest", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Floodgate Chest", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Floodgate Chest", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Sunken Treasure", False, []], + ["Sunken Treasure", False, [], ['Moon Pearl']], + ["Sunken Treasure", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sunken Treasure", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sunken Treasure", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Far Left", False, []], + ["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Left", False, []], + ["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Generous Guy", False, []], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Right", False, []], + ["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Mini Moldorm Cave - Far Right", False, []], + ["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']], + ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Ice Rod Cave", False, []], + ["Ice Rod Cave", False, [], ['Moon Pearl']], + ["Ice Rod Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ]) + + def testZoraArea(self): + self.run_tests([ + ["King Zora", False, []], + ["King Zora", False, [], ['Progressive Glove', 'Flippers']], + ["King Zora", False, [], ['Moon Pearl']], + ["King Zora", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["King Zora", True, ['Progressive Glove', 'Moon Pearl', 'Beat Agahnim 1']], + ["King Zora", True, ['Progressive Glove', 'Moon Pearl', 'Hammer']], + ["King Zora", True, ['Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Zora's Ledge", False, []], + ["Zora's Ledge", False, [], ['Flippers']], + ["Zora's Ledge", False, [], ['Moon Pearl']], + ["Zora's Ledge", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["Zora's Ledge", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Zora's Ledge", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Waterfall Fairy - Left", False, []], + ["Waterfall Fairy - Left", False, [], ['Flippers']], + ["Waterfall Fairy - Left", False, [], ['Moon Pearl']], + ["Waterfall Fairy - Left", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["Waterfall Fairy - Left", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Waterfall Fairy - Left", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Waterfall Fairy - Right", False, []], + ["Waterfall Fairy - Right", False, [], ['Flippers']], + ["Waterfall Fairy - Right", False, [], ['Moon Pearl']], + ["Waterfall Fairy - Right", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["Waterfall Fairy - Right", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Waterfall Fairy - Right", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ]) + + def testLightWorld(self): + self.run_tests([ + ["Link's Uncle", False, []], + ["Link's Uncle", False, [], ['Moon Pearl']], + ["Link's Uncle", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Link's Uncle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Link's Uncle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Secret Passage", False, []], + ["Secret Passage", False, [], ['Moon Pearl']], + ["Secret Passage", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Secret Passage", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Secret Passage", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["King's Tomb", False, []], + ["King's Tomb", False, [], ['Pegasus Boots']], + ["King's Tomb", False, ['Progressive Glove'], ['Progressive Glove']], + ["King's Tomb", False, [], ['Moon Pearl']], + ["King's Tomb", True, ['Pegasus Boots', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + + ["Sahasrahla's Hut - Left", False, []], + ["Sahasrahla's Hut - Left", False, [], ['Moon Pearl']], + ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Sahasrahla's Hut - Middle", False, []], + ["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl']], + ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Sahasrahla's Hut - Right", False, []], + ["Sahasrahla's Hut - Right", False, [], ['Moon Pearl']], + ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Sahasrahla", False, []], + ["Sahasrahla", False, [], ['Green Pendant']], + ["Sahasrahla", True, ['Green Pendant', 'Beat Agahnim 1']], + ["Sahasrahla", True, ['Green Pendant', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla", True, ['Green Pendant', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Bonk Rock Cave", False, []], + ["Bonk Rock Cave", False, [], ['Pegasus Boots']], + ["Bonk Rock Cave", False, [], ['Moon Pearl']], + ["Bonk Rock Cave", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], + ["Bonk Rock Cave", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Bonk Rock Cave", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Hobo", False, []], + ["Hobo", False, [], ['Flippers']], + ["Hobo", False, [], ['Flippers', 'Moon Pearl']], + ["Hobo", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + ["Hobo", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hobo", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Cave 45", False, []], + ["Cave 45", False, [], ['Moon Pearl']], + ["Cave 45", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Cave 45", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Cave 45", True, ['Moon Pearl', 'Beat Agahnim 1']], + + ["Graveyard Cave", False, []], + ["Graveyard Cave", False, [], ['Moon Pearl']], + ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + + ["Potion Shop", False, []], + ["Potion Shop", False, [], ['Mushroom']], + ["Potion Shop", False, [], ['Moon Pearl']], + ["Potion Shop", True, ['Mushroom', 'Moon Pearl', 'Beat Agahnim 1']], + ["Potion Shop", True, ['Mushroom', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Potion Shop", True, ['Mushroom', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Lake Hylia Island", False, []], + ["Lake Hylia Island", False, [], ['Moon Pearl']], + ["Lake Hylia Island", False, [], ['Flippers']], + ["Lake Hylia Island", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Lake Hylia Island", True, ['Flippers', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Lake Hylia Island", True, ['Flippers', 'Moon Pearl', 'Beat Agahnim 1']], + + ["Flute Spot", False, []], + ["Flute Spot", False, [], ['Shovel']], + ["Flute Spot", False, [], ['Moon Pearl']], + ["Flute Spot", True, ['Shovel', 'Moon Pearl', 'Beat Agahnim 1']], + ["Flute Spot", True, ['Shovel', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Flute Spot", True, ['Shovel', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + + ["Ganon", False, []], + ["Ganon", False, [], ['Moon Pearl']], + ["Ganon", False, [], ['Beat Agahnim 2']], + ]) \ No newline at end of file diff --git a/test/inverted/TestInvertedTurtleRock.py b/test/inverted/TestInvertedTurtleRock.py new file mode 100644 index 00000000..7975abeb --- /dev/null +++ b/test/inverted/TestInvertedTurtleRock.py @@ -0,0 +1,207 @@ +from test.inverted.TestInverted import TestInverted + + +class TestInvertedTurtleRock(TestInverted): + + def testTurtleRock(self): + self.run_tests([ + ["Turtle Rock - Compass Chest", False, []], + ["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']], + ["Turtle Rock - Compass Chest", False, [], ['Quake', 'Magic Mirror']], + ["Turtle Rock - Compass Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Chain Chomps", False, []], + ["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']], + #@todo: Item rando only needs 1 key + ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], + ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot']], + ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + + ["Turtle Rock - Roller Room - Left", False, []], + ["Turtle Rock - Roller Room - Left", False, [], ['Cane of Somaria']], + ["Turtle Rock - Roller Room - Left", False, [], ['Fire Rod']], + ["Turtle Rock - Roller Room - Left", False, [], ['Quake', 'Magic Mirror']], + ["Turtle Rock - Roller Room - Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Roller Room - Right", False, []], + ["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']], + ["Turtle Rock - Roller Room - Right", False, [], ['Fire Rod']], + ["Turtle Rock - Roller Room - Right", False, [], ['Quake', 'Magic Mirror']], + ["Turtle Rock - Roller Room - Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Big Chest", False, []], + ["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Hookshot']], + + ["Turtle Rock - Big Key Chest", False, []], + ["Turtle Rock - Big Key Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + + ["Turtle Rock - Crystaroller Room", False, []], + ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']], + ["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria']], + ["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria']], + + #@todo: Advanced? + ["Turtle Rock - Eye Bridge - Bottom Left", False, []], + ["Turtle Rock - Eye Bridge - Bottom Left", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Bottom Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Eye Bridge - Bottom Right", False, []], + ["Turtle Rock - Eye Bridge - Bottom Right", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Bottom Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Bottom Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Eye Bridge - Top Left", False, []], + ["Turtle Rock - Eye Bridge - Top Left", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Top Left", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Top Left", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Top Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Left", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Eye Bridge - Top Right", False, []], + ["Turtle Rock - Eye Bridge - Top Right", False, ['Progressive Shield', 'Progressive Shield'], ['Progressive Shield', 'Cape', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], + ["Turtle Rock - Eye Bridge - Top Right", False, [], ['Magic Mirror', 'Cane of Somaria']], + ["Turtle Rock - Eye Bridge - Top Right", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Eye Bridge - Top Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cane of Byrna']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Cape']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + ["Turtle Rock - Eye Bridge - Top Right", True, ['Moon Pearl', 'Ocarina', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + + ["Turtle Rock - Boss", False, []], + ["Turtle Rock - Boss", False, [], ['Cane of Somaria']], + ["Turtle Rock - Boss", False, [], ['Ice Rod']], + ["Turtle Rock - Boss", False, [], ['Fire Rod']], + ["Turtle Rock - Boss", False, [], ['Progressive Sword', 'Hammer']], + ["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']], + ["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Moon Pearl', 'Ocarina', 'Beat Agahnim 1', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Ocarina', 'Beat Agahnim 1', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] + ]) \ No newline at end of file diff --git a/test/inverted/__init__.py b/test/inverted/__init__.py new file mode 100644 index 00000000..e69de29b