From 9f0d736aed464e945e4205289c86babaec3f1b73 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu, 22 Feb 2024 03:44:03 -0500 Subject: [PATCH 01/66] Generate: Fix sphere calculation debug message (#2788) --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 25e4e70741a1..36d0bc267a84 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1272,12 +1272,12 @@ def create_playthrough(self, create_paths: bool = True) -> None: for location in sphere: state.collect(location.item, True, location) - required_locations -= sphere - collection_spheres.append(sphere) logging.debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations)) + + required_locations -= sphere if not sphere: raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}') From f8981a463873ac4ea6093385427199cf58c089d0 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:49:02 +0100 Subject: [PATCH 02/66] Docs: Better description for LocationScouts (#2674) * Better description for LocationScouts * Update network protocol.md * typo * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- docs/network protocol.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/network protocol.md b/docs/network protocol.md index 338db55299b6..c6d6cf6887e6 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -327,7 +327,11 @@ Sent to server to inform it of locations that the client has checked. Used to in | locations | list\[int\] | The ids of the locations checked by the client. May contain any number of checks, even ones sent before; duplicates do not cause issues with the Archipelago server. | ### LocationScouts -Sent to the server to inform it of locations the client has seen, but not checked. Useful in cases in which the item may appear in the game world, such as 'ledge items' in A Link to the Past. The server will always respond with a [LocationInfo](#LocationInfo) packet with the items located in the scouted location. +Sent to the server to retrieve the items that are on a specified list of locations. The server will respond with a [LocationInfo](#LocationInfo) packet containing the items located in the scouted locations. +Fully remote clients without a patch file may use this to "place" items onto their in-game locations, most commonly to display their names or item classifications before/upon pickup. + +LocationScouts can also be used to inform the server of locations the client has seen, but not checked. This creates a hint as if the player had run `!hint_location` on a location, but without deducting hint points. +This is useful in cases where an item appears in the game world, such as 'ledge items' in _A Link to the Past_. To do this, set the `create_as_hint` parameter to a non-zero value. #### Arguments | Name | Type | Notes | From b18641091f3090306cb2dc463ac1b43387f377b1 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:56:53 -0500 Subject: [PATCH 03/66] LTTP: Thieves' Town Big Chest fix (#2853) --- worlds/alttp/Rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 17061842dde9..a87bfd5b0cde 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -397,9 +397,9 @@ def global_rules(world, player): set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player)) set_rule(world.get_location('Thieves\' Town - Big Chest', player), - lambda state: (state._lttp_has_key('Small Key (Thieves Town)', player, 3)) and state.has('Hammer', player)) + lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player)) if world.accessibility[player] != 'locations': - allow_self_locking_items(world.get_location('Thieves\' Town - Big Chest', player), 'Small Key (Thieves Town)') + set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player) set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3)) set_rule(world.get_location('Thieves\' Town - Spike Switch Pot Key', player), From afa5ce4afe63abf1dfccb6c311f85392b7f131d1 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Fri, 23 Feb 2024 10:11:00 +0100 Subject: [PATCH 04/66] CI: add static analysis for native code / cython (#2852) * CI: add static analysis for native code / cython * CI: scan-build: also run for requirements.txt --- .github/workflows/scan-build.yml | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/scan-build.yml diff --git a/.github/workflows/scan-build.yml b/.github/workflows/scan-build.yml new file mode 100644 index 000000000000..5234d862b4d3 --- /dev/null +++ b/.github/workflows/scan-build.yml @@ -0,0 +1,65 @@ +name: Native Code Static Analysis + +on: + push: + paths: + - '**.c' + - '**.cc' + - '**.cpp' + - '**.cxx' + - '**.h' + - '**.hh' + - '**.hpp' + - '**.pyx' + - 'setup.py' + - 'requirements.txt' + - '.github/workflows/scan-build.yml' + pull_request: + paths: + - '**.c' + - '**.cc' + - '**.cpp' + - '**.cxx' + - '**.h' + - '**.hh' + - '**.hpp' + - '**.pyx' + - 'setup.py' + - 'requirements.txt' + - '.github/workflows/scan-build.yml' + +jobs: + scan-build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install newer Clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 17 + - name: Install scan-build command + run: | + sudo apt install clang-tools-17 + - name: Get a recent python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m venv venv + source venv/bin/activate + python -m pip install --upgrade pip -r requirements.txt + - name: scan-build + run: | + source venv/bin/activate + scan-build-17 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y + - name: Store report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: scan-build-reports + path: scan-build-reports From 96163c640830545fed9b5a133b6989273e8d3a30 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 23 Feb 2024 10:32:14 +0100 Subject: [PATCH 05/66] Core: provide convenience getters on World class (#2827) --- worlds/AutoWorld.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index e8d48df58c53..b282c7deb8bd 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: import random - from BaseClasses import MultiWorld, Item, Location, Tutorial + from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance from . import GamesPackage from settings import Group @@ -458,6 +458,16 @@ def remove(self, state: "CollectionState", item: "Item") -> bool: def create_filler(self) -> "Item": return self.create_item(self.get_filler_item_name()) + # convenience methods + def get_location(self, location_name: str) -> "Location": + return self.multiworld.get_location(location_name, self.player) + + def get_entrance(self, entrance_name: str) -> "Entrance": + return self.multiworld.get_entrance(entrance_name, self.player) + + def get_region(self, region_name: str) -> "Region": + return self.multiworld.get_region(region_name, self.player) + @classmethod def get_data_package_data(cls) -> "GamesPackage": sorted_item_name_groups = { From 6bf4a94537511afba4e8386361af59e31e315105 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Fri, 23 Feb 2024 13:41:59 -0500 Subject: [PATCH 06/66] TUNIC: Use push_precollected for start_with_sword (#2857) --- worlds/tunic/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index fb04570f22ca..b10ccd43af59 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -73,9 +73,6 @@ def generate_early(self) -> None: self.options.hexagon_quest.value = passthrough["hexagon_quest"] self.options.entrance_rando.value = passthrough["entrance_rando"] - if self.options.start_with_sword and "Sword" not in self.options.start_inventory: - self.options.start_inventory.value["Sword"] = 1 - def create_item(self, name: str) -> TunicItem: item_data = item_table[name] return TunicItem(name, item_data.classification, self.item_name_to_id[name], self.player) @@ -94,6 +91,9 @@ def create_items(self) -> None: items_to_create["Fool Trap"] += items_to_create[money_fool] items_to_create[money_fool] = 0 + if self.options.start_with_sword: + self.multiworld.push_precollected(self.create_item("Sword")) + if sword_progression: items_to_create["Stick"] = 0 items_to_create["Sword"] = 0 From 57fcd57a851c7b403ce15206163836c56c28c407 Mon Sep 17 00:00:00 2001 From: Ixrec Date: Sat, 24 Feb 2024 16:01:54 +0000 Subject: [PATCH 07/66] Docs: Clarify which kinds of options actually support "random" (#2845) * Clarify which kinds of options actually support "random" The current phrasing of this sentence made me expect "random" to work even on my OptionsDict option. After asking `#archipelago-dev` and checking the `Options.py` code, it's become clear that many option types don't (and can't) support "random". This is my best guess at a more correct wording. * add a sentence about from_text overrides based on black-silver's suggestion --- docs/options api.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/options api.md b/docs/options api.md index bfab0096bbaf..1141528991df 100644 --- a/docs/options api.md +++ b/docs/options api.md @@ -24,8 +24,11 @@ display as `Value1` on the webhost. (i.e. `alias_value_1 = option_value1`) which will allow users to use either `value_1` or `value1` in their yaml files, and both will resolve as `value1`. This should be used when changing options around, i.e. changing a Toggle to a Choice, and defining `alias_true = option_full`. -- All options support `random` as a generic option. `random` chooses from any of the available values for that option, -and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`. +- All options with a fixed set of possible values (i.e. those which inherit from `Toggle`, `(Text)Choice` or +`(Named/Special)Range`) support `random` as a generic option. `random` chooses from any of the available values for that +option, and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`. +However, you can override `from_text` and handle `text == "random"` to customize its behavior or +implement it for additional option types. As an example, suppose we want an option that lets the user start their game with a sword in their inventory, an option to let the player choose the difficulty, and an option to choose how much health the final boss has. Let's create our From 86a7ac466e99240b5c9c33e701009d431e3fc33e Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sat, 24 Feb 2024 21:45:23 -0600 Subject: [PATCH 08/66] Core: remove bad hardcoded behavior around plando_connections (#2170) --- Generate.py | 35 ++++++++++---------------------- worlds/generic/docs/plando_en.md | 8 ++++---- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/Generate.py b/Generate.py index fd4a5a7e1930..725a7e9fec35 100644 --- a/Generate.py +++ b/Generate.py @@ -462,20 +462,18 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b handle_option(ret, game_weights, option_key, option, plando_options) if PlandoOptions.items in plando_options: ret.plando_items = game_weights.get("plando_items", []) - if ret.game == "Minecraft" or ret.game == "Ocarina of Time": - # bad hardcoded behavior to make this work for now - ret.plando_connections = [] - if PlandoOptions.connections in plando_options: - options = game_weights.get("plando_connections", []) - for placement in options: - if roll_percentage(get_choice("percentage", placement, 100)): - ret.plando_connections.append(PlandoConnection( - get_choice("entrance", placement), - get_choice("exit", placement), - get_choice("direction", placement) - )) - elif ret.game == "A Link to the Past": + if ret.game == "A Link to the Past": roll_alttp_settings(ret, game_weights, plando_options) + if PlandoOptions.connections in plando_options: + ret.plando_connections = [] + options = game_weights.get("plando_connections", []) + for placement in options: + if roll_percentage(get_choice("percentage", placement, 100)): + ret.plando_connections.append(PlandoConnection( + get_choice("entrance", placement), + get_choice("exit", placement), + get_choice("direction", placement, "both") + )) return ret @@ -494,17 +492,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): raise Exception(f"No text target \"{at}\" found.") ret.plando_texts[at] = str(get_choice_legacy("text", placement)) - ret.plando_connections = [] - if PlandoOptions.connections in plando_options: - options = weights.get("plando_connections", []) - for placement in options: - if roll_percentage(get_choice_legacy("percentage", placement, 100)): - ret.plando_connections.append(PlandoConnection( - get_choice_legacy("entrance", placement), - get_choice_legacy("exit", placement), - get_choice_legacy("direction", placement, "both") - )) - ret.sprite_pool = weights.get('sprite_pool', []) ret.sprite = get_choice_legacy('sprite', weights, "Link") if 'random_sprite_on_event' in weights: diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md index 2d40f45195ba..9d8e6befe87f 100644 --- a/worlds/generic/docs/plando_en.md +++ b/worlds/generic/docs/plando_en.md @@ -171,16 +171,16 @@ relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the% ## Connections Plando -This is currently only supported by Minecraft and A Link to the Past. As the way that these games interact with their -connections is different, I will only explain the basics here, while more specifics for A Link to the Past connection -plando can be found in its plando guide. +This is currently only supported by a few games, including A Link to the Past, Minecraft, and Ocarina of Time. As the way that these games interact with their +connections is different, only the basics are explained here. More specific information for connection plando in A Link to the Past can be found in +its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections). * The options for connections are `percentage`, `entrance`, `exit`, and `direction`. Each of these options supports subweights. * `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100. * Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance shuffle. -* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate. +* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate. `direction` defaults to `both`. [A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852) From 8f7b0ee489711c8d67d825e2073a2840c421bbc2 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 25 Feb 2024 14:56:27 -0600 Subject: [PATCH 09/66] Core: don't allow region, location, or entrance with duplicate names (#2453) --- BaseClasses.py | 8 ++++++++ test/general/test_helpers.py | 4 ++-- worlds/mmbn3/__init__.py | 5 +---- worlds/overcooked2/__init__.py | 8 +------- worlds/ror2/regions.py | 6 +----- worlds/ror2/rules.py | 19 +++++++++++-------- worlds/ror2/test/test_limbo_goal.py | 4 ++-- worlds/ror2/test/test_mithrix_goal.py | 10 +++++----- worlds/ror2/test/test_voidling_goal.py | 6 +++--- worlds/sm64ex/Regions.py | 9 +-------- worlds/timespinner/Regions.py | 8 +------- 11 files changed, 36 insertions(+), 51 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 36d0bc267a84..f41894535170 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -110,10 +110,14 @@ def __iadd__(self, other: Iterable[Region]): return self def append(self, region: Region): + assert region.name not in self.region_cache[region.player], \ + f"{region.name} already exists in region cache." self.region_cache[region.player][region.name] = region def extend(self, regions: Iterable[Region]): for region in regions: + assert region.name not in self.region_cache[region.player], \ + f"{region.name} already exists in region cache." self.region_cache[region.player][region.name] = region def add_group(self, new_id: int): @@ -877,6 +881,8 @@ def __delitem__(self, index: int) -> None: del(self.region_manager.location_cache[location.player][location.name]) def insert(self, index: int, value: Location) -> None: + assert value.name not in self.region_manager.location_cache[value.player], \ + f"{value.name} already exists in the location cache." self._list.insert(index, value) self.region_manager.location_cache[value.player][value.name] = value @@ -887,6 +893,8 @@ def __delitem__(self, index: int) -> None: del(self.region_manager.entrance_cache[entrance.player][entrance.name]) def insert(self, index: int, value: Entrance) -> None: + assert value.name not in self.region_manager.entrance_cache[value.player], \ + f"{value.name} already exists in the entrance cache." self._list.insert(index, value) self.region_manager.entrance_cache[value.player][value.name] = value diff --git a/test/general/test_helpers.py b/test/general/test_helpers.py index 83b56b34386b..be8473975638 100644 --- a/test/general/test_helpers.py +++ b/test/general/test_helpers.py @@ -29,8 +29,8 @@ def test_region_helpers(self) -> None: "event_loc": None, }, "TestRegion2": { - "loc_1": 321, - "loc_2": 654, + "loc_3": 321, + "loc_4": 654, } } diff --git a/worlds/mmbn3/__init__.py b/worlds/mmbn3/__init__.py index acf258a730c6..762bfd11ae4a 100644 --- a/worlds/mmbn3/__init__.py +++ b/worlds/mmbn3/__init__.py @@ -100,9 +100,7 @@ def create_regions(self) -> None: for region_info in regions: region = name_to_region[region_info.name] for connection in region_info.connections: - connection_region = name_to_region[connection] - entrance = Entrance(self.player, connection, region) - entrance.connect(connection_region) + entrance = region.connect(name_to_region[connection]) # ACDC Pending with Start Randomizer # if connection == RegionName.ACDC_Overworld: @@ -141,7 +139,6 @@ def create_regions(self) -> None: if connection == RegionName.WWW_Island: entrance.access_rule = lambda state:\ state.has(ItemName.Progressive_Undernet_Rank, self.player, 8) - region.exits.append(entrance) def create_items(self) -> None: # First add in all progression and useful items diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py index 24ac175cebb8..da0e1890894a 100644 --- a/worlds/overcooked2/__init__.py +++ b/worlds/overcooked2/__init__.py @@ -90,13 +90,7 @@ def add_region(self, region_name: str): def connect_regions(self, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None): sourceRegion = self.multiworld.get_region(source, self.player) targetRegion = self.multiworld.get_region(target, self.player) - - connection = Entrance(self.player, '', sourceRegion) - if rule: - connection.access_rule = rule - - sourceRegion.exits.append(connection) - connection.connect(targetRegion) + sourceRegion.connect(targetRegion, rule=rule) def add_level_location( self, diff --git a/worlds/ror2/regions.py b/worlds/ror2/regions.py index 13b229da9249..199fdccf80e8 100644 --- a/worlds/ror2/regions.py +++ b/worlds/ror2/regions.py @@ -140,11 +140,7 @@ def create_explore_region(multiworld: MultiWorld, player: int, name: str, data: def create_connections_in_regions(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData) -> None: region = multiworld.get_region(name, player) if data.region_exits: - for region_exit in data.region_exits: - r_exit_stage = Entrance(player, region_exit, region) - exit_region = multiworld.get_region(region_exit, player) - r_exit_stage.connect(exit_region) - region.exits.append(r_exit_stage) + region.add_exits(data.region_exits) def create_classic_regions(ror2_world: "RiskOfRainWorld") -> None: diff --git a/worlds/ror2/rules.py b/worlds/ror2/rules.py index 442e6c0002aa..b4d5fe68b82e 100644 --- a/worlds/ror2/rules.py +++ b/worlds/ror2/rules.py @@ -9,14 +9,16 @@ # Rule to see if it has access to the previous stage -def has_entrance_access_rule(multiworld: MultiWorld, stage: str, entrance: str, player: int) -> None: - multiworld.get_entrance(entrance, player).access_rule = \ - lambda state: state.has(entrance, player) and state.has(stage, player) +def has_entrance_access_rule(multiworld: MultiWorld, stage: str, region: str, player: int) -> None: + rule = lambda state: state.has(region, player) and state.has(stage, player) + for entrance in multiworld.get_region(region, player).entrances: + entrance.access_rule = rule -def has_all_items(multiworld: MultiWorld, items: Set[str], entrance: str, player: int) -> None: - multiworld.get_entrance(entrance, player).access_rule = \ - lambda state: state.has_all(items, player) and state.has(entrance, player) +def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: int) -> None: + rule = lambda state: state.has_all(items, player) and state.has(region, player) + for entrance in multiworld.get_region(region, player).entrances: + entrance.access_rule = rule # Checks to see if chest/shrine are accessible @@ -45,8 +47,9 @@ def check_location(state, environment: str, player: int, item_number: int, item_ def get_stage_event(multiworld: MultiWorld, player: int, stage_number: int) -> None: if stage_number == 4: return - multiworld.get_entrance(f"OrderedStage_{stage_number + 1}", player).access_rule = \ - lambda state: state.has(f"Stage {stage_number + 1}", player) + rule = lambda state: state.has(f"Stage {stage_number + 1}", player) + for entrance in multiworld.get_region(f"OrderedStage_{stage_number + 1}", player).entrances: + entrance.access_rule = rule def set_rules(ror2_world: "RiskOfRainWorld") -> None: diff --git a/worlds/ror2/test/test_limbo_goal.py b/worlds/ror2/test/test_limbo_goal.py index f8757a917641..9be9cca1206a 100644 --- a/worlds/ror2/test/test_limbo_goal.py +++ b/worlds/ror2/test/test_limbo_goal.py @@ -8,8 +8,8 @@ class LimboGoalTest(RoR2TestBase): def test_limbo(self) -> None: self.collect_all_but(["Hidden Realm: A Moment, Whole", "Victory"]) - self.assertFalse(self.can_reach_entrance("Hidden Realm: A Moment, Whole")) + self.assertFalse(self.can_reach_region("Hidden Realm: A Moment, Whole")) self.assertBeatable(False) self.collect_by_name("Hidden Realm: A Moment, Whole") - self.assertTrue(self.can_reach_entrance("Hidden Realm: A Moment, Whole")) + self.assertTrue(self.can_reach_region("Hidden Realm: A Moment, Whole")) self.assertBeatable(True) diff --git a/worlds/ror2/test/test_mithrix_goal.py b/worlds/ror2/test/test_mithrix_goal.py index 7ed9a2cd73a2..03b82311783c 100644 --- a/worlds/ror2/test/test_mithrix_goal.py +++ b/worlds/ror2/test/test_mithrix_goal.py @@ -8,18 +8,18 @@ class MithrixGoalTest(RoR2TestBase): def test_mithrix(self) -> None: self.collect_all_but(["Commencement", "Victory"]) - self.assertFalse(self.can_reach_entrance("Commencement")) + self.assertFalse(self.can_reach_region("Commencement")) self.assertBeatable(False) self.collect_by_name("Commencement") - self.assertTrue(self.can_reach_entrance("Commencement")) + self.assertTrue(self.can_reach_region("Commencement")) self.assertBeatable(True) def test_stage5(self) -> None: self.collect_all_but(["Stage 4", "Sky Meadow", "Victory"]) - self.assertFalse(self.can_reach_entrance("Sky Meadow")) + self.assertFalse(self.can_reach_region("Sky Meadow")) self.assertBeatable(False) self.collect_by_name("Sky Meadow") - self.assertFalse(self.can_reach_entrance("Sky Meadow")) + self.assertFalse(self.can_reach_region("Sky Meadow")) self.collect_by_name("Stage 4") - self.assertTrue(self.can_reach_entrance("Sky Meadow")) + self.assertTrue(self.can_reach_region("Sky Meadow")) self.assertBeatable(True) diff --git a/worlds/ror2/test/test_voidling_goal.py b/worlds/ror2/test/test_voidling_goal.py index a7520a5c5f95..77d1349f10eb 100644 --- a/worlds/ror2/test/test_voidling_goal.py +++ b/worlds/ror2/test/test_voidling_goal.py @@ -9,17 +9,17 @@ class VoidlingGoalTest(RoR2TestBase): def test_planetarium(self) -> None: self.collect_all_but(["The Planetarium", "Victory"]) - self.assertFalse(self.can_reach_entrance("The Planetarium")) + self.assertFalse(self.can_reach_region("The Planetarium")) self.assertBeatable(False) self.collect_by_name("The Planetarium") - self.assertTrue(self.can_reach_entrance("The Planetarium")) + self.assertTrue(self.can_reach_region("The Planetarium")) self.assertBeatable(True) def test_void_locus_to_victory(self) -> None: self.collect_all_but(["Void Locus", "Commencement"]) self.assertFalse(self.can_reach_location("Victory")) self.collect_by_name("Void Locus") - self.assertTrue(self.can_reach_entrance("Victory")) + self.assertTrue(self.can_reach_location("Victory")) def test_commencement_to_victory(self) -> None: self.collect_all_but(["Void Locus", "Commencement"]) diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index c04b862fa757..8c2d32e401bf 100644 --- a/worlds/sm64ex/Regions.py +++ b/worlds/sm64ex/Regions.py @@ -200,7 +200,6 @@ def create_regions(world: MultiWorld, player: int): create_locs(thi_large_top, "THI: 100 Coins") regFloor3 = create_region("Third Floor", player, world) - world.regions.append(regFloor3) regTTC = create_region("Tick Tock Clock", player, world) create_locs(regTTC, "TTC: Stop Time for Red Coins") @@ -230,13 +229,7 @@ def create_regions(world: MultiWorld, player: int): def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None): sourceRegion = world.get_region(source, player) targetRegion = world.get_region(target, player) - - connection = Entrance(player, '', sourceRegion) - if rule: - connection.access_rule = rule - - sourceRegion.exits.append(connection) - connection.connect(targetRegion) + sourceRegion.connect(targetRegion, rule=rule) def create_region(name: str, player: int, world: MultiWorld) -> Region: diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index fc7535642949..f80babc0e6d4 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -247,13 +247,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, sourceRegion = world.get_region(source, player) targetRegion = world.get_region(target, player) - - connection = Entrance(player, "", sourceRegion) - - if rule: - connection.access_rule = rule - sourceRegion.exits.append(connection) - connection.connect(targetRegion) + sourceRegion.connect(targetRegion, rule=rule) def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]: From 46fc8df36e5e0fe4b31f78b860e7d41352a7d3bd Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 25 Feb 2024 16:27:19 -0500 Subject: [PATCH 10/66] TUNIC: Fix for incorrect Zig 3 ER rule (#2849) * Fix for incorrect ER rule in zig 3 * Add nmg logic to this same connection --- worlds/tunic/er_rules.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index ebc563c3da50..2f9604c1035a 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -502,9 +502,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re rule=lambda state: state.has(laurels, player) or (has_sword(state, player) and has_ability(state, player, prayer, options, ability_unlocks))) # unrestricted: use ladder storage to get to the front, get hit by one of the many enemies + # nmg: can ice grapple on the voidlings to the double admin fight, still need to pray at the fuse regions["Rooted Ziggurat Lower Back"].connect( connecting_region=regions["Rooted Ziggurat Lower Front"], - rule=lambda state: state.has(laurels, player) or can_ladder_storage(state, player, options)) + rule=lambda state: ((state.has(laurels, player) or + has_ice_grapple_logic(True, state, player, options, ability_unlocks)) and + has_ability(state, player, prayer, options, ability_unlocks) + and has_sword(state, player)) or can_ladder_storage(state, player, options)) regions["Rooted Ziggurat Lower Back"].connect( connecting_region=regions["Rooted Ziggurat Portal Room Entrance"], From 5c05ab1527960374ae5c8dd61fa69fa4af0f15f3 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:28:33 -0500 Subject: [PATCH 11/66] LTTP: KDS Default on (#2850) --- worlds/alttp/Options.py | 6 +- worlds/alttp/Rules.py | 6 +- .../alttp/test/dungeons/TestAgahnimsTower.py | 8 +-- .../alttp/test/dungeons/TestDesertPalace.py | 28 ++++----- .../alttp/test/dungeons/TestEasternPalace.py | 4 +- worlds/alttp/test/dungeons/TestGanonsTower.py | 58 +++++++++---------- worlds/alttp/test/dungeons/TestIcePalace.py | 36 ++++++------ worlds/alttp/test/dungeons/TestMiseryMire.py | 34 +++++------ worlds/alttp/test/dungeons/TestSkullWoods.py | 4 +- worlds/alttp/test/dungeons/TestSwampPalace.py | 18 +++--- worlds/alttp/test/dungeons/TestThievesTown.py | 15 ++--- .../test/inverted/TestInvertedTurtleRock.py | 54 ++++++++--------- .../TestInvertedTurtleRock.py | 54 ++++++++--------- .../alttp/test/inverted_owg/TestDungeons.py | 19 +++--- .../test/minor_glitches/TestDeathMountain.py | 2 +- worlds/alttp/test/owg/TestDungeons.py | 18 +++--- .../alttp/test/vanilla/TestDeathMountain.py | 2 +- 17 files changed, 176 insertions(+), 190 deletions(-) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index ed6af6dd674f..8cc5d32608d9 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -43,8 +43,7 @@ class Goal(Choice): Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle Local Triforce Hunt: Collect Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle Ganon Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then kill Ganon - Local Ganon Triforce Hunt: Collect Triforce pieces spread throughout your world, then kill Ganon - Ice Rod Hunt: You start with everything except Ice Rod. Find the Ice rod, then kill Trinexx at Turtle rock.""" + Local Ganon Triforce Hunt: Collect Triforce pieces spread throughout your world, then kill Ganon""" display_name = "Goal" default = 0 option_ganon = 0 @@ -211,13 +210,12 @@ class map_shuffle(DungeonItem): display_name = "Map Shuffle" -class key_drop_shuffle(Toggle): +class key_drop_shuffle(DefaultOnToggle): """Shuffle keys found in pots and dropped from killed enemies, respects the small key and big key shuffle options.""" display_name = "Key Drop Shuffle" - class DungeonCounters(Choice): """On: Always display amount of items checked in a dungeon. Pickup: Show when compass is picked up. Default: Show when compass is picked up if the compass itself is shuffled. Off: Never show item count in dungeons.""" diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index a87bfd5b0cde..b86a793fb937 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -420,11 +420,7 @@ def global_rules(world, player): set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player)) set_rule(world.get_location('Ice Palace - Compass Chest', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, player)) - if not world.enemy_shuffle[player]: - # Stalfos Knights can be killed by damaging them repeatedly with boomerang, swords, etc. if bombs are - # unavailable. If bombs are available, the pots can be thrown at them, so no other weapons are needed - add_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: (can_use_bombs(state, player) - or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) or has_sword(state, player) or state.has("Hammer", player))) + set_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2)) set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5)))) diff --git a/worlds/alttp/test/dungeons/TestAgahnimsTower.py b/worlds/alttp/test/dungeons/TestAgahnimsTower.py index c44a92be1ece..93c3f60463d0 100644 --- a/worlds/alttp/test/dungeons/TestAgahnimsTower.py +++ b/worlds/alttp/test/dungeons/TestAgahnimsTower.py @@ -23,14 +23,14 @@ def testTower(self): ["Castle Tower - Dark Archer Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], ["Castle Tower - Circle of Pots Key Drop", False, []], - ["Castle Tower - Circle of Pots Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']], + ["Castle Tower - Circle of Pots Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']], ["Castle Tower - Circle of Pots Key Drop", False, [], ['Lamp']], ["Castle Tower - Circle of Pots Key Drop", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], - ["Castle Tower - Circle of Pots Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], + ["Castle Tower - Circle of Pots Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], ["Agahnim 1", False, []], - ["Agahnim 1", False, ['Small Key (Agahnims Tower)'], ['Small Key (Agahnims Tower)']], + ["Agahnim 1", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', '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']], + ["Agahnim 1", True, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp', 'Progressive Sword']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestDesertPalace.py b/worlds/alttp/test/dungeons/TestDesertPalace.py index 2d1951391177..58e441f94585 100644 --- a/worlds/alttp/test/dungeons/TestDesertPalace.py +++ b/worlds/alttp/test/dungeons/TestDesertPalace.py @@ -19,35 +19,35 @@ def testDesertPalace(self): ["Desert Palace - Compass Chest", False, []], ["Desert Palace - Compass Chest", False, [], ['Small Key (Desert Palace)']], ["Desert Palace - Compass Chest", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Compass Chest", False, ['Small Key (Desert Palace)']], - ["Desert Palace - Compass Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)']], + ["Desert Palace - Compass Chest", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], + ["Desert Palace - Compass Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], ["Desert Palace - Big Key Chest", False, []], ["Desert Palace - Big Key Chest", False, [], ['Small Key (Desert Palace)']], ["Desert Palace - Big Key Chest", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Big Key Chest", False, ['Small Key (Desert Palace)']], - ["Desert Palace - Big Key Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)']], + ["Desert Palace - Big Key Chest", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], + ["Desert Palace - Big Key Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], ["Desert Palace - Desert Tiles 1 Pot Key", True, []], ["Desert Palace - Beamos Hall Pot Key", False, []], - ["Desert Palace - Beamos Hall Pot Key", False, [], ['Small Key (Desert Palace)']], + ["Desert Palace - Beamos Hall Pot Key", False, ['Small Key (Desert Palace)']], ["Desert Palace - Beamos Hall Pot Key", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Beamos Hall Pot Key", True, ['Small Key (Desert Palace)', 'Progressive Sword']], + ["Desert Palace - Beamos Hall Pot Key", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Progressive Sword']], ["Desert Palace - Desert Tiles 2 Pot Key", False, []], - ["Desert Palace - Desert Tiles 2 Pot Key", False, ['Small Key (Desert Palace)']], + ["Desert Palace - Desert Tiles 2 Pot Key", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)']], ["Desert Palace - Desert Tiles 2 Pot Key", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Desert Tiles 2 Pot Key", True, ['Small Key (Desert Palace)', 'Progressive Sword']], + ["Desert Palace - Desert Tiles 2 Pot Key", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Progressive Sword']], ["Desert Palace - Boss", False, []], - ["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']], + ["Desert Palace - Boss", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', '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', 'Cane of Somaria']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Fire Rod']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Progressive Sword']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Hammer']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Somaria']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestEasternPalace.py b/worlds/alttp/test/dungeons/TestEasternPalace.py index c1a978343b84..ee8b7a16246f 100644 --- a/worlds/alttp/test/dungeons/TestEasternPalace.py +++ b/worlds/alttp/test/dungeons/TestEasternPalace.py @@ -19,12 +19,12 @@ def testEastern(self): ["Eastern Palace - Big Key Chest", False, []], ["Eastern Palace - Big Key Chest", False, [], ['Lamp']], ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)', 'Progressive Sword']], - ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Big Key (Eastern Palace)', 'Progressive Sword']], #@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)']] + ["Eastern Palace - Boss", False, ['Small Key (Eastern Palace)', 'Small Key (Eastern Palace)']], + ["Eastern Palace - Boss", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)', 'Progressive Bow', 'Big Key (Eastern Palace)']] ]) diff --git a/worlds/alttp/test/dungeons/TestGanonsTower.py b/worlds/alttp/test/dungeons/TestGanonsTower.py index 98bc6fa552e2..1e70f580de4e 100644 --- a/worlds/alttp/test/dungeons/TestGanonsTower.py +++ b/worlds/alttp/test/dungeons/TestGanonsTower.py @@ -33,50 +33,46 @@ def testGanonsTower(self): ["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)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Firesnake Room", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hammer', 'Pegasus Boots']], + ["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', '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)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small 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)', 'Small 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)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', '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']], @@ -85,34 +81,34 @@ def testGanonsTower(self): ["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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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']], @@ -132,8 +128,8 @@ def testGanonsTower(self): ["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)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', '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)', 'Small 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)', 'Small 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']], @@ -141,8 +137,8 @@ def testGanonsTower(self): ["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)', '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)', '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)', '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)', 'Small Key (Ganons Tower)', 'Fire Rod', '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)', '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)', '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)', '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)', '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/worlds/alttp/test/dungeons/TestIcePalace.py b/worlds/alttp/test/dungeons/TestIcePalace.py index 7a15c5c09718..868631587375 100644 --- a/worlds/alttp/test/dungeons/TestIcePalace.py +++ b/worlds/alttp/test/dungeons/TestIcePalace.py @@ -12,8 +12,8 @@ def testIcePalace(self): ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Big Key Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', '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)']], @@ -23,8 +23,8 @@ def testIcePalace(self): ["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 - Compass Chest", True, ['Small Key (Ice Palace)', 'Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Bombos', 'Progressive Sword']], ["Ice Palace - Map Chest", False, []], ["Ice Palace - Map Chest", False, [], ['Hammer']], @@ -32,8 +32,8 @@ def testIcePalace(self): ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Map Chest", True, ['Small Key (Ice Palace)', 'Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Map Chest", True, ['Small Key (Ice Palace)', 'Bomb Upgrade (+5)', '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)']], @@ -43,8 +43,8 @@ def testIcePalace(self): ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Spike Room", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)', '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)']], @@ -54,23 +54,23 @@ def testIcePalace(self): ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Freezor Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Fire Rod']], - ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], ["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", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Fire Rod']], - ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], ["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", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Fire Rod']], - ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Fire Rod']], + ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Bombos', 'Progressive Sword']], ["Ice Palace - Boss", False, []], ["Ice Palace - Boss", False, [], ['Hammer']], @@ -80,8 +80,8 @@ def testIcePalace(self): ["Ice Palace - Boss", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], # need hookshot now to reach the right side for the 6th key - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestMiseryMire.py b/worlds/alttp/test/dungeons/TestMiseryMire.py index 6cbf42922fa4..ca74e9365ee6 100644 --- a/worlds/alttp/test/dungeons/TestMiseryMire.py +++ b/worlds/alttp/test/dungeons/TestMiseryMire.py @@ -32,36 +32,32 @@ def testMiseryMire(self): ["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 - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']], + ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small 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 - Big Key Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', '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)', '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)', '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)', '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)', '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 - Compass Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', '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)', '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)', '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)', '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)', '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, [], ['Small 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 - Map Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Progressive Sword', 'Hookshot']], ["Misery Mire - Spike Chest", False, []], ["Misery Mire - Spike Chest", False, [], ['Pegasus Boots', 'Hookshot']], diff --git a/worlds/alttp/test/dungeons/TestSkullWoods.py b/worlds/alttp/test/dungeons/TestSkullWoods.py index 55c8d2e29a21..7650e785c871 100644 --- a/worlds/alttp/test/dungeons/TestSkullWoods.py +++ b/worlds/alttp/test/dungeons/TestSkullWoods.py @@ -96,6 +96,6 @@ def testSkullWoodsBack(self): ["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)'], ['Small Key (Skull Woods)']], - ["Skull Woods - Boss", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']], + ["Skull Woods - Boss", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', '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)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestSwampPalace.py b/worlds/alttp/test/dungeons/TestSwampPalace.py index bddf40616f18..fb0672a5a9cc 100644 --- a/worlds/alttp/test/dungeons/TestSwampPalace.py +++ b/worlds/alttp/test/dungeons/TestSwampPalace.py @@ -16,15 +16,15 @@ def testSwampPalace(self): ["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 Chest", False, [], ['Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)']], + ["Swamp Palace - Big Chest", True, ['Open Floodgate', 'Big Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small 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 - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], ["Swamp Palace - Map Chest", False, []], ["Swamp Palace - Map Chest", False, [], ['Flippers']], @@ -38,14 +38,14 @@ def testSwampPalace(self): ["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 - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', '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 - Compass Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], ["Swamp Palace - Flooded Room - Left", False, []], ["Swamp Palace - Flooded Room - Left", False, [], ['Flippers']], @@ -53,7 +53,7 @@ def testSwampPalace(self): ["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 - Left", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ["Swamp Palace - Flooded Room - Right", False, []], ["Swamp Palace - Flooded Room - Right", False, [], ['Flippers']], @@ -61,7 +61,7 @@ def testSwampPalace(self): ["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 - Flooded Room - Right", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ["Swamp Palace - Waterfall Room", False, []], ["Swamp Palace - Waterfall Room", False, [], ['Flippers']], @@ -69,7 +69,7 @@ def testSwampPalace(self): ["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 - Waterfall Room", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ["Swamp Palace - Boss", False, []], ["Swamp Palace - Boss", False, [], ['Flippers']], @@ -77,5 +77,5 @@ def testSwampPalace(self): ["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']], + ["Swamp Palace - Boss", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestThievesTown.py b/worlds/alttp/test/dungeons/TestThievesTown.py index 752b5305772a..342823c91007 100644 --- a/worlds/alttp/test/dungeons/TestThievesTown.py +++ b/worlds/alttp/test/dungeons/TestThievesTown.py @@ -21,18 +21,19 @@ def testThievesTown(self): ["Thieves' Town - Spike Switch Pot Key", False, []], ["Thieves' Town - Spike Switch Pot Key", False, [], ['Big Key (Thieves Town)']], - ["Thieves' Town - Spike Switch Pot Key", True, ['Big Key (Thieves Town)']], + ["Thieves' Town - Spike Switch Pot Key", False, [], ['Small Key (Thieves Town)']], + ["Thieves' Town - Spike Switch Pot Key", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']], ["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 - Attic", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)']], ["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 - Big Chest", True, ['Hammer', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)']], ["Thieves' Town - Blind's Cell", False, []], ["Thieves' Town - Blind's Cell", False, [], ['Big Key (Thieves Town)']], @@ -42,8 +43,8 @@ def testThievesTown(self): ["Thieves' Town - Boss", False, [], ['Big Key (Thieves Town)']], ["Thieves' Town - Boss", False, [], ['Hammer', 'Progressive Sword', 'Cane of Somaria', 'Cane of Byrna']], ["Thieves' Town - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py index f3698c90ff06..db3084b02a5b 100644 --- a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py +++ b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py @@ -11,16 +11,16 @@ def testTurtleRock(self): ["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, ['Flute', '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', 'Flute', '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, ['Flute', '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 - 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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Flute', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", False, []], ["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', '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', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -33,10 +33,10 @@ def testTurtleRock(self): ["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', 'Flute', '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, ['Moon Pearl', 'Fire Rod', 'Flute', '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', 'Flute', '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 - 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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Right", False, []], ["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']], @@ -45,17 +45,17 @@ def testTurtleRock(self): ["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', 'Flute', '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, ['Moon Pearl', 'Fire Rod', 'Flute', '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', 'Flute', '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", 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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', '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)', '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)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', '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)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', '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)', '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']], @@ -66,12 +66,12 @@ def testTurtleRock(self): ["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, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], # Mirror in from ledge, use left side entrance, have enough keys to get to the chest - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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']], @@ -80,7 +80,7 @@ def testTurtleRock(self): ["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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)', '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)', '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', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -98,10 +98,10 @@ def testTurtleRock(self): ["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', 'Flute', '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)', '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', 'Bottle', 'Small Key (Turtle Rock)', '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)', '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', 'Flute', '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)']] + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', '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)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] ]) @@ -118,11 +118,11 @@ def testEyeBridge(self): [location, False, [], ['Magic Mirror', 'Lamp']], [location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], - [location, 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)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + [location, 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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], - [location, 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)', 'Small Key (Turtle Rock)', 'Cape']], + [location, 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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - [location, 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)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + [location, 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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], # Mirroring into Eye Bridge does not require Cane of Somaria [location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py index 3c75a2c3684b..a416e1b35d33 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py @@ -11,17 +11,17 @@ def testTurtleRock(self): ["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, ['Flute', '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', 'Flute', '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, ['Flute', '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 - 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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Flute', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", False, []], ["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']], # Item rando only needs 1 key. ER needs to consider the case when the back is accessible, but not the middle (key wasted on Trinexx door) ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', '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', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -34,10 +34,10 @@ def testTurtleRock(self): ["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', 'Flute', '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, ['Moon Pearl', 'Fire Rod', 'Flute', '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', 'Flute', '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 - 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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Right", False, []], ["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']], @@ -46,17 +46,17 @@ def testTurtleRock(self): ["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', 'Flute', '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, ['Moon Pearl', 'Fire Rod', 'Flute', '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', 'Flute', '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", 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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', '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)', '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)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', '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)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', '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)', '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']], @@ -67,12 +67,12 @@ def testTurtleRock(self): ["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, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], # Mirror in from ledge, use left side entrance, have enough keys to get to the chest - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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']], @@ -81,7 +81,7 @@ def testTurtleRock(self): ["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', '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)', '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)', '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', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -99,10 +99,10 @@ def testTurtleRock(self): ["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', 'Flute', '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)', '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', 'Bottle', 'Small Key (Turtle Rock)', '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)', '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', 'Flute', '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)']] + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', '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)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] ]) @@ -117,11 +117,11 @@ def testEyeBridge(self): [location, False, [], ['Magic Mirror', 'Lamp']], [location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], - [location, 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)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + [location, 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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], - [location, 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)', 'Small Key (Turtle Rock)', 'Cape']], + [location, 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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - [location, 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)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + [location, 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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], # Mirroring into Eye Bridge does not require Cane of Somaria [location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], diff --git a/worlds/alttp/test/inverted_owg/TestDungeons.py b/worlds/alttp/test/inverted_owg/TestDungeons.py index 0d8445895eca..53b12bdf89d1 100644 --- a/worlds/alttp/test/inverted_owg/TestDungeons.py +++ b/worlds/alttp/test/inverted_owg/TestDungeons.py @@ -13,16 +13,15 @@ def testFirstDungeonChests(self): ["Sanctuary", False, []], ["Sanctuary", False, ['Beat Agahnim 1']], ["Sanctuary", True, ['Magic Mirror', 'Beat Agahnim 1']], - ["Sanctuary", True, ['Progressive Sword', 'Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)']], + ["Sanctuary", True, ['Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], ["Sanctuary", True, ['Moon Pearl', 'Pegasus Boots']], ["Sanctuary", True, ['Magic Mirror', 'Pegasus Boots']], ["Sewers - Secret Room - Left", False, []], ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Progressive Glove', 'Pegasus Boots']], - ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+10)', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], ["Eastern Palace - Compass Chest", False, []], ["Eastern Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots']], @@ -37,8 +36,8 @@ def testFirstDungeonChests(self): ["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", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Lamp']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']], + ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Lamp']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']], ["Tower of Hera - Basement Cage", False, []], ["Tower of Hera - Basement Cage", False, [], ['Moon Pearl']], @@ -76,8 +75,8 @@ def testFirstDungeonChests(self): ["Ice Palace - Compass Chest", False, []], ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos', 'Progressive Sword']], # Qirn Jump - ["Ice Palace - Compass Chest", True, ['Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Fire Rod', 'Small Key (Ice Palace)']], + ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword', 'Small Key (Ice Palace)']], ["Misery Mire - Bridge Chest", False, []], ["Misery Mire - Bridge Chest", False, [], ['Ether']], @@ -86,7 +85,7 @@ def testFirstDungeonChests(self): ["Turtle Rock - Compass Chest", False, []], ["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']], - ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Chain Chomps", False, []], diff --git a/worlds/alttp/test/minor_glitches/TestDeathMountain.py b/worlds/alttp/test/minor_glitches/TestDeathMountain.py index 4446ee7e8f88..7d7589d2f7fe 100644 --- a/worlds/alttp/test/minor_glitches/TestDeathMountain.py +++ b/worlds/alttp/test/minor_glitches/TestDeathMountain.py @@ -49,7 +49,7 @@ def testEastDeathMountain(self): ["Mimic Cave", False, [], ['Cane of Somaria']], ["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Spiral Cave", False, []], ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], diff --git a/worlds/alttp/test/owg/TestDungeons.py b/worlds/alttp/test/owg/TestDungeons.py index f4688b7a35f9..e43e18d16cf2 100644 --- a/worlds/alttp/test/owg/TestDungeons.py +++ b/worlds/alttp/test/owg/TestDungeons.py @@ -13,7 +13,7 @@ def testFirstDungeonChests(self): ["Sewers - Secret Room - Left", False, []], ["Sewers - Secret Room - Left", True, ['Pegasus Boots', 'Progressive Glove']], - ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Bomb Upgrade (+5)', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], ["Eastern Palace - Compass Chest", True, []], @@ -26,8 +26,8 @@ def testFirstDungeonChests(self): ["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", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Lamp', 'Big Key (Desert Palace)']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Pegasus Boots', 'Fire Rod', 'Big Key (Desert Palace)']], + ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Lamp', 'Big Key (Desert Palace)']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Fire Rod', 'Big Key (Desert Palace)']], ["Tower of Hera - Basement Cage", False, []], ["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Flute", "Progressive Glove"]], @@ -90,10 +90,10 @@ def testFirstDungeonChests(self): ["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, ['Moon Pearl', 'Pegasus Boots', 'Flippers', 'Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Flippers', 'Bombos', 'Progressive Sword']], - ["Ice Palace - Compass Chest", True, ['Progressive Glove', 'Progressive Glove', 'Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Progressive Glove', 'Progressive Glove', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Moon Pearl', 'Pegasus Boots', 'Flippers', 'Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Moon Pearl', 'Pegasus Boots', 'Flippers', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Progressive Glove', 'Progressive Glove', 'Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Progressive Glove', 'Progressive Glove', 'Bombos', 'Progressive Sword']], ["Misery Mire - Bridge Chest", False, []], ["Misery Mire - Bridge Chest", False, [], ['Moon Pearl']], @@ -105,9 +105,9 @@ def testFirstDungeonChests(self): ["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']], #todo: does clip require sword? #["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', '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', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Sword']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Sword']], ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Progressive Sword', 'Quake']], - ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", False, []], #todo: does clip require sword? diff --git a/worlds/alttp/test/vanilla/TestDeathMountain.py b/worlds/alttp/test/vanilla/TestDeathMountain.py index d77f1a8dd274..a559d8869c2f 100644 --- a/worlds/alttp/test/vanilla/TestDeathMountain.py +++ b/worlds/alttp/test/vanilla/TestDeathMountain.py @@ -49,7 +49,7 @@ def testEastDeathMountain(self): ["Mimic Cave", False, [], ['Cane of Somaria']], ["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Spiral Cave", False, []], ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], From 738a9ebb7d5a04f916f80114bb81a27bff1a9b2c Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Mon, 26 Feb 2024 02:30:20 -0500 Subject: [PATCH 12/66] TUNIC: Misc Logic Changes, Additions (#2856) * Add nmg boss scav kill * Add boss quick kills * Fix name of orb * Remove getting into zig with ice grapple * Remove connection from quarry to zig * Add a few missing dependent regions * Separate the atoll statue and portal pad so that it doesn't assume you can get from one to the other without prayer --- worlds/tunic/er_data.py | 27 ++++++++++++++++++++------- worlds/tunic/er_rules.py | 12 ++++++++++-- worlds/tunic/regions.py | 2 +- worlds/tunic/rules.py | 5 ++--- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index d76af1133906..7678d77fe034 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -211,7 +211,7 @@ def scene_destination(self) -> str: # full, nonchanging name to interpret by th destination="Shop_"), Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal", destination="Transit_teleporter_atoll"), - Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Portal", + Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue", destination="Library Exterior_"), Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll", destination="Frog Stairs_eye"), @@ -600,6 +600,7 @@ class Hint(IntEnum): "Ruined Atoll Lower Entry Area": RegionInfo("Atoll Redux"), "Ruined Atoll Frog Mouth": RegionInfo("Atoll Redux"), "Ruined Atoll Portal": RegionInfo("Atoll Redux"), + "Ruined Atoll Statue": RegionInfo("Atoll Redux"), "Frog's Domain Entry": RegionInfo("Frog Stairs"), "Frog's Domain": RegionInfo("frog cave main", hint=Hint.region), "Frog's Domain Back": RegionInfo("frog cave main", hint=Hint.scene), @@ -749,6 +750,8 @@ class Hint(IntEnum): ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], ("Forest Grave Path Main", "Forest Grave Path Upper"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], ("Forest Grave Path by Grave", "Forest Hero's Grave"): @@ -762,8 +765,10 @@ class Hint(IntEnum): ("West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"): ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"], ("West Garden Portal", "West Garden Portal Item"): ["West Garden Portal", "West Garden Portal Item"], - ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"): - ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"], + ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"): + ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"], ("Frog's Domain",): ["Frog's Domain", "Frog's Domain Back"], ("Library Exterior Ladder", "Library Exterior Tree"): @@ -842,6 +847,8 @@ class Hint(IntEnum): ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"): @@ -854,8 +861,10 @@ class Hint(IntEnum): "West Garden Portal", "West Garden Portal Item"): ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", "West Garden Portal", "West Garden Portal Item"], - ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"): - ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"], + ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"): + ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"], ("Frog's Domain",): ["Frog's Domain", "Frog's Domain Back"], ("Library Exterior Ladder", "Library Exterior Tree"): @@ -934,6 +943,8 @@ class Hint(IntEnum): ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], # can use laurels, ice grapple, or ladder storage to traverse ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], @@ -948,8 +959,10 @@ class Hint(IntEnum): "West Garden Portal", "West Garden Portal Item"): ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", "West Garden Portal", "West Garden Portal Item"], - ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"): - ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"], + ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"): + ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"], ("Frog's Domain",): ["Frog's Domain", "Frog's Domain Back"], ("Library Exterior Ladder", "Library Exterior Tree"): diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index 2f9604c1035a..a7d0543c3f17 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -295,6 +295,12 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["Ruined Atoll Portal"].connect( connecting_region=regions["Ruined Atoll"]) + regions["Ruined Atoll"].connect( + connecting_region=regions["Ruined Atoll Statue"], + rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) + regions["Ruined Atoll Statue"].connect( + connecting_region=regions["Ruined Atoll"]) + regions["Frog's Domain"].connect( connecting_region=regions["Frog's Domain Back"], rule=lambda state: state.has(grapple, player)) @@ -944,10 +950,12 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) # Bosses set_rule(multiworld.get_location("Fortress Arena - Siege Engine/Vault Key Pickup", player), lambda state: has_sword(state, player)) + # nmg - kill Librarian with a lure, or gun I guess set_rule(multiworld.get_location("Librarian - Hexagon Green", player), - lambda state: has_sword(state, player)) + lambda state: has_sword(state, player) or options.logic_rules) + # nmg - kill boss scav with orb + firecracker, or similar set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), - lambda state: has_sword(state, player)) + lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) # Swamp set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player), diff --git a/worlds/tunic/regions.py b/worlds/tunic/regions.py index 5d5248f210d6..70204c639733 100644 --- a/worlds/tunic/regions.py +++ b/worlds/tunic/regions.py @@ -16,7 +16,7 @@ "Eastern Vault Fortress": {"Beneath the Vault"}, "Beneath the Vault": {"Eastern Vault Fortress"}, "Quarry Back": {"Quarry"}, - "Quarry": {"Lower Quarry", "Rooted Ziggurat"}, + "Quarry": {"Lower Quarry"}, "Lower Quarry": {"Rooted Ziggurat"}, "Rooted Ziggurat": set(), "Swamp": {"Cathedral"}, diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index 6e5639b4ebaf..b3dd0b683220 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -131,8 +131,6 @@ def set_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> No lambda state: has_mask(state, player, options) multiworld.get_entrance("Lower Quarry -> Rooted Ziggurat", player).access_rule = \ lambda state: state.has(grapple, player) and has_ability(state, player, prayer, options, ability_unlocks) - multiworld.get_entrance("Quarry -> Rooted Ziggurat", player).access_rule = \ - lambda state: has_ice_grapple_logic(False, state, player, options, ability_unlocks) multiworld.get_entrance("Swamp -> Cathedral", player).access_rule = \ lambda state: state.has(laurels, player) and has_ability(state, player, prayer, options, ability_unlocks) \ or has_ice_grapple_logic(False, state, player, options, ability_unlocks) @@ -312,8 +310,9 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> lambda state: state.has(laurels, player)) set_rule(multiworld.get_location("Quarry - [West] Upper Area Bombable Wall", player), lambda state: has_mask(state, player, options)) + # nmg - kill boss scav with orb + firecracker, or similar set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), - lambda state: has_sword(state, player)) + lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) # Swamp set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player), From c126418f35e95ccc1d22015baf79fe4f5c4a8aca Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 27 Feb 2024 08:44:34 +0100 Subject: [PATCH 13/66] Utils: YAML goes brrrt (#2868) Also tests to validate we dont break the API. --- Utils.py | 8 ++--- test/utils/test_yaml.py | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 test/utils/test_yaml.py diff --git a/Utils.py b/Utils.py index 8b91226bed9f..da2d837ad3a3 100644 --- a/Utils.py +++ b/Utils.py @@ -19,14 +19,12 @@ from argparse import Namespace from settings import Settings, get_settings from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union -from yaml import load, load_all, dump, SafeLoader +from yaml import load, load_all, dump try: - from yaml import CLoader as UnsafeLoader - from yaml import CDumper as Dumper + from yaml import CLoader as UnsafeLoader, CSafeLoader as SafeLoader, CDumper as Dumper except ImportError: - from yaml import Loader as UnsafeLoader - from yaml import Dumper + from yaml import Loader as UnsafeLoader, SafeLoader, Dumper if typing.TYPE_CHECKING: import tkinter diff --git a/test/utils/test_yaml.py b/test/utils/test_yaml.py new file mode 100644 index 000000000000..4e23857eb07f --- /dev/null +++ b/test/utils/test_yaml.py @@ -0,0 +1,68 @@ +# Tests that yaml wrappers in Utils.py do what they should + +import unittest +from typing import cast, Any, ClassVar, Dict + +from Utils import dump, Dumper # type: ignore[attr-defined] +from Utils import parse_yaml, parse_yamls, unsafe_parse_yaml + + +class AClass: + def __eq__(self, other: Any) -> bool: + return isinstance(other, self.__class__) + + +class TestYaml(unittest.TestCase): + safe_data: ClassVar[Dict[str, Any]] = { + "a": [1, 2, 3], + "b": None, + "c": True, + } + unsafe_data: ClassVar[Dict[str, Any]] = { + "a": AClass() + } + + @property + def safe_str(self) -> str: + return cast(str, dump(self.safe_data, Dumper=Dumper)) + + @property + def unsafe_str(self) -> str: + return cast(str, dump(self.unsafe_data, Dumper=Dumper)) + + def assertIsNonEmptyString(self, string: str) -> None: + self.assertTrue(string) + self.assertIsInstance(string, str) + + def test_dump(self) -> None: + self.assertIsNonEmptyString(self.safe_str) + self.assertIsNonEmptyString(self.unsafe_str) + + def test_safe_parse(self) -> None: + self.assertEqual(self.safe_data, parse_yaml(self.safe_str)) + with self.assertRaises(Exception): + parse_yaml(self.unsafe_str) + with self.assertRaises(Exception): + parse_yaml("1\n---\n2\n") + + def test_unsafe_parse(self) -> None: + self.assertEqual(self.safe_data, unsafe_parse_yaml(self.safe_str)) + self.assertEqual(self.unsafe_data, unsafe_parse_yaml(self.unsafe_str)) + with self.assertRaises(Exception): + unsafe_parse_yaml("1\n---\n2\n") + + def test_multi_parse(self) -> None: + self.assertEqual(self.safe_data, next(parse_yamls(self.safe_str))) + with self.assertRaises(Exception): + next(parse_yamls(self.unsafe_str)) + self.assertEqual(2, len(list(parse_yamls("1\n---\n2\n")))) + + def test_unique_key(self) -> None: + s = """ + a: 1 + a: 2 + """ + with self.assertRaises(Exception): + parse_yaml(s) + with self.assertRaises(Exception): + next(parse_yamls(s)) From 59a6e4a1b59881ddfff7590ec75e5c927b248b60 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed, 28 Feb 2024 04:44:22 +0100 Subject: [PATCH 14/66] The Witness: New hint type ("area hints") (#2494) This new type of "area hint" will instead give you general information about one of the named geographical areas in your world. Example: ``` There are 4 progression items in the "Quarry" region. Of them, 2 are for other players. Also, one of them is a laser for this world. ``` This also renames some of the locations in the game to better fit into an "area", such as the "River Obelisk" being renamed to the "Mountainside Obelisk". --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Scipio Wright --- worlds/witness/WitnessItems.txt | 12 +- worlds/witness/WitnessLogic.txt | 346 +++++++------ worlds/witness/WitnessLogicExpert.txt | 346 +++++++------ worlds/witness/WitnessLogicVanilla.txt | 346 +++++++------ worlds/witness/__init__.py | 35 +- worlds/witness/hints.py | 457 ++++++++++++++---- worlds/witness/locations.py | 68 +-- worlds/witness/options.py | 15 +- worlds/witness/presets.py | 3 + worlds/witness/regions.py | 5 +- .../Door_Shuffle/Complex_Door_Panels.txt | 4 +- .../settings/Door_Shuffle/Complex_Doors.txt | 16 +- .../settings/Door_Shuffle/Simple_Doors.txt | 10 +- .../settings/Door_Shuffle/Simple_Panels.txt | 2 +- .../witness/settings/EP_Shuffle/EP_Sides.txt | 12 +- worlds/witness/settings/Exclusions/Vaults.txt | 6 +- .../settings/Postgame/Mountain_Lower.txt | 6 +- worlds/witness/static_logic.py | 25 +- worlds/witness/utils.py | 15 + 19 files changed, 1135 insertions(+), 594 deletions(-) diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/WitnessItems.txt index e17464a0923a..6f63eccc9521 100644 --- a/worlds/witness/WitnessItems.txt +++ b/worlds/witness/WitnessItems.txt @@ -39,8 +39,8 @@ Jokes: Doors: 1100 - Glass Factory Entry (Panel) - 0x01A54 -1101 - Tutorial Outpost Entry (Panel) - 0x0A171 -1102 - Tutorial Outpost Exit (Panel) - 0x04CA4 +1101 - Outside Tutorial Outpost Entry (Panel) - 0x0A171 +1102 - Outside Tutorial Outpost Exit (Panel) - 0x04CA4 1105 - Symmetry Island Lower (Panel) - 0x000B0 1107 - Symmetry Island Upper (Panel) - 0x1C349 1108 - Desert Surface 3 Control (Panel) - 0x09FA0 @@ -168,9 +168,9 @@ Doors: 1750 - Theater Entry (Door) - 0x17F88 1753 - Theater Exit Left (Door) - 0x0A16D 1756 - Theater Exit Right (Door) - 0x3CCDF -1759 - Jungle Bamboo Laser Shortcut (Door) - 0x3873B +1759 - Jungle Laser Shortcut (Door) - 0x3873B 1760 - Jungle Popup Wall (Door) - 0x1475B -1762 - River Monastery Garden Shortcut (Door) - 0x0CF2A +1762 - Jungle Monastery Garden Shortcut (Door) - 0x0CF2A 1765 - Bunker Entry (Door) - 0x0C2A4 1768 - Bunker Tinted Glass Door - 0x17C79 1771 - Bunker UV Room Entry (Door) - 0x0C2A3 @@ -195,7 +195,7 @@ Doors: 1828 - Mountain Floor 2 Exit (Door) - 0x09EDD 1831 - Mountain Floor 2 Staircase Far (Door) - 0x09E07 1834 - Mountain Bottom Floor Giant Puzzle Exit (Door) - 0x09F89 -1840 - Mountain Bottom Floor Final Room Entry (Door) - 0x0C141 +1840 - Mountain Bottom Floor Pillars Room Entry (Door) - 0x0C141 1843 - Mountain Bottom Floor Rock (Door) - 0x17F33 1846 - Caves Entry (Door) - 0x2D77D 1849 - Caves Pillar Door - 0x019A5 @@ -247,7 +247,7 @@ Doors: 2035 - Mountain & Caves Control Panels - 0x09ED8,0x09E86,0x09E39,0x09EEB,0x335AB,0x335AC,0x3369D 2100 - Symmetry Island Panels - 0x1C349,0x000B0 -2101 - Tutorial Outpost Panels - 0x0A171,0x04CA4 +2101 - Outside Tutorial Outpost Panels - 0x0A171,0x04CA4 2105 - Desert Panels - 0x09FAA,0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B,0x0C339,0x0A249,0x0A015,0x09FA0,0x09F86 2110 - Quarry Outside Panels - 0x17C09,0x09E57,0x17CC4 2115 - Quarry Stoneworks Panels - 0x01E5A,0x01E59,0x03678,0x03676,0x03679,0x03675 diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/WitnessLogic.txt index ec0922bec697..e3bacfb4b0e4 100644 --- a/worlds/witness/WitnessLogic.txt +++ b/worlds/witness/WitnessLogic.txt @@ -1,12 +1,14 @@ +==Tutorial (Inside)== + Menu (Menu) - Entry - True: Entry (Entry): -First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064: +Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064: 158000 - 0x00064 (Straight) - True - True 159510 - 0x01848 (EP) - 0x00064 - True -First Hallway Room (First Hallway) - Tutorial - 0x00182: +Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182: 158001 - 0x00182 (Bend) - True - True Tutorial (Tutorial) - Outside Tutorial - 0x03629: @@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - 0x03629: 159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True 159517 - 0x3352F (Gate EP) - 0x03505 - True +==Tutorial (Outside)== + Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: 158650 - 0x033D4 (Vault Panel) - True - Dots & Black/White Squares Door - 0x033D0 (Vault Door) - 0x033D4 @@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Triangles +Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: +158071 - 0x00143 (Apple Tree 1) - True - True +158072 - 0x0003B (Apple Tree 2) - 0x00143 - True +158073 - 0x00055 (Apple Tree 3) - 0x0003B - True +Door - 0x03307 (First Gate) - 0x00055 + +Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: +158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True +158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True +Door - 0x03313 (Second Gate) - 0x032FF + +Orchard End (Orchard): + Main Island (Main Island) - Outside Tutorial - True: 159801 - 0xFFD00 (Reached Independently) - True - True -159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True + +==Glass Factory== Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29: 158027 - 0x01A54 (Entry Panel) - True - Symmetry @@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8: 158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat +==Symmetry Island== + Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E: 158040 - 0x000B0 (Lower Panel) - 0x0343A - Dots Door - 0x17F3E (Lower) - 0x000B0 @@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island): Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True -Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: -158071 - 0x00143 (Apple Tree 1) - True - True -158072 - 0x0003B (Apple Tree 2) - 0x00143 - True -158073 - 0x00055 (Apple Tree 3) - 0x0003B - True -Door - 0x03307 (First Gate) - 0x00055 - -Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: -158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True -158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True -Door - 0x03313 (Second Gate) - 0x032FF +==Desert== -Orchard End (Orchard): +Desert Obelisk (Desert) - Entry - True: +159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True +159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True +159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True +159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True +159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True +159709 - 0x00359 (Obelisk) - True - True -Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444: +Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: 158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Triangles @@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608 Desert Vault (Desert): 158653 - 0x0339E (Vault Box) - True - True -Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3: +Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3: 158087 - 0x09FAA (Light Control) - True - True 158088 - 0x00422 (Light Room 1) - 0x09FAA - True 158089 - 0x006E3 (Light Room 2) - 0x09FAA - True 158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D -Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B: +Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B: 158091 - 0x00C72 (Pond Room 1) - True - True 158092 - 0x0129D (Pond Room 2) - 0x00C72 - True 158093 - 0x008BB (Pond Room 3) - 0x0129D - True @@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249 159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True 159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True -Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: +Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316: 158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True 158098 - 0x1831E (Reduce Water Level Far Right) - True - True 158099 - 0x1C260 (Reduce Water Level Near Left) - True - True @@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: Door - 0x0C316 (Elevator Room Entry) - 0x18076 159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True -Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317: +Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317: 158111 - 0x17C31 (Elevator Room Transparent) - True - True 158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True 158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True @@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131 159035 - 0x037BB (Elevator EP) - 0x01317 - True Door - 0x01317 (Elevator) - 0x03608 -Desert Lowest Level Inbetween Shortcuts (Desert): +Desert Behind Elevator (Desert): -Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: +==Quarry== + +Quarry Obelisk (Quarry) - Entry - True: +159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True +159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True +159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True +159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True +159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True +159749 - 0x22073 (Obelisk) - True - True + +Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: 158118 - 0x09E57 (Entry 1 Panel) - True - Black/White Squares 158603 - 0x17CF0 (Discard) - True - Triangles 158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Shapers @@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4: 158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser 159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True -Quarry Between Entrys (Quarry) - Quarry - 0x17C07: +Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07: 158119 - 0x17C09 (Entry 2 Panel) - True - Shapers Door - 0x17C07 (Entry 2) - 0x17C09 @@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663 158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True +==Shadows== + Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665: 158170 - 0x334DB (Door Timer Outside) - True - True Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC @@ -361,19 +390,18 @@ Shadows Laser Room (Shadows): 158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True Laser - 0x181B3 (Laser) - 0x19650 -Treehouse Beach (Treehouse Beach) - Main Island - True: -159200 - 0x0053D (Rock Shadow EP) - True - True -159201 - 0x0053E (Sand Shadow EP) - True - True -159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True +==Keep== -Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: +Outside Keep (Keep) - Main Island - True: +159430 - 0x03E77 (Red Flowers EP) - True - True +159431 - 0x03E7C (Purple Flowers EP) - True - True + +Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: 158193 - 0x00139 (Hedge Maze 1) - True - True 158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True 158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Dots Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139 Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA -159430 - 0x03E77 (Red Flowers EP) - True - True -159431 - 0x03E7C (Purple Flowers EP) - True - True Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8: Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139 @@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F 158205 - 0x09E49 (Shadows Shortcut Panel) - True - True Door - 0x09E3D (Shadows Shortcut) - 0x09E49 +Keep Tower (Keep) - Keep - 0x04F8F: +158206 - 0x0361B (Tower Shortcut Panel) - True - True +Door - 0x04F8F (Tower Shortcut) - 0x0361B +158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots +Laser - 0x014BB (Laser) - 0x0360E | 0x03317 +159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True +159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True +159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True +159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True +159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True +159250 - 0x28AE9 (Path EP) - True - True +159251 - 0x3348F (Hedges EP) - True - True + +==Shipwreck== + Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4: 158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots Door - 0x17BB4 (Vault Door) - 0x00AFB @@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB Shipwreck Vault (Shipwreck): 158655 - 0x03535 (Vault Box) - True - True -Keep Tower (Keep) - Keep - 0x04F8F: -158206 - 0x0361B (Tower Shortcut Panel) - True - True -Door - 0x04F8F (Tower Shortcut) - 0x0361B -158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots -Laser - 0x014BB (Laser) - 0x0360E | 0x03317 -159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True -159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True -159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True -159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True -159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True -159250 - 0x28AE9 (Path EP) - True - True -159251 - 0x3348F (Hedges EP) - True - True +==Monastery== + +Monastery Obelisk (Monastery) - Entry - True: +159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True +159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True +159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True +159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True +159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True +159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True +159719 - 0x00263 (Obelisk) - True - True Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750: 158207 - 0x03713 (Laser Shortcut Panel) - True - True @@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4 159137 - 0x03DAC (Facade Left Stairs EP) - True - True 159138 - 0x03DAD (Facade Right Stairs EP) - True - True 159140 - 0x03E01 (Grass Stairs EP) - True - True +159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True +159121 - 0x03BE3 (Garden Right EP) - True - True +159122 - 0x0A409 (Wall EP) - True - True Inside Monastery (Monastery): 158213 - 0x09D9B (Shutters Control) - True - Dots @@ -470,7 +514,18 @@ Inside Monastery (Monastery): Monastery Garden (Monastery): -Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9: +==Town== + +Town Obelisk (Town) - Entry - True: +159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True +159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True +159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True +159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True +159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True +159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True +159759 - 0x0A16C (Obelisk) - True - True + +Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True: 158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat 158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 @@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28998 Door - 0x03BB0 (Church Entry) - 0x28A0D 158228 - 0x28A79 (Maze Panel) - True - True Door - 0x28AA2 (Maze Stairs) - 0x28A79 -158241 - 0x17F5F (Windmill Entry Panel) - True - Dots -Door - 0x1845B (Windmill Entry) - 0x17F5F -159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True -159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True -159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True 159540 - 0x03335 (Tower Underside Third EP) - True - True 159541 - 0x03412 (Tower Underside Fourth EP) - True - True 159542 - 0x038A6 (Tower Underside First EP) - True - True @@ -528,20 +578,26 @@ Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True 159553 - 0x03BD1 (Black Line Church EP) - True - True -RGB House (Town) - RGB Room - 0x2897B: +Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B: 158242 - 0x034E4 (Sound Room Left) - True - True 158243 - 0x034E3 (Sound Room Right) - True - Sound Dots -Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3 +Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3 -RGB Room (Town): +Town RGB House Upstairs (Town RGB House Upstairs): 158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Colored Squares -158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Colored Squares & Black/White Squares -158246 - 0x03C08 (RGB Room Right) - 0x334D8 - Stars +158245 - 0x03C0C (Left) - 0x334D8 - Colored Squares & Black/White Squares +158246 - 0x03C08 (Right) - 0x334D8 - Stars + +Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799: +Door - 0x27799 (First Door) - 0x28A69 -Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C: +Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798: Door - 0x27798 (Second Door) - 0x28ACC + +Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C: Door - 0x2779C (Third Door) - 0x28AD9 -Door - 0x27799 (First Door) - 0x28A69 + +Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A: Door - 0x2779A (Fourth Door) - 0x28B39 Town Tower Top (Town): @@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5 159422 - 0x33692 (Brown Bridge EP) - True - True 159551 - 0x03BCE (Black Line Tower EP) - True - True +==Windmill & Theater== + +Outside Windmill (Windmill) - Windmill Interior - 0x1845B: +159010 - 0x037B6 (First Blade EP) - 0x17D02 - True +159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True +159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True +158241 - 0x17F5F (Entry Panel) - True - Dots +Door - 0x1845B (Entry) - 0x17F5F + Windmill Interior (Windmill) - Theater - 0x17F88: 158247 - 0x17D02 (Turn Control) - True - Dots 158248 - 0x17F89 (Theater Entry Panel) - True - Black/White Squares @@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2 159556 - 0x33A2A (Door EP) - 0x03553 - True 159558 - 0x33B06 (Church EP) - 0x0354E - True +==Jungle== + Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF: 158251 - 0x17CDF (Shore Boat Spawn) - True - Boat 158609 - 0x17F9B (Discard) - True - Triangles @@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA 159350 - 0x035CB (Bamboo CCW EP) - True - True 159351 - 0x035CF (Bamboo CW EP) - True - True -Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287: +Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287: 158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA 158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots Door - 0x15287 (Vault Door) - 0x15ADD 159110 - 0x03AC5 (Green Leaf Moss EP) - True - True -159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True -159121 - 0x03BE3 (Monastery Garden Right EP) - True - True -159122 - 0x0A409 (Monastery Wall EP) - True - True -River Vault (River): +Jungle Vault (Jungle): 158664 - 0x03702 (Vault Box) - True - True +==Bunker== + Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4: 158268 - 0x17C2E (Entry Panel) - True - Black/White Squares Door - 0x0C2A4 (Entry) - 0x17C2E @@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67 Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay: 159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True -Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: +Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: 158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares +Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay: + Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay: 159310 - 0x000D3 (Green Room Flowers EP) - True - True @@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay: 158710 - 0x09DE0 (Laser Panel) - True - True Laser - 0x0C2B2 (Laser) - 0x09DE0 +==Swamp== + Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True: 158287 - 0x0056E (Entry Panel) - True - Shapers Door - 0x00C1C (Entry) - 0x0056E @@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615 158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers Door - 0x2D880 (Laser Shortcut) - 0x17C02 -Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95: +==Treehouse== + +Treehouse Obelisk (Treehouse) - Entry - True: +159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True +159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True +159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True +159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True +159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True +159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True +159729 - 0x00097 (Obelisk) - True - True + +Treehouse Beach (Treehouse Beach) - Main Island - True: +159200 - 0x0053D (Rock Shadow EP) - True - True +159201 - 0x0053E (Sand Shadow EP) - True - True +159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True + +Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95: 158343 - 0x17C95 (Boat Spawn) - True - Boat 158344 - 0x0288C (First Door Panel) - True - Stars Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True -Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: +Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: 158345 - 0x02886 (Second Door Panel) - True - Stars Door - 0x0C310 (Second Door) - 0x02886 @@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1 158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots 158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots -Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: +Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: 158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars 158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars 158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars @@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: 158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars 158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars -Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D: +Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF @@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse): 158403 - 0x17CBC (Laser House Door Timer Inside) - True - True Laser - 0x028A4 (Laser) - 0x03613 +==Mountain (Outside)== + +Mountainside Obelisk (Mountainside) - Entry - True: +159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True +159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True +159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True +159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True +159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True +159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True +159739 - 0x00367 (Obelisk) - True - True + Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085: +159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True 158612 - 0x17C42 (Discard) - True - Triangles 158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Dots & Black/White Squares & Dots Door - 0x00085 (Vault Door) - 0x002A6 @@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6 Mountainside Vault (Mountainside): 158666 - 0x03542 (Vault Box) - True - True -Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: +Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True 158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol @@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: 159324 - 0x336C8 (Arch White Right EP) - True - True 159326 - 0x3369A (Arch White Left EP) - True - True -Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39: +==Mountain (Inside)== + +Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: 158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Colored Squares & Eraser -Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay: +Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay: 158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots 158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots 158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Dots @@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True 158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers -Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: +Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Colored Squares & Stars + Same Colored Symbol 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol @@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares & Symmetry & Colored Dots Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8: - Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 @@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Triangles -Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB: +Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB: 158439 - 0x09EEB (Elevator Control Panel) - True - Dots -Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: 158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser 158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser 158442 - 0x09F01 (Giant Puzzle Top Right) - True - Rotated Shapers @@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO 159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True Door - 0x09F89 (Exit) - 0x09FDA -Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141: +Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141: 158614 - 0x17FA2 (Discard) - 0xFFF00 - Triangles -158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars -158446 - 0x01987 (Final Room Entry Right) - True - Colored Squares & Dots -Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987 +158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars +158446 - 0x01987 (Pillars Room Entry Right) - True - Colored Squares & Dots +Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987 Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 +Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: +158522 - 0x0383A (Right Pillar 1) - True - Stars +158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots +158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry +158526 - 0x0383D (Left Pillar 1) - True - Dots +158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares +158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers +158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry + +Elevator (Mountain Bottom Floor): +158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158531 - 0x3D9A7 (Elevator Door Close Right) - True - True +158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True +158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True +158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True +158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True +158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True + Mountain Pink Bridge EP (Mountain Floor 2): 159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True @@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D: Door - 0x2D77D (Caves Entry) - 0x00FF8 158448 - 0x334E1 (Rock Control) - True - True -Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5: +==Caves== + +Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5: 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots @@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True -Path to Challenge (Caves) - Challenge - 0x0A19A: +Caves Path to Challenge (Caves) - Challenge - 0x0A19A: 158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E +==Challenge== + Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75: 158499 - 0x0A332 (Start Timer) - 11 Lasers - True 158500 - 0x0088E (Small Basic) - 0x0A332 - True @@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4 Challenge Vault (Challenge): 158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True -Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87: +==Tunnels== + +Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87: 158668 - 0x2FAF6 (Vault Box) - True - True 158519 - 0x27732 (Theater Shortcut Panel) - True - True Door - 0x27739 (Theater Shortcut) - 0x27732 @@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D Door - 0x09E87 (Town Shortcut) - 0x09E85 159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True -Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961: -158522 - 0x0383A (Right Pillar 1) - True - Stars -158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots -158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry -158526 - 0x0383D (Left Pillar 1) - True - Dots -158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares -158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers -158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry - -Elevator (Mountain Final Room): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True -158531 - 0x3D9A7 (Elevator Door Close Right) - True - True -158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True -158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True -158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True -158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True -158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True +==Boat== The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay: 159042 - 0x22106 (Desert EP) - True - True @@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre 159521 - 0x33879 (Tutorial Reflection EP) - True - True 159522 - 0x03C19 (Tutorial Moss EP) - True - True 159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True - -Obelisks (EPs) - Entry - True: -159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True -159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True -159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True -159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True -159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True -159709 - 0x00359 (Desert Obelisk) - True - True -159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True -159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True -159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True -159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True -159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True -159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True -159719 - 0x00263 (Monastery Obelisk) - True - True -159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True -159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True -159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True -159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True -159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True -159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True -159729 - 0x00097 (Treehouse Obelisk) - True - True -159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True -159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True -159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True -159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True -159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True -159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True -159739 - 0x00367 (River Obelisk) - True - True -159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True -159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True -159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True -159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True -159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True -159749 - 0x22073 (Quarry Obelisk) - True - True -159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True -159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True -159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True -159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True -159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True -159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True -159759 - 0x0A16C (Town Obelisk) - True - True diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/WitnessLogicExpert.txt index 056ae145c47e..b01d5551ec55 100644 --- a/worlds/witness/WitnessLogicExpert.txt +++ b/worlds/witness/WitnessLogicExpert.txt @@ -1,12 +1,14 @@ +==Tutorial (Inside)== + Menu (Menu) - Entry - True: Entry (Entry): -First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064: +Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064: 158000 - 0x00064 (Straight) - True - True 159510 - 0x01848 (EP) - 0x00064 - True -First Hallway Room (First Hallway) - Tutorial - 0x00182: +Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182: 158001 - 0x00182 (Bend) - True - True Tutorial (Tutorial) - Outside Tutorial - True: @@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - True: 159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True 159517 - 0x3352F (Gate EP) - 0x03505 - True +==Tutorial (Outside)== + Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: 158650 - 0x033D4 (Vault Panel) - True - Dots & Full Dots & Squares & Black/White Squares Door - 0x033D0 (Vault Door) - 0x033D4 @@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Arrows +Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: +158071 - 0x00143 (Apple Tree 1) - True - True +158072 - 0x0003B (Apple Tree 2) - 0x00143 - True +158073 - 0x00055 (Apple Tree 3) - 0x0003B - True +Door - 0x03307 (First Gate) - 0x00055 + +Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: +158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True +158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True +Door - 0x03313 (Second Gate) - 0x032FF + +Orchard End (Orchard): + Main Island (Main Island) - Outside Tutorial - True: 159801 - 0xFFD00 (Reached Independently) - True - True -159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True + +==Glass Factory== Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29: 158027 - 0x01A54 (Entry Panel) - True - Symmetry @@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8: 158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat +==Symmetry Island== + Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E: 158040 - 0x000B0 (Lower Panel) - 0x0343A - Triangles Door - 0x17F3E (Lower) - 0x000B0 @@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island): Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True -Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: -158071 - 0x00143 (Apple Tree 1) - True - True -158072 - 0x0003B (Apple Tree 2) - 0x00143 - True -158073 - 0x00055 (Apple Tree 3) - 0x0003B - True -Door - 0x03307 (First Gate) - 0x00055 - -Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: -158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True -158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True -Door - 0x03313 (Second Gate) - 0x032FF +==Desert== -Orchard End (Orchard): +Desert Obelisk (Desert) - Entry - True: +159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True +159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True +159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True +159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True +159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True +159709 - 0x00359 (Obelisk) - True - True -Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444: +Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: 158652 - 0x0CC7B (Vault Panel) - True - Dots & Full Dots & Stars & Stars + Same Colored Symbol & Eraser & Triangles & Shapers & Negative Shapers & Colored Squares Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Arrows @@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608 Desert Vault (Desert): 158653 - 0x0339E (Vault Box) - True - True -Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3: +Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3: 158087 - 0x09FAA (Light Control) - True - True 158088 - 0x00422 (Light Room 1) - 0x09FAA - True 158089 - 0x006E3 (Light Room 2) - 0x09FAA - True 158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D -Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B: +Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B: 158091 - 0x00C72 (Pond Room 1) - True - True 158092 - 0x0129D (Pond Room 2) - 0x00C72 - True 158093 - 0x008BB (Pond Room 3) - 0x0129D - True @@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249 159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True 159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True -Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: +Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316: 158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True 158098 - 0x1831E (Reduce Water Level Far Right) - True - True 158099 - 0x1C260 (Reduce Water Level Near Left) - True - True @@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: Door - 0x0C316 (Elevator Room Entry) - 0x18076 159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True -Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317: +Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317: 158111 - 0x17C31 (Elevator Room Transparent) - True - True 158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True 158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True @@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131 159035 - 0x037BB (Elevator EP) - 0x01317 - True Door - 0x01317 (Elevator) - 0x03608 -Desert Lowest Level Inbetween Shortcuts (Desert): +Desert Behind Elevator (Desert): -Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: +==Quarry== + +Quarry Obelisk (Quarry) - Entry - True: +159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True +159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True +159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True +159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True +159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True +159749 - 0x22073 (Obelisk) - True - True + +Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: 158118 - 0x09E57 (Entry 1 Panel) - True - Squares & Black/White Squares & Triangles 158603 - 0x17CF0 (Discard) - True - Arrows 158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Triangles & Stars & Stars + Same Colored Symbol @@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4: 158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser 159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True -Quarry Between Entrys (Quarry) - Quarry - 0x17C07: +Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07: 158119 - 0x17C09 (Entry 2 Panel) - True - Shapers & Triangles Door - 0x17C07 (Entry 2) - 0x17C09 @@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663 158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True +==Shadows== + Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665: 158170 - 0x334DB (Door Timer Outside) - True - True Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC @@ -361,19 +390,18 @@ Shadows Laser Room (Shadows): 158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True Laser - 0x181B3 (Laser) - 0x19650 -Treehouse Beach (Treehouse Beach) - Main Island - True: -159200 - 0x0053D (Rock Shadow EP) - True - True -159201 - 0x0053E (Sand Shadow EP) - True - True -159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True +==Keep== -Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: +Outside Keep (Keep) - Main Island - True: +159430 - 0x03E77 (Red Flowers EP) - True - True +159431 - 0x03E7C (Purple Flowers EP) - True - True + +Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: 158193 - 0x00139 (Hedge Maze 1) - True - True 158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True 158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Colored Squares & Triangles & Stars & Stars + Same Colored Symbol Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139 Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA -159430 - 0x03E77 (Red Flowers EP) - True - True -159431 - 0x03E7C (Purple Flowers EP) - True - True Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8: Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139 @@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F 158205 - 0x09E49 (Shadows Shortcut Panel) - True - True Door - 0x09E3D (Shadows Shortcut) - 0x09E49 +Keep Tower (Keep) - Keep - 0x04F8F: +158206 - 0x0361B (Tower Shortcut Panel) - True - True +Door - 0x04F8F (Tower Shortcut) - 0x0361B +158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares +Laser - 0x014BB (Laser) - 0x0360E | 0x03317 +159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True +159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True +159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True +159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True +159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True +159250 - 0x28AE9 (Path EP) - True - True +159251 - 0x3348F (Hedges EP) - True - True + +==Shipwreck== + Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4: 158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots Door - 0x17BB4 (Vault Door) - 0x00AFB @@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB Shipwreck Vault (Shipwreck): 158655 - 0x03535 (Vault Box) - True - True -Keep Tower (Keep) - Keep - 0x04F8F: -158206 - 0x0361B (Tower Shortcut Panel) - True - True -Door - 0x04F8F (Tower Shortcut) - 0x0361B -158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares -Laser - 0x014BB (Laser) - 0x0360E | 0x03317 -159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True -159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True -159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True -159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True -159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True -159250 - 0x28AE9 (Path EP) - True - True -159251 - 0x3348F (Hedges EP) - True - True +==Monastery== + +Monastery Obelisk (Monastery) - Entry - True: +159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True +159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True +159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True +159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True +159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True +159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True +159719 - 0x00263 (Obelisk) - True - True Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750: 158207 - 0x03713 (Laser Shortcut Panel) - True - True @@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4 159137 - 0x03DAC (Facade Left Stairs EP) - True - True 159138 - 0x03DAD (Facade Right Stairs EP) - True - True 159140 - 0x03E01 (Grass Stairs EP) - True - True +159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True +159121 - 0x03BE3 (Garden Right EP) - True - True +159122 - 0x0A409 (Wall EP) - True - True Inside Monastery (Monastery): 158213 - 0x09D9B (Shutters Control) - True - Dots @@ -470,7 +514,18 @@ Inside Monastery (Monastery): Monastery Garden (Monastery): -Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9: +==Town== + +Town Obelisk (Town) - Entry - True: +159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True +159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True +159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True +159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True +159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True +159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True +159759 - 0x0A16C (Obelisk) - True - True + +Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True: 158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat 158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Squares & Black/White Squares & Shapers & Triangles Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 @@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28A0D Door - 0x03BB0 (Church Entry) - 0x03C08 158228 - 0x28A79 (Maze Panel) - True - True Door - 0x28AA2 (Maze Stairs) - 0x28A79 -158241 - 0x17F5F (Windmill Entry Panel) - True - Dots -Door - 0x1845B (Windmill Entry) - 0x17F5F -159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True -159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True -159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True 159540 - 0x03335 (Tower Underside Third EP) - True - True 159541 - 0x03412 (Tower Underside Fourth EP) - True - True 159542 - 0x038A6 (Tower Underside First EP) - True - True @@ -528,20 +578,26 @@ Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True 159553 - 0x03BD1 (Black Line Church EP) - True - True -RGB House (Town) - RGB Room - 0x2897B: +Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B: 158242 - 0x034E4 (Sound Room Left) - True - True 158243 - 0x034E3 (Sound Room Right) - True - Sound Dots -Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3 +Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3 -RGB Room (Town): +Town RGB House Upstairs (Town RGB House Upstairs): 158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Squares & Colored Squares & Triangles -158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Squares & Colored Squares & Black/White Squares & Eraser -158246 - 0x03C08 (RGB Room Right) - 0x334D8 & 0x03C0C - Symmetry & Dots & Colored Dots & Triangles +158245 - 0x03C0C (Left) - 0x334D8 - Squares & Colored Squares & Black/White Squares & Eraser +158246 - 0x03C08 (Right) - 0x334D8 & 0x03C0C - Symmetry & Dots & Colored Dots & Triangles + +Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799: +Door - 0x27799 (First Door) - 0x28A69 -Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C: +Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798: Door - 0x27798 (Second Door) - 0x28ACC + +Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C: Door - 0x2779C (Third Door) - 0x28AD9 -Door - 0x27799 (First Door) - 0x28A69 + +Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A: Door - 0x2779A (Fourth Door) - 0x28B39 Town Tower Top (Town): @@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5 159422 - 0x33692 (Brown Bridge EP) - True - True 159551 - 0x03BCE (Black Line Tower EP) - True - True +==Windmill & Theater== + +Outside Windmill (Windmill) - Windmill Interior - 0x1845B: +159010 - 0x037B6 (First Blade EP) - 0x17D02 - True +159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True +159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True +158241 - 0x17F5F (Entry Panel) - True - Dots +Door - 0x1845B (Entry) - 0x17F5F + Windmill Interior (Windmill) - Theater - 0x17F88: 158247 - 0x17D02 (Turn Control) - True - Dots 158248 - 0x17F89 (Theater Entry Panel) - True - Squares & Black/White Squares & Eraser & Triangles @@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2 159556 - 0x33A2A (Door EP) - 0x03553 - True 159558 - 0x33B06 (Church EP) - 0x0354E - True +==Jungle== + Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF: 158251 - 0x17CDF (Shore Boat Spawn) - True - Boat 158609 - 0x17F9B (Discard) - True - Arrows @@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA 159350 - 0x035CB (Bamboo CCW EP) - True - True 159351 - 0x035CF (Bamboo CW EP) - True - True -Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287: +Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287: 158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA 158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots Door - 0x15287 (Vault Door) - 0x15ADD 159110 - 0x03AC5 (Green Leaf Moss EP) - True - True -159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True -159121 - 0x03BE3 (Monastery Garden Right EP) - True - True -159122 - 0x0A409 (Monastery Wall EP) - True - True -River Vault (River): +Jungle Vault (Jungle): 158664 - 0x03702 (Vault Box) - True - True +==Bunker== + Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4: 158268 - 0x17C2E (Entry Panel) - True - Squares & Black/White Squares Door - 0x0C2A4 (Entry) - 0x17C2E @@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67 Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay: 159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True -Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: +Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: 158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares +Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay: + Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay: 159310 - 0x000D3 (Green Room Flowers EP) - True - True @@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay: 158710 - 0x09DE0 (Laser Panel) - True - True Laser - 0x0C2B2 (Laser) - 0x09DE0 +==Swamp== + Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True: 158287 - 0x0056E (Entry Panel) - True - Rotated Shapers & Black/White Squares & Triangles Door - 0x00C1C (Entry) - 0x0056E @@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615 158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Stars & Stars + Same Colored Symbol Door - 0x2D880 (Laser Shortcut) - 0x17C02 -Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95: +==Treehouse== + +Treehouse Obelisk (Treehouse) - Entry - True: +159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True +159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True +159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True +159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True +159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True +159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True +159729 - 0x00097 (Obelisk) - True - True + +Treehouse Beach (Treehouse Beach) - Main Island - True: +159200 - 0x0053D (Rock Shadow EP) - True - True +159201 - 0x0053E (Sand Shadow EP) - True - True +159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True + +Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95: 158343 - 0x17C95 (Boat Spawn) - True - Boat 158344 - 0x0288C (First Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True -Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: +Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: 158345 - 0x02886 (Second Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles Door - 0x0C310 (Second Door) - 0x02886 @@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1 158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots & Full Dots 158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots & Full Dots -Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: +Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: 158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles 158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars & Stars + Same Colored Symbol & Triangles 158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars & Stars + Same Colored Symbol & Triangles @@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: 158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars & Stars + Same Colored Symbol & Triangles 158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars & Stars + Same Colored Symbol & Triangles -Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D: +Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF @@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse): 158403 - 0x17CBC (Laser House Door Timer Inside) - True - True Laser - 0x028A4 (Laser) - 0x03613 +==Mountain (Outside)== + +Mountainside Obelisk (Mountainside) - Entry - True: +159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True +159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True +159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True +159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True +159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True +159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True +159739 - 0x00367 (Obelisk) - True - True + Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085: +159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True 158612 - 0x17C42 (Discard) - True - Arrows 158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Squares & Triangles & Stars & Stars + Same Colored Symbol Door - 0x00085 (Vault Door) - 0x002A6 @@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6 Mountainside Vault (Mountainside): 158666 - 0x03542 (Vault Box) - True - True -Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: +Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True 158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol & Triangles @@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: 159324 - 0x336C8 (Arch White Right EP) - True - True 159326 - 0x3369A (Arch White Left EP) - True - True -Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39: +==Mountain (Inside)== + +Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: 158408 - 0x09E39 (Light Bridge Controller) - True - Eraser & Triangles -Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay: +Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay: 158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots & Stars & Stars + Same Colored Symbol 158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Triangles 158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars & Stars + Same Colored Symbol @@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True 158424 - 0x09EAD (Trash Pillar 1) - True - Rotated Shapers & Stars 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Rotated Shapers & Triangles -Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: +Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Triangles & Stars + Same Colored Symbol 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol @@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8: - Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 @@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Arrows -Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB: +Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB: 158439 - 0x09EEB (Elevator Control Panel) - True - Dots -Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: 158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser & Negative Shapers 158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser & Negative Shapers 158442 - 0x09F01 (Giant Puzzle Top Right) - True - Shapers & Eraser & Negative Shapers @@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO 159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True Door - 0x09F89 (Exit) - 0x09FDA -Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141: +Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141: 158614 - 0x17FA2 (Discard) - 0xFFF00 - Arrows -158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars -158446 - 0x01987 (Final Room Entry Right) - True - Squares & Colored Squares & Dots -Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987 +158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars +158446 - 0x01987 (Pillars Room Entry Right) - True - Squares & Colored Squares & Dots +Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987 Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 +Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: +158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol +158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Dots & Full Dots & Triangles & Symmetry +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol & Symmetry +158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbol & Negative Shapers & Shapers +158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol +158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles & Symmetry +158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares +158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots + +Elevator (Mountain Bottom Floor): +158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158531 - 0x3D9A7 (Elevator Door Close Right) - True - True +158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True +158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True +158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True +158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True +158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True + Mountain Pink Bridge EP (Mountain Floor 2): 159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True @@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D: Door - 0x2D77D (Caves Entry) - 0x00FF8 158448 - 0x334E1 (Rock Control) - True - True -Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5: +==Caves== + +Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5: 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Squares & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Squares & Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Squares & Black/White Squares & Dots @@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True -Path to Challenge (Caves) - Challenge - 0x0A19A: +Caves Path to Challenge (Caves) - Challenge - 0x0A19A: 158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Arrows & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E +==Challenge== + Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75: 158499 - 0x0A332 (Start Timer) - 11 Lasers - True 158500 - 0x0088E (Small Basic) - 0x0A332 - True @@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4 Challenge Vault (Challenge): 158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True -Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87: +==Tunnels== + +Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87: 158668 - 0x2FAF6 (Vault Box) - True - True 158519 - 0x27732 (Theater Shortcut Panel) - True - True Door - 0x27739 (Theater Shortcut) - 0x27732 @@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D Door - 0x09E87 (Town Shortcut) - 0x09E85 159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True -Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961: -158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol -158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Dots & Full Dots & Triangles & Symmetry -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol & Symmetry -158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbol & Negative Shapers & Shapers -158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol -158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles & Symmetry -158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares -158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots - -Elevator (Mountain Final Room): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True -158531 - 0x3D9A7 (Elevator Door Close Right) - True - True -158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True -158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True -158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True -158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True -158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True +==Boat== The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay: 159042 - 0x22106 (Desert EP) - True - True @@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre 159521 - 0x33879 (Tutorial Reflection EP) - True - True 159522 - 0x03C19 (Tutorial Moss EP) - True - True 159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True - -Obelisks (EPs) - Entry - True: -159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True -159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True -159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True -159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True -159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True -159709 - 0x00359 (Desert Obelisk) - True - True -159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True -159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True -159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True -159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True -159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True -159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True -159719 - 0x00263 (Monastery Obelisk) - True - True -159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True -159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True -159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True -159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True -159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True -159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True -159729 - 0x00097 (Treehouse Obelisk) - True - True -159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True -159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True -159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True -159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True -159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True -159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True -159739 - 0x00367 (River Obelisk) - True - True -159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True -159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True -159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True -159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True -159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True -159749 - 0x22073 (Quarry Obelisk) - True - True -159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True -159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True -159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True -159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True -159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True -159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True -159759 - 0x0A16C (Town Obelisk) - True - True diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/WitnessLogicVanilla.txt index 71af12f76dbb..62c38d412427 100644 --- a/worlds/witness/WitnessLogicVanilla.txt +++ b/worlds/witness/WitnessLogicVanilla.txt @@ -1,12 +1,14 @@ +==Tutorial (Inside)== + Menu (Menu) - Entry - True: Entry (Entry): -First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064: +Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064: 158000 - 0x00064 (Straight) - True - True 159510 - 0x01848 (EP) - 0x00064 - True -First Hallway Room (First Hallway) - Tutorial - 0x00182: +Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182: 158001 - 0x00182 (Bend) - True - True Tutorial (Tutorial) - Outside Tutorial - 0x03629: @@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - 0x03629: 159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True 159517 - 0x3352F (Gate EP) - 0x03505 - True +==Tutorial (Outside)== + Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: 158650 - 0x033D4 (Vault Panel) - True - Dots & Black/White Squares Door - 0x033D0 (Vault Door) - 0x033D4 @@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Triangles +Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: +158071 - 0x00143 (Apple Tree 1) - True - True +158072 - 0x0003B (Apple Tree 2) - 0x00143 - True +158073 - 0x00055 (Apple Tree 3) - 0x0003B - True +Door - 0x03307 (First Gate) - 0x00055 + +Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: +158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True +158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True +Door - 0x03313 (Second Gate) - 0x032FF + +Orchard End (Orchard): + Main Island (Main Island) - Outside Tutorial - True: 159801 - 0xFFD00 (Reached Independently) - True - True -159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True + +==Glass Factory== Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29: 158027 - 0x01A54 (Entry Panel) - True - Symmetry @@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8: 158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat +==Symmetry Island== + Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E: 158040 - 0x000B0 (Lower Panel) - 0x0343A - Dots Door - 0x17F3E (Lower) - 0x000B0 @@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island): Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True -Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: -158071 - 0x00143 (Apple Tree 1) - True - True -158072 - 0x0003B (Apple Tree 2) - 0x00143 - True -158073 - 0x00055 (Apple Tree 3) - 0x0003B - True -Door - 0x03307 (First Gate) - 0x00055 - -Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: -158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True -158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True -Door - 0x03313 (Second Gate) - 0x032FF +==Desert== -Orchard End (Orchard): +Desert Obelisk (Desert) - Entry - True: +159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True +159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True +159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True +159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True +159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True +159709 - 0x00359 (Obelisk) - True - True -Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444: +Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: 158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Triangles @@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608 Desert Vault (Desert): 158653 - 0x0339E (Vault Box) - True - True -Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3: +Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3: 158087 - 0x09FAA (Light Control) - True - True 158088 - 0x00422 (Light Room 1) - 0x09FAA - True 158089 - 0x006E3 (Light Room 2) - 0x09FAA - True 158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D -Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B: +Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B: 158091 - 0x00C72 (Pond Room 1) - True - True 158092 - 0x0129D (Pond Room 2) - 0x00C72 - True 158093 - 0x008BB (Pond Room 3) - 0x0129D - True @@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249 159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True 159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True -Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: +Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316: 158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True 158098 - 0x1831E (Reduce Water Level Far Right) - True - True 158099 - 0x1C260 (Reduce Water Level Near Left) - True - True @@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: Door - 0x0C316 (Elevator Room Entry) - 0x18076 159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True -Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317: +Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317: 158111 - 0x17C31 (Elevator Room Transparent) - True - True 158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True 158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True @@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131 159035 - 0x037BB (Elevator EP) - 0x01317 - True Door - 0x01317 (Elevator) - 0x03608 -Desert Lowest Level Inbetween Shortcuts (Desert): +Desert Behind Elevator (Desert): -Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: +==Quarry== + +Quarry Obelisk (Quarry) - Entry - True: +159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True +159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True +159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True +159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True +159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True +159749 - 0x22073 (Obelisk) - True - True + +Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: 158118 - 0x09E57 (Entry 1 Panel) - True - Black/White Squares 158603 - 0x17CF0 (Discard) - True - Triangles 158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Shapers @@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4: 158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser 159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True -Quarry Between Entrys (Quarry) - Quarry - 0x17C07: +Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07: 158119 - 0x17C09 (Entry 2 Panel) - True - Shapers Door - 0x17C07 (Entry 2) - 0x17C09 @@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663 158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True +==Shadows== + Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665: 158170 - 0x334DB (Door Timer Outside) - True - True Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC @@ -361,19 +390,18 @@ Shadows Laser Room (Shadows): 158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True Laser - 0x181B3 (Laser) - 0x19650 -Treehouse Beach (Treehouse Beach) - Main Island - True: -159200 - 0x0053D (Rock Shadow EP) - True - True -159201 - 0x0053E (Sand Shadow EP) - True - True -159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True +==Keep== -Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: +Outside Keep (Keep) - Main Island - True: +159430 - 0x03E77 (Red Flowers EP) - True - True +159431 - 0x03E7C (Purple Flowers EP) - True - True + +Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: 158193 - 0x00139 (Hedge Maze 1) - True - True 158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True 158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Dots Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139 Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA -159430 - 0x03E77 (Red Flowers EP) - True - True -159431 - 0x03E7C (Purple Flowers EP) - True - True Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8: Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139 @@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F 158205 - 0x09E49 (Shadows Shortcut Panel) - True - True Door - 0x09E3D (Shadows Shortcut) - 0x09E49 +Keep Tower (Keep) - Keep - 0x04F8F: +158206 - 0x0361B (Tower Shortcut Panel) - True - True +Door - 0x04F8F (Tower Shortcut) - 0x0361B +158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers +Laser - 0x014BB (Laser) - 0x0360E | 0x03317 +159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True +159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 & 0x01BEA - True +159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True +159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True +159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True +159250 - 0x28AE9 (Path EP) - True - True +159251 - 0x3348F (Hedges EP) - True - True + +==Shipwreck== + Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4: 158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots Door - 0x17BB4 (Vault Door) - 0x00AFB @@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB Shipwreck Vault (Shipwreck): 158655 - 0x03535 (Vault Box) - True - True -Keep Tower (Keep) - Keep - 0x04F8F: -158206 - 0x0361B (Tower Shortcut Panel) - True - True -Door - 0x04F8F (Tower Shortcut) - 0x0361B -158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers -Laser - 0x014BB (Laser) - 0x0360E | 0x03317 -159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True -159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 & 0x01BEA - True -159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True -159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True -159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True -159250 - 0x28AE9 (Path EP) - True - True -159251 - 0x3348F (Hedges EP) - True - True +==Monastery== + +Monastery Obelisk (Monastery) - Entry - True: +159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True +159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True +159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True +159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True +159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True +159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True +159719 - 0x00263 (Obelisk) - True - True Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750: 158207 - 0x03713 (Laser Shortcut Panel) - True - True @@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4 159137 - 0x03DAC (Facade Left Stairs EP) - True - True 159138 - 0x03DAD (Facade Right Stairs EP) - True - True 159140 - 0x03E01 (Grass Stairs EP) - True - True +159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True +159121 - 0x03BE3 (Garden Right EP) - True - True +159122 - 0x0A409 (Wall EP) - True - True Inside Monastery (Monastery): 158213 - 0x09D9B (Shutters Control) - True - Dots @@ -470,7 +514,18 @@ Inside Monastery (Monastery): Monastery Garden (Monastery): -Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9: +==Town== + +Town Obelisk (Town) - Entry - True: +159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True +159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True +159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True +159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True +159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True +159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True +159759 - 0x0A16C (Obelisk) - True - True + +Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True: 158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat 158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 @@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28998 Door - 0x03BB0 (Church Entry) - 0x28A0D 158228 - 0x28A79 (Maze Panel) - True - True Door - 0x28AA2 (Maze Stairs) - 0x28A79 -158241 - 0x17F5F (Windmill Entry Panel) - True - Dots -Door - 0x1845B (Windmill Entry) - 0x17F5F -159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True -159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True -159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True 159540 - 0x03335 (Tower Underside Third EP) - True - True 159541 - 0x03412 (Tower Underside Fourth EP) - True - True 159542 - 0x038A6 (Tower Underside First EP) - True - True @@ -528,20 +578,26 @@ Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True 159553 - 0x03BD1 (Black Line Church EP) - True - True -RGB House (Town) - RGB Room - 0x2897B: +Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B: 158242 - 0x034E4 (Sound Room Left) - True - True 158243 - 0x034E3 (Sound Room Right) - True - Sound Dots -Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3 +Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3 -RGB Room (Town): +Town RGB House Upstairs (Town RGB House Upstairs): 158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Colored Squares -158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Colored Squares & Black/White Squares -158246 - 0x03C08 (RGB Room Right) - 0x334D8 - Stars +158245 - 0x03C0C (Left) - 0x334D8 - Colored Squares & Black/White Squares +158246 - 0x03C08 (Right) - 0x334D8 - Stars + +Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799: +Door - 0x27799 (First Door) - 0x28A69 -Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C: +Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798: Door - 0x27798 (Second Door) - 0x28ACC + +Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C: Door - 0x2779C (Third Door) - 0x28AD9 -Door - 0x27799 (First Door) - 0x28A69 + +Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A: Door - 0x2779A (Fourth Door) - 0x28B39 Town Tower Top (Town): @@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5 159422 - 0x33692 (Brown Bridge EP) - True - True 159551 - 0x03BCE (Black Line Tower EP) - True - True +==Windmill & Theater== + +Outside Windmill (Windmill) - Windmill Interior - 0x1845B: +159010 - 0x037B6 (First Blade EP) - 0x17D02 - True +159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True +159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True +158241 - 0x17F5F (Entry Panel) - True - Dots +Door - 0x1845B (Entry) - 0x17F5F + Windmill Interior (Windmill) - Theater - 0x17F88: 158247 - 0x17D02 (Turn Control) - True - Dots 158248 - 0x17F89 (Theater Entry Panel) - True - Black/White Squares @@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2 159556 - 0x33A2A (Door EP) - 0x03553 - True 159558 - 0x33B06 (Church EP) - 0x0354E - True +==Jungle== + Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF: 158251 - 0x17CDF (Shore Boat Spawn) - True - Boat 158609 - 0x17F9B (Discard) - True - Triangles @@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA 159350 - 0x035CB (Bamboo CCW EP) - True - True 159351 - 0x035CF (Bamboo CW EP) - True - True -Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287: +Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287: 158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA 158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots Door - 0x15287 (Vault Door) - 0x15ADD 159110 - 0x03AC5 (Green Leaf Moss EP) - True - True -159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True -159121 - 0x03BE3 (Monastery Garden Right EP) - True - True -159122 - 0x0A409 (Monastery Wall EP) - True - True -River Vault (River): +Jungle Vault (Jungle): 158664 - 0x03702 (Vault Box) - True - True +==Bunker== + Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4: 158268 - 0x17C2E (Entry Panel) - True - Black/White Squares Door - 0x0C2A4 (Entry) - 0x17C2E @@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67 Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay: 159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True -Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: +Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: 158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares +Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay: + Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay: 159310 - 0x000D3 (Green Room Flowers EP) - True - True @@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay: 158710 - 0x09DE0 (Laser Panel) - True - True Laser - 0x0C2B2 (Laser) - 0x09DE0 +==Swamp== + Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True: 158287 - 0x0056E (Entry Panel) - True - Shapers Door - 0x00C1C (Entry) - 0x0056E @@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615 158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers Door - 0x2D880 (Laser Shortcut) - 0x17C02 -Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95: +==Treehouse== + +Treehouse Obelisk (Treehouse) - Entry - True: +159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True +159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True +159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True +159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True +159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True +159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True +159729 - 0x00097 (Obelisk) - True - True + +Treehouse Beach (Treehouse Beach) - Main Island - True: +159200 - 0x0053D (Rock Shadow EP) - True - True +159201 - 0x0053E (Sand Shadow EP) - True - True +159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True + +Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95: 158343 - 0x17C95 (Boat Spawn) - True - Boat 158344 - 0x0288C (First Door Panel) - True - Stars Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True -Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: +Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: 158345 - 0x02886 (Second Door Panel) - True - Stars Door - 0x0C310 (Second Door) - 0x02886 @@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1 158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots 158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots -Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: +Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: 158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars 158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars 158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars @@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: 158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars 158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars -Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D: +Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF @@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse): 158403 - 0x17CBC (Laser House Door Timer Inside) - True - True Laser - 0x028A4 (Laser) - 0x03613 +==Mountain (Outside)== + +Mountainside Obelisk (Mountainside) - Entry - True: +159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True +159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True +159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True +159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True +159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True +159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True +159739 - 0x00367 (Obelisk) - True - True + Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085: +159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True 158612 - 0x17C42 (Discard) - True - Triangles 158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Dots & Black/White Squares Door - 0x00085 (Vault Door) - 0x002A6 @@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6 Mountainside Vault (Mountainside): 158666 - 0x03542 (Vault Box) - True - True -Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: +Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True 158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Black/White Squares @@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: 159324 - 0x336C8 (Arch White Right EP) - True - True 159326 - 0x3369A (Arch White Left EP) - True - True -Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39: +==Mountain (Inside)== + +Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: 158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Rotated Shapers -Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay: +Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay: 158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots 158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots 158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers @@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True 158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers -Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: +Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Colored Squares 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Colored Squares & Dots 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol @@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8: - Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 @@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Triangles -Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB: +Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB: 158439 - 0x09EEB (Elevator Control Panel) - True - Dots -Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: 158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser 158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Rotated Shapers & Eraser 158442 - 0x09F01 (Giant Puzzle Top Right) - True - Shapers & Eraser @@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO 159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True Door - 0x09F89 (Exit) - 0x09FDA -Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141: +Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141: 158614 - 0x17FA2 (Discard) - 0xFFF00 - Triangles -158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars -158446 - 0x01987 (Final Room Entry Right) - True - Colored Squares & Dots -Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987 +158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars +158446 - 0x01987 (Pillars Room Entry Right) - True - Colored Squares & Dots +Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987 Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 +Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: +158522 - 0x0383A (Right Pillar 1) - True - Stars +158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots +158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry +158526 - 0x0383D (Left Pillar 1) - True - Dots & Full Dots +158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares +158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers +158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry + +Elevator (Mountain Bottom Floor): +158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158531 - 0x3D9A7 (Elevator Door Close Right) - True - True +158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True +158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True +158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True +158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True +158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True + Mountain Pink Bridge EP (Mountain Floor 2): 159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True @@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D: Door - 0x2D77D (Caves Entry) - 0x00FF8 158448 - 0x334E1 (Rock Control) - True - True -Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5: +==Caves== + +Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5: 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots @@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True -Path to Challenge (Caves) - Challenge - 0x0A19A: +Caves Path to Challenge (Caves) - Challenge - 0x0A19A: 158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E +==Challenge== + Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75: 158499 - 0x0A332 (Start Timer) - 11 Lasers - True 158500 - 0x0088E (Small Basic) - 0x0A332 - True @@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4 Challenge Vault (Challenge): 158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True -Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87: +==Tunnels== + +Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87: 158668 - 0x2FAF6 (Vault Box) - True - True 158519 - 0x27732 (Theater Shortcut Panel) - True - True Door - 0x27739 (Theater Shortcut) - 0x27732 @@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D Door - 0x09E87 (Town Shortcut) - 0x09E85 159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True -Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961: -158522 - 0x0383A (Right Pillar 1) - True - Stars -158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots -158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry -158526 - 0x0383D (Left Pillar 1) - True - Dots & Full Dots -158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares -158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers -158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry - -Elevator (Mountain Final Room): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True -158531 - 0x3D9A7 (Elevator Door Close Right) - True - True -158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True -158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True -158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True -158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True -158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True +==Boat== The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay: 159042 - 0x22106 (Desert EP) - True - True @@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre 159521 - 0x33879 (Tutorial Reflection EP) - True - True 159522 - 0x03C19 (Tutorial Moss EP) - True - True 159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True - -Obelisks (EPs) - Entry - True: -159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True -159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True -159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True -159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True -159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True -159709 - 0x00359 (Desert Obelisk) - True - True -159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True -159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True -159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True -159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True -159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True -159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True -159719 - 0x00263 (Monastery Obelisk) - True - True -159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True -159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True -159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True -159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True -159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True -159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True -159729 - 0x00097 (Treehouse Obelisk) - True - True -159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True -159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True -159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True -159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True -159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True -159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True -159739 - 0x00367 (River Obelisk) - True - True -159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True -159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True -159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True -159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True -159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True -159749 - 0x22073 (Quarry Obelisk) - True - True -159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True -159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True -159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True -159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True -159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True -159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True -159759 - 0x0A16C (Town Obelisk) - True - True diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index d99aab5cffbd..c38898b33d4e 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -2,16 +2,17 @@ Archipelago init file for The Witness """ import dataclasses -from typing import Dict, Optional +from typing import Dict, Optional from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState from Options import PerGameCommonOptions, Toggle from .presets import witness_option_presets -from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ - get_priority_hint_items, make_hints, generate_joke_hints from worlds.AutoWorld import World, WebWorld from .player_logic import WitnessPlayerLogic from .static_logic import StaticWitnessLogic +from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ + get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \ + make_extra_location_hints, create_all_hints from .locations import WitnessPlayerLocations, StaticWitnessLocations from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData from .regions import WitnessRegions @@ -57,6 +58,7 @@ class WitnessWorld(World): } location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID item_name_groups = StaticWitnessItems.item_groups + location_name_groups = StaticWitnessLocations.AREA_LOCATION_GROUPS required_client_version = (0, 4, 4) @@ -191,8 +193,8 @@ def create_regions(self): # Then, add checks in order until the required amount of sphere 1 checks is met. extra_checks = [ - ("First Hallway Room", "First Hallway Bend"), - ("First Hallway", "First Hallway Straight"), + ("Tutorial First Hallway Room", "Tutorial First Hallway Bend"), + ("Tutorial First Hallway", "Tutorial First Hallway Straight"), ("Desert Outside", "Desert Surface 1"), ("Desert Outside", "Desert Surface 2"), ] @@ -277,26 +279,35 @@ def fill_slot_data(self) -> dict: hint_amount = self.options.hint_amount.value credits_hint = ( - "This Randomizer is brought to you by", - "NewSoupVi, Jarno, blastron,", - "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1 + "This Randomizer is brought to you by\n" + "NewSoupVi, Jarno, blastron,\n", + "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1 ) audio_logs = get_audio_logs().copy() if hint_amount: - generated_hints = make_hints(self, hint_amount, self.own_itempool) + area_hints = round(self.options.area_hint_percentage / 100 * hint_amount) + + generated_hints = create_all_hints(self, hint_amount, area_hints) self.random.shuffle(audio_logs) duplicates = min(3, len(audio_logs) // hint_amount) - for _ in range(0, hint_amount): - hint = generated_hints.pop(0) + for hint in generated_hints: + location = hint.location + area_amount = hint.area_amount + + # None if junk hint, address if location hint, area string if area hint + arg_1 = location.address if location else (hint.area if hint.area else None) + + # self.player if junk hint, player if location hint, progression amount if area hint + arg_2 = area_amount if area_amount is not None else (location.player if location else self.player) for _ in range(0, duplicates): audio_log = audio_logs.pop() - self.log_ids_to_hints[int(audio_log, 16)] = hint + self.log_ids_to_hints[int(audio_log, 16)] = (hint.wording, arg_1, arg_2) if audio_logs: audio_log = audio_logs.pop() diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 0354660b5ee0..545aef221677 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -1,6 +1,9 @@ -from typing import Tuple, List, TYPE_CHECKING - -from BaseClasses import Item +import logging +from dataclasses import dataclass +from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional +from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState +from . import StaticWitnessLogic +from .utils import weighted_sample if TYPE_CHECKING: from . import WitnessWorld @@ -164,6 +167,27 @@ ] +@dataclass +class WitnessLocationHint: + location: Location + hint_came_from_location: bool + + # If a hint gets added to a set twice, but once as an item hint and once as a location hint, those are the same + def __hash__(self): + return hash(self.location) + + def __eq__(self, other): + return self.location == other.location + + +@dataclass +class WitnessWordedHint: + wording: str + location: Optional[Location] = None + area: Optional[str] = None + area_amount: Optional[int] = None + + def get_always_hint_items(world: "WitnessWorld") -> List[str]: always = [ "Boat", @@ -182,7 +206,7 @@ def get_always_hint_items(world: "WitnessWorld") -> List[str]: always.append("Triangles") if wincon == "elevator": - always += ["Mountain Bottom Floor Final Room Entry (Door)", "Mountain Bottom Floor Doors"] + always += ["Mountain Bottom Floor Pillars Room Entry (Door)", "Mountain Bottom Floor Doors"] if wincon == "challenge": always += ["Challenge Entry (Panel)", "Caves Panels"] @@ -200,12 +224,14 @@ def get_always_hint_locations(world: "WitnessWorld") -> List[str]: ] # Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side - if world.options.EP_difficulty == "eclipse": + if "0x339B6" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: always.append("Town Obelisk Side 6") # Eclipse EP - if world.options.EP_difficulty != "normal": + if "0x3388F" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: always.append("Treehouse Obelisk Side 4") # Couch EP - always.append("River Obelisk Side 1") # Cloud Cycle EP. Needs to be changed to "Mountainside Obelisk" soon + + if "0x335AE" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: + always.append("Mountainside Obelisk Side 1") # Cloud Cycle EP. return always @@ -263,10 +289,12 @@ def get_priority_hint_items(world: "WitnessWorld") -> List[str]: def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: priority = [ + "Tutorial Patio Floor", + "Tutorial Patio Flowers EP", "Swamp Purple Underwater", "Shipwreck Vault Box", - "Town RGB Room Left", - "Town RGB Room Right", + "Town RGB House Upstairs Left", + "Town RGB House Upstairs Right", "Treehouse Green Bridge 7", "Treehouse Green Bridge Discard", "Shipwreck Discard", @@ -279,14 +307,38 @@ def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: ] # Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side - if world.options.EP_difficulty != "normal": + if "0x33A20" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: priority.append("Town Obelisk Side 6") # Theater Flowers EP + + if "0x28B29" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: priority.append("Treehouse Obelisk Side 4") # Shipwreck Green EP + if "0x33600" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: + priority.append("Town Obelisk Side 2") # Tutorial Patio Flowers EP. + return priority -def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]): +def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint): + location_name = hint.location.name + if hint.location.player != world.player: + location_name += " (" + world.multiworld.get_player_name(hint.location.player) + ")" + + item = hint.location.item + item_name = item.name + if item.player != world.player: + item_name += " (" + world.multiworld.get_player_name(item.player) + ")" + + if hint.hint_came_from_location: + hint_text = f"{location_name} contains {item_name}." + else: + hint_text = f"{item_name} can be found at {location_name}." + + return WitnessWordedHint(hint_text, hint.location) + + +def hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]) -> Optional[WitnessLocationHint]: + locations = [item.location for item in own_itempool if item.name == item_name and item.location] if not locations: @@ -298,28 +350,39 @@ def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: Lis if location_obj.player != world.player: location_name += " (" + world.multiworld.get_player_name(location_obj.player) + ")" - return location_name, item_name, location_obj.address if (location_obj.player == world.player) else -1 + return WitnessLocationHint(location_obj, False) -def make_hint_from_location(world: "WitnessWorld", location: str): +def hint_from_location(world: "WitnessWorld", location: str) -> Optional[WitnessLocationHint]: location_obj = world.multiworld.get_location(location, world.player) item_obj = world.multiworld.get_location(location, world.player).item item_name = item_obj.name if item_obj.player != world.player: item_name += " (" + world.multiworld.get_player_name(item_obj.player) + ")" - return location, item_name, location_obj.address if (location_obj.player == world.player) else -1 + return WitnessLocationHint(location_obj, True) -def make_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item]): - hints = list() +def get_items_and_locations_in_random_order(world: "WitnessWorld", own_itempool: List[Item]): + prog_items_in_this_world = sorted( + item.name for item in own_itempool + if item.advancement and item.code and item.location + ) + locations_in_this_world = sorted( + location.name for location in world.multiworld.get_locations(world.player) + if location.address and location.progress_type != LocationProgressType.EXCLUDED + ) - prog_items_in_this_world = { - item.name for item in own_itempool if item.advancement and item.code and item.location - } - loc_in_this_world = { - location.name for location in world.multiworld.get_locations(world.player) if location.address - } + world.random.shuffle(prog_items_in_this_world) + world.random.shuffle(locations_in_this_world) + + return prog_items_in_this_world, locations_in_this_world + + +def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List[Item], + already_hinted_locations: Set[Location] + ) -> Tuple[List[WitnessLocationHint], List[WitnessLocationHint]]: + prog_items_in_this_world, loc_in_this_world = get_items_and_locations_in_random_order(world, own_itempool) always_locations = [ location for location in get_always_hint_locations(world) @@ -338,105 +401,323 @@ def make_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item] if item in prog_items_in_this_world ] - always_hint_pairs = dict() + # Get always and priority location/item hints + always_location_hints = {hint_from_location(world, location) for location in always_locations} + always_item_hints = {hint_from_item(world, item, own_itempool) for item in always_items} + priority_location_hints = {hint_from_location(world, location) for location in priority_locations} + priority_item_hints = {hint_from_item(world, item, own_itempool) for item in priority_items} - for item in always_items: - hint_pair = make_hint_from_item(world, item, own_itempool) + # Combine the sets. This will get rid of duplicates + always_hints_set = always_item_hints | always_location_hints + priority_hints_set = priority_item_hints | priority_location_hints - if not hint_pair or hint_pair[2] == 158007: # Tutorial Gate Open - continue + # Make sure priority hints doesn't contain any hints that are already always hints. + priority_hints_set -= always_hints_set - always_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2]) + always_generator = [hint for hint in always_hints_set if hint and hint.location not in already_hinted_locations] + priority_generator = [hint for hint in priority_hints_set if hint and hint.location not in already_hinted_locations] - for location in always_locations: - hint_pair = make_hint_from_location(world, location) - always_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2]) + # Convert both hint types to list and then shuffle. Also, get rid of None and Tutorial Gate Open. + always_hints = sorted(always_generator, key=lambda h: h.location) + priority_hints = sorted(priority_generator, key=lambda h: h.location) + world.random.shuffle(always_hints) + world.random.shuffle(priority_hints) - priority_hint_pairs = dict() + return always_hints, priority_hints - for item in priority_items: - hint_pair = make_hint_from_item(world, item, own_itempool) - if not hint_pair or hint_pair[2] == 158007: # Tutorial Gate Open - continue +def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item], + already_hinted_locations: Set[Location], hints_to_use_first: List[WitnessLocationHint], + unhinted_locations_for_hinted_areas: Dict[str, Set[Location]]) -> List[WitnessWordedHint]: + prog_items_in_this_world, locations_in_this_world = get_items_and_locations_in_random_order(world, own_itempool) - priority_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2]) + next_random_hint_is_location = world.random.randrange(0, 2) - for location in priority_locations: - hint_pair = make_hint_from_location(world, location) - priority_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2]) + hints = [] - already_hinted_locations = set() + # This is a way to reverse a Dict[a,List[b]] to a Dict[b,a] + area_reverse_lookup = {v: k for k, l in unhinted_locations_for_hinted_areas.items() for v in l} - for loc, item in always_hint_pairs.items(): - if loc in already_hinted_locations: + while len(hints) < hint_amount: + if not prog_items_in_this_world and not locations_in_this_world and not hints_to_use_first: + player_name = world.multiworld.get_player_name(world.player) + logging.warning(f"Ran out of items/locations to hint for player {player_name}.") + break + + if hints_to_use_first: + location_hint = hints_to_use_first.pop() + elif next_random_hint_is_location and locations_in_this_world: + location_hint = hint_from_location(world, locations_in_this_world.pop()) + elif not next_random_hint_is_location and prog_items_in_this_world: + location_hint = hint_from_item(world, prog_items_in_this_world.pop(), own_itempool) + # The list that the hint was supposed to be taken from was empty. + # Try the other list, which has to still have something, as otherwise, all lists would be empty, + # which would have triggered the guard condition above. + else: + next_random_hint_is_location = not next_random_hint_is_location continue - if item[1]: - hints.append((f"{item[0]} can be found at {loc}.", item[2])) - else: - hints.append((f"{loc} contains {item[0]}.", item[2])) + if not location_hint or location_hint.location in already_hinted_locations: + continue - already_hinted_locations.add(loc) + # Don't hint locations in areas that are almost fully hinted out already + if location_hint.location in area_reverse_lookup: + area = area_reverse_lookup[location_hint.location] + if len(unhinted_locations_for_hinted_areas[area]) == 1: + continue + del area_reverse_lookup[location_hint.location] + unhinted_locations_for_hinted_areas[area] -= {location_hint.location} - world.random.shuffle(hints) # shuffle always hint order in case of low hint amount + hints.append(word_direct_hint(world, location_hint)) + already_hinted_locations.add(location_hint.location) - remaining_hints = hint_amount - len(hints) - priority_hint_amount = int(max(0.0, min(len(priority_hint_pairs) / 2, remaining_hints / 2))) + next_random_hint_is_location = not next_random_hint_is_location - prog_items_in_this_world = sorted(prog_items_in_this_world) - locations_in_this_world = sorted(loc_in_this_world) + return hints - world.random.shuffle(prog_items_in_this_world) - world.random.shuffle(locations_in_this_world) - priority_hint_list = list(priority_hint_pairs.items()) - world.random.shuffle(priority_hint_list) - for _ in range(0, priority_hint_amount): - next_priority_hint = priority_hint_list.pop() - loc = next_priority_hint[0] - item = next_priority_hint[1] +def generate_joke_hints(world: "WitnessWorld", amount: int) -> List[Tuple[str, int, int]]: + return [(x, -1, -1) for x in world.random.sample(joke_hints, amount)] - if loc in already_hinted_locations: - continue - if item[1]: - hints.append((f"{item[0]} can be found at {loc}.", item[2])) - else: - hints.append((f"{loc} contains {item[0]}.", item[2])) +def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[str, List[Location]], + already_hinted_locations: Set[Location]) -> Tuple[List[str], Dict[str, Set[Location]]]: + """ + Choose areas to hint. + This takes into account that some areas may already have had items hinted in them through location hints. + When this happens, they are made less likely to receive an area hint. + """ - already_hinted_locations.add(loc) + unhinted_locations_per_area = dict() + unhinted_location_percentage_per_area = dict() - next_random_hint_is_item = world.random.randrange(0, 2) + for area_name, locations in locations_per_area.items(): + not_yet_hinted_locations = sum(location not in already_hinted_locations for location in locations) + unhinted_locations_per_area[area_name] = {loc for loc in locations if loc not in already_hinted_locations} + unhinted_location_percentage_per_area[area_name] = not_yet_hinted_locations / len(locations) - while len(hints) < hint_amount: - if next_random_hint_is_item: - if not prog_items_in_this_world: - next_random_hint_is_item = not next_random_hint_is_item - continue + items_per_area = {area_name: [location.item for location in locations] + for area_name, locations in locations_per_area.items()} - hint = make_hint_from_item(world, prog_items_in_this_world.pop(), own_itempool) + areas = sorted(area for area in items_per_area if unhinted_location_percentage_per_area[area]) + weights = [unhinted_location_percentage_per_area[area] for area in areas] - if not hint or hint[0] in already_hinted_locations: - continue + amount = min(amount, len(weights)) + + hinted_areas = weighted_sample(world.random, areas, weights, amount) + + return hinted_areas, unhinted_locations_per_area + + +def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]: + potential_areas = list(StaticWitnessLogic.ALL_AREAS_BY_NAME.keys()) + + locations_per_area = dict() + items_per_area = dict() + + for area in potential_areas: + regions = [ + world.regio.created_regions[region] + for region in StaticWitnessLogic.ALL_AREAS_BY_NAME[area]["regions"] + if region in world.regio.created_regions + ] + locations = [location for region in regions for location in region.get_locations() if location.address] - hints.append((f"{hint[1]} can be found at {hint[0]}.", hint[2])) + if locations: + locations_per_area[area] = locations + items_per_area[area] = [location.item for location in locations] - already_hinted_locations.add(hint[0]) + return locations_per_area, items_per_area + + +def word_area_hint(world: "WitnessWorld", hinted_area: str, corresponding_items: List[Item]) -> Tuple[str, int]: + """ + Word the hint for an area using natural sounding language. + This takes into account how much progression there is, how much of it is local/non-local, and whether there are + any local lasers to be found in this area. + """ + + local_progression = sum(item.player == world.player and item.advancement for item in corresponding_items) + non_local_progression = sum(item.player != world.player and item.advancement for item in corresponding_items) + + laser_names = {"Symmetry Laser", "Desert Laser", "Quarry Laser", "Shadows Laser", "Town Laser", "Monastery Laser", + "Jungle Laser", "Bunker Laser", "Swamp Laser", "Treehouse Laser", "Keep Laser", } + + local_lasers = sum( + item.player == world.player and item.name in laser_names + for item in corresponding_items + ) + + total_progression = non_local_progression + local_progression + + player_count = world.multiworld.players + + area_progression_word = "Both" if total_progression == 2 else "All" + + if not total_progression: + hint_string = f"In the {hinted_area} area, you will find no progression items." + + elif total_progression == 1: + hint_string = f"In the {hinted_area} area, you will find 1 progression item." + + if player_count > 1: + if local_lasers: + hint_string += "\nThis item is a laser for this world." + elif non_local_progression: + other_player_str = "the other player" if player_count == 2 else "another player" + hint_string += f"\nThis item is for {other_player_str}." + else: + hint_string += "\nThis item is for this world." else: - hint = make_hint_from_location(world, locations_in_this_world.pop()) + if local_lasers: + hint_string += "\nThis item is a laser." + + else: + hint_string = f"In the {hinted_area} area, you will find {total_progression} progression items." + + if local_lasers == total_progression: + sentence_end = (" for this world." if player_count > 1 else ".") + hint_string += f"\nAll of them are lasers" + sentence_end + + elif player_count > 1: + if local_progression and non_local_progression: + if non_local_progression == 1: + other_player_str = "the other player" if player_count == 2 else "another player" + hint_string += f"\nOne of them is for {other_player_str}." + else: + other_player_str = "the other player" if player_count == 2 else "other players" + hint_string += f"\n{non_local_progression} of them are for {other_player_str}." + elif non_local_progression: + other_players_str = "the other player" if player_count == 2 else "other players" + hint_string += f"\n{area_progression_word} of them are for {other_players_str}." + elif local_progression: + hint_string += f"\n{area_progression_word} of them are for this world." + + if local_lasers == 1: + if not non_local_progression: + hint_string += "\nAlso, one of them is a laser." + else: + hint_string += "\nAlso, one of them is a laser for this world." + elif local_lasers: + if not non_local_progression: + hint_string += f"\nAlso, {local_lasers} of them are lasers." + else: + hint_string += f"\nAlso, {local_lasers} of them are lasers for this world." - if hint[0] in already_hinted_locations: - continue + else: + if local_lasers == 1: + hint_string += "\nOne of them is a laser." + elif local_lasers: + hint_string += f"\n{local_lasers} of them are lasers." - hints.append((f"{hint[0]} contains {hint[1]}.", hint[2])) + return hint_string, total_progression - already_hinted_locations.add(hint[0]) - next_random_hint_is_item = not next_random_hint_is_item +def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations: Set[Location] + ) -> Tuple[List[WitnessWordedHint], Dict[str, Set[Location]]]: + locs_per_area, items_per_area = get_hintable_areas(world) - return hints + hinted_areas, unhinted_locations_per_area = choose_areas(world, amount, locs_per_area, already_hinted_locations) + + hints = [] + + for hinted_area in hinted_areas: + hint_string, prog_amount = word_area_hint(world, hinted_area, items_per_area[hinted_area]) + + hints.append(WitnessWordedHint(hint_string, None, f"hinted_area:{hinted_area}", prog_amount)) + + if len(hinted_areas) < amount: + player_name = world.multiworld.get_player_name(world.player) + logging.warning(f"Was not able to make {amount} area hints for player {player_name}. " + f"Made {len(hinted_areas)} instead, and filled the rest with random location hints.") + + return hints, unhinted_locations_per_area + + +def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -> List[WitnessWordedHint]: + generated_hints: List[WitnessWordedHint] = [] + + state = CollectionState(world.multiworld) + + # Keep track of already hinted locations. Consider early Tutorial as "already hinted" + + already_hinted_locations = { + loc for loc in world.multiworld.get_reachable_locations(state, world.player) + if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" + } + + intended_location_hints = hint_amount - area_hints + + # First, make always and priority hints. + + always_hints, priority_hints = make_always_and_priority_hints( + world, world.own_itempool, already_hinted_locations + ) + + generated_always_hints = len(always_hints) + possible_priority_hints = len(priority_hints) + + # Make as many always hints as possible + always_hints_to_use = min(intended_location_hints, generated_always_hints) + + # Make up to half of the rest of the location hints priority hints, using up to half of the possibly priority hints + remaining_location_hints = intended_location_hints - always_hints_to_use + priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2))) + + for _ in range(always_hints_to_use): + location_hint = always_hints.pop() + generated_hints.append(word_direct_hint(world, location_hint)) + already_hinted_locations.add(location_hint.location) + + for _ in range(priority_hints_to_use): + location_hint = priority_hints.pop() + generated_hints.append(word_direct_hint(world, location_hint)) + already_hinted_locations.add(location_hint.location) + + location_hints_created_in_round_1 = len(generated_hints) + + unhinted_locations_per_area: Dict[str, Set[Location]] = dict() + + # Then, make area hints. + if area_hints: + generated_area_hints, unhinted_locations_per_area = make_area_hints(world, area_hints, already_hinted_locations) + generated_hints += generated_area_hints + + # If we don't have enough hints yet, recalculate always and priority hints, then fill with random hints + if len(generated_hints) < hint_amount: + remaining_needed_location_hints = hint_amount - len(generated_hints) + + # Save old values for used always and priority hints for later calculations + amt_of_used_always_hints = always_hints_to_use + amt_of_used_priority_hints = priority_hints_to_use + + # Recalculate how many always hints and priority hints are supposed to be used + intended_location_hints = remaining_needed_location_hints + location_hints_created_in_round_1 + + always_hints_to_use = min(intended_location_hints, generated_always_hints) + priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2))) + + # If we now need more always hints and priority hints than we thought previously, make some more. + more_always_hints = always_hints_to_use - amt_of_used_always_hints + more_priority_hints = priority_hints_to_use - amt_of_used_priority_hints + + extra_always_and_priority_hints: List[WitnessLocationHint] = [] + + for _ in range(more_always_hints): + extra_always_and_priority_hints.append(always_hints.pop()) + + for _ in range(more_priority_hints): + extra_always_and_priority_hints.append(priority_hints.pop()) + + generated_hints += make_extra_location_hints( + world, hint_amount - len(generated_hints), world.own_itempool, already_hinted_locations, + extra_always_and_priority_hints, unhinted_locations_per_area + ) + # If we still don't have enough for whatever reason, throw a warning, proceed with the lower amount + if len(generated_hints) != hint_amount: + player_name = world.multiworld.get_player_name(world.player) + logging.warning(f"Couldn't generate {hint_amount} hints for player {player_name}. " + f"Generated {len(generated_hints)} instead.") -def generate_joke_hints(world: "WitnessWorld", amount: int) -> List[Tuple[str, int]]: - return [(x, -1) for x in world.random.sample(joke_hints, amount)] + return generated_hints diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index 781cc4e25d94..d38cf9025806 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -110,13 +110,13 @@ class StaticWitnessLocations: "Town Red Rooftop 5", "Town Wooden Roof Lower Row 5", "Town Wooden Rooftop", - "Town Windmill Entry Panel", + "Windmill Entry Panel", "Town RGB House Entry Panel", "Town Laser Panel", - "Town RGB Room Left", - "Town RGB Room Right", - "Town Sound Room Right", + "Town RGB House Upstairs Left", + "Town RGB House Upstairs Right", + "Town RGB House Sound Room Right", "Windmill Theater Entry Panel", "Theater Exit Left Panel", @@ -134,8 +134,8 @@ class StaticWitnessLocations: "Jungle Popup Wall 6", "Jungle Laser Panel", - "River Vault Box", - "River Monastery Garden Shortcut Panel", + "Jungle Vault Box", + "Jungle Monastery Garden Shortcut Panel", "Bunker Entry Panel", "Bunker Intro Left 5", @@ -177,7 +177,7 @@ class StaticWitnessLocations: "Mountainside Vault Box", "Mountaintop River Shape", - "First Hallway EP", + "Tutorial First Hallway EP", "Tutorial Cloud EP", "Tutorial Patio Flowers EP", "Tutorial Gate EP", @@ -185,7 +185,7 @@ class StaticWitnessLocations: "Outside Tutorial Town Sewer EP", "Outside Tutorial Path EP", "Outside Tutorial Tractor EP", - "Main Island Thundercloud EP", + "Mountainside Thundercloud EP", "Glass Factory Vase EP", "Symmetry Island Glass Factory Black Line Reflection EP", "Symmetry Island Glass Factory Black Line EP", @@ -242,9 +242,9 @@ class StaticWitnessLocations: "Monastery Left Shutter EP", "Monastery Middle Shutter EP", "Monastery Right Shutter EP", - "Town Windmill First Blade EP", - "Town Windmill Second Blade EP", - "Town Windmill Third Blade EP", + "Windmill First Blade EP", + "Windmill Second Blade EP", + "Windmill Third Blade EP", "Town Tower Underside Third EP", "Town Tower Underside Fourth EP", "Town Tower Underside First EP", @@ -268,10 +268,10 @@ class StaticWitnessLocations: "Jungle Tree Halo EP", "Jungle Bamboo CCW EP", "Jungle Bamboo CW EP", - "River Green Leaf Moss EP", - "River Monastery Garden Left EP", - "River Monastery Garden Right EP", - "River Monastery Wall EP", + "Jungle Green Leaf Moss EP", + "Monastery Garden Left EP", + "Monastery Garden Right EP", + "Monastery Wall EP", "Bunker Tinted Door EP", "Bunker Green Room Flowers EP", "Swamp Purple Sand Middle EP", @@ -330,12 +330,12 @@ class StaticWitnessLocations: "Treehouse Obelisk Side 4", "Treehouse Obelisk Side 5", "Treehouse Obelisk Side 6", - "River Obelisk Side 1", - "River Obelisk Side 2", - "River Obelisk Side 3", - "River Obelisk Side 4", - "River Obelisk Side 5", - "River Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", "Quarry Obelisk Side 1", "Quarry Obelisk Side 2", "Quarry Obelisk Side 3", @@ -407,13 +407,13 @@ class StaticWitnessLocations: "Mountain Floor 2 Elevator Discard", "Mountain Bottom Floor Giant Puzzle", - "Mountain Bottom Floor Final Room Entry Left", - "Mountain Bottom Floor Final Room Entry Right", + "Mountain Bottom Floor Pillars Room Entry Left", + "Mountain Bottom Floor Pillars Room Entry Right", "Mountain Bottom Floor Caves Entry Panel", - "Mountain Final Room Left Pillar 4", - "Mountain Final Room Right Pillar 4", + "Mountain Bottom Floor Left Pillar 4", + "Mountain Bottom Floor Right Pillar 4", "Challenge Vault Box", "Theater Challenge Video", @@ -438,12 +438,12 @@ class StaticWitnessLocations: "Treehouse Obelisk Side 4", "Treehouse Obelisk Side 5", "Treehouse Obelisk Side 6", - "River Obelisk Side 1", - "River Obelisk Side 2", - "River Obelisk Side 3", - "River Obelisk Side 4", - "River Obelisk Side 5", - "River Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", "Quarry Obelisk Side 1", "Quarry Obelisk Side 2", "Quarry Obelisk Side 3", @@ -459,6 +459,8 @@ class StaticWitnessLocations: ALL_LOCATIONS_TO_ID = dict() + AREA_LOCATION_GROUPS = dict() + @staticmethod def get_id(chex: str): """ @@ -491,6 +493,10 @@ def __init__(self): for key, item in all_loc_to_id.items(): self.ALL_LOCATIONS_TO_ID[key] = item + for loc in all_loc_to_id: + area = StaticWitnessLogic.ENTITIES_BY_NAME[loc]["area"]["name"] + self.AREA_LOCATION_GROUPS.setdefault(area, []).append(loc) + class WitnessPlayerLocations: """ diff --git a/worlds/witness/options.py b/worlds/witness/options.py index ac1f2bc82830..68a4ac7fc231 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -187,7 +187,19 @@ class HintAmount(Range): display_name = "Hints on Audio Logs" range_start = 0 range_end = 49 - default = 10 + default = 12 + + +class AreaHintPercentage(Range): + """There are two types of hints for The Witness. + "Location hints" hint one location in your world / containing an item for your world. + "Area hints" will tell you some general info about the items you can find in one of the + main geographic areas on the island. + Use this option to specify how many of your hints you want to be area hints. The rest will be location hints.""" + display_name = "Area Hint Percentage" + range_start = 0 + range_end = 100 + default = 33 class DeathLink(Toggle): @@ -227,5 +239,6 @@ class TheWitnessOptions(PerGameCommonOptions): trap_percentage: TrapPercentage puzzle_skip_amount: PuzzleSkipAmount hint_amount: HintAmount + area_hint_percentage: AreaHintPercentage death_link: DeathLink death_link_amnesty: DeathLinkAmnesty diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py index 1fee1a7968b2..3f02de550b15 100644 --- a/worlds/witness/presets.py +++ b/worlds/witness/presets.py @@ -32,6 +32,7 @@ "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": PuzzleSkipAmount.default, "hint_amount": HintAmount.default, + "area_hint_percentage": AreaHintPercentage.default, "death_link": DeathLink.default, }, @@ -64,6 +65,7 @@ "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, + "area_hint_percentage": AreaHintPercentage.default, "death_link": DeathLink.default, }, @@ -96,6 +98,7 @@ "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, + "area_hint_percentage": AreaHintPercentage.default, "death_link": DeathLink.default, }, } diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py index 3a1a1781b77e..350017c6943a 100644 --- a/worlds/witness/regions.py +++ b/worlds/witness/regions.py @@ -129,9 +129,9 @@ def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic regions_to_check.add(target.name) reachable_regions.add(target.name) - final_regions_list = [v for k, v in regions_by_name.items() if k in reachable_regions] + self.created_regions = {k: v for k, v in regions_by_name.items() if k in reachable_regions} - world.multiworld.regions += final_regions_list + world.multiworld.regions += self.created_regions.values() def __init__(self, locat: WitnessPlayerLocations, world: "WitnessWorld"): difficulty = world.options.puzzle_randomization @@ -145,3 +145,4 @@ def __init__(self, locat: WitnessPlayerLocations, world: "WitnessWorld"): self.locat = locat self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = KeyedDefaultDict(lambda _: []) + self.created_regions: Dict[str, Region] = dict() diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt b/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt index 472403962065..70223bd74924 100644 --- a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt +++ b/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt @@ -1,7 +1,7 @@ Items: Glass Factory Entry (Panel) -Tutorial Outpost Entry (Panel) -Tutorial Outpost Exit (Panel) +Outside Tutorial Outpost Entry (Panel) +Outside Tutorial Outpost Exit (Panel) Symmetry Island Lower (Panel) Symmetry Island Upper (Panel) Desert Light Room Entry (Panel) diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt b/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt index 2f2b32171079..87ec69f59c81 100644 --- a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt +++ b/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt @@ -58,9 +58,9 @@ Town Tower Third (Door) Theater Entry (Door) Theater Exit Left (Door) Theater Exit Right (Door) -Jungle Bamboo Laser Shortcut (Door) +Jungle Laser Shortcut (Door) Jungle Popup Wall (Door) -River Monastery Garden Shortcut (Door) +Jungle Monastery Garden Shortcut (Door) Bunker Entry (Door) Bunker Tinted Glass Door Bunker UV Room Entry (Door) @@ -85,7 +85,7 @@ Mountain Floor 2 Staircase Near (Door) Mountain Floor 2 Exit (Door) Mountain Floor 2 Staircase Far (Door) Mountain Bottom Floor Giant Puzzle Exit (Door) -Mountain Bottom Floor Final Room Entry (Door) +Mountain Bottom Floor Pillars Room Entry (Door) Mountain Bottom Floor Rock (Door) Caves Entry (Door) Caves Pillar Door @@ -143,8 +143,8 @@ Town Wooden Roof Lower Row 5 Town RGB House Entry Panel Town Church Entry Panel Town Maze Panel -Town Windmill Entry Panel -Town Sound Room Right +Windmill Entry Panel +Town RGB House Sound Room Right Town Red Rooftop 5 Town Church Lattice Town Tall Hexagonal @@ -154,7 +154,7 @@ Theater Exit Left Panel Theater Exit Right Panel Jungle Laser Shortcut Panel Jungle Popup Wall Control -River Monastery Garden Shortcut Panel +Jungle Monastery Garden Shortcut Panel Bunker Entry Panel Bunker Tinted Glass Door Panel Bunker Glass Room 3 @@ -186,8 +186,8 @@ Mountain Floor 2 Light Bridge Controller Near Mountain Floor 2 Light Bridge Controller Far Mountain Floor 2 Far Row 6 Mountain Bottom Floor Giant Puzzle -Mountain Bottom Floor Final Room Entry Left -Mountain Bottom Floor Final Room Entry Right +Mountain Bottom Floor Pillars Room Entry Left +Mountain Bottom Floor Pillars Room Entry Right Mountain Bottom Floor Discard Mountain Bottom Floor Rock Control Mountain Bottom Floor Caves Entry Panel diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt b/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt index 91a7132ec113..2059f43af62c 100644 --- a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt +++ b/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt @@ -76,8 +76,8 @@ Town Wooden Roof Lower Row 5 Town RGB House Entry Panel Town Church Entry Panel Town Maze Panel -Town Windmill Entry Panel -Town Sound Room Right +Windmill Entry Panel +Town RGB House Sound Room Right Town Red Rooftop 5 Town Church Lattice Town Tall Hexagonal @@ -87,7 +87,7 @@ Theater Exit Left Panel Theater Exit Right Panel Jungle Laser Shortcut Panel Jungle Popup Wall Control -River Monastery Garden Shortcut Panel +Jungle Monastery Garden Shortcut Panel Bunker Entry Panel Bunker Tinted Glass Door Panel Bunker Glass Room 3 @@ -119,8 +119,8 @@ Mountain Floor 2 Light Bridge Controller Near Mountain Floor 2 Light Bridge Controller Far Mountain Floor 2 Far Row 6 Mountain Bottom Floor Giant Puzzle -Mountain Bottom Floor Final Room Entry Left -Mountain Bottom Floor Final Room Entry Right +Mountain Bottom Floor Pillars Room Entry Left +Mountain Bottom Floor Pillars Room Entry Right Mountain Bottom Floor Discard Mountain Bottom Floor Rock Control Mountain Bottom Floor Caves Entry Panel diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt b/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt index 42258bca1a47..23501d20d3a7 100644 --- a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt +++ b/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt @@ -1,6 +1,6 @@ Items: Symmetry Island Panels -Tutorial Outpost Panels +Outside Tutorial Outpost Panels Desert Panels Quarry Outside Panels Quarry Stoneworks Panels diff --git a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt b/worlds/witness/settings/EP_Shuffle/EP_Sides.txt index 82ab63329500..d561ffdc183f 100644 --- a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt +++ b/worlds/witness/settings/EP_Shuffle/EP_Sides.txt @@ -16,12 +16,12 @@ Added Locations: 0xFFE23 (Treehouse Obelisk Side 4) 0xFFE24 (Treehouse Obelisk Side 5) 0xFFE25 (Treehouse Obelisk Side 6) -0xFFE30 (River Obelisk Side 1) -0xFFE31 (River Obelisk Side 2) -0xFFE32 (River Obelisk Side 3) -0xFFE33 (River Obelisk Side 4) -0xFFE34 (River Obelisk Side 5) -0xFFE35 (River Obelisk Side 6) +0xFFE30 (Mountainside Obelisk Side 1) +0xFFE31 (Mountainside Obelisk Side 2) +0xFFE32 (Mountainside Obelisk Side 3) +0xFFE33 (Mountainside Obelisk Side 4) +0xFFE34 (Mountainside Obelisk Side 5) +0xFFE35 (Mountainside Obelisk Side 6) 0xFFE40 (Quarry Obelisk Side 1) 0xFFE41 (Quarry Obelisk Side 2) 0xFFE42 (Quarry Obelisk Side 3) diff --git a/worlds/witness/settings/Exclusions/Vaults.txt b/worlds/witness/settings/Exclusions/Vaults.txt index f23a13183326..d9e5d28cd694 100644 --- a/worlds/witness/settings/Exclusions/Vaults.txt +++ b/worlds/witness/settings/Exclusions/Vaults.txt @@ -8,9 +8,9 @@ Disabled Locations: 0x00AFB (Shipwreck Vault) 0x03535 (Shipwreck Vault Box) 0x17BB4 (Shipwreck Vault Door) -0x15ADD (River Vault) -0x03702 (River Vault Box) -0x15287 (River Vault Door) +0x15ADD (Jungle Vault) +0x03702 (Jungle Vault Box) +0x15287 (Jungle Vault Door) 0x002A6 (Mountainside Vault) 0x03542 (Mountainside Vault Box) 0x00085 (Mountainside Vault Door) diff --git a/worlds/witness/settings/Postgame/Mountain_Lower.txt b/worlds/witness/settings/Postgame/Mountain_Lower.txt index 354e3feb82c3..aecddec5adde 100644 --- a/worlds/witness/settings/Postgame/Mountain_Lower.txt +++ b/worlds/witness/settings/Postgame/Mountain_Lower.txt @@ -7,9 +7,9 @@ Disabled Locations: 0x09EFF (Giant Puzzle Top Left) 0x09FDA (Giant Puzzle) 0x09F89 (Exit Door) -0x01983 (Final Room Entry Left) -0x01987 (Final Room Entry Right) -0x0C141 (Final Room Entry Door) +0x01983 (Pillars Room Entry Left) +0x01987 (Pillars Room Entry Right) +0x0C141 (Pillars Room Entry Door) 0x0383A (Right Pillar 1) 0x09E56 (Right Pillar 2) 0x09E5A (Right Pillar 3) diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py index 0e8d649af6ff..5a3e8b1b580e 100644 --- a/worlds/witness/static_logic.py +++ b/worlds/witness/static_logic.py @@ -56,6 +56,11 @@ def read_logic_file(self, lines): """ current_region = dict() + current_area = { + "name": "Misc", + "regions": [], + } + self.ALL_AREAS_BY_NAME["Misc"] = current_area for line in lines: if line == "" or line[0] == "#": @@ -67,6 +72,16 @@ def read_logic_file(self, lines): region_name = current_region["name"] self.ALL_REGIONS_BY_NAME[region_name] = current_region self.STATIC_CONNECTIONS_BY_REGION_NAME[region_name] = new_region_and_connections[1] + current_area["regions"].append(region_name) + continue + + if line[0] == "=": + area_name = line[2:-2] + current_area = { + "name": area_name, + "regions": [], + } + self.ALL_AREAS_BY_NAME[area_name] = current_area continue line_split = line.split(" - ") @@ -88,7 +103,8 @@ def read_logic_file(self, lines): "entity_hex": entity_hex, "region": None, "id": None, - "entityType": location_id + "entityType": location_id, + "area": current_area, } self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex] @@ -120,7 +136,6 @@ def read_logic_file(self, lines): location_type = "Laser" elif "Obelisk Side" in entity_name: location_type = "Obelisk Side" - full_entity_name = entity_name elif "EP" in entity_name: location_type = "EP" else: @@ -151,7 +166,8 @@ def read_logic_file(self, lines): "entity_hex": entity_hex, "region": current_region, "id": int(location_id), - "entityType": location_type + "entityType": location_type, + "area": current_area, } self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name @@ -167,6 +183,7 @@ def __init__(self, lines=None): # All regions with a list of panels in them and the connections to other regions, before logic adjustments self.ALL_REGIONS_BY_NAME = dict() + self.ALL_AREAS_BY_NAME = dict() self.STATIC_CONNECTIONS_BY_REGION_NAME = dict() self.ENTITIES_BY_HEX = dict() @@ -188,6 +205,7 @@ class StaticWitnessLogic: _progressive_lookup: Dict[str, str] = {} ALL_REGIONS_BY_NAME = dict() + ALL_AREAS_BY_NAME = dict() STATIC_CONNECTIONS_BY_REGION_NAME = dict() OBELISK_SIDE_ID_TO_EP_HEXES = dict() @@ -265,6 +283,7 @@ def __init__(self): self.parse_items() self.ALL_REGIONS_BY_NAME.update(self.sigma_normal.ALL_REGIONS_BY_NAME) + self.ALL_AREAS_BY_NAME.update(self.sigma_normal.ALL_AREAS_BY_NAME) self.STATIC_CONNECTIONS_BY_REGION_NAME.update(self.sigma_normal.STATIC_CONNECTIONS_BY_REGION_NAME) self.ENTITIES_BY_HEX.update(self.sigma_normal.ENTITIES_BY_HEX) diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py index fbb670fd0877..b1f1b6d83100 100644 --- a/worlds/witness/utils.py +++ b/worlds/witness/utils.py @@ -2,6 +2,21 @@ from math import floor from typing import List, Collection, FrozenSet, Tuple, Dict, Any, Set from pkgutil import get_data +from random import random + + +def weighted_sample(world_random: random, population: List, weights: List[float], k: int): + positions = range(len(population)) + indices = [] + while True: + needed = k - len(indices) + if not needed: + break + for i in world_random.choices(positions, weights, k=needed): + if weights[i]: + weights[i] = 0.0 + indices.append(i) + return [population[i] for i in indices] def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]: From 36cee91a2c620316c3da1b4233d499b8d4e2a6dc Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:53:13 -0500 Subject: [PATCH 15/66] DKC3: Long-overdue World code cleanup (#2820) Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com> --- worlds/dkc3/Locations.py | 7 +- worlds/dkc3/Options.py | 39 ++-- worlds/dkc3/Regions.py | 375 ++++++++++++++++++++------------------- worlds/dkc3/Rom.py | 56 +++--- worlds/dkc3/Rules.py | 33 ++-- worlds/dkc3/__init__.py | 57 +++--- 6 files changed, 287 insertions(+), 280 deletions(-) diff --git a/worlds/dkc3/Locations.py b/worlds/dkc3/Locations.py index e8d5409b1563..6d8833872b03 100644 --- a/worlds/dkc3/Locations.py +++ b/worlds/dkc3/Locations.py @@ -2,6 +2,7 @@ from BaseClasses import Location from .Names import LocationName +from worlds.AutoWorld import World class DKC3Location(Location): @@ -321,13 +322,13 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None location_table = {} -def setup_locations(world, player: int): +def setup_locations(world: World): location_table = {**level_location_table, **boss_location_table, **secret_cave_location_table} - if False:#world.include_trade_sequence[player].value: + if False:#world.options.include_trade_sequence: location_table.update({**brothers_bear_location_table}) - if world.kongsanity[player].value: + if world.options.kongsanity: location_table.update({**kong_location_table}) return location_table diff --git a/worlds/dkc3/Options.py b/worlds/dkc3/Options.py index 7c0f532cfc76..06be30cf15ae 100644 --- a/worlds/dkc3/Options.py +++ b/worlds/dkc3/Options.py @@ -1,6 +1,7 @@ +from dataclasses import dataclass import typing -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, PerGameCommonOptions class Goal(Choice): @@ -158,21 +159,21 @@ class StartingLifeCount(Range): default = 5 -dkc3_options: typing.Dict[str, type(Option)] = { - #"death_link": DeathLink, # Disabled - "goal": Goal, - #"include_trade_sequence": IncludeTradeSequence, # Disabled - "dk_coins_for_gyrocopter": DKCoinsForGyrocopter, - "krematoa_bonus_coin_cost": KrematoaBonusCoinCost, - "percentage_of_extra_bonus_coins": PercentageOfExtraBonusCoins, - "number_of_banana_birds": NumberOfBananaBirds, - "percentage_of_banana_birds": PercentageOfBananaBirds, - "kongsanity": KONGsanity, - "level_shuffle": LevelShuffle, - "difficulty": Difficulty, - "autosave": Autosave, - "merry": MERRY, - "music_shuffle": MusicShuffle, - "kong_palette_swap": KongPaletteSwap, - "starting_life_count": StartingLifeCount, -} +@dataclass +class DKC3Options(PerGameCommonOptions): + #death_link: DeathLink # Disabled + goal: Goal + #include_trade_sequence: IncludeTradeSequence # Disabled + dk_coins_for_gyrocopter: DKCoinsForGyrocopter + krematoa_bonus_coin_cost: KrematoaBonusCoinCost + percentage_of_extra_bonus_coins: PercentageOfExtraBonusCoins + number_of_banana_birds: NumberOfBananaBirds + percentage_of_banana_birds: PercentageOfBananaBirds + kongsanity: KONGsanity + level_shuffle: LevelShuffle + difficulty: Difficulty + autosave: Autosave + merry: MERRY + music_shuffle: MusicShuffle + kong_palette_swap: KongPaletteSwap + starting_life_count: StartingLifeCount diff --git a/worlds/dkc3/Regions.py b/worlds/dkc3/Regions.py index ca6545ca14cc..ae505b78d84b 100644 --- a/worlds/dkc3/Regions.py +++ b/worlds/dkc3/Regions.py @@ -4,38 +4,39 @@ from .Items import DKC3Item from .Locations import DKC3Location from .Names import LocationName, ItemName +from worlds.AutoWorld import World -def create_regions(world, player: int, active_locations): - menu_region = create_region(world, player, active_locations, 'Menu', None) +def create_regions(world: World, active_locations): + menu_region = create_region(world, active_locations, 'Menu', None) overworld_1_region_locations = {} - if world.goal[player] != "knautilus": + if world.options.goal != "knautilus": overworld_1_region_locations.update({LocationName.banana_bird_mother: []}) - overworld_1_region = create_region(world, player, active_locations, LocationName.overworld_1_region, + overworld_1_region = create_region(world, active_locations, LocationName.overworld_1_region, overworld_1_region_locations) overworld_2_region_locations = {} - overworld_2_region = create_region(world, player, active_locations, LocationName.overworld_2_region, + overworld_2_region = create_region(world, active_locations, LocationName.overworld_2_region, overworld_2_region_locations) overworld_3_region_locations = {} - overworld_3_region = create_region(world, player, active_locations, LocationName.overworld_3_region, + overworld_3_region = create_region(world, active_locations, LocationName.overworld_3_region, overworld_3_region_locations) overworld_4_region_locations = {} - overworld_4_region = create_region(world, player, active_locations, LocationName.overworld_4_region, + overworld_4_region = create_region(world, active_locations, LocationName.overworld_4_region, overworld_4_region_locations) - lake_orangatanga_region = create_region(world, player, active_locations, LocationName.lake_orangatanga_region, None) - kremwood_forest_region = create_region(world, player, active_locations, LocationName.kremwood_forest_region, None) - cotton_top_cove_region = create_region(world, player, active_locations, LocationName.cotton_top_cove_region, None) - mekanos_region = create_region(world, player, active_locations, LocationName.mekanos_region, None) - k3_region = create_region(world, player, active_locations, LocationName.k3_region, None) - razor_ridge_region = create_region(world, player, active_locations, LocationName.razor_ridge_region, None) - kaos_kore_region = create_region(world, player, active_locations, LocationName.kaos_kore_region, None) - krematoa_region = create_region(world, player, active_locations, LocationName.krematoa_region, None) + lake_orangatanga_region = create_region(world, active_locations, LocationName.lake_orangatanga_region, None) + kremwood_forest_region = create_region(world, active_locations, LocationName.kremwood_forest_region, None) + cotton_top_cove_region = create_region(world, active_locations, LocationName.cotton_top_cove_region, None) + mekanos_region = create_region(world, active_locations, LocationName.mekanos_region, None) + k3_region = create_region(world, active_locations, LocationName.k3_region, None) + razor_ridge_region = create_region(world, active_locations, LocationName.razor_ridge_region, None) + kaos_kore_region = create_region(world, active_locations, LocationName.kaos_kore_region, None) + krematoa_region = create_region(world, active_locations, LocationName.krematoa_region, None) lakeside_limbo_region_locations = { @@ -44,9 +45,9 @@ def create_regions(world, player: int, active_locations): LocationName.lakeside_limbo_bonus_2 : [0x657, 3], LocationName.lakeside_limbo_dk : [0x657, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: lakeside_limbo_region_locations[LocationName.lakeside_limbo_kong] = [] - lakeside_limbo_region = create_region(world, player, active_locations, LocationName.lakeside_limbo_region, + lakeside_limbo_region = create_region(world, active_locations, LocationName.lakeside_limbo_region, lakeside_limbo_region_locations) doorstop_dash_region_locations = { @@ -55,9 +56,9 @@ def create_regions(world, player: int, active_locations): LocationName.doorstop_dash_bonus_2 : [0x65A, 3], LocationName.doorstop_dash_dk : [0x65A, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: doorstop_dash_region_locations[LocationName.doorstop_dash_kong] = [] - doorstop_dash_region = create_region(world, player, active_locations, LocationName.doorstop_dash_region, + doorstop_dash_region = create_region(world, active_locations, LocationName.doorstop_dash_region, doorstop_dash_region_locations) tidal_trouble_region_locations = { @@ -66,9 +67,9 @@ def create_regions(world, player: int, active_locations): LocationName.tidal_trouble_bonus_2 : [0x659, 3], LocationName.tidal_trouble_dk : [0x659, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tidal_trouble_region_locations[LocationName.tidal_trouble_kong] = [] - tidal_trouble_region = create_region(world, player, active_locations, LocationName.tidal_trouble_region, + tidal_trouble_region = create_region(world, active_locations, LocationName.tidal_trouble_region, tidal_trouble_region_locations) skiddas_row_region_locations = { @@ -77,9 +78,9 @@ def create_regions(world, player: int, active_locations): LocationName.skiddas_row_bonus_2 : [0x65D, 3], LocationName.skiddas_row_dk : [0x65D, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: skiddas_row_region_locations[LocationName.skiddas_row_kong] = [] - skiddas_row_region = create_region(world, player, active_locations, LocationName.skiddas_row_region, + skiddas_row_region = create_region(world, active_locations, LocationName.skiddas_row_region, skiddas_row_region_locations) murky_mill_region_locations = { @@ -88,9 +89,9 @@ def create_regions(world, player: int, active_locations): LocationName.murky_mill_bonus_2 : [0x65C, 3], LocationName.murky_mill_dk : [0x65C, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: murky_mill_region_locations[LocationName.murky_mill_kong] = [] - murky_mill_region = create_region(world, player, active_locations, LocationName.murky_mill_region, + murky_mill_region = create_region(world, active_locations, LocationName.murky_mill_region, murky_mill_region_locations) barrel_shield_bust_up_region_locations = { @@ -99,9 +100,9 @@ def create_regions(world, player: int, active_locations): LocationName.barrel_shield_bust_up_bonus_2 : [0x662, 3], LocationName.barrel_shield_bust_up_dk : [0x662, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: barrel_shield_bust_up_region_locations[LocationName.barrel_shield_bust_up_kong] = [] - barrel_shield_bust_up_region = create_region(world, player, active_locations, + barrel_shield_bust_up_region = create_region(world, active_locations, LocationName.barrel_shield_bust_up_region, barrel_shield_bust_up_region_locations) @@ -111,9 +112,9 @@ def create_regions(world, player: int, active_locations): LocationName.riverside_race_bonus_2 : [0x664, 3], LocationName.riverside_race_dk : [0x664, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: riverside_race_region_locations[LocationName.riverside_race_kong] = [] - riverside_race_region = create_region(world, player, active_locations, LocationName.riverside_race_region, + riverside_race_region = create_region(world, active_locations, LocationName.riverside_race_region, riverside_race_region_locations) squeals_on_wheels_region_locations = { @@ -122,9 +123,9 @@ def create_regions(world, player: int, active_locations): LocationName.squeals_on_wheels_bonus_2 : [0x65B, 3], LocationName.squeals_on_wheels_dk : [0x65B, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: squeals_on_wheels_region_locations[LocationName.squeals_on_wheels_kong] = [] - squeals_on_wheels_region = create_region(world, player, active_locations, LocationName.squeals_on_wheels_region, + squeals_on_wheels_region = create_region(world, active_locations, LocationName.squeals_on_wheels_region, squeals_on_wheels_region_locations) springin_spiders_region_locations = { @@ -133,9 +134,9 @@ def create_regions(world, player: int, active_locations): LocationName.springin_spiders_bonus_2 : [0x661, 3], LocationName.springin_spiders_dk : [0x661, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: springin_spiders_region_locations[LocationName.springin_spiders_kong] = [] - springin_spiders_region = create_region(world, player, active_locations, LocationName.springin_spiders_region, + springin_spiders_region = create_region(world, active_locations, LocationName.springin_spiders_region, springin_spiders_region_locations) bobbing_barrel_brawl_region_locations = { @@ -144,9 +145,9 @@ def create_regions(world, player: int, active_locations): LocationName.bobbing_barrel_brawl_bonus_2 : [0x666, 3], LocationName.bobbing_barrel_brawl_dk : [0x666, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: bobbing_barrel_brawl_region_locations[LocationName.bobbing_barrel_brawl_kong] = [] - bobbing_barrel_brawl_region = create_region(world, player, active_locations, + bobbing_barrel_brawl_region = create_region(world, active_locations, LocationName.bobbing_barrel_brawl_region, bobbing_barrel_brawl_region_locations) @@ -156,9 +157,9 @@ def create_regions(world, player: int, active_locations): LocationName.bazzas_blockade_bonus_2 : [0x667, 3], LocationName.bazzas_blockade_dk : [0x667, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: bazzas_blockade_region_locations[LocationName.bazzas_blockade_kong] = [] - bazzas_blockade_region = create_region(world, player, active_locations, LocationName.bazzas_blockade_region, + bazzas_blockade_region = create_region(world, active_locations, LocationName.bazzas_blockade_region, bazzas_blockade_region_locations) rocket_barrel_ride_region_locations = { @@ -167,9 +168,9 @@ def create_regions(world, player: int, active_locations): LocationName.rocket_barrel_ride_bonus_2 : [0x66A, 3], LocationName.rocket_barrel_ride_dk : [0x66A, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: rocket_barrel_ride_region_locations[LocationName.rocket_barrel_ride_kong] = [] - rocket_barrel_ride_region = create_region(world, player, active_locations, LocationName.rocket_barrel_ride_region, + rocket_barrel_ride_region = create_region(world, active_locations, LocationName.rocket_barrel_ride_region, rocket_barrel_ride_region_locations) kreeping_klasps_region_locations = { @@ -178,9 +179,9 @@ def create_regions(world, player: int, active_locations): LocationName.kreeping_klasps_bonus_2 : [0x658, 3], LocationName.kreeping_klasps_dk : [0x658, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: kreeping_klasps_region_locations[LocationName.kreeping_klasps_kong] = [] - kreeping_klasps_region = create_region(world, player, active_locations, LocationName.kreeping_klasps_region, + kreeping_klasps_region = create_region(world, active_locations, LocationName.kreeping_klasps_region, kreeping_klasps_region_locations) tracker_barrel_trek_region_locations = { @@ -189,9 +190,9 @@ def create_regions(world, player: int, active_locations): LocationName.tracker_barrel_trek_bonus_2 : [0x66B, 3], LocationName.tracker_barrel_trek_dk : [0x66B, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tracker_barrel_trek_region_locations[LocationName.tracker_barrel_trek_kong] = [] - tracker_barrel_trek_region = create_region(world, player, active_locations, LocationName.tracker_barrel_trek_region, + tracker_barrel_trek_region = create_region(world, active_locations, LocationName.tracker_barrel_trek_region, tracker_barrel_trek_region_locations) fish_food_frenzy_region_locations = { @@ -200,9 +201,9 @@ def create_regions(world, player: int, active_locations): LocationName.fish_food_frenzy_bonus_2 : [0x668, 3], LocationName.fish_food_frenzy_dk : [0x668, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: fish_food_frenzy_region_locations[LocationName.fish_food_frenzy_kong] = [] - fish_food_frenzy_region = create_region(world, player, active_locations, LocationName.fish_food_frenzy_region, + fish_food_frenzy_region = create_region(world, active_locations, LocationName.fish_food_frenzy_region, fish_food_frenzy_region_locations) fire_ball_frenzy_region_locations = { @@ -211,9 +212,9 @@ def create_regions(world, player: int, active_locations): LocationName.fire_ball_frenzy_bonus_2 : [0x66D, 3], LocationName.fire_ball_frenzy_dk : [0x66D, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: fire_ball_frenzy_region_locations[LocationName.fire_ball_frenzy_kong] = [] - fire_ball_frenzy_region = create_region(world, player, active_locations, LocationName.fire_ball_frenzy_region, + fire_ball_frenzy_region = create_region(world, active_locations, LocationName.fire_ball_frenzy_region, fire_ball_frenzy_region_locations) demolition_drain_pipe_region_locations = { @@ -222,9 +223,9 @@ def create_regions(world, player: int, active_locations): LocationName.demolition_drain_pipe_bonus_2 : [0x672, 3], LocationName.demolition_drain_pipe_dk : [0x672, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: demolition_drain_pipe_region_locations[LocationName.demolition_drain_pipe_kong] = [] - demolition_drain_pipe_region = create_region(world, player, active_locations, + demolition_drain_pipe_region = create_region(world, active_locations, LocationName.demolition_drain_pipe_region, demolition_drain_pipe_region_locations) @@ -234,9 +235,9 @@ def create_regions(world, player: int, active_locations): LocationName.ripsaw_rage_bonus_2 : [0x660, 3], LocationName.ripsaw_rage_dk : [0x660, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: ripsaw_rage_region_locations[LocationName.ripsaw_rage_kong] = [] - ripsaw_rage_region = create_region(world, player, active_locations, LocationName.ripsaw_rage_region, + ripsaw_rage_region = create_region(world, active_locations, LocationName.ripsaw_rage_region, ripsaw_rage_region_locations) blazing_bazookas_region_locations = { @@ -245,9 +246,9 @@ def create_regions(world, player: int, active_locations): LocationName.blazing_bazookas_bonus_2 : [0x66E, 3], LocationName.blazing_bazookas_dk : [0x66E, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: blazing_bazookas_region_locations[LocationName.blazing_bazookas_kong] = [] - blazing_bazookas_region = create_region(world, player, active_locations, LocationName.blazing_bazookas_region, + blazing_bazookas_region = create_region(world, active_locations, LocationName.blazing_bazookas_region, blazing_bazookas_region_locations) low_g_labyrinth_region_locations = { @@ -256,9 +257,9 @@ def create_regions(world, player: int, active_locations): LocationName.low_g_labyrinth_bonus_2 : [0x670, 3], LocationName.low_g_labyrinth_dk : [0x670, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: low_g_labyrinth_region_locations[LocationName.low_g_labyrinth_kong] = [] - low_g_labyrinth_region = create_region(world, player, active_locations, LocationName.low_g_labyrinth_region, + low_g_labyrinth_region = create_region(world, active_locations, LocationName.low_g_labyrinth_region, low_g_labyrinth_region_locations) krevice_kreepers_region_locations = { @@ -267,9 +268,9 @@ def create_regions(world, player: int, active_locations): LocationName.krevice_kreepers_bonus_2 : [0x673, 3], LocationName.krevice_kreepers_dk : [0x673, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: krevice_kreepers_region_locations[LocationName.krevice_kreepers_kong] = [] - krevice_kreepers_region = create_region(world, player, active_locations, LocationName.krevice_kreepers_region, + krevice_kreepers_region = create_region(world, active_locations, LocationName.krevice_kreepers_region, krevice_kreepers_region_locations) tearaway_toboggan_region_locations = { @@ -278,9 +279,9 @@ def create_regions(world, player: int, active_locations): LocationName.tearaway_toboggan_bonus_2 : [0x65F, 3], LocationName.tearaway_toboggan_dk : [0x65F, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tearaway_toboggan_region_locations[LocationName.tearaway_toboggan_kong] = [] - tearaway_toboggan_region = create_region(world, player, active_locations, LocationName.tearaway_toboggan_region, + tearaway_toboggan_region = create_region(world, active_locations, LocationName.tearaway_toboggan_region, tearaway_toboggan_region_locations) barrel_drop_bounce_region_locations = { @@ -289,9 +290,9 @@ def create_regions(world, player: int, active_locations): LocationName.barrel_drop_bounce_bonus_2 : [0x66C, 3], LocationName.barrel_drop_bounce_dk : [0x66C, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: barrel_drop_bounce_region_locations[LocationName.barrel_drop_bounce_kong] = [] - barrel_drop_bounce_region = create_region(world, player, active_locations, LocationName.barrel_drop_bounce_region, + barrel_drop_bounce_region = create_region(world, active_locations, LocationName.barrel_drop_bounce_region, barrel_drop_bounce_region_locations) krack_shot_kroc_region_locations = { @@ -300,9 +301,9 @@ def create_regions(world, player: int, active_locations): LocationName.krack_shot_kroc_bonus_2 : [0x66F, 3], LocationName.krack_shot_kroc_dk : [0x66F, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: krack_shot_kroc_region_locations[LocationName.krack_shot_kroc_kong] = [] - krack_shot_kroc_region = create_region(world, player, active_locations, LocationName.krack_shot_kroc_region, + krack_shot_kroc_region = create_region(world, active_locations, LocationName.krack_shot_kroc_region, krack_shot_kroc_region_locations) lemguin_lunge_region_locations = { @@ -311,9 +312,9 @@ def create_regions(world, player: int, active_locations): LocationName.lemguin_lunge_bonus_2 : [0x65E, 3], LocationName.lemguin_lunge_dk : [0x65E, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: lemguin_lunge_region_locations[LocationName.lemguin_lunge_kong] = [] - lemguin_lunge_region = create_region(world, player, active_locations, LocationName.lemguin_lunge_region, + lemguin_lunge_region = create_region(world, active_locations, LocationName.lemguin_lunge_region, lemguin_lunge_region_locations) buzzer_barrage_region_locations = { @@ -322,9 +323,9 @@ def create_regions(world, player: int, active_locations): LocationName.buzzer_barrage_bonus_2 : [0x676, 3], LocationName.buzzer_barrage_dk : [0x676, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: buzzer_barrage_region_locations[LocationName.buzzer_barrage_kong] = [] - buzzer_barrage_region = create_region(world, player, active_locations, LocationName.buzzer_barrage_region, + buzzer_barrage_region = create_region(world, active_locations, LocationName.buzzer_barrage_region, buzzer_barrage_region_locations) kong_fused_cliffs_region_locations = { @@ -333,9 +334,9 @@ def create_regions(world, player: int, active_locations): LocationName.kong_fused_cliffs_bonus_2 : [0x674, 3], LocationName.kong_fused_cliffs_dk : [0x674, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: kong_fused_cliffs_region_locations[LocationName.kong_fused_cliffs_kong] = [] - kong_fused_cliffs_region = create_region(world, player, active_locations, LocationName.kong_fused_cliffs_region, + kong_fused_cliffs_region = create_region(world, active_locations, LocationName.kong_fused_cliffs_region, kong_fused_cliffs_region_locations) floodlit_fish_region_locations = { @@ -344,9 +345,9 @@ def create_regions(world, player: int, active_locations): LocationName.floodlit_fish_bonus_2 : [0x669, 3], LocationName.floodlit_fish_dk : [0x669, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: floodlit_fish_region_locations[LocationName.floodlit_fish_kong] = [] - floodlit_fish_region = create_region(world, player, active_locations, LocationName.floodlit_fish_region, + floodlit_fish_region = create_region(world, active_locations, LocationName.floodlit_fish_region, floodlit_fish_region_locations) pothole_panic_region_locations = { @@ -355,9 +356,9 @@ def create_regions(world, player: int, active_locations): LocationName.pothole_panic_bonus_2 : [0x677, 3], LocationName.pothole_panic_dk : [0x677, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: pothole_panic_region_locations[LocationName.pothole_panic_kong] = [] - pothole_panic_region = create_region(world, player, active_locations, LocationName.pothole_panic_region, + pothole_panic_region = create_region(world, active_locations, LocationName.pothole_panic_region, pothole_panic_region_locations) ropey_rumpus_region_locations = { @@ -366,9 +367,9 @@ def create_regions(world, player: int, active_locations): LocationName.ropey_rumpus_bonus_2 : [0x675, 3], LocationName.ropey_rumpus_dk : [0x675, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: ropey_rumpus_region_locations[LocationName.ropey_rumpus_kong] = [] - ropey_rumpus_region = create_region(world, player, active_locations, LocationName.ropey_rumpus_region, + ropey_rumpus_region = create_region(world, active_locations, LocationName.ropey_rumpus_region, ropey_rumpus_region_locations) konveyor_rope_clash_region_locations = { @@ -377,9 +378,9 @@ def create_regions(world, player: int, active_locations): LocationName.konveyor_rope_clash_bonus_2 : [0x657, 3], LocationName.konveyor_rope_clash_dk : [0x657, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: konveyor_rope_clash_region_locations[LocationName.konveyor_rope_clash_kong] = [] - konveyor_rope_clash_region = create_region(world, player, active_locations, LocationName.konveyor_rope_clash_region, + konveyor_rope_clash_region = create_region(world, active_locations, LocationName.konveyor_rope_clash_region, konveyor_rope_clash_region_locations) creepy_caverns_region_locations = { @@ -388,9 +389,9 @@ def create_regions(world, player: int, active_locations): LocationName.creepy_caverns_bonus_2 : [0x678, 3], LocationName.creepy_caverns_dk : [0x678, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: creepy_caverns_region_locations[LocationName.creepy_caverns_kong] = [] - creepy_caverns_region = create_region(world, player, active_locations, LocationName.creepy_caverns_region, + creepy_caverns_region = create_region(world, active_locations, LocationName.creepy_caverns_region, creepy_caverns_region_locations) lightning_lookout_region_locations = { @@ -399,9 +400,9 @@ def create_regions(world, player: int, active_locations): LocationName.lightning_lookout_bonus_2 : [0x665, 3], LocationName.lightning_lookout_dk : [0x665, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: lightning_lookout_region_locations[LocationName.lightning_lookout_kong] = [] - lightning_lookout_region = create_region(world, player, active_locations, LocationName.lightning_lookout_region, + lightning_lookout_region = create_region(world, active_locations, LocationName.lightning_lookout_region, lightning_lookout_region_locations) koindozer_klamber_region_locations = { @@ -410,9 +411,9 @@ def create_regions(world, player: int, active_locations): LocationName.koindozer_klamber_bonus_2 : [0x679, 3], LocationName.koindozer_klamber_dk : [0x679, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: koindozer_klamber_region_locations[LocationName.koindozer_klamber_kong] = [] - koindozer_klamber_region = create_region(world, player, active_locations, LocationName.koindozer_klamber_region, + koindozer_klamber_region = create_region(world, active_locations, LocationName.koindozer_klamber_region, koindozer_klamber_region_locations) poisonous_pipeline_region_locations = { @@ -421,9 +422,9 @@ def create_regions(world, player: int, active_locations): LocationName.poisonous_pipeline_bonus_2 : [0x671, 3], LocationName.poisonous_pipeline_dk : [0x671, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: poisonous_pipeline_region_locations[LocationName.poisonous_pipeline_kong] = [] - poisonous_pipeline_region = create_region(world, player, active_locations, LocationName.poisonous_pipeline_region, + poisonous_pipeline_region = create_region(world, active_locations, LocationName.poisonous_pipeline_region, poisonous_pipeline_region_locations) stampede_sprint_region_locations = { @@ -433,9 +434,9 @@ def create_regions(world, player: int, active_locations): LocationName.stampede_sprint_bonus_3 : [0x67B, 4], LocationName.stampede_sprint_dk : [0x67B, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: stampede_sprint_region_locations[LocationName.stampede_sprint_kong] = [] - stampede_sprint_region = create_region(world, player, active_locations, LocationName.stampede_sprint_region, + stampede_sprint_region = create_region(world, active_locations, LocationName.stampede_sprint_region, stampede_sprint_region_locations) criss_cross_cliffs_region_locations = { @@ -444,9 +445,9 @@ def create_regions(world, player: int, active_locations): LocationName.criss_cross_cliffs_bonus_2 : [0x67C, 3], LocationName.criss_cross_cliffs_dk : [0x67C, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: criss_cross_cliffs_region_locations[LocationName.criss_cross_cliffs_kong] = [] - criss_cross_cliffs_region = create_region(world, player, active_locations, LocationName.criss_cross_cliffs_region, + criss_cross_cliffs_region = create_region(world, active_locations, LocationName.criss_cross_cliffs_region, criss_cross_cliffs_region_locations) tyrant_twin_tussle_region_locations = { @@ -456,9 +457,9 @@ def create_regions(world, player: int, active_locations): LocationName.tyrant_twin_tussle_bonus_3 : [0x67D, 4], LocationName.tyrant_twin_tussle_dk : [0x67D, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tyrant_twin_tussle_region_locations[LocationName.tyrant_twin_tussle_kong] = [] - tyrant_twin_tussle_region = create_region(world, player, active_locations, LocationName.tyrant_twin_tussle_region, + tyrant_twin_tussle_region = create_region(world, active_locations, LocationName.tyrant_twin_tussle_region, tyrant_twin_tussle_region_locations) swoopy_salvo_region_locations = { @@ -468,147 +469,147 @@ def create_regions(world, player: int, active_locations): LocationName.swoopy_salvo_bonus_3 : [0x663, 4], LocationName.swoopy_salvo_dk : [0x663, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: swoopy_salvo_region_locations[LocationName.swoopy_salvo_kong] = [] - swoopy_salvo_region = create_region(world, player, active_locations, LocationName.swoopy_salvo_region, + swoopy_salvo_region = create_region(world, active_locations, LocationName.swoopy_salvo_region, swoopy_salvo_region_locations) rocket_rush_region_locations = { LocationName.rocket_rush_flag : [0x67E, 1], LocationName.rocket_rush_dk : [0x67E, 5], } - rocket_rush_region = create_region(world, player, active_locations, LocationName.rocket_rush_region, + rocket_rush_region = create_region(world, active_locations, LocationName.rocket_rush_region, rocket_rush_region_locations) belchas_barn_region_locations = { LocationName.belchas_barn: [0x64F, 1], } - belchas_barn_region = create_region(world, player, active_locations, LocationName.belchas_barn_region, + belchas_barn_region = create_region(world, active_locations, LocationName.belchas_barn_region, belchas_barn_region_locations) arichs_ambush_region_locations = { LocationName.arichs_ambush: [0x650, 1], } - arichs_ambush_region = create_region(world, player, active_locations, LocationName.arichs_ambush_region, + arichs_ambush_region = create_region(world, active_locations, LocationName.arichs_ambush_region, arichs_ambush_region_locations) squirts_showdown_region_locations = { LocationName.squirts_showdown: [0x651, 1], } - squirts_showdown_region = create_region(world, player, active_locations, LocationName.squirts_showdown_region, + squirts_showdown_region = create_region(world, active_locations, LocationName.squirts_showdown_region, squirts_showdown_region_locations) kaos_karnage_region_locations = { LocationName.kaos_karnage: [0x652, 1], } - kaos_karnage_region = create_region(world, player, active_locations, LocationName.kaos_karnage_region, + kaos_karnage_region = create_region(world, active_locations, LocationName.kaos_karnage_region, kaos_karnage_region_locations) bleaks_house_region_locations = { LocationName.bleaks_house: [0x653, 1], } - bleaks_house_region = create_region(world, player, active_locations, LocationName.bleaks_house_region, + bleaks_house_region = create_region(world, active_locations, LocationName.bleaks_house_region, bleaks_house_region_locations) barboss_barrier_region_locations = { LocationName.barboss_barrier: [0x654, 1], } - barboss_barrier_region = create_region(world, player, active_locations, LocationName.barboss_barrier_region, + barboss_barrier_region = create_region(world, active_locations, LocationName.barboss_barrier_region, barboss_barrier_region_locations) kastle_kaos_region_locations = { LocationName.kastle_kaos: [0x655, 1], } - kastle_kaos_region = create_region(world, player, active_locations, LocationName.kastle_kaos_region, + kastle_kaos_region = create_region(world, active_locations, LocationName.kastle_kaos_region, kastle_kaos_region_locations) knautilus_region_locations = { LocationName.knautilus: [0x656, 1], } - knautilus_region = create_region(world, player, active_locations, LocationName.knautilus_region, + knautilus_region = create_region(world, active_locations, LocationName.knautilus_region, knautilus_region_locations) belchas_burrow_region_locations = { LocationName.belchas_burrow: [0x647, 1], } - belchas_burrow_region = create_region(world, player, active_locations, LocationName.belchas_burrow_region, + belchas_burrow_region = create_region(world, active_locations, LocationName.belchas_burrow_region, belchas_burrow_region_locations) kong_cave_region_locations = { LocationName.kong_cave: [0x645, 1], } - kong_cave_region = create_region(world, player, active_locations, LocationName.kong_cave_region, + kong_cave_region = create_region(world, active_locations, LocationName.kong_cave_region, kong_cave_region_locations) undercover_cove_region_locations = { LocationName.undercover_cove: [0x644, 1], } - undercover_cove_region = create_region(world, player, active_locations, LocationName.undercover_cove_region, + undercover_cove_region = create_region(world, active_locations, LocationName.undercover_cove_region, undercover_cove_region_locations) ks_cache_region_locations = { LocationName.ks_cache: [0x642, 1], } - ks_cache_region = create_region(world, player, active_locations, LocationName.ks_cache_region, + ks_cache_region = create_region(world, active_locations, LocationName.ks_cache_region, ks_cache_region_locations) hill_top_hoard_region_locations = { LocationName.hill_top_hoard: [0x643, 1], } - hill_top_hoard_region = create_region(world, player, active_locations, LocationName.hill_top_hoard_region, + hill_top_hoard_region = create_region(world, active_locations, LocationName.hill_top_hoard_region, hill_top_hoard_region_locations) bounty_beach_region_locations = { LocationName.bounty_beach: [0x646, 1], } - bounty_beach_region = create_region(world, player, active_locations, LocationName.bounty_beach_region, + bounty_beach_region = create_region(world, active_locations, LocationName.bounty_beach_region, bounty_beach_region_locations) smugglers_cove_region_locations = { LocationName.smugglers_cove: [0x648, 1], } - smugglers_cove_region = create_region(world, player, active_locations, LocationName.smugglers_cove_region, + smugglers_cove_region = create_region(world, active_locations, LocationName.smugglers_cove_region, smugglers_cove_region_locations) arichs_hoard_region_locations = { LocationName.arichs_hoard: [0x649, 1], } - arichs_hoard_region = create_region(world, player, active_locations, LocationName.arichs_hoard_region, + arichs_hoard_region = create_region(world, active_locations, LocationName.arichs_hoard_region, arichs_hoard_region_locations) bounty_bay_region_locations = { LocationName.bounty_bay: [0x64A, 1], } - bounty_bay_region = create_region(world, player, active_locations, LocationName.bounty_bay_region, + bounty_bay_region = create_region(world, active_locations, LocationName.bounty_bay_region, bounty_bay_region_locations) sky_high_secret_region_locations = {} - if False:#world.include_trade_sequence[player]: + if False:#world.options.include_trade_sequence: sky_high_secret_region_locations[LocationName.sky_high_secret] = [0x64B, 1] - sky_high_secret_region = create_region(world, player, active_locations, LocationName.sky_high_secret_region, + sky_high_secret_region = create_region(world, active_locations, LocationName.sky_high_secret_region, sky_high_secret_region_locations) glacial_grotto_region_locations = { LocationName.glacial_grotto: [0x64C, 1], } - glacial_grotto_region = create_region(world, player, active_locations, LocationName.glacial_grotto_region, + glacial_grotto_region = create_region(world, active_locations, LocationName.glacial_grotto_region, glacial_grotto_region_locations) cifftop_cache_region_locations = {} - if False:#world.include_trade_sequence[player]: + if False:#world.options.include_trade_sequence: cifftop_cache_region_locations[LocationName.cifftop_cache] = [0x64D, 1] - cifftop_cache_region = create_region(world, player, active_locations, LocationName.cifftop_cache_region, + cifftop_cache_region = create_region(world, active_locations, LocationName.cifftop_cache_region, cifftop_cache_region_locations) sewer_stockpile_region_locations = { LocationName.sewer_stockpile: [0x64E, 1], } - sewer_stockpile_region = create_region(world, player, active_locations, LocationName.sewer_stockpile_region, + sewer_stockpile_region = create_region(world, active_locations, LocationName.sewer_stockpile_region, sewer_stockpile_region_locations) # Set up the regions correctly. - world.regions += [ + world.multiworld.regions += [ menu_region, overworld_1_region, overworld_2_region, @@ -693,7 +694,7 @@ def create_regions(world, player: int, active_locations): blue_region_locations = {} blizzard_region_locations = {} - if False:#world.include_trade_sequence[player]: + if False:#world.options.include_trade_sequence: bazaar_region_locations.update({ LocationName.bazaars_general_store_1: [0x615, 2, True], LocationName.bazaars_general_store_2: [0x615, 3, True], @@ -713,19 +714,19 @@ def create_regions(world, player: int, active_locations): blizzard_region_locations[LocationName.blizzards_basecamp] = [0x625, 4, True] - bazaar_region = create_region(world, player, active_locations, LocationName.bazaar_region, bazaar_region_locations) - bramble_region = create_region(world, player, active_locations, LocationName.bramble_region, + bazaar_region = create_region(world, active_locations, LocationName.bazaar_region, bazaar_region_locations) + bramble_region = create_region(world, active_locations, LocationName.bramble_region, bramble_region_locations) - flower_spot_region = create_region(world, player, active_locations, LocationName.flower_spot_region, + flower_spot_region = create_region(world, active_locations, LocationName.flower_spot_region, flower_spot_region_locations) - barter_region = create_region(world, player, active_locations, LocationName.barter_region, barter_region_locations) - barnacle_region = create_region(world, player, active_locations, LocationName.barnacle_region, + barter_region = create_region(world, active_locations, LocationName.barter_region, barter_region_locations) + barnacle_region = create_region(world, active_locations, LocationName.barnacle_region, barnacle_region_locations) - blue_region = create_region(world, player, active_locations, LocationName.blue_region, blue_region_locations) - blizzard_region = create_region(world, player, active_locations, LocationName.blizzard_region, + blue_region = create_region(world, active_locations, LocationName.blue_region, blue_region_locations) + blizzard_region = create_region(world, active_locations, LocationName.blizzard_region, blizzard_region_locations) - world.regions += [ + world.multiworld.regions += [ bazaar_region, bramble_region, flower_spot_region, @@ -736,41 +737,41 @@ def create_regions(world, player: int, active_locations): ] -def connect_regions(world, player, level_list): +def connect_regions(world: World, level_list): names: typing.Dict[str, int] = {} # Overworld - connect(world, player, names, 'Menu', LocationName.overworld_1_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_2_region, - lambda state: (state.has(ItemName.progressive_boat, player, 1))) - connect(world, player, names, LocationName.overworld_2_region, LocationName.overworld_3_region, - lambda state: (state.has(ItemName.progressive_boat, player, 3))) - connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_4_region, - lambda state: (state.has(ItemName.dk_coin, player, world.dk_coins_for_gyrocopter[player].value) and - state.has(ItemName.progressive_boat, player, 3))) + connect(world, world.player, names, 'Menu', LocationName.overworld_1_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_2_region, + lambda state: (state.has(ItemName.progressive_boat, world.player, 1))) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.overworld_3_region, + lambda state: (state.has(ItemName.progressive_boat, world.player, 3))) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_4_region, + lambda state: (state.has(ItemName.dk_coin, world.player, world.options.dk_coins_for_gyrocopter.value) and + state.has(ItemName.progressive_boat, world.player, 3))) # World Connections - connect(world, player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.bazaar_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bazaar_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.mekanos_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.kong_cave_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.bramble_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.mekanos_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.kong_cave_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.bramble_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.k3_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.krematoa_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.flower_spot_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.barter_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.k3_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.krematoa_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.flower_spot_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.barter_region) - connect(world, player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region) - connect(world, player, names, LocationName.overworld_4_region, LocationName.ks_cache_region) - connect(world, player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region) + connect(world, world.player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region) + connect(world, world.player, names, LocationName.overworld_4_region, LocationName.ks_cache_region) + connect(world, world.player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region) # Lake Orangatanga Connections @@ -786,7 +787,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(lake_orangatanga_levels)): - connect(world, player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i]) + connect(world, world.player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i]) # Kremwood Forest Connections kremwood_forest_levels = [ @@ -800,10 +801,10 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(kremwood_forest_levels) - 1): - connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i]) + connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i]) - connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1], - lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", player))) + connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1], + lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", world.player))) # Cotton-Top Cove Connections cotton_top_cove_levels = [ @@ -818,7 +819,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(cotton_top_cove_levels)): - connect(world, player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i]) + connect(world, world.player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i]) # Mekanos Connections mekanos_levels = [ @@ -831,14 +832,14 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(mekanos_levels)): - connect(world, player, names, LocationName.mekanos_region, mekanos_levels[i]) + connect(world, world.player, names, LocationName.mekanos_region, mekanos_levels[i]) - if False:#world.include_trade_sequence[player]: - connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, - lambda state: (state.has(ItemName.bowling_ball, player, 1))) + if False:#world.options.include_trade_sequence: + connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, + lambda state: (state.has(ItemName.bowling_ball, world.player, 1))) else: - connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, - lambda state: (state.can_reach(LocationName.bleaks_house, "Location", player))) + connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, + lambda state: (state.can_reach(LocationName.bleaks_house, "Location", world.player))) # K3 Connections k3_levels = [ @@ -853,7 +854,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(k3_levels)): - connect(world, player, names, LocationName.k3_region, k3_levels[i]) + connect(world, world.player, names, LocationName.k3_region, k3_levels[i]) # Razor Ridge Connections razor_ridge_levels = [ @@ -866,13 +867,13 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(razor_ridge_levels)): - connect(world, player, names, LocationName.razor_ridge_region, razor_ridge_levels[i]) + connect(world, world.player, names, LocationName.razor_ridge_region, razor_ridge_levels[i]) - if False:#world.include_trade_sequence[player]: - connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region, - lambda state: (state.has(ItemName.wrench, player, 1))) + if False:#world.options.include_trade_sequence: + connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region, + lambda state: (state.has(ItemName.wrench, world.player, 1))) else: - connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region) + connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region) # KAOS Kore Connections kaos_kore_levels = [ @@ -885,7 +886,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(kaos_kore_levels)): - connect(world, player, names, LocationName.kaos_kore_region, kaos_kore_levels[i]) + connect(world, world.player, names, LocationName.kaos_kore_region, kaos_kore_levels[i]) # Krematoa Connections krematoa_levels = [ @@ -897,22 +898,22 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(krematoa_levels)): - connect(world, player, names, LocationName.krematoa_region, krematoa_levels[i], - lambda state, i=i: (state.has(ItemName.bonus_coin, player, world.krematoa_bonus_coin_cost[player].value * (i+1)))) + connect(world, world.player, names, LocationName.krematoa_region, krematoa_levels[i], + lambda state, i=i: (state.has(ItemName.bonus_coin, world.player, world.options.krematoa_bonus_coin_cost.value * (i+1)))) - if world.goal[player] == "knautilus": - connect(world, player, names, LocationName.kaos_kore_region, LocationName.knautilus_region) - connect(world, player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region, - lambda state: (state.has(ItemName.krematoa_cog, player, 5))) + if world.options.goal == "knautilus": + connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.knautilus_region) + connect(world, world.player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region, + lambda state: (state.has(ItemName.krematoa_cog, world.player, 5))) else: - connect(world, player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region) - connect(world, player, names, LocationName.krematoa_region, LocationName.knautilus_region, - lambda state: (state.has(ItemName.krematoa_cog, player, 5))) + connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region) + connect(world, world.player, names, LocationName.krematoa_region, LocationName.knautilus_region, + lambda state: (state.has(ItemName.krematoa_cog, world.player, 5))) -def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None): +def create_region(world: World, active_locations, name: str, locations=None): # Shamelessly stolen from the ROR2 definition - ret = Region(name, player, world) + ret = Region(name, world.player, world.multiworld) if locations: for locationName, locationData in locations.items(): loc_id = active_locations.get(locationName, 0) @@ -921,16 +922,16 @@ def create_region(world: MultiWorld, player: int, active_locations, name: str, l loc_bit = locationData[1] if (len(locationData) > 1) else 0 loc_invert = locationData[2] if (len(locationData) > 2) else False - location = DKC3Location(player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert) + location = DKC3Location(world.player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert) ret.locations.append(location) return ret -def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, +def connect(world: World, player: int, used_names: typing.Dict[str, int], source: str, target: str, rule: typing.Optional[typing.Callable] = None): - source_region = world.get_region(source, player) - target_region = world.get_region(target, player) + source_region = world.multiworld.get_region(source, player) + target_region = world.multiworld.get_region(target, player) if target not in used_names: used_names[target] = 1 diff --git a/worlds/dkc3/Rom.py b/worlds/dkc3/Rom.py index 4255a0a38280..efe8033d0fa5 100644 --- a/worlds/dkc3/Rom.py +++ b/worlds/dkc3/Rom.py @@ -1,5 +1,6 @@ import Utils from Utils import read_snes_rom +from worlds.AutoWorld import World from worlds.Files import APDeltaPatch from .Locations import lookup_id_to_name, all_locations from .Levels import level_list, level_dict @@ -475,11 +476,10 @@ def read_from_file(self, file): -def patch_rom(world, rom, player, active_level_list): - local_random = world.per_slot_randoms[player] +def patch_rom(world: World, rom: LocalRom, active_level_list): # Boomer Costs - bonus_coin_cost = world.krematoa_bonus_coin_cost[player] + bonus_coin_cost = world.options.krematoa_bonus_coin_cost inverted_bonus_coin_cost = 0x100 - bonus_coin_cost rom.write_byte(0x3498B9, inverted_bonus_coin_cost) rom.write_byte(0x3498BA, inverted_bonus_coin_cost) @@ -491,7 +491,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_byte(0x349862, bonus_coin_cost) # Gyrocopter Costs - dk_coin_cost = world.dk_coins_for_gyrocopter[player] + dk_coin_cost = world.options.dk_coins_for_gyrocopter rom.write_byte(0x3484A6, dk_coin_cost) rom.write_byte(0x3484D5, dk_coin_cost) rom.write_byte(0x3484D7, 0x90) @@ -508,8 +508,8 @@ def patch_rom(world, rom, player, active_level_list): rom.write_bytes(0x34ACD0, bytearray([0xEA, 0xEA])) # Banana Bird Costs - if world.goal[player] == "banana_bird_hunt": - banana_bird_cost = math.floor(world.number_of_banana_birds[player] * world.percentage_of_banana_birds[player] / 100.0) + if world.options.goal == "banana_bird_hunt": + banana_bird_cost = math.floor(world.options.number_of_banana_birds * world.options.percentage_of_banana_birds / 100.0) rom.write_byte(0x34AB85, banana_bird_cost) rom.write_byte(0x329FD8, banana_bird_cost) rom.write_byte(0x32A025, banana_bird_cost) @@ -528,65 +528,65 @@ def patch_rom(world, rom, player, active_level_list): # Palette Swap rom.write_byte(0x3B96A5, 0xD0) - if world.kong_palette_swap[player] == "default": + if world.options.kong_palette_swap == "default": rom.write_byte(0x3B96A9, 0x00) rom.write_byte(0x3B96A8, 0x00) - elif world.kong_palette_swap[player] == "purple": + elif world.options.kong_palette_swap == "purple": rom.write_byte(0x3B96A9, 0x00) rom.write_byte(0x3B96A8, 0x3C) - elif world.kong_palette_swap[player] == "spooky": + elif world.options.kong_palette_swap == "spooky": rom.write_byte(0x3B96A9, 0x00) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "dark": + elif world.options.kong_palette_swap == "dark": rom.write_byte(0x3B96A9, 0x05) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "chocolate": + elif world.options.kong_palette_swap == "chocolate": rom.write_byte(0x3B96A9, 0x1D) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "shadow": + elif world.options.kong_palette_swap == "shadow": rom.write_byte(0x3B96A9, 0x45) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "red_gold": + elif world.options.kong_palette_swap == "red_gold": rom.write_byte(0x3B96A9, 0x5D) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "gbc": + elif world.options.kong_palette_swap == "gbc": rom.write_byte(0x3B96A9, 0x20) rom.write_byte(0x3B96A8, 0x3C) - elif world.kong_palette_swap[player] == "halloween": + elif world.options.kong_palette_swap == "halloween": rom.write_byte(0x3B96A9, 0x70) rom.write_byte(0x3B96A8, 0x3C) - if world.music_shuffle[player]: + if world.options.music_shuffle: for address in music_rom_data: - rand_song = local_random.choice(level_music_ids) + rand_song = world.random.choice(level_music_ids) rom.write_byte(address, rand_song) # Starting Lives - rom.write_byte(0x9130, world.starting_life_count[player].value) - rom.write_byte(0x913B, world.starting_life_count[player].value) + rom.write_byte(0x9130, world.options.starting_life_count.value) + rom.write_byte(0x913B, world.options.starting_life_count.value) # Cheat options cheat_bytes = [0x00, 0x00] - if world.merry[player]: + if world.options.merry: cheat_bytes[0] |= 0x01 - if world.autosave[player]: + if world.options.autosave: cheat_bytes[0] |= 0x02 - if world.difficulty[player] == "tufst": + if world.options.difficulty == "tufst": cheat_bytes[0] |= 0x80 cheat_bytes[1] |= 0x80 - elif world.difficulty[player] == "hardr": + elif world.options.difficulty == "hardr": cheat_bytes[0] |= 0x00 cheat_bytes[1] |= 0x00 - elif world.difficulty[player] == "norml": + elif world.options.difficulty == "norml": cheat_bytes[1] |= 0x40 rom.write_bytes(0x8303, bytearray(cheat_bytes)) # Handle Level Shuffle Here - if world.level_shuffle[player]: + if world.options.level_shuffle: for i in range(len(active_level_list)): rom.write_byte(level_dict[level_list[i]].nameIDAddress, level_dict[active_level_list[i]].nameID) rom.write_byte(level_dict[level_list[i]].levelIDAddress, level_dict[active_level_list[i]].levelID) @@ -611,7 +611,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_byte(0x34C213, (0x32 + level_dict[active_level_list[25]].levelID)) rom.write_byte(0x34C21B, (0x32 + level_dict[active_level_list[26]].levelID)) - if world.goal[player] == "knautilus": + if world.options.goal == "knautilus": # Swap Kastle KAOS and Knautilus rom.write_byte(0x34D4E1, 0xC2) rom.write_byte(0x34D4E2, 0x24) @@ -621,7 +621,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_byte(0x32F339, 0x55) # Handle KONGsanity Here - if world.kongsanity[player]: + if world.options.kongsanity: # Arich's Hoard KONGsanity fix rom.write_bytes(0x34BA8C, bytearray([0xEA, 0xEA])) @@ -668,7 +668,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_bytes(0x32A5EE, bytearray([0x00, 0x03, 0x50, 0x4F, 0x52, 0x59, 0x47, 0x4F, 0x4E, 0xC5])) # "PORYGONE" from Utils import __version__ - rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] rom.name.extend([0] * (21 - len(rom.name))) rom.write_bytes(0x7FC0, rom.name) diff --git a/worlds/dkc3/Rules.py b/worlds/dkc3/Rules.py index dc90eefd1367..cc45e4ef3ad5 100644 --- a/worlds/dkc3/Rules.py +++ b/worlds/dkc3/Rules.py @@ -1,32 +1,31 @@ import math -from BaseClasses import MultiWorld from .Names import LocationName, ItemName -from worlds.AutoWorld import LogicMixin +from worlds.AutoWorld import LogicMixin, World from worlds.generic.Rules import add_rule, set_rule -def set_rules(world: MultiWorld, player: int): +def set_rules(world: World): - if False:#world.include_trade_sequence[player]: - add_rule(world.get_location(LocationName.barnacles_island, player), - lambda state: state.has(ItemName.shell, player)) + if False:#world.options.include_trade_sequence: + add_rule(world.multiworld.get_location(LocationName.barnacles_island, world.player), + lambda state: state.has(ItemName.shell, world.player)) - add_rule(world.get_location(LocationName.blues_beach_hut, player), - lambda state: state.has(ItemName.present, player)) + add_rule(world.multiworld.get_location(LocationName.blues_beach_hut, world.player), + lambda state: state.has(ItemName.present, world.player)) - add_rule(world.get_location(LocationName.brambles_bungalow, player), - lambda state: state.has(ItemName.flower, player)) + add_rule(world.multiworld.get_location(LocationName.brambles_bungalow, world.player), + lambda state: state.has(ItemName.flower, world.player)) - add_rule(world.get_location(LocationName.barters_swap_shop, player), - lambda state: state.has(ItemName.mirror, player)) + add_rule(world.multiworld.get_location(LocationName.barters_swap_shop, world.player), + lambda state: state.has(ItemName.mirror, world.player)) - if world.goal[player] != "knautilus": + if world.options.goal != "knautilus": required_banana_birds = math.floor( - world.number_of_banana_birds[player].value * (world.percentage_of_banana_birds[player].value / 100.0)) + world.options.number_of_banana_birds.value * (world.options.percentage_of_banana_birds.value / 100.0)) - add_rule(world.get_location(LocationName.banana_bird_mother, player), - lambda state: state.has(ItemName.banana_bird, player, required_banana_birds)) + add_rule(world.multiworld.get_location(LocationName.banana_bird_mother, world.player), + lambda state: state.has(ItemName.banana_bird, world.player, required_banana_birds)) - world.completion_condition[player] = lambda state: state.has(ItemName.victory, player) + world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.victory, world.player) diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py index 462e1416d9e6..dfb42bd04ca8 100644 --- a/worlds/dkc3/__init__.py +++ b/worlds/dkc3/__init__.py @@ -1,3 +1,4 @@ +import dataclasses import os import typing import math @@ -5,9 +6,10 @@ import settings from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from Options import PerGameCommonOptions from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table from .Locations import DKC3Location, all_locations, setup_locations -from .Options import dkc3_options +from .Options import DKC3Options from .Regions import create_regions, connect_regions from .Levels import level_list from .Rules import set_rules @@ -50,8 +52,11 @@ class DKC3World(World): mystery of why Donkey Kong and Diddy disappeared while on vacation. """ game: str = "Donkey Kong Country 3" - option_definitions = dkc3_options settings: typing.ClassVar[DK3Settings] + + options_dataclass = DKC3Options + options: DKC3Options + topology_present = False data_version = 2 #hint_blacklist = {LocationName.rocket_rush_flag} @@ -74,24 +79,25 @@ def stage_assert_generate(cls, multiworld: MultiWorld): def _get_slot_data(self): return { - #"death_link": self.world.death_link[self.player].value, + #"death_link": self.options.death_link.value, "active_levels": self.active_level_list, } def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() - for option_name in dkc3_options: - option = getattr(self.multiworld, option_name)[self.player] + for option_name in (attr.name for attr in dataclasses.fields(DKC3Options) + if attr not in dataclasses.fields(PerGameCommonOptions)): + option = getattr(self.options, option_name) slot_data[option_name] = option.value return slot_data def create_regions(self): - location_table = setup_locations(self.multiworld, self.player) - create_regions(self.multiworld, self.player, location_table) + location_table = setup_locations(self) + create_regions(self, location_table) # Not generate basic - self.topology_present = self.multiworld.level_shuffle[self.player].value + self.topology_present = self.options.level_shuffle.value itempool: typing.List[DKC3Item] = [] # Levels @@ -103,12 +109,12 @@ def create_regions(self): number_of_cogs = 4 self.multiworld.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog)) number_of_bosses = 8 - if self.multiworld.goal[self.player] == "knautilus": + if self.options.goal == "knautilus": self.multiworld.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory)) number_of_bosses = 7 else: self.multiworld.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory)) - number_of_banana_birds = self.multiworld.number_of_banana_birds[self.player] + number_of_banana_birds = self.options.number_of_banana_birds # Bosses total_required_locations += number_of_bosses @@ -116,15 +122,15 @@ def create_regions(self): # Secret Caves total_required_locations += 13 - if self.multiworld.kongsanity[self.player]: + if self.options.kongsanity: total_required_locations += 39 ## Brothers Bear - if False:#self.world.include_trade_sequence[self.player]: + if False:#self.options.include_trade_sequence: total_required_locations += 10 - number_of_bonus_coins = (self.multiworld.krematoa_bonus_coin_cost[self.player] * 5) - number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.multiworld.percentage_of_extra_bonus_coins[self.player] / 100) + number_of_bonus_coins = (self.options.krematoa_bonus_coin_cost * 5) + number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.options.percentage_of_extra_bonus_coins / 100) itempool += [self.create_item(ItemName.bonus_coin) for _ in range(number_of_bonus_coins)] itempool += [self.create_item(ItemName.dk_coin) for _ in range(41)] @@ -142,20 +148,17 @@ def create_regions(self): self.active_level_list = level_list.copy() - if self.multiworld.level_shuffle[self.player]: - self.multiworld.random.shuffle(self.active_level_list) + if self.options.level_shuffle: + self.random.shuffle(self.active_level_list) - connect_regions(self.multiworld, self.player, self.active_level_list) + connect_regions(self, self.active_level_list) self.multiworld.itempool += itempool def generate_output(self, output_directory: str): try: - world = self.multiworld - player = self.player - rom = LocalRom(get_base_rom_path()) - patch_rom(self.multiworld, rom, self.player, self.active_level_list) + patch_rom(self, rom, self.active_level_list) self.active_level_list.append(LocationName.rocket_rush_region) @@ -163,15 +166,15 @@ def generate_output(self, output_directory: str): rom.write_to_file(rompath) self.rom_name = rom.name - patch = DKC3DeltaPatch(os.path.splitext(rompath)[0]+DKC3DeltaPatch.patch_file_ending, player=player, - player_name=world.player_name[player], patched_path=rompath) + patch = DKC3DeltaPatch(os.path.splitext(rompath)[0]+DKC3DeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=rompath) patch.write() except: raise finally: + self.rom_name_available_event.set() # make sure threading continues and errors are collected if os.path.exists(rompath): os.unlink(rompath) - self.rom_name_available_event.set() # make sure threading continues and errors are collected def modify_multidata(self, multidata: dict): import base64 @@ -183,6 +186,7 @@ def modify_multidata(self, multidata: dict): new_name = base64.b64encode(bytes(self.rom_name)).decode() multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): if self.topology_present: world_names = [ LocationName.lake_orangatanga_region, @@ -200,7 +204,8 @@ def modify_multidata(self, multidata: dict): level_region = self.multiworld.get_region(self.active_level_list[world_index * 5 + level_index], self.player) for location in level_region.locations: er_hint_data[location.address] = world_names[world_index] - multidata['er_hint_data'][self.player] = er_hint_data + + hint_data[self.player] = er_hint_data def create_item(self, name: str, force_non_progression=False) -> Item: data = item_table[name] @@ -220,4 +225,4 @@ def get_filler_item_name(self) -> str: return self.multiworld.random.choice(list(junk_table.keys())) def set_rules(self): - set_rules(self.multiworld, self.player) + set_rules(self) From a659036e959694b419ffd6d0ab178eb3252e18f3 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:55:55 +0100 Subject: [PATCH 16/66] Docs: mention that IDs for items and locations can overlap (#2854) * Docs: mention that IDs for items and locations can overlap * Update docs/world api.md Co-authored-by: Ixrec --------- Co-authored-by: Ixrec --- docs/world api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/world api.md b/docs/world api.md index 72a67bca9de3..fd8e0988e567 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -170,6 +170,7 @@ could also be progress in a research tree, or even something more abstract like Each location has a `name` and an `address` (hereafter referred to as an `id`), is placed in a Region, has access rules, and has a classification. The name needs to be unique within each game and must not be numeric (must contain least 1 letter or symbol). The ID needs to be unique across all games, and is best kept in the same range as the item IDs. +Locations and items can share IDs, so typically a game's locations and items start at the same ID. World-specific IDs must be in the range 1 to 253-1; IDs ≤ 0 are global and reserved. From 7ebd5d3891b62650f41406619b135e128e533efa Mon Sep 17 00:00:00 2001 From: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:26:52 -0500 Subject: [PATCH 17/66] DS3: Modified theme and warning color for accessibility (#2312) --- worlds/dark_souls_3/__init__.py | 1 + worlds/dark_souls_3/docs/setup_en.md | 2 +- worlds/dark_souls_3/docs/setup_fr.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index 6efe4e4bc961..b4c231cdea1b 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -14,6 +14,7 @@ class DarkSouls3Web(WebWorld): bug_report_page = "https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/issues" + theme = "stone" setup_en = Tutorial( "Multiworld Setup Guide", "A guide to setting up the Archipelago Dark Souls III randomizer on your computer.", diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md index 7a3ca4e9bd86..72c665af9507 100644 --- a/worlds/dark_souls_3/docs/setup_en.md +++ b/worlds/dark_souls_3/docs/setup_en.md @@ -11,7 +11,7 @@ ## General Concept - + **This mod can ban you permanently from the FromSoftware servers if used online.** The Dark Souls III AP Client is a dinput8.dll triggered when launching Dark Souls III. This .dll file will launch a command diff --git a/worlds/dark_souls_3/docs/setup_fr.md b/worlds/dark_souls_3/docs/setup_fr.md index 6ad86c4aff13..769d331bb98d 100644 --- a/worlds/dark_souls_3/docs/setup_fr.md +++ b/worlds/dark_souls_3/docs/setup_fr.md @@ -12,7 +12,7 @@ permettant de lire des informations de la partie et écrire des commandes pour i ## Procédures d'installation - + **Il y a des risques de bannissement permanent des serveurs FromSoftware si ce mod est utilisé en ligne.** Ce client a été testé sur la version Steam officielle du jeu (v1.15/1.35), peu importe les DLCs actuellement installés. From 3bc2c44ac3dd5f5c547b148c0cd92d9b0b37bbd4 Mon Sep 17 00:00:00 2001 From: Hisu Date: Wed, 28 Feb 2024 21:54:54 -0300 Subject: [PATCH 18/66] Docs: Add Spanish Guide for Pokemon Emerald (#2696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Docs: Add Spanish Guide for Pokemon Emerald * Docs: Add Spanish Guide for Pokémon Emerald * Docs: Add Spanish Guide for Pokemon Emerald * Docs: Add Spanish Guide for Pokemon Emerald * Docs: Add Spanish Guide for Pokemon Emerald --- worlds/pokemon_emerald/__init__.py | 12 +++- worlds/pokemon_emerald/docs/setup_es.md | 74 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 worlds/pokemon_emerald/docs/setup_es.md diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 95e549a32ef0..4d40dd196688 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -36,6 +36,7 @@ class PokemonEmeraldWebWorld(WebWorld): Webhost info for Pokemon Emerald """ theme = "ocean" + setup_en = Tutorial( "Multiworld Setup Guide", "A guide to playing Pokémon Emerald with Archipelago.", @@ -45,7 +46,16 @@ class PokemonEmeraldWebWorld(WebWorld): ["Zunawe"] ) - tutorials = [setup_en] + setup_es = Tutorial( + "Guía de configuración para Multiworld", + "Una guía para jugar Pokémon Emerald en Archipelago", + "Español", + "setup_es.md", + "setup/es", + ["nachocua"] + ) + + tutorials = [setup_en, setup_es] class PokemonEmeraldSettings(settings.Group): diff --git a/worlds/pokemon_emerald/docs/setup_es.md b/worlds/pokemon_emerald/docs/setup_es.md new file mode 100644 index 000000000000..65a74a9ddc70 --- /dev/null +++ b/worlds/pokemon_emerald/docs/setup_es.md @@ -0,0 +1,74 @@ +# Guía de Configuración para Pokémon Emerald + +## Software Requerido + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- Una ROM de Pokémon Emerald en Inglés. La comunidad de Archipelago no puede proveerla. +- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 o posterior + +### Configuración de BizHawk + +Una vez que hayas instalado BizHawk, abre `EmuHawk.exe` y cambia las siguientes configuraciones: + +- Si estás usando BizHawk 2.7 o 2.8, ve a `Config > Customize`. En la pestaña Advanced, cambia el Lua Core de +`NLua+KopiLua` a `Lua+LuaInterface`, luego reinicia EmuHawk. (Si estás usando BizHawk 2.9, puedes saltar este paso.) +- En `Config > Customize`, activa la opción "Run in background" para prevenir desconexiones del cliente mientras +la aplicación activa no sea EmuHawk. +- Abre el archivo `.gba` en EmuHawk y luego ve a `Config > Controllers…` para configurar los controles. Si no puedes +hacer clic en `Controllers…`, debes abrir cualquier ROM `.gba` primeramente. +- Considera limpiar tus macros y atajos en `Config > Hotkeys…` si no quieres usarlas de manera intencional. Para +limpiarlas, selecciona el atajo y presiona la tecla Esc. + +## Software Opcional + +- [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest), para usar con +[PopTracker](https://github.com/black-sliver/PopTracker/releases) + +## Generando y Parcheando el Juego + +1. Crea tu archivo de configuración (YAML). Puedes hacerlo en +[Página de Opciones de Pokémon Emerald](../../../games/Pokemon%20Emerald/player-options). +2. Sigue las instrucciones generales de Archipelago para [Generar un juego] +(../../Archipelago/setup/en#generating-a-game). Esto generará un archivo de salida (output file) para ti. Tu archivo +de parche tendrá la extensión de archivo`.apemerald`. +3. Abre `ArchipelagoLauncher.exe` +4. Selecciona "Open Patch" en el lado derecho y elige tu archivo de parcheo. +5. Si esta es la primera vez que vas a parchear, se te pedirá que selecciones la ROM sin parchear. +6. Un archivo parcheado con extensión `.gba` será creado en el mismo lugar que el archivo de parcheo. +7. La primera vez que abras un archivo parcheado con el BizHawk Client, se te preguntará donde está localizado +`EmuHawk.exe` en tu instalación de BizHawk. + +Si estás jugando una seed Single-Player y no te interesa el auto-tracking o las pistas, puedes parar aquí, cierra el +cliente, y carga la ROM ya parcheada en cualquier emulador. Pero para partidas multi-worlds y para otras +implementaciones de Archipelago, continúa usando BizHawk como tu emulador + +## Conectando con el Servidor + +Por defecto, al abrir un archivo parcheado, se harán de manera automática 1-5 pasos. Aun así, ten en cuenta lo +siguiente en caso de que debas cerrar y volver a abrir la ventana en mitad de la partida por algún motivo. + +1. Pokémon Emerald usa el Archipelago BizHawk Client. Si el cliente no se encuentra abierto al abrir la rom +parcheada, puedes volver a abrirlo desde el Archipelago Launcher. +2. Asegúrate que EmuHawk está corriendo la ROM parcheada. +3. En EmuHawk, ve a `Tools > Lua Console`. Debes tener esta ventana abierta mientras juegas. +4. En la ventana de Lua Console, ve a `Script > Open Script…`. +5. Ve a la carpeta donde está instalado Archipelago y abre `data/lua/connector_bizhawk_generic.lua`. +6. El emulador y el cliente eventualmente se conectarán uno con el otro. La ventana de BizHawk Client indicará que te +has conectado y reconocerá Pokémon Emerald. +7. Para conectar el cliente con el servidor, ingresa la dirección y el puerto de la sala (ej. `archipelago.gg:38281`) +en el campo de texto que se encuentra en la parte superior del cliente y haz click en Connect. + +Ahora deberías poder enviar y recibir ítems. Debes seguir estos pasos cada vez que quieras reconectarte. Es seguro +jugar de manera offline; se sincronizará todo cuando te vuelvas a conectar. + +## Tracking Automático + +Pokémon Emerald tiene un Map Tracker completamente funcional que soporta auto-tracking. + +1. Descarga [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest) y +[PopTracker](https://github.com/black-sliver/PopTracker/releases). +2. Coloca la carpeta del Tracker en la carpeta packs/ dentro de la carpeta de instalación del PopTracker. +3. Abre PopTracker, y carga el Pack de Pokémon Emerald Map Tracker. +4. Para utilizar el auto-tracking, haz click en el símbolo "AP" que se encuentra en la parte superior. +5. Entra la dirección del Servidor de Archipelago (la misma a la que te conectaste para jugar), nombre del jugador, y +contraseña (deja vacío este campo en caso de no utilizar contraseña). From 5e06a75bf2b24174488ab99f7cd743aa1db94a2b Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Wed, 28 Feb 2024 17:22:42 -0800 Subject: [PATCH 19/66] Core: typing: return type of `fill_slot_data` to `Mapping` (#2876) * Core: typing: return type of `fill_slot_data` to `Mapping` type checker be like: "Wait a minute! If you give this mutable dict to those sussy sketchbags, they might mutate it and invalidate your more specific typing!" Note that this doesn't mean the return value needs to be immutable. It just means the caller won't mutate it (which matches current `Main.py` implementation). I've seen some talk of introducing ownership to the type system. https://discuss.python.org/t/we-may-need-better-specification-for-existing-and-future-refinement-types-in-the-type-system/43955/5 Then maybe I could say: "Do whatever you want with it, because I'm giving up ownership." But that doesn't exist in the type system currently. * in docs too * docs talk less about type and more about json * keep `dict` to be safe with .net client and json --- docs/world api.md | 5 +++-- worlds/AutoWorld.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/world api.md b/docs/world api.md index fd8e0988e567..f82ef40a98f8 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -738,8 +738,9 @@ def generate_output(self, output_directory: str) -> None: If the game client needs to know information about the generated seed, a preferred method of transferring the data is through the slot data. This is filled with the `fill_slot_data` method of your world by returning -a `Dict[str, Any]`, but, to not waste resources, should be limited to data that is absolutely necessary. Slot data is -sent to your client once it has successfully [connected](network%20protocol.md#connected). +a `dict` with `str` keys that can be serialized with json. +But, to not waste resources, it should be limited to data that is absolutely necessary. Slot data is sent to your client +once it has successfully [connected](network%20protocol.md#connected). If you need to know information about locations in your world, instead of propagating the slot data, it is preferable to use [LocationScouts](network%20protocol.md#locationscouts), since that data already exists on the server. The most common usage of slot data is sending option results that the client needs to be aware of. diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index b282c7deb8bd..dd0f46f6a6d1 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -7,8 +7,8 @@ import sys import time from dataclasses import make_dataclass -from typing import Any, Callable, ClassVar, Dict, Set, Tuple, FrozenSet, List, Optional, TYPE_CHECKING, TextIO, Type, \ - Union +from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, + Optional, Set, TextIO, Tuple, TYPE_CHECKING, Type, Union) from Options import PerGameCommonOptions from BaseClasses import CollectionState @@ -365,13 +365,19 @@ def generate_output(self, output_directory: str) -> None: If you need any last-second randomization, use self.random instead.""" pass - def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot - """Fill in the `slot_data` field in the `Connected` network package. + def fill_slot_data(self) -> Mapping[str, Any]: # json of WebHostLib.models.Slot + """What is returned from this function will be in the `slot_data` field + in the `Connected` network package. + It should be a `dict` with `str` keys, and should be serializable with json. + This is a way the generator can give custom data to the client. The client will receive this as JSON in the `Connected` response. The generation does not wait for `generate_output` to complete before calling this. `threading.Event` can be used if you need to wait for something from `generate_output`.""" + # The reason for the `Mapping` type annotation, rather than `dict` + # is so that type checkers won't worry about the mutability of `dict`, + # so you can have more specific typing in your world implementation. return {} def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): From 184dedfa699451f885d433b7670327e94e12cb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dana=C3=ABl=20V?= <104455676+ReverM@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:30:28 -0500 Subject: [PATCH 20/66] Core: Default YAML header updates (#2723) * Cleaning up (#4) Cleanup * Adressed change about spaces no longer being replaced to underscores. Added a "that" to remove an ambiguity * Update data/options.yaml Combined the two sentences into one, per suggestion Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --------- Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --- data/options.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/options.yaml b/data/options.yaml index b9bacaa0d103..30bd328f99a0 100644 --- a/data/options.yaml +++ b/data/options.yaml @@ -17,10 +17,10 @@ # A. This is a .yaml file. You are allowed to use most characters. # To test if your yaml is valid or not, you can use this website: # http://www.yamllint.com/ -# You can also verify your Archipelago settings are valid at this site: +# You can also verify that your Archipelago options are valid at this site: # https://archipelago.gg/check -# Your name in-game. Spaces will be replaced with underscores and there is a 16-character limit. +# Your name in-game, limited to 16 characters. # {player} will be replaced with the player's slot number. # {PLAYER} will be replaced with the player's slot number, if that slot number is greater than 1. # {number} will be replaced with the counter value of the name. From e60a2636cd30e02aa281a6e9c7f6346a51bd8883 Mon Sep 17 00:00:00 2001 From: Jarno Date: Thu, 29 Feb 2024 02:40:59 +0100 Subject: [PATCH 21/66] Docs: Fixed broken ClientStatus hyperlink in network protocol.md (#2844) --- docs/network protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/network protocol.md b/docs/network protocol.md index c6d6cf6887e6..9f2c07883b9d 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -345,7 +345,7 @@ Sent to the server to update on the sender's status. Examples include readiness #### Arguments | Name | Type | Notes | | ---- | ---- | ----- | -| status | ClientStatus\[int\] | One of [Client States](#Client-States). Send as int. Follow the link for more information. | +| status | ClientStatus\[int\] | One of [Client States](#ClientStatus). Send as int. Follow the link for more information. | ### Say Basic chat command which sends text to the server to be distributed to other clients. From 7a85ee7ed106a551a15b2c6f6cc888e9bfd635e0 Mon Sep 17 00:00:00 2001 From: Trevor L <80716066+TRPG0@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:56:20 -0700 Subject: [PATCH 22/66] Blasphemous: Remove poptracker pack from setup guide (#2759) --- worlds/blasphemous/docs/setup_en.md | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/blasphemous/docs/setup_en.md b/worlds/blasphemous/docs/setup_en.md index cc238a492eb3..070d1ca4964b 100644 --- a/worlds/blasphemous/docs/setup_en.md +++ b/worlds/blasphemous/docs/setup_en.md @@ -15,7 +15,6 @@ Optional: - Quick Prie Dieu warp mod: [GitHub](https://github.com/BadMagic100/Blasphemous-PrieWarp) - Boots of Pleading mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Boots-of-Pleading) - Double Jump mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Double-Jump) -- PopTracker pack: [GitHub](https://github.com/sassyvania/Blasphemous-Randomizer-Maptracker) ## Mod Installer (Recommended) From 564ec8c32e96f33c6acfd27cd8c64025973fdbb0 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 29 Feb 2024 07:40:08 +0100 Subject: [PATCH 23/66] The Witness: Allow specifying custom trap weights (#2835) * Trap weights * Slightly change the way the option works * Wording one more time * Non optional to bring in line with Ixrec's implementation * Be clear that it's not an absolute amount, but a weight * E x c l a m a t i o n p o i n t * Update worlds/witness/items.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Wait I can just do this now lol --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/witness/__init__.py | 4 ---- worlds/witness/items.py | 12 +++++++++--- worlds/witness/locations.py | 3 +++ worlds/witness/options.py | 26 +++++++++++++++++++++++++- worlds/witness/static_logic.py | 3 +++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index c38898b33d4e..e985dde353aa 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -44,10 +44,6 @@ class WitnessWorld(World): """ game = "The Witness" topology_present = False - - StaticWitnessLogic() - StaticWitnessLocations() - StaticWitnessItems() web = WitnessWebWorld() options_dataclass = TheWitnessOptions diff --git a/worlds/witness/items.py b/worlds/witness/items.py index 41bc3c1bb8da..6802fd2a21b5 100644 --- a/worlds/witness/items.py +++ b/worlds/witness/items.py @@ -176,9 +176,14 @@ def get_filler_items(self, quantity: int) -> Dict[str, int]: # Read trap configuration data. trap_weight = self._world.options.trap_percentage / 100 - filler_weight = 1 - trap_weight + trap_items = self._world.options.trap_weights.value + + if not sum(trap_items.values()): + trap_weight = 0 # Add filler items to the list. + filler_weight = 1 - trap_weight + filler_items: Dict[str, float] filler_items = {name: data.definition.weight if isinstance(data.definition, WeightedItemDefinition) else 1 for (name, data) in self.item_data.items() if data.definition.category is ItemCategory.FILLER} @@ -187,8 +192,6 @@ def get_filler_items(self, quantity: int) -> Dict[str, int]: # Add trap items. if trap_weight > 0: - trap_items = {name: data.definition.weight if isinstance(data.definition, WeightedItemDefinition) else 1 - for (name, data) in self.item_data.items() if data.definition.category is ItemCategory.TRAP} filler_items.update({name: base_weight * trap_weight / sum(trap_items.values()) for name, base_weight in trap_items.items() if base_weight > 0}) @@ -267,3 +270,6 @@ def get_progressive_item_ids_in_pool(self) -> Dict[int, List[int]]: output[item.ap_code] = [StaticWitnessItems.item_data[child_item].ap_code for child_item in item.definition.child_item_names] return output + + +StaticWitnessItems() diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index d38cf9025806..cd6d71f46911 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -569,3 +569,6 @@ def add_location_late(self, entity_name: str): entity_hex = StaticWitnessLogic.ENTITIES_BY_NAME[entity_name]["entity_hex"] self.CHECK_LOCATION_TABLE[entity_hex] = entity_name self.CHECK_PANELHEX_TO_ID[entity_hex] = StaticWitnessLocations.get_id(entity_hex) + + +StaticWitnessLocations() diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 68a4ac7fc231..18aa76d95ae9 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,5 +1,10 @@ from dataclasses import dataclass -from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions + +from schema import Schema, And, Optional + +from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions, OptionDict + +from worlds.witness.static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic class DisableNonRandomizedPuzzles(Toggle): @@ -172,6 +177,24 @@ class TrapPercentage(Range): default = 20 +class TrapWeights(OptionDict): + """Specify the weights determining how many copies of each trap item will be in your itempool. + If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!). + If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option.""" + + display_name = "Trap Weights" + schema = Schema({ + trap_name: And(int, lambda n: n >= 0) + for trap_name, item_definition in StaticWitnessLogic.all_items.items() + if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP + }) + default = { + trap_name: item_definition.weight + for trap_name, item_definition in StaticWitnessLogic.all_items.items() + if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP + } + + class PuzzleSkipAmount(Range): """Adds this number of Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel. Works on most panels in the game - The only big exception is The Challenge.""" @@ -237,6 +260,7 @@ class TheWitnessOptions(PerGameCommonOptions): early_caves: EarlyCaves elevators_come_to_you: ElevatorsComeToYou trap_percentage: TrapPercentage + trap_weights: TrapWeights puzzle_skip_amount: PuzzleSkipAmount hint_amount: HintAmount area_hint_percentage: AreaHintPercentage diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py index 5a3e8b1b580e..3efab4915e69 100644 --- a/worlds/witness/static_logic.py +++ b/worlds/witness/static_logic.py @@ -295,3 +295,6 @@ def __init__(self): self.EP_TO_OBELISK_SIDE.update(self.sigma_normal.EP_TO_OBELISK_SIDE) self.ENTITY_ID_TO_NAME.update(self.sigma_normal.ENTITY_ID_TO_NAME) + + +StaticWitnessLogic() From 983da12a03aba795ced47cd547470132085c3940 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Thu, 29 Feb 2024 12:42:13 -0700 Subject: [PATCH 24/66] Pokemon Emerald: Add exhaustive list of ROM changes (#2801) --- worlds/pokemon_emerald/docs/rom changes.md | 75 ++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 worlds/pokemon_emerald/docs/rom changes.md diff --git a/worlds/pokemon_emerald/docs/rom changes.md b/worlds/pokemon_emerald/docs/rom changes.md new file mode 100644 index 000000000000..9b189d08e76a --- /dev/null +++ b/worlds/pokemon_emerald/docs/rom changes.md @@ -0,0 +1,75 @@ +## QoL + +- The catch tutorial and cutscenes during your first visit to Petalburg are skipped +- The match call tutorial after you leave Devon Corp is skipped +- Cycling and running is allowed in every map (some exceptions like Fortree and Pacifidlog) +- When you run out of Repel steps, you'll be prompted to use another one if you have more in your bag +- Text is always rendered in its entirety on the first frame (instant text) +- With an option set, text will advance if A is held +- The message explaining that the trainer is about to send out a new pokemon is shortened to fit on two lines so that +you can still read the species when deciding whether to change pokemon +- The Pokemon Center Nurse dialogue is entirely removed except for the final text box +- When receiving TMs and HMs, the move that it teaches is consistently displayed in the "received item" message (by +default, certain ways of receiving items would only display the TM/HM number) +- The Pokedex starts in national mode +- The Oldale Pokemart sells Poke Balls at the start of the game +- Pauses during battles (e.g. the ~1 second pause at the start of a turn before an opponent uses a potion) are shorter +by 62.5% +- The sliding animation for trainers and wild pokemon at the start of a battle runs at double speed. +- Bag space was greatly expanded (there is room for one stack of every unique item in every pocket, plus a little bit +extra for some pockets) + - Save data format was changed as a result of this. Shrank some unused space and removed some multiplayer phrases from + the save data. + - Pretty much any code that checks for bag space is ignored or bypassed (this sounds dangerous, but with expanded bag + space you should pretty much never have a full bag unless you're trying to fill it up, and skipping those checks + greatly simplifies detecting when items are picked up) +- Pokemon are never disobedient +- When moving in the overworld, set the input priority based on the most recently pressed direction rather than by some +predetermined priority +- Shoal cave changes state every time you reload the map and is no longer tied to the RTC +- Increased safari zone steps from 500 to 50000 +- Trainers will not approach the player if the blind trainers option is set +- Changed trade evolutions to be possible without trading: + - Politoed: Use King's Rock in bag menu + - Alakazam: Level 37 + - Machamp: Level 37 + - Golem: Level 37 + - Slowking: Use King's Rock in bag menu + - Gengar: Level 37 + - Steelix: Use Metal Coat in bag menu + - Kingdra: Use Dragon Scale in bag menu + - Scizor: Use Metal Coat in bag menu + - Porygon2: Use Up-Grade in bag menu + - Milotic: Level 30 + - Huntail: Use Deep Sea Tooth in bag menu + - Gorebyss: Use Deep Sea Scale in bag menu + +## Game State Changes/Softlock Prevention + +- Mr. Briney never disappears or stops letting you use his ferry +- Prevent the player from flying or surfing until they have received the Pokedex +- The S.S. Tidal will be available at all times if you have the option enabled +- Some NPCs or tiles are removed on the creation of a new save file based on player options +- Ensured that every species has some damaging move by level 5 +- Route 115 may have strength boulders between the beach and cave entrance based on player options +- The Petalburg Gym is set up based on your player options rather than after the first 4 gyms +- The E4 guards will actually check all your badges (or gyms beaten based on your options) instead of just the Feather +Badge +- Steven cuts the conversation short in Granite Cave if you don't have the Letter +- Dock checks that you have the Devon Goods before asking you to deliver them (and thus opening the museum) +- Rydel gives you both bikes at the same time +- The man in Pacifidlog who gives you Frustration and Return will give you both at the same time, does not check +friendship first, and no longer has any behavior related to the RTC +- The woman who gives you the Soothe Bell in Slateport does not check friendship +- When trading the Scanner with Captain Stern, you will receive both the Deep Sea Tooth and Deep Sea Scale + +## Misc + +- You can no longer try to switch bikes in the bike shop +- The Seashore House only rewards you with 1 Soda Pop instead of 6 +- Many small changes that make it possible to swap single battles to double battles + - Includes some safeguards against two trainers seeing you and initiating a battle while one or both of them are + "single trainer double battles" +- Game now properly waits on vblank instead of spinning in a while loop +- Misc small changes to text for consistency +- Many bugfixes to the vanilla game code From f17ff156692b10678f9ce3e14c19f0f53510a271 Mon Sep 17 00:00:00 2001 From: zig-for Date: Sat, 2 Mar 2024 21:28:26 -0800 Subject: [PATCH 25/66] LADX: fix modifying item pool in pre_fill (#2060) --- worlds/ladx/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index 181cc053222d..6742dffd30c3 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -276,6 +276,11 @@ def create_items(self) -> None: # Properly fill locations within dungeon location.dungeon = r.dungeon_index + # For now, special case first item + FORCE_START_ITEM = True + if FORCE_START_ITEM: + self.force_start_item() + def force_start_item(self): start_loc = self.multiworld.get_location("Tarin's Gift (Mabe Village)", self.player) if not start_loc.item: @@ -287,17 +292,12 @@ def force_start_item(self): start_item = self.multiworld.itempool.pop(index) start_loc.place_locked_item(start_item) - def get_pre_fill_items(self): return self.pre_fill_items def pre_fill(self) -> None: allowed_locations_by_item = {} - # For now, special case first item - FORCE_START_ITEM = True - if FORCE_START_ITEM: - self.force_start_item() # Set up filter rules From ad3ffde7851d4410526fc08132dbfa5c1bb665ec Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 3 Mar 2024 00:31:22 -0500 Subject: [PATCH 26/66] FFMQ: Remove debug print statements (#2882) --- worlds/ffmq/Regions.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py index 61f70864c0b4..8b83c88e72c9 100644 --- a/worlds/ffmq/Regions.py +++ b/worlds/ffmq/Regions.py @@ -220,15 +220,12 @@ def stage_set_rules(multiworld): for player in no_enemies_players: for location in vendor_locations: if multiworld.accessibility[player] == "locations": - print("exclude") multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED else: - print("unreachable") multiworld.get_location(location, player).access_rule = lambda state: False else: # There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing - # advancement items so that useful items can be placed. - print("no advancement") + # advancement items so that useful items can be placed for player in no_enemies_players: for location in vendor_locations: multiworld.get_location(location, player).item_rule = lambda item: not item.advancement From 01cf60f48df5b5d298ff7e9781434cff16ae1a24 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:32:58 -0600 Subject: [PATCH 27/66] Launcher: make launcher scrollable (#2881) --- Launcher.py | 34 +++++++++++++++++----------------- kvui.py | 13 +++++++++++++ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Launcher.py b/Launcher.py index 9e184bf1088d..890957958391 100644 --- a/Launcher.py +++ b/Launcher.py @@ -161,7 +161,7 @@ def launch(exe, in_terminal=False): def run_gui(): - from kvui import App, ContainerLayout, GridLayout, Button, Label + from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget from kivy.uix.image import AsyncImage from kivy.uix.relativelayout import RelativeLayout @@ -185,11 +185,16 @@ def build(self): self.container = ContainerLayout() self.grid = GridLayout(cols=2) self.container.add_widget(self.grid) - self.grid.add_widget(Label(text="General")) - self.grid.add_widget(Label(text="Clients")) - button_layout = self.grid # make buttons fill the window - - def build_button(component: Component): + self.grid.add_widget(Label(text="General", size_hint_y=None, height=40)) + self.grid.add_widget(Label(text="Clients", size_hint_y=None, height=40)) + tool_layout = ScrollBox() + tool_layout.layout.orientation = "vertical" + self.grid.add_widget(tool_layout) + client_layout = ScrollBox() + client_layout.layout.orientation = "vertical" + self.grid.add_widget(client_layout) + + def build_button(component: Component) -> Widget: """ Builds a button widget for a given component. @@ -200,31 +205,26 @@ def build_button(component: Component): None. The button is added to the parent grid layout. """ - button = Button(text=component.display_name) + button = Button(text=component.display_name, size_hint_y=None, height=40) button.component = component button.bind(on_release=self.component_action) if component.icon != "icon": image = AsyncImage(source=icon_paths[component.icon], size=(38, 38), size_hint=(None, 1), pos=(5, 0)) - box_layout = RelativeLayout() + box_layout = RelativeLayout(size_hint_y=None, height=40) box_layout.add_widget(button) box_layout.add_widget(image) - button_layout.add_widget(box_layout) - else: - button_layout.add_widget(button) + return box_layout + return button for (tool, client) in itertools.zip_longest(itertools.chain( self._tools.items(), self._miscs.items(), self._adjusters.items()), self._clients.items()): # column 1 if tool: - build_button(tool[1]) - else: - button_layout.add_widget(Label()) + tool_layout.layout.add_widget(build_button(tool[1])) # column 2 if client: - build_button(client[1]) - else: - button_layout.add_widget(Label()) + client_layout.layout.add_widget(build_button(client[1])) return self.container diff --git a/kvui.py b/kvui.py index 22e179d5be94..5e1b0fc03048 100644 --- a/kvui.py +++ b/kvui.py @@ -38,11 +38,13 @@ from kivy.factory import Factory from kivy.properties import BooleanProperty, ObjectProperty from kivy.metrics import dp +from kivy.effects.scroll import ScrollEffect from kivy.uix.widget import Widget from kivy.uix.button import Button from kivy.uix.gridlayout import GridLayout from kivy.uix.layout import Layout from kivy.uix.textinput import TextInput +from kivy.uix.scrollview import ScrollView from kivy.uix.recycleview import RecycleView from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem from kivy.uix.boxlayout import BoxLayout @@ -118,6 +120,17 @@ class ServerToolTip(ToolTip): pass +class ScrollBox(ScrollView): + layout: BoxLayout + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.layout = BoxLayout(size_hint_y=None) + self.layout.bind(minimum_height=self.layout.setter("height")) + self.add_widget(self.layout) + self.effect_cls = ScrollEffect + + class HovererableLabel(HoverBehavior, Label): pass From b65a3b7464a503a614dcf0726fe0863540bc18f1 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 3 Mar 2024 06:33:48 +0100 Subject: [PATCH 28/66] Subnautica: cleanup (#2828) --- worlds/subnautica/__init__.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py index de4f4e33dc87..e9341ec3b9de 100644 --- a/worlds/subnautica/__init__.py +++ b/worlds/subnautica/__init__.py @@ -115,7 +115,7 @@ def create_items(self): for i in range(item.count): subnautica_item = self.create_item(item.name) if item.name == "Neptune Launch Platform": - self.multiworld.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item( + self.get_location("Aurora - Captain Data Terminal").place_locked_item( subnautica_item) else: pool.append(subnautica_item) @@ -128,7 +128,7 @@ def create_items(self): pool.append(self.create_item(name)) extras -= group_amount - for item_name in self.multiworld.random.sample( + for item_name in self.random.sample( # list of high-count important fragments as priority filler [ "Cyclops Engine Fragment", @@ -175,18 +175,6 @@ def create_item(self, name: str) -> SubnauticaItem: item_table[item_id].classification, item_id, player=self.player) - def create_region(self, name: str, region_locations=None, exits=None): - ret = Region(name, self.player, self.multiworld) - if region_locations: - for location in region_locations: - loc_id = self.location_name_to_id.get(location, None) - location = SubnauticaLocation(self.player, location, loc_id, ret) - ret.locations.append(location) - if exits: - for region_exit in exits: - ret.exits.append(Entrance(self.player, region_exit, ret)) - return ret - def get_filler_item_name(self) -> str: return item_table[self.multiworld.random.choice(items_by_type[ItemType.resource])].name From 2c5b2e07590b926f55f6c817a3971b2b8c86154b Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 3 Mar 2024 06:34:48 +0100 Subject: [PATCH 29/66] MultiServer: make !hint without further arguments only reply to the instigating player (#2339) --- MultiServer.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index 15ed22d715e8..62dab3298e6b 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -656,7 +656,8 @@ def get_aliased_name(self, team: int, slot: int): else: return self.player_names[team, slot] - def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: bool = False): + def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: bool = False, + recipients: typing.Sequence[int] = None): """Send and remember hints.""" if only_new: hints = [hint for hint in hints if hint not in self.hints[team, hint.finding_player]] @@ -685,12 +686,13 @@ def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: b for slot in new_hint_events: self.on_new_hint(team, slot) for slot, hint_data in concerns.items(): - clients = self.clients[team].get(slot) - if not clients: - continue - client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)] - for client in clients: - async_start(self.send_msgs(client, client_hints)) + if recipients is None or slot in recipients: + clients = self.clients[team].get(slot) + if not clients: + continue + client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)] + for client in clients: + async_start(self.send_msgs(client, client_hints)) # "events" @@ -1429,9 +1431,13 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool: hints = {hint.re_check(self.ctx, self.client.team) for hint in self.ctx.hints[self.client.team, self.client.slot]} self.ctx.hints[self.client.team, self.client.slot] = hints - self.ctx.notify_hints(self.client.team, list(hints)) + self.ctx.notify_hints(self.client.team, list(hints), recipients=(self.client.slot,)) self.output(f"A hint costs {self.ctx.get_hint_cost(self.client.slot)} points. " f"You have {points_available} points.") + if hints and Utils.version_tuple < (0, 5, 0): + self.output("It was recently changed, so that the above hints are only shown to you. " + "If you meant to alert another player of an above hint, " + "please let them know of the content or to run !hint themselves.") return True elif input_text.isnumeric(): From b8bf67a1664c55eb690e1128dd69021f324b5033 Mon Sep 17 00:00:00 2001 From: wildham <64616385+wildham0@users.noreply.github.com> Date: Sun, 3 Mar 2024 00:43:45 -0500 Subject: [PATCH 30/66] FF1: Update Location Names (#2838) --- worlds/ff1/data/locations.json | 490 ++++++++++++++++----------------- 1 file changed, 245 insertions(+), 245 deletions(-) diff --git a/worlds/ff1/data/locations.json b/worlds/ff1/data/locations.json index 9771d51de088..2f465a78970e 100644 --- a/worlds/ff1/data/locations.json +++ b/worlds/ff1/data/locations.json @@ -1,253 +1,253 @@ { - "Coneria1": 257, - "Coneria2": 258, - "ConeriaMajor": 259, - "Coneria4": 260, - "Coneria5": 261, - "Coneria6": 262, - "MatoyasCave1": 299, - "MatoyasCave3": 301, - "MatoyasCave2": 300, - "NorthwestCastle1": 273, - "NorthwestCastle3": 275, - "NorthwestCastle2": 274, - "ToFTopLeft1": 263, - "ToFBottomLeft": 265, - "ToFTopLeft2": 264, - "ToFRevisited6": 509, - "ToFRevisited4": 507, - "ToFRMasmune": 504, - "ToFRevisited5": 508, - "ToFRevisited3": 506, - "ToFRevisited2": 505, - "ToFRevisited7": 510, - "ToFTopRight1": 267, - "ToFTopRight2": 268, - "ToFBottomRight": 266, - "IceCave15": 377, - "IceCave16": 378, - "IceCave9": 371, - "IceCave11": 373, - "IceCave10": 372, - "IceCave12": 374, - "IceCave13": 375, - "IceCave14": 376, - "IceCave1": 363, - "IceCave2": 364, - "IceCave3": 365, - "IceCave4": 366, - "IceCave5": 367, - "IceCaveMajor": 370, - "IceCave7": 369, - "IceCave6": 368, - "Elfland1": 269, - "Elfland2": 270, - "Elfland3": 271, - "Elfland4": 272, - "Ordeals5": 383, - "Ordeals6": 384, - "Ordeals7": 385, - "Ordeals1": 379, - "Ordeals2": 380, - "Ordeals3": 381, - "Ordeals4": 382, - "OrdealsMajor": 387, - "Ordeals8": 386, - "SeaShrine7": 411, - "SeaShrine8": 412, - "SeaShrine9": 413, - "SeaShrine10": 414, - "SeaShrine1": 405, - "SeaShrine2": 406, - "SeaShrine3": 407, - "SeaShrine4": 408, - "SeaShrine5": 409, - "SeaShrine6": 410, - "SeaShrine13": 417, - "SeaShrine14": 418, - "SeaShrine11": 415, - "SeaShrine15": 419, - "SeaShrine16": 420, - "SeaShrineLocked": 421, - "SeaShrine18": 422, - "SeaShrine19": 423, - "SeaShrine20": 424, - "SeaShrine23": 427, - "SeaShrine21": 425, - "SeaShrine22": 426, - "SeaShrine24": 428, - "SeaShrine26": 430, - "SeaShrine28": 432, - "SeaShrine25": 429, - "SeaShrine30": 434, - "SeaShrine31": 435, - "SeaShrine27": 431, - "SeaShrine29": 433, - "SeaShrineMajor": 436, - "SeaShrine12": 416, - "DwarfCave3": 291, - "DwarfCave4": 292, - "DwarfCave6": 294, - "DwarfCave7": 295, - "DwarfCave5": 293, - "DwarfCave8": 296, - "DwarfCave9": 297, - "DwarfCave10": 298, - "DwarfCave1": 289, - "DwarfCave2": 290, - "Waterfall1": 437, - "Waterfall2": 438, - "Waterfall3": 439, - "Waterfall4": 440, - "Waterfall5": 441, - "Waterfall6": 442, - "MirageTower5": 456, - "MirageTower16": 467, - "MirageTower17": 468, - "MirageTower15": 466, - "MirageTower18": 469, - "MirageTower14": 465, - "SkyPalace1": 470, - "SkyPalace2": 471, - "SkyPalace3": 472, - "SkyPalace4": 473, - "SkyPalace18": 487, - "SkyPalace19": 488, - "SkyPalace16": 485, - "SkyPalaceMajor": 489, - "SkyPalace17": 486, - "SkyPalace22": 491, - "SkyPalace21": 490, - "SkyPalace23": 492, - "SkyPalace24": 493, - "SkyPalace31": 500, - "SkyPalace32": 501, - "SkyPalace33": 502, - "SkyPalace34": 503, - "SkyPalace29": 498, - "SkyPalace26": 495, - "SkyPalace25": 494, - "SkyPalace28": 497, - "SkyPalace27": 496, - "SkyPalace30": 499, - "SkyPalace14": 483, - "SkyPalace11": 480, - "SkyPalace12": 481, - "SkyPalace13": 482, - "SkyPalace15": 484, - "SkyPalace10": 479, - "SkyPalace5": 474, - "SkyPalace6": 475, - "SkyPalace7": 476, - "SkyPalace8": 477, - "SkyPalace9": 478, - "MirageTower9": 460, - "MirageTower13": 464, - "MirageTower10": 461, - "MirageTower12": 463, - "MirageTower11": 462, - "MirageTower1": 452, - "MirageTower2": 453, - "MirageTower4": 455, - "MirageTower3": 454, - "MirageTower8": 459, - "MirageTower7": 458, - "MirageTower6": 457, - "Volcano30": 359, - "Volcano32": 361, - "Volcano31": 360, - "Volcano28": 357, - "Volcano29": 358, - "Volcano21": 350, - "Volcano20": 349, - "Volcano24": 353, - "Volcano19": 348, - "Volcano25": 354, - "VolcanoMajor": 362, - "Volcano26": 355, - "Volcano27": 356, - "Volcano22": 351, - "Volcano23": 352, - "Volcano1": 330, - "Volcano9": 338, - "Volcano2": 331, - "Volcano10": 339, - "Volcano3": 332, - "Volcano8": 337, - "Volcano4": 333, - "Volcano13": 342, - "Volcano11": 340, - "Volcano7": 336, - "Volcano6": 335, - "Volcano5": 334, - "Volcano14": 343, - "Volcano12": 341, - "Volcano15": 344, - "Volcano18": 347, - "Volcano17": 346, - "Volcano16": 345, - "MarshCave6": 281, - "MarshCave5": 280, - "MarshCave7": 282, - "MarshCave8": 283, - "MarshCave10": 285, - "MarshCave2": 277, - "MarshCave11": 286, - "MarshCave3": 278, - "MarshCaveMajor": 284, - "MarshCave12": 287, - "MarshCave4": 279, - "MarshCave1": 276, - "MarshCave13": 288, - "TitansTunnel1": 326, - "TitansTunnel2": 327, - "TitansTunnel3": 328, - "TitansTunnel4": 329, - "EarthCave1": 302, - "EarthCave2": 303, - "EarthCave5": 306, - "EarthCave3": 304, - "EarthCave4": 305, - "EarthCave9": 310, - "EarthCave10": 311, - "EarthCave11": 312, - "EarthCave6": 307, - "EarthCave7": 308, - "EarthCave12": 313, - "EarthCaveMajor": 317, - "EarthCave19": 320, - "EarthCave17": 318, - "EarthCave18": 319, - "EarthCave20": 321, - "EarthCave24": 325, - "EarthCave21": 322, - "EarthCave22": 323, - "EarthCave23": 324, - "EarthCave13": 314, - "EarthCave15": 316, - "EarthCave14": 315, - "EarthCave8": 309, - "Cardia11": 398, - "Cardia9": 396, - "Cardia10": 397, - "Cardia6": 393, - "Cardia8": 395, - "Cardia7": 394, - "Cardia13": 400, - "Cardia12": 399, - "Cardia4": 391, - "Cardia5": 392, - "Cardia3": 390, - "Cardia1": 388, - "Cardia2": 389, - "CaravanShop": 767, + "Matoya's Cave - Chest 1": 299, + "Matoya's Cave - Chest 2": 301, + "Matoya's Cave - Chest 3": 300, + "Dwarf Cave - Entrance 1": 289, + "Dwarf Cave - Entrance 2": 290, + "Dwarf Cave - Treasury 1": 291, + "Dwarf Cave - Treasury 2": 292, + "Dwarf Cave - Treasury 3": 295, + "Dwarf Cave - Treasury 4": 293, + "Dwarf Cave - Treasury 5": 294, + "Dwarf Cave - Treasury 6": 296, + "Dwarf Cave - Treasury 7": 297, + "Dwarf Cave - Treasury 8": 298, + "Coneria Castle - Treasury 1": 257, + "Coneria Castle - Treasury 2": 258, + "Coneria Castle - Treasury 3": 260, + "Coneria Castle - Treasury 4": 261, + "Coneria Castle - Treasury 5": 262, + "Coneria Castle - Treasury Major": 259, + "Elf Castle - Treasury 1": 269, + "Elf Castle - Treasury 2": 270, + "Elf Castle - Treasury 3": 271, + "Elf Castle - Treasury 4": 272, + "Northwest Castle - Treasury 1": 273, + "Northwest Castle - Treasury 2": 275, + "Northwest Castle - Treasury 3": 274, + "Titan's Tunnel - Chest 1": 327, + "Titan's Tunnel - Chest 2": 328, + "Titan's Tunnel - Chest 3": 329, + "Titan's Tunnel - Major": 326, + "Cardia Grass Island - Entrance": 398, + "Cardia Grass Island - Duo Room 1": 396, + "Cardia Grass Island - Duo Rooom 2": 397, + "Cardia Swamp Island - Chest 1": 393, + "Cardia Swamp Island - Chest 2": 395, + "Cardia Swamp Island - Chest 3": 394, + "Cardia Forest Island - Entrance 1": 389, + "Cardia Forest Island - Entrance 2": 388, + "Cardia Forest Island - Entrance 3": 390, + "Cardia Forest Island - Incentive 1": 400, + "Cardia Forest Island - Incentive 2": 399, + "Cardia Forest Island - Incentive 3": 392, + "Cardia Forest Island - Incentive Major": 391, + "Temple of Fiends - Unlocked Single": 265, + "Temple of Fiends - Unlocked Duo 1": 263, + "Temple of Fiends - Unlocked Duo 2": 264, + "Temple of Fiends - Locked Single": 266, + "Temple of Fiends - Locked Duo 1": 267, + "Temple of Fiends - Locked Duo 2": 268, + "Marsh Cave Top (B1) - Single": 283, + "Marsh Cave Top (B1) - Corner": 282, + "Marsh Cave Top (B1) - Duo 1": 281, + "Marsh Cave Top (B1) - Duo 2": 280, + "Marsh Cave Bottom (B2) - Distant": 276, + "Marsh Cave Bottom (B2) - Tetris-Z First": 277, + "Marsh Cave Bottom (B2) - Tetris-Z Middle 1": 278, + "Marsh Cave Bottom (B2) - Tetris-Z Middle 2": 285, + "Marsh Cave Bottom (B2) - Tetris-Z Incentive": 284, + "Marsh Cave Bottom (B2) - Tetris-Z Last": 279, + "Marsh Cave Bottom (B2) - Locked Corner": 286, + "Marsh Cave Bottom (B2) - Locked Middle": 287, + "Marsh Cave Bottom (B2) - Locked Incentive": 288, + "Earth Cave Giant's Floor (B1) - Single": 306, + "Earth Cave Giant's Floor (B1) - Appendix 1": 302, + "Earth Cave Giant's Floor (B1) - Appendix 2": 303, + "Earth Cave Giant's Floor (B1) - Side Path 1": 304, + "Earth Cave Giant's Floor (B1) - Side Path 2": 305, + "Earth Cave (B2) - Side Room 1": 307, + "Earth Cave (B2) - Side Room 2": 308, + "Earth Cave (B2) - Side Room 3": 309, + "Earth Cave (B2) - Guarded 1": 310, + "Earth Cave (B2) - Guarded 2": 311, + "Earth Cave (B2) - Guarded 3": 312, + "Earth Cave Vampire Floor (B3) - Side Room": 315, + "Earth Cave Vampire Floor (B3) - TFC": 316, + "Earth Cave Vampire Floor (B3) - Asher Trunk": 314, + "Earth Cave Vampire Floor (B3) - Vampire's Closet": 313, + "Earth Cave Vampire Floor (B3) - Incentive": 317, + "Earth Cave Rod Locked Floor (B4) - Armory 1": 321, + "Earth Cave Rod Locked Floor (B4) - Armory 2": 322, + "Earth Cave Rod Locked Floor (B4) - Armory 3": 325, + "Earth Cave Rod Locked Floor (B4) - Armory 4": 323, + "Earth Cave Rod Locked Floor (B4) - Armory 5": 324, + "Earth Cave Rod Locked Floor (B4) - Lich's Closet 1": 318, + "Earth Cave Rod Locked Floor (B4) - Lich's Closet 2": 319, + "Earth Cave Rod Locked Floor (B4) - Lich's Closet 3": 320, + "Gurgu Volcano Armory Floor (B2) - Guarded": 346, + "Gurgu Volcano Armory Floor (B2) - Center": 347, + "Gurgu Volcano Armory Floor (B2) - Hairpins": 344, + "Gurgu Volcano Armory Floor (B2) - Shortpins": 345, + "Gurgu Volcano Armory Floor (B2) - Vertpins 1": 342, + "Gurgu Volcano Armory Floor (B2) - Vertpins 2": 343, + "Gurgu Volcano Armory Floor (B2) - Armory 1": 338, + "Gurgu Volcano Armory Floor (B2) - Armory 2": 330, + "Gurgu Volcano Armory Floor (B2) - Armory 3": 331, + "Gurgu Volcano Armory Floor (B2) - Armory 4": 337, + "Gurgu Volcano Armory Floor (B2) - Armory 5": 335, + "Gurgu Volcano Armory Floor (B2) - Armory 6": 332, + "Gurgu Volcano Armory Floor (B2) - Armory 7": 333, + "Gurgu Volcano Armory Floor (B2) - Armory 8": 334, + "Gurgu Volcano Armory Floor (B2) - Armory 9": 341, + "Gurgu Volcano Armory Floor (B2) - Armory 10": 336, + "Gurgu Volcano Armory Floor (B2) - Armory 11": 340, + "Gurgu Volcano Armory Floor (B2) - Armory 12": 339, + "Gurgu Volcano Agama Floor (B4) - Entrance 1": 349, + "Gurgu Volcano Agama Floor (B4) - Entrance 2": 348, + "Gurgu Volcano Agama Floor (B4) - First Greed": 350, + "Gurgu Volcano Agama Floor (B4) - Worm Room 1": 361, + "Gurgu Volcano Agama Floor (B4) - Worm Room 2": 359, + "Gurgu Volcano Agama Floor (B4) - Worm Room 3": 360, + "Gurgu Volcano Agama Floor (B4) - Worm Room 4": 357, + "Gurgu Volcano Agama Floor (B4) - Worm Room 5": 358, + "Gurgu Volcano Agama Floor (B4) - Second Greed 1": 353, + "Gurgu Volcano Agama Floor (B4) - Second Greed 2": 354, + "Gurgu Volcano Agama Floor (B4) - Side Room 1": 355, + "Gurgu Volcano Agama Floor (B4) - Side Room 2": 356, + "Gurgu Volcano Agama Floor (B4) - Grind Room 1": 351, + "Gurgu Volcano Agama Floor (B4) - Grind Room 2": 352, + "Gurgu Volcano Kary Floor (B5) - Incentive": 362, + "Ice Cave Incentive Floor (B2) - Chest 1": 368, + "Ice Cave Incentive Floor (B2) - Chest 2": 369, + "Ice Cave Incentive Floor (B2) - Major": 370, + "Ice Cave Bottom (B3) - IceD Room 1": 377, + "Ice Cave Bottom (B3) - IceD Room 2": 378, + "Ice Cave Bottom (B3) - Six-Pack 1": 371, + "Ice Cave Bottom (B3) - Six-Pack 2": 372, + "Ice Cave Bottom (B3) - Six-Pack 3": 375, + "Ice Cave Bottom (B3) - Six-Pack 4": 373, + "Ice Cave Bottom (B3) - Six-Pack 5": 374, + "Ice Cave Bottom (B3) - Six-Pack 6": 376, + "Ice Cave Exit Floor (B1) - Greeds Checks 1": 363, + "Ice Cave Exit Floor (B1) - Greeds Checks 2": 364, + "Ice Cave Exit Floor (B1) - Drop Room 1": 365, + "Ice Cave Exit Floor (B1) - Drop Room 2": 366, + "Ice Cave Exit Floor (B1) - Drop Room 3": 367, + "Castle of Ordeals Top Floor (3F) - Single": 386, + "Castle of Ordeals Top Floor (3F) - Three-Pack 1": 383, + "Castle of Ordeals Top Floor (3F) - Three-Pack 2": 384, + "Castle of Ordeals Top Floor (3F) - Three-Pack 3": 385, + "Castle of Ordeals Top Floor (3F) - Four-Pack 1": 379, + "Castle of Ordeals Top Floor (3F) - Four-Pack 2": 380, + "Castle of Ordeals Top Floor (3F) - Four-Pack 3": 381, + "Castle of Ordeals Top Floor (3F) - Four-Pack 4": 382, + "Castle of Ordeals Top Floor (3F) - Incentive": 387, + "Sea Shrine Split Floor (B3) - Kraken Side": 415, + "Sea Shrine Split Floor (B3) - Mermaid Side": 416, + "Sea Shrine TFC Floor (B2) - TFC": 421, + "Sea Shrine TFC Floor (B2) - TFC North": 420, + "Sea Shrine TFC Floor (B2) - Side Corner": 419, + "Sea Shrine TFC Floor (B2) - First Greed": 422, + "Sea Shrine TFC Floor (B2) - Second Greed": 423, + "Sea Shrine Mermaids (B1) - Passby": 427, + "Sea Shrine Mermaids (B1) - Bubbles 1": 428, + "Sea Shrine Mermaids (B1) - Bubbles 2": 429, + "Sea Shrine Mermaids (B1) - Incentive 1": 434, + "Sea Shrine Mermaids (B1) - Incentive 2": 435, + "Sea Shrine Mermaids (B1) - Incentive Major": 436, + "Sea Shrine Mermaids (B1) - Entrance 1": 424, + "Sea Shrine Mermaids (B1) - Entrance 2": 425, + "Sea Shrine Mermaids (B1) - Entrance 3": 426, + "Sea Shrine Mermaids (B1) - Four-Corner First": 430, + "Sea Shrine Mermaids (B1) - Four-Corner Second": 431, + "Sea Shrine Mermaids (B1) - Four-Corner Third": 432, + "Sea Shrine Mermaids (B1) - Four-Corner Fourth": 433, + "Sea Shrine Greed Floor (B3) - Chest 1": 418, + "Sea Shrine Greed Floor (B3) - Chest 2": 417, + "Sea Shrine Sharknado Floor (B4) - Dengbait 1": 409, + "Sea Shrine Sharknado Floor (B4) - Dengbait 2": 410, + "Sea Shrine Sharknado Floor (B4) - Side Corner 1": 411, + "Sea Shrine Sharknado Floor (B4) - Side Corner 2": 412, + "Sea Shrine Sharknado Floor (B4) - Side Corner 3": 413, + "Sea Shrine Sharknado Floor (B4) - Exit": 414, + "Sea Shrine Sharknado Floor (B4) - Greed Room 1": 405, + "Sea Shrine Sharknado Floor (B4) - Greed Room 2": 406, + "Sea Shrine Sharknado Floor (B4) - Greed Room 3": 407, + "Sea Shrine Sharknado Floor (B4) - Greed Room 4": 408, + "Waterfall Cave - Chest 1": 437, + "Waterfall Cave - Chest 2": 438, + "Waterfall Cave - Chest 3": 439, + "Waterfall Cave - Chest 4": 440, + "Waterfall Cave - Chest 5": 441, + "Waterfall Cave - Chest 6": 442, + "Mirage Tower (1F) - Chest 1": 456, + "Mirage Tower (1F) - Chest 2": 452, + "Mirage Tower (1F) - Chest 3": 453, + "Mirage Tower (1F) - Chest 4": 455, + "Mirage Tower (1F) - Chest 5": 454, + "Mirage Tower (1F) - Chest 6": 459, + "Mirage Tower (1F) - Chest 7": 457, + "Mirage Tower (1F) - Chest 8": 458, + "Mirage Tower (2F) - Lesser 1": 469, + "Mirage Tower (2F) - Lesser 2": 468, + "Mirage Tower (2F) - Lesser 3": 467, + "Mirage Tower (2F) - Lesser 4": 466, + "Mirage Tower (2F) - Lesser 5": 465, + "Mirage Tower (2F) - Greater 1": 460, + "Mirage Tower (2F) - Greater 2": 461, + "Mirage Tower (2F) - Greater 3": 462, + "Mirage Tower (2F) - Greater 4": 463, + "Mirage Tower (2F) - Greater 5": 464, + "Sky Fortress Plus (1F) - Solo": 479, + "Sky Fortress Plus (1F) - Five-Pack 1": 474, + "Sky Fortress Plus (1F) - Five-Pack 2": 475, + "Sky Fortress Plus (1F) - Five-Pack 3": 476, + "Sky Fortress Plus (1F) - Five-Pack 4": 477, + "Sky Fortress Plus (1F) - Five-Pack 5": 478, + "Sky Fortress Plus (1F) - Four-Pack 1": 470, + "Sky Fortress Plus (1F) - Four-Pack 2": 471, + "Sky Fortress Plus (1F) - Four-Pack 3": 472, + "Sky Fortress Plus (1F) - Four-Pack 4": 473, + "Sky Fortress Spider (2F) - Cheap Room 1": 485, + "Sky Fortress Spider (2F) - Cheap Room 2": 486, + "Sky Fortress Spider (2F) - Vault 1": 487, + "Sky Fortress Spider (2F) - Vault 2": 488, + "Sky Fortress Spider (2F) - Incentive": 489, + "Sky Fortress Spider (2F) - Gauntlet Room": 483, + "Sky Fortress Spider (2F) - Ribbon Room 1": 482, + "Sky Fortress Spider (2F) - Ribbon Room 2": 484, + "Sky Fortress Spider (2F) - Wardrobe 1": 480, + "Sky Fortress Spider (2F) - Wardrobe 2": 481, + "Sky Fortress Provides (3F) - Six-Pack 1": 498, + "Sky Fortress Provides (3F) - Six-Pack 2": 495, + "Sky Fortress Provides (3F) - Six-Pack 3": 494, + "Sky Fortress Provides (3F) - Six-Pack 4": 497, + "Sky Fortress Provides (3F) - Six-Pack 5": 496, + "Sky Fortress Provides (3F) - Six-Pack 6": 499, + "Sky Fortress Provides (3F) - CC's Gambit 1": 500, + "Sky Fortress Provides (3F) - CC's Gambit 2": 501, + "Sky Fortress Provides (3F) - CC's Gambit 3": 502, + "Sky Fortress Provides (3F) - CC's Gambit 4": 503, + "Sky Fortress Provides (3F) - Greed 1": 491, + "Sky Fortress Provides (3F) - Greed 2": 490, + "Sky Fortress Provides (3F) - Greed 3": 492, + "Sky Fortress Provides (3F) - Greed 4": 493, + "Temple of Fiends Revisited (3F) - Validation 1": 509, + "Temple of Fiends Revisited (3F) - Validation 2": 510, + "Temple of Fiends Revisited Kary Floor (6F) - Greed Checks 1": 507, + "Temple of Fiends Revisited Kary Floor (6F) - Greed Checks 2": 508, + "Temple of Fiends Revisited Kary Floor (6F) - Katana Chest": 506, + "Temple of Fiends Revisited Kary Floor (6F) - Vault": 505, + "Temple of Fiends Revisited Tiamat Floor (8F) - Masamune Chest": 504, + "Shop Item": 767, "King": 513, - "Princess2": 530, + "Princess": 530, "Matoya": 522, "Astos": 519, "Bikke": 516, - "CanoeSage": 533, - "ElfPrince": 518, + "Canoe Sage": 533, + "Elf Prince": 518, "Nerrick": 520, "Smith": 521, "CubeBot": 529, From b2f30d5fd001ec7a37ad56c3c989015a991a6089 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 3 Mar 2024 02:20:37 -0500 Subject: [PATCH 31/66] Lingo: Add a third location to Starting Room (#2839) Despite earlier efforts, there were still rare fill errors when door shuffle and color shuffle were on and early color hallways was off, because sphere 1 was too small. This turns "Starting Room - HI" back into a location, which should give the algorithm more room. The "forced good item" pool has been reconsidered. The problem with the specific item that caused the recent failure (Welcome Back - Shortcut to Starting Room) is that it only provided one location when color shuffle was on, which is a net of zero considering that the GOOD LUCK check was forced. It will no longer show up as a good item unless color shuffle is off. On an opposite vein, Rhyme Room Doors will now show up even if color shuffle is on, because it gives color hallways access by itself. A good item will only be forced onto GOOD LUCK now if there is more than one player. --- worlds/lingo/data/LL1.yaml | 1 + worlds/lingo/player_logic.py | 38 +++++++++++++++++++--------- worlds/lingo/test/TestDoors.py | 10 -------- worlds/lingo/test/TestOrangeTower.py | 4 --- worlds/lingo/test/TestProgressive.py | 6 ----- worlds/lingo/test/__init__.py | 9 ------- 6 files changed, 27 insertions(+), 41 deletions(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 1a149f2db9f0..f72e63c1427e 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -112,6 +112,7 @@ HI: id: Entry Room/Panel_hi_hi tag: midwhite + check: True HIDDEN: id: Entry Room/Panel_hidden_hidden tag: midwhite diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index 0ae303518cf1..3a6eedfe0ae1 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -248,30 +248,44 @@ def __init__(self, world: "LingoWorld"): "kind of logic error.") if door_shuffle != ShuffleDoors.option_none and location_classification != LocationClassification.insanity \ - and not early_color_hallways: - # If shuffle doors is on, force a useful item onto the HI panel. This may not necessarily get you out of BK, - # but the goal is to allow you to reach at least one more check. The non-painting ones are hardcoded right - # now. We only allow the entrance to the Pilgrim Room if color shuffle is off, because otherwise there are - # no extra checks in there. We only include the entrance to the Rhyme Room when color shuffle is off and - # door shuffle is on simple, because otherwise there are no extra checks in there. + and not early_color_hallways and world.multiworld.players > 1: + # Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is + # only three checks. In a multiplayer situation, this can be frustrating for the player because they are + # more likely to be stuck in the starting room for a long time. To remedy this, we will force a useful item + # onto the GOOD LUCK check under these circumstances. The goal is to expand sphere 1 to at least four + # checks (and likely more than that). + # + # Note: A very low LEVEL 2 requirement would naturally expand sphere 1 to four checks, but this is a very + # uncommon configuration, so we will ignore it and force a good item anyway. + + # Starting Room - Back Right Door gives access to OPEN and DEAD END. + # Starting Room - Exit Door gives access to OPEN and TRACE. good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"] if not color_shuffle: + # HOT CRUST and THIS. good_item_options.append("Pilgrim Room - Sun Painting") - if door_shuffle == ShuffleDoors.option_simple: - good_item_options += ["Welcome Back Doors"] + if door_shuffle == ShuffleDoors.option_simple: + # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS. + good_item_options.append("Welcome Back Doors") + else: + # WELCOME BACK and CLOCKWISE. + good_item_options.append("Welcome Back Area - Shortcut to Starting Room") - if not color_shuffle: - good_item_options.append("Rhyme Room Doors") - else: - good_item_options += ["Welcome Back Area - Shortcut to Starting Room"] + if door_shuffle == ShuffleDoors.option_simple: + # Color hallways access (NOTE: reconsider when sunwarp shuffling exists). + good_item_options.append("Rhyme Room Doors") + # When painting shuffle is off, most Starting Room paintings give color hallways access. The Wondrous's + # painting does not, but it gives access to SHRINK and WELCOME BACK. for painting_obj in PAINTINGS_BY_ROOM["Starting Room"]: if not painting_obj.enter_only or painting_obj.required_door is None: continue # If painting shuffle is on, we only want to consider paintings that actually go somewhere. + # + # NOTE: This does not guarantee that there will be any checks on the other side. if painting_shuffle and painting_obj.id not in self.painting_mapping.keys(): continue diff --git a/worlds/lingo/test/TestDoors.py b/worlds/lingo/test/TestDoors.py index 49a0f9c49010..f496c5f5785a 100644 --- a/worlds/lingo/test/TestDoors.py +++ b/worlds/lingo/test/TestDoors.py @@ -8,8 +8,6 @@ class TestRequiredRoomLogic(LingoTestBase): } def test_pilgrim_first(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Pilgrim Antechamber", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) @@ -30,8 +28,6 @@ def test_pilgrim_first(self) -> None: self.assertTrue(self.can_reach_location("The Seeker - Achievement")) def test_hidden_first(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) self.assertFalse(self.can_reach_location("The Seeker - Achievement")) @@ -59,8 +55,6 @@ class TestRequiredDoorLogic(LingoTestBase): } def test_through_rhyme(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) self.collect_by_name("Starting Room - Rhyme Room Entrance") @@ -70,8 +64,6 @@ def test_through_rhyme(self) -> None: self.assertTrue(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) def test_through_hidden(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) self.collect_by_name("Starting Room - Rhyme Room Entrance") @@ -91,8 +83,6 @@ class TestSimpleDoors(LingoTestBase): } def test_requirement(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) diff --git a/worlds/lingo/test/TestOrangeTower.py b/worlds/lingo/test/TestOrangeTower.py index 9170de108ad0..7b0c3bb52518 100644 --- a/worlds/lingo/test/TestOrangeTower.py +++ b/worlds/lingo/test/TestOrangeTower.py @@ -8,8 +8,6 @@ class TestProgressiveOrangeTower(LingoTestBase): } def test_from_welcome_back(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) @@ -85,8 +83,6 @@ def test_from_welcome_back(self) -> None: self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) def test_from_hub_room(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py index 081d6743a5f2..e79fd6bc9087 100644 --- a/worlds/lingo/test/TestProgressive.py +++ b/worlds/lingo/test/TestProgressive.py @@ -7,8 +7,6 @@ class TestComplexProgressiveHallwayRoom(LingoTestBase): } def test_item(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) @@ -60,8 +58,6 @@ class TestSimpleHallwayRoom(LingoTestBase): } def test_item(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) @@ -90,8 +86,6 @@ class TestProgressiveArtGallery(LingoTestBase): } def test_item(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) diff --git a/worlds/lingo/test/__init__.py b/worlds/lingo/test/__init__.py index 7ff456d8fcc3..a4196de110db 100644 --- a/worlds/lingo/test/__init__.py +++ b/worlds/lingo/test/__init__.py @@ -6,12 +6,3 @@ class LingoTestBase(WorldTestBase): game = "Lingo" player: ClassVar[int] = 1 - - def world_setup(self, *args, **kwargs): - super().world_setup(*args, **kwargs) - - def remove_forced_good_item(self): - location = self.multiworld.get_location("Second Room - Good Luck", self.player) - self.remove(location.item) - self.multiworld.itempool.append(location.item) - self.multiworld.state.events.add(location) From 526eb090891c9b0fed6b9fadeee2d02bf8d763c1 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 3 Mar 2024 07:11:44 -0600 Subject: [PATCH 32/66] Options: add a DeathLinkMixin dataclass to easily standardize death_link (#2355) * Options: add a DeathLinkOption dataclass to easily standardize death_link * rename to DeathLinkMixin * Update worlds/messenger/options.py --- Options.py | 5 +++++ worlds/messenger/options.py | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Options.py b/Options.py index 2e3927aae3f3..5fe4087132f7 100644 --- a/Options.py +++ b/Options.py @@ -1110,6 +1110,11 @@ class PerGameCommonOptions(CommonOptions): item_links: ItemLinks +@dataclass +class DeathLinkMixin: + death_link: DeathLink + + def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True): import os diff --git a/worlds/messenger/options.py b/worlds/messenger/options.py index 1da544bee70c..6984e215472a 100644 --- a/worlds/messenger/options.py +++ b/worlds/messenger/options.py @@ -3,7 +3,7 @@ from schema import And, Optional, Or, Schema -from Options import Accessibility, Choice, DeathLink, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \ +from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \ StartInventoryPool, Toggle @@ -133,7 +133,7 @@ class PlannedShopPrices(OptionDict): @dataclass -class MessengerOptions(PerGameCommonOptions): +class MessengerOptions(DeathLinkMixin, PerGameCommonOptions): accessibility: MessengerAccessibility start_inventory: StartInventoryPool logic_level: Logic @@ -146,5 +146,3 @@ class MessengerOptions(PerGameCommonOptions): percent_seals_required: RequiredSeals shop_price: ShopPrices shop_price_plan: PlannedShopPrices - death_link: DeathLink - From ef37ee81f9f3bbe501c4e52ed955e4cb7cb6aa43 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 07:23:02 -0800 Subject: [PATCH 33/66] Zillion: apworld-compatible package data (#2860) * Zillion: apworld-compatible module data * fixed `World` import --- setup.py | 1 - typings/kivy/graphics/texture.pyi | 2 +- worlds/zillion/__init__.py | 2 +- worlds/zillion/client.py | 24 ++++++++++++++++++------ worlds/zillion/config.py | 3 --- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 272e6de0be27..3f9a7f0ba63f 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,6 @@ "Super Mario 64", "VVVVVV", "Wargroove", - "Zillion", } # LogicMixin is broken before 3.10 import revamp diff --git a/typings/kivy/graphics/texture.pyi b/typings/kivy/graphics/texture.pyi index 19e03aad69dd..ca643b1cada5 100644 --- a/typings/kivy/graphics/texture.pyi +++ b/typings/kivy/graphics/texture.pyi @@ -10,4 +10,4 @@ class FillType_Drawable: class Texture: - pass + size: FillType_Vec diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index d30bef144464..d7e653bb8017 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -25,7 +25,7 @@ from zilliandomizer.logic_components.locations import Location as ZzLocation, Req from zilliandomizer.options import Chars -from ..AutoWorld import World, WebWorld +from worlds.AutoWorld import World, WebWorld class ZillionSettings(settings.Group): diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py index b10507aaf885..1a85b9df25f0 100644 --- a/worlds/zillion/client.py +++ b/worlds/zillion/client.py @@ -1,5 +1,7 @@ import asyncio import base64 +import io +import pkgutil import platform from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, cast @@ -17,7 +19,7 @@ from zilliandomizer.patch import RescueInfo from .id_maps import loc_name_to_id, make_id_to_others -from .config import base_id, zillion_map +from .config import base_id class ZillionCommandProcessor(ClientCommandProcessor): @@ -138,7 +140,9 @@ def run_gui(self) -> None: from kvui import GameManager from kivy.core.text import Label as CoreLabel from kivy.graphics import Ellipse, Color, Rectangle + from kivy.graphics.texture import Texture from kivy.uix.layout import Layout + from kivy.uix.image import CoreImage from kivy.uix.widget import Widget class ZillionManager(GameManager): @@ -150,12 +154,21 @@ class ZillionManager(GameManager): class MapPanel(Widget): MAP_WIDTH: ClassVar[int] = 281 - _number_textures: List[Any] = [] + map_background: CoreImage + _number_textures: List[Texture] = [] rooms: List[List[int]] = [] def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) + FILE_NAME = "empty-zillion-map-row-col-labels-281.png" + image_file_data = pkgutil.get_data(__name__, FILE_NAME) + if not image_file_data: + raise FileNotFoundError(f"{__name__=} {FILE_NAME=}") + data = io.BytesIO(image_file_data) + self.map_background = CoreImage(data, ext="png") + assert self.map_background.texture.size[0] == ZillionManager.MapPanel.MAP_WIDTH + self.rooms = [[0 for _ in range(8)] for _ in range(16)] self._make_numbers() @@ -176,10 +189,9 @@ def update_map(self, *args: Any) -> None: with self.canvas: Color(1, 1, 1, 1) - Rectangle(source=zillion_map, + Rectangle(texture=self.map_background.texture, pos=self.pos, - size=(ZillionManager.MapPanel.MAP_WIDTH, - int(ZillionManager.MapPanel.MAP_WIDTH * 1.456))) # aspect ratio of that image + size=self.map_background.texture.size) for y in range(16): for x in range(8): num = self.rooms[15 - y][x] @@ -194,7 +206,7 @@ def update_map(self, *args: Any) -> None: def build(self) -> Layout: container = super().build() - self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=0) + self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=ZillionManager.MapPanel.MAP_WIDTH) self.main_area_container.add_widget(self.map_widget) return container diff --git a/worlds/zillion/config.py b/worlds/zillion/config.py index ca02f9a99f41..e08c4f4278ed 100644 --- a/worlds/zillion/config.py +++ b/worlds/zillion/config.py @@ -1,4 +1 @@ -import os - base_id = 8675309 -zillion_map = os.path.join(os.path.dirname(__file__), "empty-zillion-map-row-col-labels-281.png") From 57d1fe6d799caa2a848dd7ca377fe1c6a04829c3 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:00:32 +0100 Subject: [PATCH 34/66] Docs: add note for stage_assert_generate to settings api (#2885) --- docs/settings api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/settings api.md b/docs/settings api.md index f9cbe5e021cc..41023879adf8 100644 --- a/docs/settings api.md +++ b/docs/settings api.md @@ -121,6 +121,10 @@ Path to a single file. Automatically resolves as user_path: Source folder or AP install path on Windows. ~/Archipelago for the AppImage. Will open a file browser if the file is missing when in GUI mode. +If the file is used in the world's `generate_output`, make sure to add a `stage_assert_generate` that checks if the +file is available, otherwise generation may fail at the very end. +See also [world api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#generation). + #### class method validate(cls, path: str) Override this and raise ValueError if validation fails. From d124df72e4624d0c159667c6a272901ee8b504d3 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 3 Mar 2024 10:25:21 -0600 Subject: [PATCH 35/66] Core: add specific can_reach helpers to CollectionState (#2867) --- BaseClasses.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index f41894535170..2be9a9820d07 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -717,14 +717,23 @@ def can_reach(self, assert isinstance(player, int), "can_reach: player is required if spot is str" # try to resolve a name if resolution_hint == 'Location': - spot = self.multiworld.get_location(spot, player) + return self.can_reach_location(spot, player) elif resolution_hint == 'Entrance': - spot = self.multiworld.get_entrance(spot, player) + return self.can_reach_entrance(spot, player) else: # default to Region - spot = self.multiworld.get_region(spot, player) + return self.can_reach_region(spot, player) return spot.can_reach(self) + def can_reach_location(self, spot: str, player: int) -> bool: + return self.multiworld.get_location(spot, player).can_reach(self) + + def can_reach_entrance(self, spot: str, player: int) -> bool: + return self.multiworld.get_entrance(spot, player).can_reach(self) + + def can_reach_region(self, spot: str, player: int) -> bool: + return self.multiworld.get_region(spot, player).can_reach(self) + def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None: if locations is None: locations = self.multiworld.get_filled_locations() From 519dffdb7371c8f7769124713de8861de59881dd Mon Sep 17 00:00:00 2001 From: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Date: Sun, 3 Mar 2024 11:59:31 -0500 Subject: [PATCH 36/66] TLOZ: Fix Logic for Gleeok guarded locations (#2734) Turns out you can't kill Gleeok with bombs or a candle as I happened to find out in a community async. While I'll be fine, a rare combination of settings could put all 4 possible weapons (the three levels of sword and the Magical Rod) to kill Gleeoks behind killing Gleeoks. This fix should prevent that from happening. Note: Even though there are technically 5 weapons that can kill Gleeok in the pool because at the moment we have an extra copy of the base Sword, I want to future-proof this incase we make changes to the item pool later. Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- worlds/tloz/Locations.py | 4 ++++ worlds/tloz/Rules.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/worlds/tloz/Locations.py b/worlds/tloz/Locations.py index 3e46c4383373..5b30357c940c 100644 --- a/worlds/tloz/Locations.py +++ b/worlds/tloz/Locations.py @@ -105,6 +105,10 @@ "Level 7 Bomb Drop (Dodongos)", "Level 7 Rupee Drop (Goriyas North)" ] +gleeok_locations = [ + "Level 4 Boss", "Level 4 Triforce", "Level 8 Boss", "Level 8 Triforce" +] + floor_location_game_offsets_early = { "Level 1 Item (Bow)": 0x7F, "Level 1 Item (Boomerang)": 0x44, diff --git a/worlds/tloz/Rules.py b/worlds/tloz/Rules.py index b94002f25da2..f8b21bff712c 100644 --- a/worlds/tloz/Rules.py +++ b/worlds/tloz/Rules.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from worlds.generic.Rules import add_rule -from .Locations import food_locations, shop_locations +from .Locations import food_locations, shop_locations, gleeok_locations from .ItemPool import dangerous_weapon_locations from .Options import StartingPosition @@ -80,6 +80,10 @@ def set_rules(tloz_world: "TLoZWorld"): add_rule(world.get_location(location, player), lambda state: state.has("Food", player)) + for location in gleeok_locations: + add_rule(world.get_location(location, player), + lambda state: state.has_group("swords", player) or state.has("Magical Rod", player)) + add_rule(world.get_location("Level 8 Item (Magical Key)", player), lambda state: state.has("Bow", player) and state.has_group("arrows", player)) if options.ExpandedPool: From 4e31e51d7aa181c742102047665d43aaae960dc5 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 11:09:06 -0800 Subject: [PATCH 37/66] Core: clarify error message when reading an `APContainer` (#2887) --- worlds/Files.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/worlds/Files.py b/worlds/Files.py index 336a3090937b..dbeb54cfde7d 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -41,6 +41,13 @@ def get_handler(file: str) -> Optional[AutoPatchRegister]: current_patch_version: int = 5 +class InvalidDataError(Exception): + """ + Since games can override `read_contents` in APContainer, + this is to report problems in that process. + """ + + class APContainer: """A zipfile containing at least archipelago.json""" version: int = current_patch_version @@ -89,7 +96,15 @@ def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None: with zipfile.ZipFile(zip_file, "r") as zf: if file: self.path = zf.filename - self.read_contents(zf) + try: + self.read_contents(zf) + except Exception as e: + message = "" + if len(e.args): + arg0 = e.args[0] + if isinstance(e.args[0], str): + message = f"{arg0} - " + raise InvalidDataError(f"{message}This might be the incorrect world version for this file") from e def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: with opened_zipfile.open("archipelago.json", "r") as f: From 113c54f9bea7138946b9bcc9b583a34254414b4f Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 13:10:14 -0800 Subject: [PATCH 38/66] Zillion: remove rom requirement for generation (#2875) * in the middle of work towards no rom for generation (not working) * no rom needed for Zillion generation * revert core changes --- worlds/zillion/__init__.py | 113 +++++++++++--------------------- worlds/zillion/client.py | 3 +- worlds/zillion/gen_data.py | 35 ++++++++++ worlds/zillion/id_maps.py | 75 +++++++++++++++++++-- worlds/zillion/patch.py | 57 ++++++++++++++-- worlds/zillion/requirements.txt | 2 +- 6 files changed, 200 insertions(+), 85 deletions(-) create mode 100644 worlds/zillion/gen_data.py diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index d7e653bb8017..b4e382e097d2 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -4,20 +4,22 @@ import settings import threading import typing -from typing import Any, Dict, List, Set, Tuple, Optional, cast +from typing import Any, Dict, List, Set, Tuple, Optional import os import logging from BaseClasses import ItemClassification, LocationProgressType, \ MultiWorld, Item, CollectionState, Entrance, Tutorial + +from .gen_data import GenData from .logic import cs_to_zz_locs from .region import ZillionLocation, ZillionRegion from .options import ZillionOptions, validate -from .id_maps import item_name_to_id as _item_name_to_id, \ +from .id_maps import ZillionSlotInfo, get_slot_info, item_name_to_id as _item_name_to_id, \ loc_name_to_id as _loc_name_to_id, make_id_to_others, \ zz_reg_name_to_reg_name, base_id from .item import ZillionItem -from .patch import ZillionDeltaPatch, get_base_rom_path +from .patch import ZillionPatch from zilliandomizer.randomizer import Randomizer as ZzRandomizer from zilliandomizer.system import System @@ -33,8 +35,8 @@ class RomFile(settings.UserFilePath): """File name of the Zillion US rom""" description = "Zillion US ROM File" copy_to = "Zillion (UE) [!].sms" - assert ZillionDeltaPatch.hash - md5s = [ZillionDeltaPatch.hash] + assert ZillionPatch.hash + md5s = [ZillionPatch.hash] class RomStart(str): """ @@ -134,14 +136,6 @@ def _make_item_maps(self, start_char: Chars) -> None: _id_to_name, _id_to_zz_id, id_to_zz_item = make_id_to_others(start_char) self.id_to_zz_item = id_to_zz_item - @classmethod - def stage_assert_generate(cls, multiworld: MultiWorld) -> None: - """Checks that a game is capable of generating, usually checks for some base file like a ROM. - Not run for unittests since they don't produce output""" - rom_file = get_base_rom_path() - if not os.path.exists(rom_file): - raise FileNotFoundError(rom_file) - def generate_early(self) -> None: if not hasattr(self.multiworld, "zillion_logic_cache"): setattr(self.multiworld, "zillion_logic_cache", {}) @@ -311,7 +305,9 @@ def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None: if sc != to_stay: group_players.remove(p) assert "world" in group - cast(ZillionWorld, group["world"])._make_item_maps(to_stay) + group_world = group["world"] + assert isinstance(group_world, ZillionWorld) + group_world._make_item_maps(to_stay) def post_fill(self) -> None: """Optional Method that is called after regular fill. Can be used to do adjustments before output generation. @@ -319,27 +315,28 @@ def post_fill(self) -> None: self.zz_system.post_fill() - def finalize_item_locations(self) -> None: + def finalize_item_locations(self) -> GenData: """ sync zilliandomizer item locations with AP item locations + + return the data needed to generate output """ - rom_dir_name = os.path.dirname(get_base_rom_path()) - self.zz_system.make_patcher(rom_dir_name) - assert self.zz_system.randomizer and self.zz_system.patcher, "generate_early hasn't been called" - zz_options = self.zz_system.randomizer.options + + assert self.zz_system.randomizer, "generate_early hasn't been called" # debug_zz_loc_ids: Dict[str, int] = {} empty = zz_items[4] multi_item = empty # a different patcher method differentiates empty from ap multi item multi_items: Dict[str, Tuple[str, str]] = {} # zz_loc_name to (item_name, player_name) - for loc in self.multiworld.get_locations(self.player): - z_loc = cast(ZillionLocation, loc) + for z_loc in self.multiworld.get_locations(self.player): + assert isinstance(z_loc, ZillionLocation) # debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc) if z_loc.item is None: self.logger.warn("generate_output location has no item - is that ok?") z_loc.zz_loc.item = empty elif z_loc.item.player == self.player: - z_item = cast(ZillionItem, z_loc.item) + z_item = z_loc.item + assert isinstance(z_item, ZillionItem) z_loc.zz_loc.item = z_item.zz_item else: # another player's item # print(f"put multi item in {z_loc.zz_loc.name}") @@ -368,47 +365,32 @@ def finalize_item_locations(self) -> None: f"in world {self.player} didn't get an item" ) - zz_patcher = self.zz_system.patcher - - zz_patcher.write_locations(self.zz_system.randomizer.regions, - zz_options.start_char, - self.zz_system.randomizer.loc_name_2_pretty) - self.slot_data_ready.set() - rm = self.zz_system.resource_managers - assert rm, "missing resource_managers from generate_early" - zz_patcher.all_fixes_and_options(zz_options, rm) - zz_patcher.set_external_item_interface(zz_options.start_char, zz_options.max_level) - zz_patcher.set_multiworld_items(multi_items) game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode() - zz_patcher.set_rom_to_ram_data(game_id) - def generate_output(self, output_directory: str) -> None: - """This method gets called from a threadpool, do not use world.random here. - If you need any last-second randomization, use MultiWorld.per_slot_randoms[slot] instead.""" - self.finalize_item_locations() + return GenData(multi_items, self.zz_system.get_game(), game_id) - assert self.zz_system.patcher, "didn't get patcher from finalize_item_locations" - # original_rom_bytes = self.zz_patcher.rom - patched_rom_bytes = self.zz_system.patcher.get_patched_bytes() + def generate_output(self, output_directory: str) -> None: + """This method gets called from a threadpool, do not use multiworld.random here. + If you need any last-second randomization, use self.random instead.""" + try: + gen_data = self.finalize_item_locations() + except BaseException: + raise + finally: + self.slot_data_ready.set() out_file_base = self.multiworld.get_out_file_name_base(self.player) - filename = os.path.join( - output_directory, - f'{out_file_base}{ZillionDeltaPatch.result_file_ending}' - ) - with open(filename, "wb") as binary_file: - binary_file.write(patched_rom_bytes) - patch = ZillionDeltaPatch( - os.path.splitext(filename)[0] + ZillionDeltaPatch.patch_file_ending, - player=self.player, - player_name=self.multiworld.player_name[self.player], - patched_path=filename - ) + patch_file_name = os.path.join(output_directory, f"{out_file_base}{ZillionPatch.patch_file_ending}") + patch = ZillionPatch(patch_file_name, + player=self.player, + player_name=self.multiworld.player_name[self.player], + gen_data_str=gen_data.to_json()) patch.write() - os.remove(filename) - def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot + self.logger.debug(f"Zillion player {self.player} finished generate_output") + + def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot """Fill in the `slot_data` field in the `Connected` network package. This is a way the generator can give custom data to the client. The client will receive this as JSON in the `Connected` response.""" @@ -418,25 +400,10 @@ def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot # TODO: tell client which canisters are keywords # so it can open and get those when restoring doors - assert self.zz_system.randomizer, "didn't get randomizer from generate_early" - - rescues: Dict[str, Any] = {} self.slot_data_ready.wait() - zz_patcher = self.zz_system.patcher - assert zz_patcher, "didn't get patcher from generate_output" - for i in (0, 1): - if i in zz_patcher.rescue_locations: - ri = zz_patcher.rescue_locations[i] - rescues[str(i)] = { - "start_char": ri.start_char, - "room_code": ri.room_code, - "mask": ri.mask - } - return { - "start_char": self.zz_system.randomizer.options.start_char, - "rescues": rescues, - "loc_mem_to_id": zz_patcher.loc_memory_to_loc_id - } + assert self.zz_system.randomizer, "didn't get randomizer from generate_early" + game = self.zz_system.get_game() + return get_slot_info(game.regions, game.char_order[0], game.loc_name_2_pretty) # def modify_multidata(self, multidata: Dict[str, Any]) -> None: # """For deeper modification of server multidata.""" diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py index 1a85b9df25f0..5c2e11453036 100644 --- a/worlds/zillion/client.py +++ b/worlds/zillion/client.py @@ -12,11 +12,10 @@ import colorama -from zilliandomizer.zri.memory import Memory +from zilliandomizer.zri.memory import Memory, RescueInfo from zilliandomizer.zri import events from zilliandomizer.utils.loc_name_maps import id_to_loc from zilliandomizer.options import Chars -from zilliandomizer.patch import RescueInfo from .id_maps import loc_name_to_id, make_id_to_others from .config import base_id diff --git a/worlds/zillion/gen_data.py b/worlds/zillion/gen_data.py new file mode 100644 index 000000000000..aa24ff8961b3 --- /dev/null +++ b/worlds/zillion/gen_data.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass +import json +from typing import Dict, Tuple + +from zilliandomizer.game import Game as ZzGame + + +@dataclass +class GenData: + """ data passed from generation to patcher """ + + multi_items: Dict[str, Tuple[str, str]] + """ zz_loc_name to (item_name, player_name) """ + zz_game: ZzGame + game_id: bytes + """ the byte string used to detect the rom """ + + def to_json(self) -> str: + """ serialized data from generation needed to patch rom """ + jsonable = { + "multi_items": self.multi_items, + "zz_game": self.zz_game.to_jsonable(), + "game_id": list(self.game_id) + } + return json.dumps(jsonable) + + @staticmethod + def from_json(gen_data_str: str) -> "GenData": + """ the reverse of `to_json` """ + from_json = json.loads(gen_data_str) + return GenData( + from_json["multi_items"], + ZzGame.from_jsonable(from_json["zz_game"]), + bytes(from_json["game_id"]) + ) diff --git a/worlds/zillion/id_maps.py b/worlds/zillion/id_maps.py index bc9caeeece2e..32d71fc79b30 100644 --- a/worlds/zillion/id_maps.py +++ b/worlds/zillion/id_maps.py @@ -1,10 +1,22 @@ -from typing import Dict, Tuple -from zilliandomizer.logic_components.items import Item as ZzItem, \ - item_name_to_id as zz_item_name_to_zz_id, items as zz_items, \ - item_name_to_item as zz_item_name_to_zz_item +from collections import defaultdict +from typing import Dict, Iterable, Mapping, Tuple, TypedDict + +from zilliandomizer.logic_components.items import ( + Item as ZzItem, + KEYWORD, + NORMAL, + RESCUE, + item_name_to_id as zz_item_name_to_zz_id, + items as zz_items, + item_name_to_item as zz_item_name_to_zz_item, +) +from zilliandomizer.logic_components.regions import RegionData +from zilliandomizer.low_resources.item_rooms import item_room_codes from zilliandomizer.options import Chars from zilliandomizer.utils.loc_name_maps import loc_to_id as pretty_loc_name_to_id -from zilliandomizer.utils import parse_reg_name +from zilliandomizer.utils import parse_loc_name, parse_reg_name +from zilliandomizer.zri.memory import RescueInfo + from .config import base_id as base_id item_name_to_id = { @@ -91,3 +103,56 @@ def zz_reg_name_to_reg_name(zz_reg_name: str) -> str: end = zz_reg_name[5:] return f"{make_room_name(row, col)} {end.upper()}" return zz_reg_name + + +class ClientRescue(TypedDict): + start_char: Chars + room_code: int + mask: int + + +class ZillionSlotInfo(TypedDict): + start_char: Chars + rescues: Dict[str, ClientRescue] + loc_mem_to_id: Dict[int, int] + """ memory location of canister to Archipelago location id number """ + + +def get_slot_info(regions: Iterable[RegionData], + start_char: Chars, + loc_name_to_pretty: Mapping[str, str]) -> ZillionSlotInfo: + items_placed_in_map_index: Dict[int, int] = defaultdict(int) + rescue_locations: Dict[int, RescueInfo] = {} + loc_memory_to_loc_id: Dict[int, int] = {} + for region in regions: + for loc in region.locations: + assert loc.item, ("There should be an item placed in every location before " + f"writing slot info. {loc.name} is missing item.") + if loc.item.code in {KEYWORD, NORMAL, RESCUE}: + row, col, _y, _x = parse_loc_name(loc.name) + map_index = row * 8 + col + item_no = items_placed_in_map_index[map_index] + room_code = item_room_codes[map_index] + + r = room_code + m = 1 << item_no + if loc.item.code == RESCUE: + rescue_locations[loc.item.id] = RescueInfo(start_char, r, m) + loc_memory = (r << 7) | m + loc_memory_to_loc_id[loc_memory] = pretty_loc_name_to_id[loc_name_to_pretty[loc.name]] + items_placed_in_map_index[map_index] += 1 + + rescues: Dict[str, ClientRescue] = {} + for i in (0, 1): + if i in rescue_locations: + ri = rescue_locations[i] + rescues[str(i)] = { + "start_char": ri.start_char, + "room_code": ri.room_code, + "mask": ri.mask + } + return { + "start_char": start_char, + "rescues": rescues, + "loc_mem_to_id": loc_memory_to_loc_id + } diff --git a/worlds/zillion/patch.py b/worlds/zillion/patch.py index 148caac9fb7b..dcbb85bcfc89 100644 --- a/worlds/zillion/patch.py +++ b/worlds/zillion/patch.py @@ -1,22 +1,53 @@ -from typing import BinaryIO, Optional, cast -import Utils -from worlds.Files import APDeltaPatch import os +from typing import Any, BinaryIO, Optional, cast +import zipfile + +from typing_extensions import override + +import Utils +from worlds.Files import APPatch + +from zilliandomizer.patch import Patcher + +from .gen_data import GenData USHASH = 'd4bf9e7bcf9a48da53785d2ae7bc4270' -class ZillionDeltaPatch(APDeltaPatch): +class ZillionPatch(APPatch): hash = USHASH game = "Zillion" patch_file_ending = ".apzl" result_file_ending = ".sms" + gen_data_str: str + """ JSON encoded """ + + def __init__(self, *args: Any, gen_data_str: str = "", **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.gen_data_str = gen_data_str + @classmethod def get_source_data(cls) -> bytes: with open(get_base_rom_path(), "rb") as stream: return read_rom(stream) + @override + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super().write_contents(opened_zipfile) + opened_zipfile.writestr("gen_data.json", + self.gen_data_str, + compress_type=zipfile.ZIP_DEFLATED) + + @override + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super().read_contents(opened_zipfile) + self.gen_data_str = opened_zipfile.read("gen_data.json").decode() + + def patch(self, target: str) -> None: + self.read() + write_rom_from_gen_data(self.gen_data_str, target) + def get_base_rom_path(file_name: Optional[str] = None) -> str: options = Utils.get_options() @@ -32,3 +63,21 @@ def read_rom(stream: BinaryIO) -> bytes: data = stream.read() # I'm not aware of any sms header. return data + + +def write_rom_from_gen_data(gen_data_str: str, output_rom_file_name: str) -> None: + """ take the output of `GenData.to_json`, and create rom from it """ + gen_data = GenData.from_json(gen_data_str) + + base_rom_path = get_base_rom_path() + zz_patcher = Patcher(base_rom_path) + + zz_patcher.write_locations(gen_data.zz_game.regions, gen_data.zz_game.char_order[0]) + zz_patcher.all_fixes_and_options(gen_data.zz_game) + zz_patcher.set_external_item_interface(gen_data.zz_game.char_order[0], gen_data.zz_game.options.max_level) + zz_patcher.set_multiworld_items(gen_data.multi_items) + zz_patcher.set_rom_to_ram_data(gen_data.game_id) + + patched_rom_bytes = zz_patcher.get_patched_bytes() + with open(output_rom_file_name, "wb") as binary_file: + binary_file.write(patched_rom_bytes) diff --git a/worlds/zillion/requirements.txt b/worlds/zillion/requirements.txt index c8944925acac..3a784846a891 100644 --- a/worlds/zillion/requirements.txt +++ b/worlds/zillion/requirements.txt @@ -1,2 +1,2 @@ -zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@ae00a4b186be897c7cfaf429a0e0ff83c4ecf28c#0.6.0 +zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@b36a23b5a138c78732ac8efb5b5ca8b0be07dcff#0.7.0 typing-extensions>=4.7, <5 From 37a871eab1b58afc7abd10d40dd1f4a917670b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Sun, 3 Mar 2024 16:30:51 -0500 Subject: [PATCH 39/66] Core: Allow common collections in OptionSet and OptionList constructors (#2874) * allow common collection in set and list option constructors * allow any iterable of strings * add return None --------- Co-authored-by: beauxq --- Options.py | 25 +++++++++++++------------ Utils.py | 11 +++++++++++ requirements.txt | 1 + 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Options.py b/Options.py index 5fe4087132f7..139dc0a0bbe6 100644 --- a/Options.py +++ b/Options.py @@ -1,19 +1,18 @@ from __future__ import annotations import abc -import logging -from copy import deepcopy -from dataclasses import dataclass import functools +import logging import math import numbers import random import typing from copy import deepcopy +from dataclasses import dataclass from schema import And, Optional, Or, Schema -from Utils import get_fuzzy_results +from Utils import get_fuzzy_results, is_iterable_of_str if typing.TYPE_CHECKING: from BaseClasses import PlandoOptions @@ -59,6 +58,7 @@ def __new__(mcs, name, bases, attrs): def verify(self, *args, **kwargs) -> None: for f in verifiers: f(self, *args, **kwargs) + attrs["verify"] = verify else: assert verifiers, "class Option is supposed to implement def verify" @@ -183,6 +183,7 @@ def get_option_name(cls, value: str) -> str: class NumericOption(Option[int], numbers.Integral, abc.ABC): default = 0 + # note: some of the `typing.Any`` here is a result of unresolved issue in python standards # `int` is not a `numbers.Integral` according to the official typestubs # (even though isinstance(5, numbers.Integral) == True) @@ -598,7 +599,7 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P if isinstance(self.value, int): return from BaseClasses import PlandoOptions - if not(PlandoOptions.bosses & plando_options): + if not (PlandoOptions.bosses & plando_options): # plando is disabled but plando options were given so pull the option and change it to an int option = self.value.split(";")[-1] self.value = self.options[option] @@ -765,7 +766,7 @@ class VerifyKeys(metaclass=FreezeValidKeys): value: typing.Any @classmethod - def verify_keys(cls, data: typing.List[str]): + def verify_keys(cls, data: typing.Iterable[str]) -> None: if cls.valid_keys: data = set(data) dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data) @@ -843,11 +844,11 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): # If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead. # Not a docstring so it doesn't get grabbed by the options system. - default: typing.List[typing.Any] = [] + default: typing.Union[typing.List[typing.Any], typing.Tuple[typing.Any, ...]] = () supports_weighting = False - def __init__(self, value: typing.List[typing.Any]): - self.value = deepcopy(value) + def __init__(self, value: typing.Iterable[str]): + self.value = list(deepcopy(value)) super(OptionList, self).__init__() @classmethod @@ -856,7 +857,7 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): - if type(data) == list: + if is_iterable_of_str(data): cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -882,7 +883,7 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): - if isinstance(data, (list, set, frozenset)): + if is_iterable_of_str(data): cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -932,7 +933,7 @@ def __new__(mcs, bases: typing.Tuple[type, ...], attrs: typing.Dict[str, typing.Any]) -> "OptionsMetaProperty": for attr_type in attrs.values(): - assert not isinstance(attr_type, AssembleOptions),\ + assert not isinstance(attr_type, AssembleOptions), \ f"Options for {name} should be type hinted on the class, not assigned" return super().__new__(mcs, name, bases, attrs) diff --git a/Utils.py b/Utils.py index da2d837ad3a3..cea6405a38b4 100644 --- a/Utils.py +++ b/Utils.py @@ -19,6 +19,7 @@ from argparse import Namespace from settings import Settings, get_settings from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union +from typing_extensions import TypeGuard from yaml import load, load_all, dump try: @@ -966,3 +967,13 @@ def __bool__(self): def __len__(self): return sum(len(iterable) for iterable in self.iterable) + + +def is_iterable_of_str(obj: object) -> TypeGuard[typing.Iterable[str]]: + """ but not a `str` (because technically, `str` is `Iterable[str]`) """ + if isinstance(obj, str): + return False + if not isinstance(obj, typing.Iterable): + return False + obj_it: typing.Iterable[object] = obj + return all(isinstance(v, str) for v in obj_it) diff --git a/requirements.txt b/requirements.txt index e2ccb67c18d4..9531e3058e8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ certifi>=2023.11.17 cython>=3.0.8 cymem>=2.0.8 orjson>=3.9.10 +typing-extensions>=4.7.0 From a70b94fd62f6dea76592e6df93deda8645c34d4c Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 3 Mar 2024 19:52:03 -0500 Subject: [PATCH 40/66] LTTP: Open Pyramid and Shop Prog Balancing Bug Fixes (#2890) --- worlds/alttp/Options.py | 4 ++-- worlds/alttp/Shops.py | 9 ++++++--- worlds/alttp/__init__.py | 8 ++++++-- worlds/alttp/test/options/TestOpenPyramid.py | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index 8cc5d32608d9..afd52955455b 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -153,9 +153,9 @@ class OpenPyramid(Choice): def to_bool(self, world: MultiWorld, player: int) -> bool: if self.value == self.option_goal: - return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} + return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} elif self.value == self.option_auto: - return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ + return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ and (world.entrance_shuffle[player] in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not world.shuffle_ganon) elif self.value == self.option_open: diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 64a385a18587..1d548d8fdb53 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -176,6 +176,9 @@ def push_shop_inventories(multiworld): get_price(multiworld, location.shop.inventory[location.shop_slot], location.player, location.shop_price_type)[1]) + for world in multiworld.get_game_worlds("A Link to the Past"): + world.pushed_shop_inventories.set() + def create_shops(multiworld, player: int): @@ -451,15 +454,15 @@ def get_price(multiworld, item, player: int, price_type=None): if multiworld.randomize_shop_prices[player]: adjust = 2 if price < 100 else 5 price = int((price / adjust) * (0.5 + multiworld.random.random() * 1.5)) * adjust - multiworld.random.shuffle(price_types) + multiworld.per_slot_randoms[player].shuffle(price_types) for p_type in price_types: if any(x in item['item'] for x in price_blacklist[p_type]): continue return p_type, price_chart[p_type](price, diff) else: # This is an AP location and the price will be adjusted after an item is shuffled into it - p_type = multiworld.random.choice(price_types) - return p_type, price_chart[p_type](min(int(multiworld.random.randint(8, 56) + p_type = multiworld.per_slot_randoms[player].choice(price_types) + return p_type, price_chart[p_type](min(int(multiworld.per_slot_randoms[player].randint(8, 56) * multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 7a2664b3f4bc..a7ade61c9e33 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -256,6 +256,7 @@ def __init__(self, *args, **kwargs): self.dungeon_local_item_names = set() self.dungeon_specific_item_names = set() self.rom_name_available_event = threading.Event() + self.pushed_shop_inventories = threading.Event() self.has_progressive_bows = False self.dungeons = {} self.waterfall_fairy_bottle_fill = "Bottle" @@ -508,8 +509,8 @@ def stage_pre_fill(cls, world): fill_dungeons_restrictive(world) @classmethod - def stage_post_fill(cls, world): - push_shop_inventories(world) + def stage_generate_output(cls, multiworld, output_directory): + push_shop_inventories(multiworld) @property def use_enemizer(self) -> bool: @@ -523,6 +524,9 @@ def use_enemizer(self) -> bool: def generate_output(self, output_directory: str): multiworld = self.multiworld player = self.player + + self.pushed_shop_inventories.wait() + try: use_enemizer = self.use_enemizer diff --git a/worlds/alttp/test/options/TestOpenPyramid.py b/worlds/alttp/test/options/TestOpenPyramid.py index c66eb2ee98ec..895ecb95a949 100644 --- a/worlds/alttp/test/options/TestOpenPyramid.py +++ b/worlds/alttp/test/options/TestOpenPyramid.py @@ -23,7 +23,7 @@ class GoalPyramidTest(PyramidTestBase): } def testCrystalsGoalAccess(self): - self.multiworld.goal[1] = "crystals" + self.multiworld.goal[1].value = 1 # crystals self.assertFalse(self.can_reach_entrance("Pyramid Hole")) self.collect_by_name(["Hammer", "Progressive Glove", "Moon Pearl"]) self.assertTrue(self.can_reach_entrance("Pyramid Hole")) From ecec931e9f40838cee5a735313fe2219b4038977 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 23:26:52 -0800 Subject: [PATCH 41/66] Core: fix (typing) mistake in PR #2887 (#2891) I made this variable for more compatible and safer type narrowing, and then I didn't use if for the type narrowing. --- worlds/Files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/Files.py b/worlds/Files.py index dbeb54cfde7d..f46b9cba7a7c 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -102,7 +102,7 @@ def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None: message = "" if len(e.args): arg0 = e.args[0] - if isinstance(e.args[0], str): + if isinstance(arg0, str): message = f"{arg0} - " raise InvalidDataError(f"{message}This might be the incorrect world version for this file") from e From b9d561ae25187f0b8abfa926ec1dd7c5f4563480 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Mon, 4 Mar 2024 21:55:46 -0500 Subject: [PATCH 42/66] Core: Update generic.Rules.py (#2896) --- worlds/generic/Rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index ac5e1aa50750..c434351e9493 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -41,12 +41,12 @@ def forbid(sender: int, receiver: int, items: typing.Set[str]): forbid_data[sender][receiver].update(items) for receiving_player in world.player_ids: - local_items: typing.Set[str] = world.local_items[receiving_player].value + local_items: typing.Set[str] = world.worlds[receiving_player].options.local_items.value if local_items: for sending_player in world.player_ids: if receiving_player != sending_player: forbid(sending_player, receiving_player, local_items) - non_local_items: typing.Set[str] = world.non_local_items[receiving_player].value + non_local_items: typing.Set[str] = world.worlds[receiving_player].options.non_local_items.value if non_local_items: forbid(receiving_player, receiving_player, non_local_items) From 12cc93082557e939b119f3ee2ea449b7f829b7e8 Mon Sep 17 00:00:00 2001 From: Justus Lind Date: Tue, 5 Mar 2024 18:33:15 +1000 Subject: [PATCH 43/66] Muse Dash: Add Muse Dash 4.1.0 songs (#2878) --- worlds/musedash/MuseDashCollection.py | 9 +++++++++ worlds/musedash/MuseDashData.txt | 10 +++++++++- worlds/musedash/test/TestCollection.py | 7 +------ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py index cc4cc71ce33f..20bb8decebcc 100644 --- a/worlds/musedash/MuseDashCollection.py +++ b/worlds/musedash/MuseDashCollection.py @@ -37,6 +37,12 @@ class MuseDashCollections: "PeroPero in the Universe", "umpopoff" ] + + REMOVED_SONGS = [ + "CHAOS Glitch", + "FM 17314 SUGAR RADIO", + "Yume Ou Mono Yo Secret" + ] album_items: Dict[str, AlbumData] = {} album_locations: Dict[str, int] = {} @@ -130,6 +136,9 @@ def get_songs_with_settings(self, dlc_songs: Set[str], streamer_mode_active: boo for songKey, songData in self.song_items.items(): if not self.song_matches_dlc_filter(songData, dlc_songs): continue + + if songKey in self.REMOVED_SONGS: + continue if streamer_mode_active and not songData.streamer_mode: continue diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt index ce5929bfd00d..620c1968bda8 100644 --- a/worlds/musedash/MuseDashData.txt +++ b/worlds/musedash/MuseDashData.txt @@ -529,4 +529,12 @@ Dance of the Corpses|70-5|Rin Len's Mirrorland|False|2|5|8| Bitter Choco Decoration|70-6|Rin Len's Mirrorland|False|3|6|9| Dance Robot Dance|70-7|Rin Len's Mirrorland|False|4|7|10| Sweet Devil|70-8|Rin Len's Mirrorland|False|5|7|9| -Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9| \ No newline at end of file +Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9| +Yume Ou Mono Yo Secret|0-53|Default Music|True|6|8|10| +Yume Ou Mono Yo|0-54|Default Music|True|1|4|0| +Sweet Dream VIVINOS|71-0|Valentine Stage|False|1|4|7| +Ruler Of My Heart VIVINOS|71-1|Valentine Stage|False|2|4|6| +Reality Show|71-2|Valentine Stage|False|5|7|10| +SIG feat.Tobokegao|71-3|Valentine Stage|True|3|6|8| +Rose Love|71-4|Valentine Stage|True|2|4|7| +Euphoria|71-5|Valentine Stage|True|1|3|6| \ No newline at end of file diff --git a/worlds/musedash/test/TestCollection.py b/worlds/musedash/test/TestCollection.py index f9422388ae1e..48cb69e403ad 100644 --- a/worlds/musedash/test/TestCollection.py +++ b/worlds/musedash/test/TestCollection.py @@ -3,11 +3,6 @@ class CollectionsTest(unittest.TestCase): - REMOVED_SONGS = [ - "CHAOS Glitch", - "FM 17314 SUGAR RADIO", - ] - def test_all_names_are_ascii(self) -> None: bad_names = list() collection = MuseDashCollections() @@ -58,5 +53,5 @@ def test_remove_songs_are_not_generated(self) -> None: collection = MuseDashCollections() songs = collection.get_songs_with_settings({x for x in collection.DLC}, False, 0, 12) - for song_name in self.REMOVED_SONGS: + for song_name in collection.REMOVED_SONGS: self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.") From 26ee9fe05cefea24c907a9a71941846dcf6db6bd Mon Sep 17 00:00:00 2001 From: Remy Jette Date: Tue, 5 Mar 2024 00:36:18 -0800 Subject: [PATCH 44/66] Pokemon RB: Fix exceptions raised by /bank (#2836) * If the user tried to run `/bank` with no arguments to see the current value while disconnected, previously it threw an exception `KeyError: 'EnergyLinkNone'`. Now it informs the user that they must be connected and in-game, like `/bank deposit` and `/bank withdraw` do. I'm also open to adding another `if` branch to make `/bank` only check for `ctx.server` instead of combining it with the other bank commands, to allow connecting to check the bank before the game save is loaded. If that's preferred let me know. * If the user tried to run `/bank` or `/bank deposit` when the EnergyLink hadn't been used yet, they would get a `TypeError` exception. Trying `/bank withdraw` would give no output and would crash the lua connector script. Now it treats a `None` EnergyLink as `0` and works properly. --- worlds/pokemon_rb/client.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/worlds/pokemon_rb/client.py b/worlds/pokemon_rb/client.py index 9e2689bccc37..8ed21443e0d4 100644 --- a/worlds/pokemon_rb/client.py +++ b/worlds/pokemon_rb/client.py @@ -206,7 +206,7 @@ async def game_watcher(self, ctx): money = int(original_money.hex()) if self.banking_command > money: logger.warning(f"You do not have ${self.banking_command} to deposit!") - elif (-self.banking_command * BANK_EXCHANGE_RATE) > ctx.stored_data[f"EnergyLink{ctx.team}"]: + elif (-self.banking_command * BANK_EXCHANGE_RATE) > (ctx.stored_data[f"EnergyLink{ctx.team}"] or 0): logger.warning("Not enough money in the EnergyLink storage!") else: if self.banking_command + money > 999999: @@ -258,11 +258,12 @@ def cmd_bank(self, cmd: str = "", amount: str = ""): if self.ctx.game != "Pokemon Red and Blue": logger.warning("This command can only be used while playing Pokémon Red and Blue") return - if not cmd: - logger.info(f"Money available: {int(self.ctx.stored_data[f'EnergyLink{self.ctx.team}'] / BANK_EXCHANGE_RATE)}") - return - elif (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state: + if (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state: logger.info(f"Must be connected to server and in game.") + return + elif not cmd: + logger.info(f"Money available: {int((self.ctx.stored_data[f'EnergyLink{self.ctx.team}'] or 0) / BANK_EXCHANGE_RATE)}") + return elif not amount: logger.warning("You must specify an amount.") elif cmd == "withdraw": From bcbb06d78d36efcef268e6a460bcf88423a347e7 Mon Sep 17 00:00:00 2001 From: Nicholas Brochu Date: Tue, 5 Mar 2024 10:46:09 -0500 Subject: [PATCH 45/66] Fix usage of `__new__` for `SpecialRange` compatibility fallback (#2513) --- Options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Options.py b/Options.py index 139dc0a0bbe6..ff8ad11c5a5a 100644 --- a/Options.py +++ b/Options.py @@ -728,7 +728,7 @@ def __new__(cls, value: int) -> SpecialRange: "Consider switching to NamedRange, which supports all use-cases of SpecialRange, and more. In " "NamedRange, range_start specifies the lower end of the regular range, while special values can be " "placed anywhere (below, inside, or above the regular range).") - return super().__new__(cls, value) + return super().__new__(cls) @classmethod def weighted_range(cls, text) -> Range: From 7384bbdf23d380ad7da3b0d16a01c2743654af98 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Tue, 5 Mar 2024 08:48:37 -0700 Subject: [PATCH 46/66] BizHawkClient: Add README (#2689) Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --- worlds/_bizhawk/README.md | 279 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 worlds/_bizhawk/README.md diff --git a/worlds/_bizhawk/README.md b/worlds/_bizhawk/README.md new file mode 100644 index 000000000000..ddc70c3dd748 --- /dev/null +++ b/worlds/_bizhawk/README.md @@ -0,0 +1,279 @@ +# BizHawk Client + +`BizHawkClient` is an abstract base class for a client that can access the memory of a ROM running in BizHawk. It does +the legwork of connecting Python to a Lua connector script, letting you focus on the loop of checking locations and +making on-the-fly modifications based on updates from the server. It also provides the same experience to users across +multiple games that use it, and was built in response to a growing number of similar but separate bespoke game clients +which are/were largely exclusive to BizHawk anyway. + +It's similar to `SNIClient`, but where `SNIClient` is designed to work for specifically SNES games across different +emulators/hardware, `BizHawkClient` is designed to work for specifically BizHawk across the different systems BizHawk +supports. + +The idea is that `BizHawkClient` connects to and communicates with a Lua script running in BizHawk. It provides an API +that will call BizHawk functions for you to do things like read and write memory. And on an interval, control will be +handed to a function you write for your game (`game_watcher`) which should interact with the game's memory to check what +locations have been checked, give the player items, detect and send deathlinks, etc... + +Table of Contents: +- [Connector Requests](#connector-requests) + - [Requests that depend on other requests](#requests-that-depend-on-other-requests) +- [Implementing a Client](#implementing-a-client) + - [Example](#example) +- [Tips](#tips) + +## Connector Requests + +Communication with BizHawk is done through `connector_bizhawk_generic.lua`. The client sends requests to the Lua script +via sockets; the Lua script processes the request and sends the corresponding responses. + +The Lua script includes its own documentation, but you probably don't need to worry about the specifics. Instead, you'll +be using the functions in `worlds/_bizhawk/__init__.py`. If you do need more control over the specific requests being +sent or their order, you can still use `send_requests` to directly communicate with the connector script. + +It's not necessary to use the UI or client context if you only want to interact with the connector script. You can +import and use just `worlds/_bizhawk/__init__.py`, which only depends on default modules. + +Here's a list of the included classes and functions. I would highly recommend looking at the actual function signatures +and docstrings to learn more about each function. + +``` +class ConnectionStatus +class BizHawkContext + +class NotConnectedError +class RequestFailedError +class ConnectorError +class SyncError + +async def read(ctx, read_list) -> list[bytes] +async def write(ctx, write_list) -> None: +async def guarded_read(ctx, read_list, guard_list) -> (list[bytes] | None) +async def guarded_write(ctx, write_list, guard_list) -> bool + +async def lock(ctx) -> None +async def unlock(ctx) -> None + +async def get_hash(ctx) -> str +async def get_system(ctx) -> str +async def get_cores(ctx) -> dict[str, str] +async def ping(ctx) -> None + +async def display_message(ctx, message: str) -> None +async def set_message_interval(ctx, value: float) -> None + +async def connect(ctx) -> bool +def disconnect(ctx) -> None + +async def get_script_version(ctx) -> int +async def send_requests(ctx, req_list) -> list[dict[str, Any]] +``` + +`send_requests` is what actually communicates with the connector, and any functions like `guarded_read` will build the +requests and then call `send_requests` for you. You can call `send_requests` yourself for more direct control, but make +sure to read the docs in `connector_bizhawk_generic.lua`. + +A bundle of requests sent by `send_requests` will all be executed on the same frame, and by extension, so will any +helper that calls `send_requests`. For example, if you were to call `read` with 3 items on your `read_list`, all 3 +addresses will be read on the same frame and then sent back. + +It also means that, by default, the only way to run multiple requests on the same frame is for them to be included in +the same `send_requests` call. As soon as the connector finishes responding to a list of requests, it will advance the +frame before checking for the next batch. + +### Requests that depend on other requests + +The fact that you have to wait at least a frame to act on any response may raise concerns. For example, Pokemon +Emerald's save data is at a dynamic location in memory; it moves around when you load a new map. There is a static +variable that holds the address of the save data, so we want to read the static variable to get the save address, and +then use that address in a `write` to send the player an item. But between the `read` that tells us the address of the +save data and the `write` to save data itself, an arbitrary number of frames have been executed, and the player may have +loaded a new map, meaning we've written data to who knows where. + +There are two solutions to this problem. + +1. Use `guarded_write` instead of `write`. We can include a guard against the address changing, and the script will only +perform the write if the data in memory matches what's in the guard. In the below example, `write_result` will be `True` +if the guard validated and the data was written, and `False` if the guard failed to validate. + +```py +# Get the address of the save data +read_result: bytes = (await _bizhawk.read(ctx, [(0x3001111, 4, "System Bus")]))[0] +save_data_address = int.from_bytes(read_result, "little") + +# Write to `save_data_address` if it hasn't changed +write_result: bool = await _bizhawk.guarded_write( + ctx, + [(save_data_address, [0xAA, 0xBB], "System Bus")], + [(0x3001111, read_result, "System Bus")] +) + +if write_result: + # The data at 0x3001111 was still the same value as + # what was returned from the first `_bizhawk.read`, + # so the data was written. + ... +else: + # The data at 0x3001111 has changed since the + # first `_bizhawk.read`, so the data was not written. + ... +``` + +2. Use `lock` and `unlock` (discouraged if not necessary). When you call `lock`, you tell the emulator to stop advancing +frames and just process requests until it receives an unlock request. This means you can lock, read the address, write +the data, and then unlock on a single frame. **However**, this is _slow_. If you can't get in and get out quickly +enough, players will notice a stutter in the emulation. + +```py +# Pause emulation +await _bizhawk.lock(ctx) + +# Get the address of the save data +read_result: bytes = (await _bizhawk.read(ctx, [(0x3001111, 4, "System Bus")]))[0] +save_data_address = int.from_bytes(read_result, "little") + +# Write to `save_data_address` +await _bizhawk.write(ctx, [(save_data_address, [0xAA, 0xBB], "System Bus")]) + +# Resume emulation +await _bizhawk.unlock(ctx) +``` + +You should always use `guarded_read` and `guarded_write` instead of locking the emulator if possible. It may be +unreliable, but that's by design. Most of the time you should have no problem giving up and retrying. Data that is +volatile but only changes occasionally is the perfect use case. + +If data is almost guaranteed to change between frames, locking may be the better solution. You can lower the time spent +locked by using `send_requests` directly to include as many requests alongside the `LOCK` and `UNLOCK` requests as +possible. But in general it's probably worth doing some extra asm hacking and designing to make guards work instead. + +## Implementing a Client + +`BizHawkClient` itself is built on `CommonClient` and inspired heavily by `SNIClient`. Your world's client should +inherit from `BizHawkClient` in `worlds/_bizhawk/client.py`. It must implement `validate_rom` and `game_watcher`, and +must define values for `system` and `game`. + +As with the functions and classes in the previous section, I would highly recommend looking at the types and docstrings +of the code itself. + +`game` should be the same value you use for your world definition. + +`system` can either be a string or a tuple of strings. This is the system (or systems) that your client is intended to +handle games on (SNES, GBA, etc.). It's used to prevent validators from running on unknown systems and crashing. The +actual abbreviation corresponds to whatever BizHawk returns from `emu.getsystemid()`. + +`patch_suffix` is an optional `ClassVar` meant to specify the file extensions you want to register. It can be a string +or tuple of strings. When a player clicks "Open Patch" in a launcher, the suffix(es) will be whitelisted in the file +select dialog and they will be associated with BizHawkClient. This does not affect whether the user's computer will +associate the file extension with Archipelago. + +`validate_rom` is called to figure out whether a given ROM belongs to your client. It will only be called when a ROM is +running on a system you specified in your `system` class variable. In most cases, that will be a single system and you +can be sure that you're not about to try to read from nonexistent domains or out of bounds. If you decide to claim this +ROM as yours, this is where you should do setup for things like `items_handling`. + +`game_watcher` is the "main loop" of your client where you should be checking memory and sending new items to the ROM. +`BizHawkClient` will make sure that your `game_watcher` only runs when your client has validated the ROM, and will do +its best to make sure you're connected to the connector script before calling your watcher. It runs this loop either +immediately once it receives a message from the server, or a specified amount of time after the last iteration of the +loop finished. + +`validate_rom`, `game_watcher`, and other methods will be passed an instance of `BizHawkClientContext`, which is a +subclass of `CommonContext`. It additionally includes `slot_data` (if you are connected and asked for slot data), +`bizhawk_ctx` (the instance of `BizHawkContext` that you should be giving to functions like `guarded_read`), and +`watcher_timeout` (the amount of time in seconds between iterations of the game watcher loop). + +### Example + +A very simple client might look like this. All addresses here are made up; you should instead be using addresses that +make sense for your specific ROM. The `validate_rom` here tries to read the name of the ROM. If it gets the value it +wanted, it sets a couple values on `ctx` and returns `True`. The `game_watcher` reads some data from memory and acts on +it by sending messages to AP. You should be smarter than this example, which will send `LocationChecks` messages even if +there's nothing new since the last loop. + +```py +from typing import TYPE_CHECKING + +from NetUtils import ClientStatus + +import worlds._bizhawk as bizhawk +from worlds._bizhawk.client import BizHawkClient + +if TYPE_CHECKING: + from worlds._bizhawk.context import BizHawkClientContext + + +class MyGameClient(BizHawkClient): + game = "My Game" + system = "GBA" + patch_suffix = ".apextension" + + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: + try: + # Check ROM name/patch version + rom_name = ((await bizhawk.read(ctx.bizhawk_ctx, [(0x100, 6, "ROM")]))[0]).decode("ascii") + if rom_name != "MYGAME": + return False # Not a MYGAME ROM + except bizhawk.RequestFailedError: + return False # Not able to get a response, say no for now + + # This is a MYGAME ROM + ctx.game = self.game + ctx.items_handling = 0b001 + ctx.want_slot_data = True + + return True + + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: + try: + # Read save data + save_data = await bizhawk.read( + ctx.bizhawk_ctx, + [(0x3000100, 20, "System Bus")] + )[0] + + # Check locations + if save_data[2] & 0x04: + await ctx.send_msgs([{ + "cmd": "LocationChecks", + "locations": [23] + }]) + + # Send game clear + if not ctx.finished_game and (save_data[5] & 0x01): + await ctx.send_msgs([{ + "cmd": "StatusUpdate", + "status": ClientStatus.CLIENT_GOAL + }]) + + except bizhawk.RequestFailedError: + # The connector didn't respond. Exit handler and return to main loop to reconnect + pass +``` + +### Tips + +- Make sure your client gets imported when your world is imported. You probably don't need to actually use anything in +your `client.py` elsewhere, but you still have to import the file for your client to register itself. +- When it comes to performance, there are two directions to optimize: + 1. If you need to execute multiple commands on the same frame, do as little work as possible. Only read and write necessary data, + and if you have to use locks, unlock as soon as it's okay to advance frames. This is probably the obvious one. + 2. Multiple things that don't have to happen on the same frame should be split up if they're likely to be slow. + Remember, the game watcher runs only a few times per second. Extra function calls on the client aren't that big of a + deal; the player will not notice if your `game_watcher` is slow. But the emulator has to be done with any given set of + commands in 1/60th of a second to avoid hiccups (faster still if your players use speedup). Too many reads of too much + data at the same time is more likely to cause a bad user experience. +- Your `game_watcher` will be called regardless of the status of the client's connection to the server. Double-check the +server connection before trying to interact with it. +- By default, the player will be asked to provide their slot name after connecting to the server and validating, and +that input will be used to authenticate with the `Connect` command. You can override `set_auth` in your own client to +set it automatically based on data in the ROM or on your client instance. +- You can override `on_package` in your client to watch raw packages, but don't forget you also have access to a +subclass of `CommonContext` and its API. +- You can import `BizHawkClientContext` for type hints using `typing.TYPE_CHECKING`. Importing it without conditions at +the top of the file will probably cause a circular dependency. +- Your game's system may have multiple usable cores in BizHawk. You can use `get_cores` to try to determine which one is +currently loaded (it's the best we can do). Some cores may differ in the names of memory domains. It's good to check all +the available cores to find differences before your users do. +- The connector script includes a DEBUG variable that you can use to log requests/responses. (Be aware that as the log +grows in size in BizHawk, it begins to stutter while trying to print it.) From ce43c5258975b4338c53c942be00f5895e6d84bd Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:49:34 +0100 Subject: [PATCH 47/66] Doc: fix typo in commands_en.md (#2765) --- worlds/generic/docs/commands_en.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/generic/docs/commands_en.md b/worlds/generic/docs/commands_en.md index 3e7c0bd4bd30..fe12f10ee3af 100644 --- a/worlds/generic/docs/commands_en.md +++ b/worlds/generic/docs/commands_en.md @@ -42,9 +42,9 @@ including the exclamation point. ### Collect/Release - `!collect` Grants you all the remaining items for your world by collecting them from all games. Typically used after -goal completion. -- `!release` Releases all items contained in your world to other worlds. Typically, done automatically by the sever, but -can be configured to allow/require manual usage of this command. + goal completion. +- `!release` Releases all items contained in your world to other worlds. Typically, done automatically by the server, + but can be configured to allow/require manual usage of this command. ### Cheats - `!getitem ` Cheats an item to the currently connected slot, if it is enabled in the server. From 45a15004a48d99c79939ed8bcdb28abf093562eb Mon Sep 17 00:00:00 2001 From: Silent <110704408+silent-destroyer@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:54:18 -0500 Subject: [PATCH 48/66] TUNIC: Update setup guide and game page docs (#2832) --- worlds/tunic/docs/en_TUNIC.md | 16 +++++++++----- worlds/tunic/docs/setup_en.md | 41 +++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/worlds/tunic/docs/en_TUNIC.md b/worlds/tunic/docs/en_TUNIC.md index e957f9eafaf5..1204f2ef4ca2 100644 --- a/worlds/tunic/docs/en_TUNIC.md +++ b/worlds/tunic/docs/en_TUNIC.md @@ -14,13 +14,15 @@ It is recommended that you achieve both endings in the vanilla game before playi In the TUNIC Randomizer, every item in the game is randomized. All chests, key item pickups, instruction manual pages, hero relics, and other unique items are shuffled.
-Ability shuffling is an option available from the options page to shuffle certain abilities (prayer, holy cross, and the ice rod combo), +Ability shuffling is an option available from the options page to shuffle certain abilities (prayer, holy cross, and the icebolt combo), preventing them from being used until they are unlocked.
+Entrances can also be randomized, shuffling the connections between every door, teleporter, etc. in the game. + Enemy randomization and other options are also available and can be turned on in the client mod. ## What is the goal of TUNIC when randomized? -The standard goal is the same as the vanilla game, which is to find the three hexagon keys, at which point you may either Take Your +The standard goal is the same as the vanilla game. Find the three hexagon keys, then Take Your Rightful Place or seek another path and Share Your Wisdom. Alternatively, Hexagon Quest is a mode that shuffles a certain number of Gold Questagons into the item pool, with the goal @@ -44,12 +46,16 @@ There is a [tracker pack](https://github.com/SapphireSapphic/TunicTracker/releas There is also a [standalone item tracker](https://github.com/radicoon/tunic-rando-tracker/releases/latest), which tracks what items you have received. It is great for adding an item overlay to streaming setups. This item tracker was created by Radicoon. +There is an [entrance tracker](https://scipiowright.gitlab.io/tunic-tracker/) for the entrance randomizer. This is a manual tracker that runs in your browser. This tracker was created by ScipioWright, and is a fork of the Pokémon Tracker by [Sergi "Sekii" Santana](https://gitlab.com/Sekii/pokemon-tracker). + +You can also use the Universal Tracker (by Faris and qwint) to find a complete list of what checks are in logic with your current items. You can find it on the Archipelago Discord, in its post in the future-game-design channel. This tracker is an extension of the regular Archipelago Text Client. + ## What should I know regarding logic? - Nighttime is not considered in logic. Every check in the game is obtainable during the day. - The Cathedral is accessible during the day by using the Hero's Laurels to reach the Overworld fuse near the Swamp entrance. - The Secret Legend chest at the Cathedral can be obtained during the day by opening the Holy Cross door from the outside. -For Entrance Rando specifically: +For the Entrance Randomizer: - Activating a fuse to turn on a yellow teleporter pad also activates its counterpart in the Far Shore. - The West Garden fuse can be activated from below. - You can pray at the tree at the exterior of the Library. @@ -58,7 +64,7 @@ For Entrance Rando specifically: - The elevator in Cathedral is immediately usable without activating the fuse. Activating the fuse does nothing. ## What item groups are there? -Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), keys, hexagons, offerings, hero relics, cards, golden treasures, money, pages, and abilities (the three ability pages). There are also a few groups being used for singular items: laurels, orb, dagger, magic rod, holy cross, prayer, ice rod, and progressive sword. +Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), keys, hexagons, offerings, hero relics, cards, golden treasures, money, pages, and abilities (the three ability pages). There are also a few groups being used for singular items: laurels, orb, dagger, magic rod, holy cross, prayer, icebolt, and progressive sword. ## What location groups are there? -Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), and shop. Additionally, for checks that do not fall into the above categories, the name of the region is the name of the location group. +Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), and shop. Additionally, for checks that do not fall into the above categories, the name of the region is the name of the location group. \ No newline at end of file diff --git a/worlds/tunic/docs/setup_en.md b/worlds/tunic/docs/setup_en.md index 3c13331fe5f1..5ec41e8d526e 100644 --- a/worlds/tunic/docs/setup_en.md +++ b/worlds/tunic/docs/setup_en.md @@ -1,16 +1,17 @@ # TUNIC Setup Guide -## Installation - -### Required Software +## Required Software - [TUNIC](https://tunicgame.com/) for PC (Steam Deck also supported) -- [BepInEx](https://builds.bepinex.dev/projects/bepinex_be/572/BepInEx_UnityIL2CPP_x64_9c2b17f_6.0.0-be.572.zip) -- [TUNIC Randomizer Archipelago Mod](https://github.com/silent-destroyer/tunic-randomizer-archipelago/releases/latest) +- [BepInEx (Unity IL2CPP)](https://github.com/BepInEx/BepInEx/releases/tag/v6.0.0-pre.1) +- [TUNIC Randomizer Mod](https://github.com/silent-destroyer/tunic-randomizer/releases/latest) -### Optional Software +## Optional Software - [TUNIC Randomizer Map Tracker](https://github.com/SapphireSapphic/TunicTracker/releases/latest) (For use with EmoTracker/PopTracker) - [TUNIC Randomizer Item Auto-tracker](https://github.com/radicoon/tunic-rando-tracker/releases/latest) +- [Archipelago Text Client](https://github.com/ArchipelagoMW/Archipelago/releases/latest) + +## Installation ### Find Your Relevant Game Directories @@ -24,29 +25,30 @@ Find your TUNIC game installation directory: ### Install BepInEx -BepInEx is a general purpose framework for modding Unity games, and is used by the TUNIC Randomizer. +BepInEx is a general purpose framework for modding Unity games, and is used to run the TUNIC Randomizer. -Download [BepInEx](https://builds.bepinex.dev/projects/bepinex_be/572/BepInEx_UnityIL2CPP_x64_9c2b17f_6.0.0-be.572.zip). +Download [BepInEx](https://github.com/BepInEx/BepInEx/releases/download/v6.0.0-pre.1/BepInEx_UnityIL2CPP_x64_6.0.0-pre.1.zip). If playing on Steam Deck, follow this [guide to set up BepInEx via Proton](https://docs.bepinex.dev/articles/advanced/proton_wine.html). Extract the contents of the BepInEx .zip file into your TUNIC game directory:
- **Steam**: Steam\steamapps\common\TUNIC
- **PC Game Pass**: XboxGames\Tunic\Content
-- **Other platforms**: Place into the same folder that the Tunic_Data/Secret Legend_Data folder is found. +- **Other platforms**: Place into the same folder that the Tunic_Data or Secret Legend_Data folder is found. Launch the game once and close it to finish the BepInEx installation. -### Install The TUNIC Randomizer Archipelago Client Mod +### Install The TUNIC Randomizer Mod + +Download the latest release of the [TUNIC Randomizer Mod](https://github.com/silent-destroyer/tunic-randomizer/releases/latest). -Download the latest release of the [TUNIC Randomizer Archipelago Mod](https://github.com/silent-destroyer/tunic-randomizer-archipelago/releases/latest). +Extract the contents of the downloaded .zip file, and find the folder labeled `Tunic Randomizer`. -The downloaded .zip will contain a folder called `Tunic Archipelago`. +Copy the `Tunic Randomizer` folder into `BepInEx/plugins` in your TUNIC game installation directory. -Copy the `Tunic Archipelago` folder into `BepInEx/plugins` in your TUNIC game installation directory. -The filepath to the mod should look like `BepInEx/plugins/Tunic Archipelago/TunicArchipelago.dll`
+The filepath to the mod should look like `BepInEx/plugins/Tunic Randomizer/TunicRandomizer.dll`
-Launch the game, and if everything was installed correctly you should see `Randomizer + Archipelago Mod Ver. x.y.z` in the top left corner of the title screen! +Launch the game, and if everything was installed correctly you should see `Randomizer Mod Ver. x.y.z` in the top left corner of the title screen! ## Configure Archipelago Options @@ -55,11 +57,12 @@ Launch the game, and if everything was installed correctly you should see `Rando Visit the [TUNIC options page](/games/Tunic/player-options) to generate a YAML with your selected options. ### Configure Your Mod Settings -Launch the game and click the button labeled `Open AP Config` on the Title Screen. -In the menu that opens, fill in *Player*, *Hostname*, *Port*, and *Password* (if required) with the correct information for your room. +Launch the game, and using the menu on the Title Screen select `Archipelago` under `Randomizer Mode`. + +Click the button labeled `Edit AP Config`, and fill in *Player*, *Hostname*, *Port*, and *Password* (if required) with the correct information for your room. -Once you've input your information, click on Close. If everything was configured properly, you should see `Status: Connected!` and your chosen game options will be shown under `World Settings`. +Once you've input your information, click the `Close` button. If everything was configured properly, you should see `Status: Connected!` and your chosen game options will be shown under `World Settings`. An error message will display if the game fails to connect to the server. -Be sure to also look at the in-game options menu for a variety of additional settings, such as enemy randomization! +Be sure to also look at the in-game options menu for a variety of additional settings, such as enemy randomization! \ No newline at end of file From af4172f32fa29c364f1bc62eefbee80df601643c Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:55:12 +0100 Subject: [PATCH 49/66] Docs: Add review expectations to contributing.md (#2843) Co-authored-by: BadMagic100 Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- docs/contributing.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index 9b5f93e1980b..e7f3516712d2 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -17,7 +17,17 @@ It is recommended that automated github actions are turned on in your fork to ha You can turn them on here: ![Github actions example](./img/github-actions-example.png) -Other than these requests, we tend to judge code on a case by case basis. +* **When reviewing PRs, please leave a message about what was done.** +We don't have full test coverage, so manual testing can help. +For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing +or checking if all code paths are covered by automated tests is desired. The original author may not have been able +to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to +state which games or settings were rolled, if any. +Please also tell us if you looked at code, just did functional testing, did both, or did neither. +If testing the PR depends on other PRs, please state what you merged into what for testing. +We cannot determine what "LGTM" means without additional context, so that should not be the norm. + +Other than these requests, we tend to judge code on a case-by-case basis. For contribution to the website, please refer to the [WebHost README](/WebHostLib/README.md). From 644f75978d879cd490576ee7d852abcd9d2c4c20 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:59:55 -0600 Subject: [PATCH 50/66] Kirby's Dream Land 3: Implement New Game (#2119) Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com> Co-authored-by: Aaron Wagener Co-authored-by: Doug Hoskisson Co-authored-by: Fabian Dill --- README.md | 1 + docs/CODEOWNERS | 3 + inno_setup.iss | 5 + worlds/LauncherComponents.py | 2 +- worlds/generic/docs/plando_en.md | 38 +- worlds/kdl3/Aesthetics.py | 434 +++++++ worlds/kdl3/Client.py | 417 +++++++ worlds/kdl3/ClientAddrs.py | 816 ++++++++++++ worlds/kdl3/Compression.py | 57 + worlds/kdl3/Gifting.py | 282 +++++ worlds/kdl3/Items.py | 105 ++ worlds/kdl3/Locations.py | 940 ++++++++++++++ worlds/kdl3/Names/AnimalFriendSpawns.py | 199 +++ worlds/kdl3/Names/EnemyAbilities.py | 822 ++++++++++++ worlds/kdl3/Names/LocationName.py | 928 ++++++++++++++ worlds/kdl3/Names/__init__.py | 0 worlds/kdl3/Options.py | 432 +++++++ worlds/kdl3/Presets.py | 56 + worlds/kdl3/Regions.py | 231 ++++ worlds/kdl3/Rom.py | 577 +++++++++ worlds/kdl3/Room.py | 95 ++ worlds/kdl3/Rules.py | 332 +++++ worlds/kdl3/__init__.py | 350 ++++++ worlds/kdl3/data/APConsumable.bsdiff4 | Bin 0 -> 585 bytes worlds/kdl3/data/APHeartStar.bsdiff4 | Bin 0 -> 603 bytes worlds/kdl3/data/APPauseIcons.dat | Bin 0 -> 448 bytes worlds/kdl3/data/APStars.bsdiff4 | Bin 0 -> 250 bytes worlds/kdl3/data/Rooms.json | 1 + worlds/kdl3/data/kdl3_basepatch.bsdiff4 | Bin 0 -> 2411 bytes worlds/kdl3/docs/en_Kirby's Dream Land 3.md | 38 + worlds/kdl3/docs/setup_en.md | 148 +++ worlds/kdl3/src/kdl3_basepatch.asm | 1237 +++++++++++++++++++ worlds/kdl3/test/__init__.py | 37 + worlds/kdl3/test/test_goal.py | 64 + worlds/kdl3/test/test_locations.py | 68 + worlds/kdl3/test/test_shuffles.py | 245 ++++ 36 files changed, 8956 insertions(+), 4 deletions(-) create mode 100644 worlds/kdl3/Aesthetics.py create mode 100644 worlds/kdl3/Client.py create mode 100644 worlds/kdl3/ClientAddrs.py create mode 100644 worlds/kdl3/Compression.py create mode 100644 worlds/kdl3/Gifting.py create mode 100644 worlds/kdl3/Items.py create mode 100644 worlds/kdl3/Locations.py create mode 100644 worlds/kdl3/Names/AnimalFriendSpawns.py create mode 100644 worlds/kdl3/Names/EnemyAbilities.py create mode 100644 worlds/kdl3/Names/LocationName.py create mode 100644 worlds/kdl3/Names/__init__.py create mode 100644 worlds/kdl3/Options.py create mode 100644 worlds/kdl3/Presets.py create mode 100644 worlds/kdl3/Regions.py create mode 100644 worlds/kdl3/Rom.py create mode 100644 worlds/kdl3/Room.py create mode 100644 worlds/kdl3/Rules.py create mode 100644 worlds/kdl3/__init__.py create mode 100644 worlds/kdl3/data/APConsumable.bsdiff4 create mode 100644 worlds/kdl3/data/APHeartStar.bsdiff4 create mode 100644 worlds/kdl3/data/APPauseIcons.dat create mode 100644 worlds/kdl3/data/APStars.bsdiff4 create mode 100644 worlds/kdl3/data/Rooms.json create mode 100644 worlds/kdl3/data/kdl3_basepatch.bsdiff4 create mode 100644 worlds/kdl3/docs/en_Kirby's Dream Land 3.md create mode 100644 worlds/kdl3/docs/setup_en.md create mode 100644 worlds/kdl3/src/kdl3_basepatch.asm create mode 100644 worlds/kdl3/test/__init__.py create mode 100644 worlds/kdl3/test/test_goal.py create mode 100644 worlds/kdl3/test/test_locations.py create mode 100644 worlds/kdl3/test/test_shuffles.py diff --git a/README.md b/README.md index ce2130ce8e7c..4a3c53548c38 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Currently, the following games are supported: * Landstalker: The Treasures of King Nole * Final Fantasy Mystic Quest * TUNIC +* Kirby's Dream Land 3 For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 95c0baea3a1f..6ec3802edea6 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -67,6 +67,9 @@ # Hylics 2 /worlds/hylics2/ @TRPG0 +# Kirby's Dream Land 3 +/worlds/kdl3/ @Silvris + # Kingdom Hearts 2 /worlds/kh2/ @JaredWeakStrike diff --git a/inno_setup.iss b/inno_setup.iss index b122cdc00b18..c1b634292fc9 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -131,6 +131,11 @@ Root: HKCR; Subkey: "{#MyAppName}l2acpatch"; ValueData: "Arc Root: HKCR; Subkey: "{#MyAppName}l2acpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}l2acpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apkdl3"; ValueData: "{#MyAppName}kdl3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}kdl3patch"; ValueData: "Archipelago Kirby's Dream Land 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni + Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}mcdata\DefaultIcon"; ValueData: "{app}\ArchipelagoMinecraftClient.exe,0"; ValueType: string; ValueName: ""; diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index 03c89b75ff11..7814aac5ae49 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -88,7 +88,7 @@ def launch_textclient(): # SNI Component('SNI Client', 'SNIClient', file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3', - '.apsmw', '.apl2ac')), + '.apsmw', '.apl2ac', '.apkdl3')), Component('Links Awakening DX Client', 'LinksAwakeningClient', file_identifier=SuffixIdentifier('.apladx')), Component('LttP Adjuster', 'LttPAdjuster'), diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md index 9d8e6befe87f..d6a09cf4e610 100644 --- a/worlds/generic/docs/plando_en.md +++ b/worlds/generic/docs/plando_en.md @@ -161,8 +161,40 @@ into any locations within the game slots named BobsSlaytheSpire and BobsRogueLeg ## Boss Plando -As this is currently only supported by A Link to the Past, instead of finding an explanation here, please refer to the -relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the%20Past/plando/en) +This is currently only supported by A Link to the Past and Kirby's Dream Land 3. Boss plando allows a player to place a +given boss within an arena. More specific information for boss plando in A Link to the Past can be found in +its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en). + +Boss plando takes in a list of instructions for placing bosses, separated by a semicolon `;`. +There are three types of placement: direct, full, and shuffle. +* Direct placement takes both an arena and a boss, and places the boss into that arena. + * `Eastern Palace-Trinexx` +* Full placement will take a boss, and place it into as many remaining arenas as possible. + * `King Dedede` +* Shuffle will fill any remaining arenas using a given boss shuffle option, typically to be used as the last instruction. + * `full` + +### Examples + +```yaml +A Link to the Past: + boss_shuffle: + # Basic boss shuffle, but prevent Trinexx from being outside Turtle Rock + Turtle Rock-Trinexx;basic: 1 + # Place as many Arrghus as possible, then let the rest be random + Arrghus;chaos: 1 + +Kirby's Dream Land 3: + boss_shuffle: + # Ensure Iceberg's boss will be King Dedede, but randomize the rest + Iceberg-King Dedede;full: 1 + # Have all bosses be Whispy Woods + Whispy Woods: 1 + # Ensure Ripple Field's boss is Pon & Con, but let the method others + # are placed with be random + Ripple Field-Pon & Con;random: 1 +``` + ## Text Plando @@ -184,7 +216,7 @@ its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections). [A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852) -[Minecraft connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Regions.py#L62) +[Minecraft connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/data/regions.json#L18****) ### Examples diff --git a/worlds/kdl3/Aesthetics.py b/worlds/kdl3/Aesthetics.py new file mode 100644 index 000000000000..8c7363908f52 --- /dev/null +++ b/worlds/kdl3/Aesthetics.py @@ -0,0 +1,434 @@ +import struct +from .Options import KirbyFlavorPreset, GooeyFlavorPreset + +kirby_flavor_presets = { + 1: { + "1": "B50029", + "2": "FF91C6", + "3": "B0123B", + "4": "630F0F", + "5": "D60052", + "6": "DE4873", + "7": "D07880", + "8": "000000", + "9": "F770A5", + "10": "E01784", + "11": "CA4C74", + "12": "A7443F", + "13": "FF1784", + "14": "FFA1DE", + "15": "B03830", + }, + 2: { + "1": "C70057", + "2": "FF3554", + "3": "AA0040", + "4": "C02D47", + "5": "E02068", + "6": "C2183F", + "7": "D03F80", + "8": "872939", + "9": "E82B47", + "10": "E80067", + "11": "D52F40", + "12": "9F1C33", + "13": "FD187F", + "14": "F85068", + "15": "D2386F", + }, + 3: { + "1": "5858e2", + "2": "e6e6fa", + "3": "bcbcf2", + "4": "8484e6", + "5": "2929ec", + "6": "b5b5f0", + "7": "847bd6", + "8": "3232d6", + "9": "d6d6ef", + "10": "4a52ef", + "11": "c6c6e6", + "12": "4343ad", + "13": "6767ff", + "14": "f6f6fd", + "15": "3139b6", + }, + 4: { + "1": "B01810", + "2": "F0E08D", + "3": "C8A060", + "4": "A87043", + "5": "E03700", + "6": "EFC063", + "7": "D07818", + "8": "A8501C", + "9": "E8D070", + "10": "E2501E", + "11": "E8C55C", + "12": "B08833", + "13": "E8783B", + "14": "F8F8A5", + "15": "B03800", + }, + 5: { + "1": "9F4410", + "2": "88F27B", + "3": "57A044", + "4": "227029", + "5": "C75418", + "6": "57BA23", + "7": "1C6B00", + "8": "2D6823", + "9": "3FD744", + "10": "E06C16", + "11": "54C053", + "12": "1A541E", + "13": "F06B10", + "14": "98F89A", + "15": "B05830", + }, + 6: { + "1": "7C1060", + "2": "CA8AE8", + "3": "8250A5", + "4": "604B7B", + "5": "A52068", + "6": "8D64B8", + "7": "B73B80", + "8": "672D9A", + "9": "BA82D5", + "10": "B55098", + "11": "9F5CCF", + "12": "632B74", + "13": "CF78B5", + "14": "DA98F8", + "15": "8D3863", + }, + 7: { + "1": "6F1410", + "2": "C2735C", + "3": "5C351C", + "4": "875440", + "5": "9F2F0C", + "6": "874C3B", + "7": "88534C", + "8": "4C1E00", + "9": "B06458", + "10": "921C16", + "11": "9F5C54", + "12": "5B3125", + "13": "C01A14", + "14": "CF785B", + "15": "6B3125", + }, + 8: { + "1": "a6a6a6", + "2": "e6e6e6", + "3": "bcbcbc", + "4": "848484", + "5": "909090", + "6": "b5b5b5", + "7": "848484", + "8": "646464", + "9": "d6d6d6", + "10": "525252", + "11": "c6c6c6", + "12": "737373", + "13": "949494", + "14": "f6f6f6", + "15": "545454", + }, + 9: { + "1": "400000", + "2": "6B6B6B", + "3": "2B2B2B", + "4": "181818", + "5": "640000", + "6": "3D3D3D", + "7": "878787", + "8": "020202", + "9": "606060", + "10": "980000", + "11": "505050", + "12": "474747", + "13": "C80000", + "14": "808080", + "15": "AF0000", + }, + 10: { + "1": "2B4B10", + "2": "EF8A9D", + "3": "C84F6B", + "4": "B74F54", + "5": "126018", + "6": "D85F6F", + "7": "D06870", + "8": "A24858", + "9": "E77B8D", + "10": "168025", + "11": "DF5C68", + "12": "9D4353", + "13": "48953F", + "14": "F897AD", + "15": "B03830", + }, + 11: { + "1": "7B290C", + "2": "FF9A00", + "3": "B05C1C", + "4": "8F3F0E", + "5": "D23B0C", + "6": "E08200", + "7": "D05800", + "8": "8A2B16", + "9": "EF970A", + "10": "E24800", + "11": "E58F00", + "12": "A03700", + "13": "ED3B00", + "14": "FFAF27", + "15": "A84700", + }, + 12: { + "1": "AFA810", + "2": "4FF29D", + "3": "2BA04C", + "4": "007043", + "5": "C7C218", + "6": "33BA5F", + "7": "006B40", + "8": "2D6823", + "9": "1CD773", + "10": "E0CF16", + "11": "2DC06C", + "12": "00543F", + "13": "F0F010", + "14": "43F8B2", + "15": "B0A230", + }, + 13: { + "1": "7C73B0", + "2": "CACAE7", + "3": "7B7BA8", + "4": "5F5FA7", + "5": "B57EDC", + "6": "8585C5", + "7": "5B5B82", + "8": "474796", + "9": "B2B2D8", + "10": "B790EF", + "11": "9898C2", + "12": "6B6BB7", + "13": "CDADFA", + "14": "E6E6FA", + "15": "976FBD", + }, +} + +gooey_flavor_presets = { + 1: { + "1": "CD539D", + "2": "D270AD", + "3": "F27CBF", + "4": "FF91C6", + "5": "FFA1DE", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 2: { + "1": "161600", + "2": "592910", + "3": "5A3118", + "4": "AB3918", + "5": "EB3918", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 3: { + "1": "001616", + "2": "102959", + "3": "18315A", + "4": "1839AB", + "5": "1839EB", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 4: { + "1": "C8A031", + "2": "C5BD38", + "3": "D2CD48", + "4": "E2E040", + "5": "EAE2A0", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 5: { + "1": "54A208", + "2": "5CB021", + "3": "6CB206", + "4": "8AC54C", + "5": "8DD554", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 6: { + "1": "3D083D", + "2": "4B024B", + "3": "4C104C", + "4": "5F0A5F", + "5": "9F1D9F", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 7: { + "1": "270C08", + "2": "481C10", + "3": "581E10", + "4": "5B2712", + "5": "743316", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 8: { + "1": "7F7F7F", + "2": "909090", + "3": "9D9D9D", + "4": "BFBFBF", + "5": "D2D2D2", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 9: { + "1": "141414", + "2": "2D2D2D", + "3": "404040", + "4": "585858", + "5": "7F7F7F", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 10: { + "1": "954353", + "2": "AF4F68", + "3": "CD6073", + "4": "E06774", + "5": "E587A2", + "6": "17AF10", + "7": "4FE748", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 11: { + "1": "CF4700", + "2": "D85C08", + "3": "E26C04", + "4": "EA7B16", + "5": "EF8506", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 12: { + "1": "1C4708", + "2": "105B1C", + "3": "186827", + "4": "187C3B", + "5": "188831", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 13: { + "1": "501E70", + "2": "673B87", + "3": "7848A7", + "4": "9067C7", + "5": "B57EDC", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, +} + +kirby_target_palettes = { + 0x64646: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x64846: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E007E: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E009C: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), + 0x1E00F6: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E0114: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), + 0x1E0216: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E0234: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), + 0x1E0486: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E04A4: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), +} + +gooey_target_palettes = { + 0x604C2: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x64592: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x64692: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x64892: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E02CA: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E0342: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E05A6: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E05B8: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 0.5), + 0x1E0636: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E065A: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1.5), +} + + +def get_kirby_palette(world): + palette = world.options.kirby_flavor_preset.value + if palette == KirbyFlavorPreset.option_custom: + return world.options.kirby_flavor.value + return kirby_flavor_presets.get(palette, None) + + +def get_gooey_palette(world): + palette = world.options.gooey_flavor_preset.value + if palette == GooeyFlavorPreset.option_custom: + return world.options.gooey_flavor.value + return gooey_flavor_presets.get(palette, None) + + +def rgb888_to_bgr555(red, green, blue) -> bytes: + red = red >> 3 + green = green >> 3 + blue = blue >> 3 + outcol = (blue << 10) + (green << 5) + red + return struct.pack("H", outcol) + + +def get_palette_bytes(palette, target, offset, factor): + output_data = bytearray() + for color in target: + hexcol = palette[color] + if hexcol.startswith("#"): + hexcol = hexcol.replace("#", "") + colint = int(hexcol, 16) + col = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF) + col = tuple(int(int(factor*x) + offset) for x in col) + byte_data = rgb888_to_bgr555(col[0], col[1], col[2]) + output_data.extend(bytearray(byte_data)) + return output_data diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py new file mode 100644 index 000000000000..3fef0429004d --- /dev/null +++ b/worlds/kdl3/Client.py @@ -0,0 +1,417 @@ +import logging +import struct +import time +import typing +import uuid +from struct import unpack, pack +from collections import defaultdict +import random + +from MultiServer import mark_raw +from NetUtils import ClientStatus, color +from Utils import async_start +from worlds.AutoSNIClient import SNIClient +from .Locations import boss_locations +from .Gifting import kdl3_gifting_options, kdl3_trap_gifts, kdl3_gifts, update_object, pop_object, initialize_giftboxes +from .ClientAddrs import consumable_addrs, star_addrs +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SNIClient import SNIClientCommandProcessor + +snes_logger = logging.getLogger("SNES") + +# FXPAK Pro protocol memory mapping used by SNI +ROM_START = 0x000000 +SRAM_1_START = 0xE00000 + +# KDL3 +KDL3_HALKEN = SRAM_1_START + 0x80F0 +KDL3_NINTEN = SRAM_1_START + 0x8FF0 +KDL3_ROMNAME = SRAM_1_START + 0x8100 +KDL3_DEATH_LINK_ADDR = SRAM_1_START + 0x9010 +KDL3_GOAL_ADDR = SRAM_1_START + 0x9012 +KDL3_CONSUMABLE_FLAG = SRAM_1_START + 0x9018 +KDL3_STARS_FLAG = SRAM_1_START + 0x901A +KDL3_GIFTING_FLAG = SRAM_1_START + 0x901C +KDL3_LEVEL_ADDR = SRAM_1_START + 0x9020 +KDL3_IS_DEMO = SRAM_1_START + 0x5AD5 +KDL3_GAME_STATE = SRAM_1_START + 0x36D0 +KDL3_GAME_SAVE = SRAM_1_START + 0x3617 +KDL3_LIFE_COUNT = SRAM_1_START + 0x39CF +KDL3_KIRBY_HP = SRAM_1_START + 0x39D1 +KDL3_BOSS_HP = SRAM_1_START + 0x39D5 +KDL3_STAR_COUNT = SRAM_1_START + 0x39D7 +KDL3_LIFE_VISUAL = SRAM_1_START + 0x39E3 +KDL3_HEART_STARS = SRAM_1_START + 0x53A7 +KDL3_WORLD_UNLOCK = SRAM_1_START + 0x53CB +KDL3_LEVEL_UNLOCK = SRAM_1_START + 0x53CD +KDL3_CURRENT_WORLD = SRAM_1_START + 0x53CF +KDL3_CURRENT_LEVEL = SRAM_1_START + 0x53D3 +KDL3_BOSS_STATUS = SRAM_1_START + 0x53D5 +KDL3_INVINCIBILITY_TIMER = SRAM_1_START + 0x54B1 +KDL3_MG5_STATUS = SRAM_1_START + 0x5EE4 +KDL3_BOSS_BUTCH_STATUS = SRAM_1_START + 0x5EEA +KDL3_JUMPING_STATUS = SRAM_1_START + 0x5EF0 +KDL3_CURRENT_BGM = SRAM_1_START + 0x733E +KDL3_SOUND_FX = SRAM_1_START + 0x7F62 +KDL3_ANIMAL_FRIENDS = SRAM_1_START + 0x8000 +KDL3_ABILITY_ARRAY = SRAM_1_START + 0x8020 +KDL3_RECV_COUNT = SRAM_1_START + 0x8050 +KDL3_HEART_STAR_COUNT = SRAM_1_START + 0x8070 +KDL3_GOOEY_TRAP = SRAM_1_START + 0x8080 +KDL3_SLOWNESS_TRAP = SRAM_1_START + 0x8082 +KDL3_ABILITY_TRAP = SRAM_1_START + 0x8084 +KDL3_GIFTING_SEND = SRAM_1_START + 0x8086 +KDL3_COMPLETED_STAGES = SRAM_1_START + 0x8200 +KDL3_CONSUMABLES = SRAM_1_START + 0xA000 +KDL3_STARS = SRAM_1_START + 0xB000 +KDL3_ITEM_QUEUE = SRAM_1_START + 0xC000 + +deathlink_messages = defaultdict(lambda: " was defeated.", { + 0x0200: " was bonked by apples from Whispy Woods.", + 0x0201: " was out-maneuvered by Acro.", + 0x0202: " was out-numbered by Pon & Con.", + 0x0203: " was defeated by Ado's powerful paintings.", + 0x0204: " was clobbered by King Dedede.", + 0x0205: " lost their battle against Dark Matter." +}) + + +@mark_raw +def cmd_gift(self: "SNIClientCommandProcessor"): + """Toggles gifting for the current game.""" + if not getattr(self.ctx, "gifting", None): + self.ctx.gifting = True + else: + self.ctx.gifting = not self.ctx.gifting + self.output(f"Gifting set to {self.ctx.gifting}") + async_start(update_object(self.ctx, f"Giftboxes;{self.ctx.team}", { + f"{self.ctx.slot}": + { + "IsOpen": self.ctx.gifting, + **kdl3_gifting_options + } + })) + + +class KDL3SNIClient(SNIClient): + game = "Kirby's Dream Land 3" + levels = None + consumables = None + stars = None + item_queue: typing.List = [] + initialize_gifting = False + giftbox_key: str = "" + motherbox_key: str = "" + client_random: random.Random = random.Random() + + async def deathlink_kill_player(self, ctx) -> None: + from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read + game_state = await snes_read(ctx, KDL3_GAME_STATE, 1) + if game_state[0] == 0xFF: + return # despite how funny it is, don't try to kill Kirby in a menu + + current_stage = await snes_read(ctx, KDL3_CURRENT_LEVEL, 1) + if current_stage[0] == 0x7: # boss stage + boss_hp = await snes_read(ctx, KDL3_BOSS_HP, 1) + if boss_hp[0] == 0: + return # receiving a deathlink after defeating a boss has softlock potential + + current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + if current_hp[0] == 0: + return # don't kill Kirby while he's already dead + snes_buffered_write(ctx, KDL3_KIRBY_HP, bytes([0x00])) + + await snes_flush_writes(ctx) + + ctx.death_state = DeathState.dead + ctx.last_death_link = time.time() + + async def validate_rom(self, ctx) -> bool: + from SNIClient import snes_read + rom_name = await snes_read(ctx, KDL3_ROMNAME, 0x15) + if rom_name is None or rom_name == bytes([0] * 0x15) or rom_name[:4] != b"KDL3": + if "gift" in ctx.command_processor.commands: + ctx.command_processor.commands.pop("gift") + return False + + ctx.game = self.game + ctx.rom = rom_name + ctx.items_handling = 0b111 # always remote items + ctx.allow_collect = True + if "gift" not in ctx.command_processor.commands: + ctx.command_processor.commands["gift"] = cmd_gift + + death_link = await snes_read(ctx, KDL3_DEATH_LINK_ADDR, 1) + if death_link: + await ctx.update_death_link(bool(death_link[0] & 0b1)) + return True + + async def pop_item(self, ctx, in_stage): + from SNIClient import snes_buffered_write, snes_read + if len(self.item_queue) > 0: + item = self.item_queue.pop() + if not in_stage and item & 0xC0: + # can't handle this item right now, send it to the back and return to handle the rest + self.item_queue.append(item) + return + ingame_queue = list(unpack("HHHHHHHH", await snes_read(ctx, KDL3_ITEM_QUEUE, 16))) + for i in range(len(ingame_queue)): + if ingame_queue[i] == 0x00: + ingame_queue[i] = item + snes_buffered_write(ctx, KDL3_ITEM_QUEUE, pack("HHHHHHHH", *ingame_queue)) + break + else: + self.item_queue.append(item) # no more slots, get it next go around + + async def pop_gift(self, ctx): + if ctx.stored_data[self.giftbox_key]: + from SNIClient import snes_read, snes_buffered_write + key, gift = ctx.stored_data[self.giftbox_key].popitem() + await pop_object(ctx, self.giftbox_key, key) + # first, special cases + traits = [trait["Trait"] for trait in gift["Traits"]] + if "Candy" in traits or "Invincible" in traits: + # apply invincibility candy + self.item_queue.append(0x43) + elif "Tomato" in traits or "tomato" in gift["ItemName"].lower(): + # apply maxim tomato + # only want tomatos here, no other vegetable is that good + self.item_queue.append(0x42) + elif "Life" in traits: + # Apply 1-Up + self.item_queue.append(0x41) + elif "Currency" in traits or "Star" in traits: + value = gift["ItemValue"] + if value >= 50000: + self.item_queue.append(0x46) + elif value >= 30000: + self.item_queue.append(0x45) + else: + self.item_queue.append(0x44) + elif "Trap" in traits: + # find the best trap to apply + if "Goo" in traits or "Gel" in traits: + self.item_queue.append(0x80) + elif "Slow" in traits or "Slowness" in traits: + self.item_queue.append(0x81) + elif "Eject" in traits or "Removal" in traits: + self.item_queue.append(0x82) + else: + # just deal damage to Kirby + kirby_hp = struct.unpack("H", await snes_read(ctx, KDL3_KIRBY_HP, 2))[0] + snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", max(kirby_hp - 1, 0))) + else: + # check if it's tasty + if any(x in traits for x in ["Consumable", "Food", "Drink", "Heal", "Health"]): + # it's tasty!, use quality to decide how much to heal + quality = max((trait["Quality"] for trait in gift["Traits"] + if trait["Trait"] in ["Consumable", "Food", "Drink", "Heal", "Health"])) + quality = min(10, quality * 2) + else: + # it's not really edible, but he'll eat it anyway + quality = self.client_random.choices(range(0, 2), {0: 75, 1: 25})[0] + kirby_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + gooey_hp = await snes_read(ctx, KDL3_KIRBY_HP + 2, 1) + snes_buffered_write(ctx, KDL3_SOUND_FX, bytes([0x26])) + if gooey_hp[0] > 0x00: + snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", min(kirby_hp[0] + quality // 2, 8))) + snes_buffered_write(ctx, KDL3_KIRBY_HP + 2, struct.pack("H", min(gooey_hp[0] + quality // 2, 8))) + else: + snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", min(kirby_hp[0] + quality, 10))) + + async def pick_gift_recipient(self, ctx, gift): + if gift != 4: + gift_base = kdl3_gifts[gift] + else: + gift_base = kdl3_trap_gifts[self.client_random.randint(0, 3)] + most_applicable = -1 + most_applicable_slot = ctx.slot + for slot, info in ctx.stored_data[self.motherbox_key].items(): + if int(slot) == ctx.slot and len(ctx.stored_data[self.motherbox_key]) > 1: + continue + desire = len(set(info["DesiredTraits"]).intersection([trait["Trait"] for trait in gift_base["Traits"]])) + if desire > most_applicable: + most_applicable = desire + most_applicable_slot = int(slot) + elif most_applicable_slot == ctx.slot and info["AcceptsAnyGift"]: + # only send to ourselves if no one else will take it + most_applicable_slot = int(slot) + # print(most_applicable, most_applicable_slot) + item_uuid = uuid.uuid4().hex + item = { + **gift_base, + "ID": item_uuid, + "Sender": ctx.player_names[ctx.slot], + "Receiver": ctx.player_names[most_applicable_slot], + "SenderTeam": ctx.team, + "ReceiverTeam": ctx.team, # for the moment + "IsRefund": False + } + # print(item) + await update_object(ctx, f"Giftbox;{ctx.team};{most_applicable_slot}", { + item_uuid: item, + }) + + async def game_watcher(self, ctx) -> None: + try: + from SNIClient import snes_buffered_write, snes_flush_writes, snes_read + rom = await snes_read(ctx, KDL3_ROMNAME, 0x15) + if rom != ctx.rom: + ctx.rom = None + halken = await snes_read(ctx, KDL3_HALKEN, 6) + if halken != b"halken": + return + ninten = await snes_read(ctx, KDL3_NINTEN, 6) + if ninten != b"ninten": + return + if not ctx.slot: + return + if not self.initialize_gifting: + self.giftbox_key = f"Giftbox;{ctx.team};{ctx.slot}" + self.motherbox_key = f"Giftboxes;{ctx.team}" + enable_gifting = await snes_read(ctx, KDL3_GIFTING_FLAG, 0x01) + await initialize_giftboxes(ctx, self.giftbox_key, self.motherbox_key, bool(enable_gifting[0])) + self.initialize_gifting = True + # can't check debug anymore, without going and copying the value. might be important later. + if self.levels is None: + self.levels = dict() + for i in range(5): + level_data = await snes_read(ctx, KDL3_LEVEL_ADDR + (14 * i), 14) + self.levels[i] = unpack("HHHHHHH", level_data) + + if self.consumables is None: + consumables = await snes_read(ctx, KDL3_CONSUMABLE_FLAG, 1) + self.consumables = consumables[0] == 0x01 + if self.stars is None: + stars = await snes_read(ctx, KDL3_STARS_FLAG, 1) + self.stars = stars[0] == 0x01 + is_demo = await snes_read(ctx, KDL3_IS_DEMO, 1) + # 1 - recording a demo, 2 - playing back recorded, 3+ is a demo + if is_demo[0] > 0x00: + return + current_save = await snes_read(ctx, KDL3_GAME_SAVE, 1) + goal = await snes_read(ctx, KDL3_GOAL_ADDR, 1) + boss_butch_status = await snes_read(ctx, KDL3_BOSS_BUTCH_STATUS + (current_save[0] * 2), 1) + mg5_status = await snes_read(ctx, KDL3_MG5_STATUS + (current_save[0] * 2), 1) + jumping_status = await snes_read(ctx, KDL3_JUMPING_STATUS + (current_save[0] * 2), 1) + if boss_butch_status[0] == 0xFF: + return # save file is not created, ignore + if (goal[0] == 0x00 and boss_butch_status[0] == 0x01) \ + or (goal[0] == 0x01 and boss_butch_status[0] == 0x03) \ + or (goal[0] == 0x02 and mg5_status[0] == 0x03) \ + or (goal[0] == 0x03 and jumping_status[0] == 0x03): + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + current_bgm = await snes_read(ctx, KDL3_CURRENT_BGM, 1) + if current_bgm[0] in (0x00, 0x21, 0x22, 0x23, 0x25, 0x2A, 0x2B): + return # null, title screen, opening, save select, true and false endings + game_state = await snes_read(ctx, KDL3_GAME_STATE, 1) + current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + if "DeathLink" in ctx.tags and game_state[0] == 0x00 and ctx.last_death_link + 1 < time.time(): + currently_dead = current_hp[0] == 0x00 + await ctx.handle_deathlink_state(currently_dead) + + recv_count = await snes_read(ctx, KDL3_RECV_COUNT, 2) + recv_amount = unpack("H", recv_count)[0] + if recv_amount < len(ctx.items_received): + item = ctx.items_received[recv_amount] + recv_amount += 1 + logging.info('Received %s from %s (%s) (%d/%d in list)' % ( + color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.player_names[item.player], 'yellow'), + ctx.location_names[item.location], recv_amount, len(ctx.items_received))) + + snes_buffered_write(ctx, KDL3_RECV_COUNT, pack("H", recv_amount)) + item_idx = item.item & 0x00000F + if item.item & 0x000070 == 0: + self.item_queue.append(item_idx | 0x10) + elif item.item & 0x000010 > 0: + self.item_queue.append(item_idx | 0x20) + elif item.item & 0x000020 > 0: + # Positive + self.item_queue.append(item_idx | 0x40) + elif item.item & 0x000040 > 0: + self.item_queue.append(item_idx | 0x80) + + # handle gifts here + gifting_status = await snes_read(ctx, KDL3_GIFTING_FLAG, 0x01) + if hasattr(ctx, "gifting") and ctx.gifting: + if gifting_status[0]: + gift = await snes_read(ctx, KDL3_GIFTING_SEND, 0x01) + if gift[0]: + # we have a gift to send + await self.pick_gift_recipient(ctx, gift[0]) + snes_buffered_write(ctx, KDL3_GIFTING_SEND, bytes([0x00])) + else: + snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x01])) + else: + if gifting_status[0]: + snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x00])) + + await snes_flush_writes(ctx) + + new_checks = [] + # level completion status + world_unlocks = await snes_read(ctx, KDL3_WORLD_UNLOCK, 1) + if world_unlocks[0] > 0x06: + return # save is not loaded, ignore + stages_raw = await snes_read(ctx, KDL3_COMPLETED_STAGES, 60) + stages = struct.unpack("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", stages_raw) + for i in range(30): + loc_id = 0x770000 + i + 1 + if stages[i] == 1 and loc_id not in ctx.checked_locations: + new_checks.append(loc_id) + elif loc_id in ctx.checked_locations: + snes_buffered_write(ctx, KDL3_COMPLETED_STAGES + (i * 2), struct.pack("H", 1)) + + # heart star status + heart_stars = await snes_read(ctx, KDL3_HEART_STARS, 35) + for i in range(5): + start_ind = i * 7 + for j in range(1, 7): + level_ind = start_ind + j - 1 + loc_id = 0x770100 + (6 * i) + j + if heart_stars[level_ind] and loc_id not in ctx.checked_locations: + new_checks.append(loc_id) + elif loc_id in ctx.checked_locations: + snes_buffered_write(ctx, KDL3_HEART_STARS + level_ind, bytes([0x01])) + if self.consumables: + consumables = await snes_read(ctx, KDL3_CONSUMABLES, 1920) + for consumable in consumable_addrs: + # TODO: see if this can be sped up in any way + loc_id = 0x770300 + consumable + if loc_id not in ctx.checked_locations and consumables[consumable_addrs[consumable]] == 0x01: + new_checks.append(loc_id) + if self.stars: + stars = await snes_read(ctx, KDL3_STARS, 1920) + for star in star_addrs: + if star not in ctx.checked_locations and stars[star_addrs[star]] == 0x01: + new_checks.append(star) + + if game_state[0] != 0xFF: + await self.pop_gift(ctx) + await self.pop_item(ctx, game_state[0] != 0xFF) + await snes_flush_writes(ctx) + + # boss status + boss_flag_bytes = await snes_read(ctx, KDL3_BOSS_STATUS, 2) + boss_flag = unpack("H", boss_flag_bytes)[0] + for bitmask, boss in zip(range(1, 11, 2), boss_locations.keys()): + if boss_flag & (1 << bitmask) > 0 and boss not in ctx.checked_locations: + new_checks.append(boss) + + for new_check_id in new_checks: + ctx.locations_checked.add(new_check_id) + location = ctx.location_names[new_check_id] + snes_logger.info( + f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) + except Exception as ex: + # we crashed, so print log and clean up + snes_logger.error("", exc_info=ex) + if "gift" in ctx.command_processor.commands: + ctx.command_processor.commands.pop("gift") + ctx.rom = None + ctx.game = None diff --git a/worlds/kdl3/ClientAddrs.py b/worlds/kdl3/ClientAddrs.py new file mode 100644 index 000000000000..1f5475741b3b --- /dev/null +++ b/worlds/kdl3/ClientAddrs.py @@ -0,0 +1,816 @@ +consumable_addrs = { + 0: 14, + 1: 15, + 2: 84, + 3: 138, + 4: 139, + 5: 204, + 6: 214, + 7: 215, + 8: 224, + 9: 330, + 10: 353, + 11: 458, + 12: 459, + 13: 522, + 14: 525, + 15: 605, + 16: 606, + 17: 630, + 18: 671, + 19: 672, + 20: 693, + 21: 791, + 22: 851, + 23: 883, + 24: 971, + 25: 985, + 26: 986, + 27: 1024, + 28: 1035, + 29: 1036, + 30: 1038, + 31: 1039, + 32: 1170, + 33: 1171, + 34: 1377, + 35: 1378, + 36: 1413, + 37: 1494, + 38: 1666, + 39: 1808, + 40: 1809, + 41: 1816, + 42: 1856, + 43: 1857, +} + +star_addrs = { + 0x770401: 0, + 0x770402: 1, + 0x770403: 2, + 0x770404: 3, + 0x770405: 4, + 0x770406: 5, + 0x770407: 7, + 0x770408: 8, + 0x770409: 9, + 0x77040a: 10, + 0x77040b: 11, + 0x77040c: 12, + 0x77040d: 13, + 0x77040e: 16, + 0x77040f: 17, + 0x770410: 19, + 0x770411: 20, + 0x770412: 21, + 0x770413: 22, + 0x770414: 23, + 0x770415: 24, + 0x770416: 25, + 0x770417: 26, + 0x770418: 65, + 0x770419: 66, + 0x77041a: 67, + 0x77041b: 68, + 0x77041c: 69, + 0x77041d: 70, + 0x77041e: 71, + 0x77041f: 72, + 0x770420: 73, + 0x770421: 74, + 0x770422: 76, + 0x770423: 77, + 0x770424: 78, + 0x770425: 79, + 0x770426: 80, + 0x770427: 81, + 0x770428: 82, + 0x770429: 83, + 0x77042a: 85, + 0x77042b: 86, + 0x77042c: 87, + 0x77042d: 128, + 0x77042e: 129, + 0x77042f: 130, + 0x770430: 131, + 0x770431: 132, + 0x770432: 133, + 0x770433: 134, + 0x770434: 135, + 0x770435: 136, + 0x770436: 137, + 0x770437: 140, + 0x770438: 141, + 0x770439: 142, + 0x77043a: 143, + 0x77043b: 144, + 0x77043c: 145, + 0x77043d: 146, + 0x77043e: 147, + 0x77043f: 148, + 0x770440: 149, + 0x770441: 150, + 0x770442: 151, + 0x770443: 152, + 0x770444: 153, + 0x770445: 154, + 0x770446: 155, + 0x770447: 156, + 0x770448: 157, + 0x770449: 158, + 0x77044a: 159, + 0x77044b: 160, + 0x77044c: 192, + 0x77044d: 193, + 0x77044e: 194, + 0x77044f: 195, + 0x770450: 197, + 0x770451: 198, + 0x770452: 199, + 0x770453: 200, + 0x770454: 201, + 0x770455: 203, + 0x770456: 205, + 0x770457: 206, + 0x770458: 207, + 0x770459: 208, + 0x77045a: 209, + 0x77045b: 210, + 0x77045c: 211, + 0x77045d: 212, + 0x77045e: 213, + 0x77045f: 216, + 0x770460: 217, + 0x770461: 218, + 0x770462: 219, + 0x770463: 220, + 0x770464: 221, + 0x770465: 222, + 0x770466: 225, + 0x770467: 227, + 0x770468: 228, + 0x770469: 229, + 0x77046a: 230, + 0x77046b: 231, + 0x77046c: 232, + 0x77046d: 233, + 0x77046e: 234, + 0x77046f: 235, + 0x770470: 236, + 0x770471: 257, + 0x770472: 258, + 0x770473: 259, + 0x770474: 260, + 0x770475: 261, + 0x770476: 262, + 0x770477: 263, + 0x770478: 264, + 0x770479: 265, + 0x77047a: 266, + 0x77047b: 267, + 0x77047c: 268, + 0x77047d: 270, + 0x77047e: 271, + 0x77047f: 272, + 0x770480: 273, + 0x770481: 275, + 0x770482: 276, + 0x770483: 277, + 0x770484: 278, + 0x770485: 279, + 0x770486: 280, + 0x770487: 281, + 0x770488: 282, + 0x770489: 283, + 0x77048a: 284, + 0x77048b: 285, + 0x77048c: 286, + 0x77048d: 287, + 0x77048e: 321, + 0x77048f: 322, + 0x770490: 323, + 0x770491: 324, + 0x770492: 325, + 0x770493: 326, + 0x770494: 327, + 0x770495: 328, + 0x770496: 329, + 0x770497: 332, + 0x770498: 334, + 0x770499: 335, + 0x77049a: 336, + 0x77049b: 337, + 0x77049c: 340, + 0x77049d: 341, + 0x77049e: 342, + 0x77049f: 343, + 0x7704a0: 345, + 0x7704a1: 346, + 0x7704a2: 347, + 0x7704a3: 348, + 0x7704a4: 349, + 0x7704a5: 350, + 0x7704a6: 351, + 0x7704a7: 354, + 0x7704a8: 355, + 0x7704a9: 356, + 0x7704aa: 357, + 0x7704ab: 384, + 0x7704ac: 385, + 0x7704ad: 386, + 0x7704ae: 387, + 0x7704af: 388, + 0x7704b0: 389, + 0x7704b1: 391, + 0x7704b2: 392, + 0x7704b3: 393, + 0x7704b4: 394, + 0x7704b5: 396, + 0x7704b6: 397, + 0x7704b7: 398, + 0x7704b8: 399, + 0x7704b9: 400, + 0x7704ba: 401, + 0x7704bb: 402, + 0x7704bc: 403, + 0x7704bd: 404, + 0x7704be: 449, + 0x7704bf: 450, + 0x7704c0: 451, + 0x7704c1: 453, + 0x7704c2: 454, + 0x7704c3: 455, + 0x7704c4: 456, + 0x7704c5: 457, + 0x7704c6: 460, + 0x7704c7: 461, + 0x7704c8: 462, + 0x7704c9: 463, + 0x7704ca: 464, + 0x7704cb: 465, + 0x7704cc: 466, + 0x7704cd: 467, + 0x7704ce: 468, + 0x7704cf: 513, + 0x7704d0: 514, + 0x7704d1: 515, + 0x7704d2: 516, + 0x7704d3: 517, + 0x7704d4: 518, + 0x7704d5: 519, + 0x7704d6: 520, + 0x7704d7: 521, + 0x7704d8: 523, + 0x7704d9: 524, + 0x7704da: 527, + 0x7704db: 528, + 0x7704dc: 529, + 0x7704dd: 531, + 0x7704de: 532, + 0x7704df: 533, + 0x7704e0: 534, + 0x7704e1: 535, + 0x7704e2: 536, + 0x7704e3: 537, + 0x7704e4: 576, + 0x7704e5: 577, + 0x7704e6: 578, + 0x7704e7: 579, + 0x7704e8: 580, + 0x7704e9: 582, + 0x7704ea: 583, + 0x7704eb: 584, + 0x7704ec: 585, + 0x7704ed: 586, + 0x7704ee: 587, + 0x7704ef: 588, + 0x7704f0: 589, + 0x7704f1: 590, + 0x7704f2: 591, + 0x7704f3: 592, + 0x7704f4: 593, + 0x7704f5: 594, + 0x7704f6: 595, + 0x7704f7: 596, + 0x7704f8: 597, + 0x7704f9: 598, + 0x7704fa: 599, + 0x7704fb: 600, + 0x7704fc: 601, + 0x7704fd: 602, + 0x7704fe: 603, + 0x7704ff: 604, + 0x770500: 607, + 0x770501: 608, + 0x770502: 609, + 0x770503: 610, + 0x770504: 611, + 0x770505: 612, + 0x770506: 613, + 0x770507: 614, + 0x770508: 615, + 0x770509: 616, + 0x77050a: 617, + 0x77050b: 618, + 0x77050c: 619, + 0x77050d: 620, + 0x77050e: 621, + 0x77050f: 622, + 0x770510: 623, + 0x770511: 624, + 0x770512: 625, + 0x770513: 626, + 0x770514: 627, + 0x770515: 628, + 0x770516: 629, + 0x770517: 640, + 0x770518: 641, + 0x770519: 642, + 0x77051a: 643, + 0x77051b: 644, + 0x77051c: 645, + 0x77051d: 646, + 0x77051e: 647, + 0x77051f: 648, + 0x770520: 649, + 0x770521: 650, + 0x770522: 651, + 0x770523: 652, + 0x770524: 653, + 0x770525: 654, + 0x770526: 655, + 0x770527: 656, + 0x770528: 657, + 0x770529: 658, + 0x77052a: 659, + 0x77052b: 660, + 0x77052c: 661, + 0x77052d: 662, + 0x77052e: 663, + 0x77052f: 664, + 0x770530: 665, + 0x770531: 666, + 0x770532: 667, + 0x770533: 668, + 0x770534: 669, + 0x770535: 670, + 0x770536: 674, + 0x770537: 675, + 0x770538: 676, + 0x770539: 677, + 0x77053a: 678, + 0x77053b: 679, + 0x77053c: 680, + 0x77053d: 681, + 0x77053e: 682, + 0x77053f: 683, + 0x770540: 684, + 0x770541: 686, + 0x770542: 687, + 0x770543: 688, + 0x770544: 689, + 0x770545: 690, + 0x770546: 691, + 0x770547: 692, + 0x770548: 694, + 0x770549: 695, + 0x77054a: 704, + 0x77054b: 705, + 0x77054c: 706, + 0x77054d: 707, + 0x77054e: 708, + 0x77054f: 709, + 0x770550: 710, + 0x770551: 711, + 0x770552: 712, + 0x770553: 713, + 0x770554: 714, + 0x770555: 715, + 0x770556: 716, + 0x770557: 717, + 0x770558: 718, + 0x770559: 719, + 0x77055a: 720, + 0x77055b: 721, + 0x77055c: 722, + 0x77055d: 723, + 0x77055e: 724, + 0x77055f: 725, + 0x770560: 726, + 0x770561: 769, + 0x770562: 770, + 0x770563: 771, + 0x770564: 772, + 0x770565: 773, + 0x770566: 774, + 0x770567: 775, + 0x770568: 776, + 0x770569: 777, + 0x77056a: 778, + 0x77056b: 779, + 0x77056c: 780, + 0x77056d: 781, + 0x77056e: 782, + 0x77056f: 783, + 0x770570: 784, + 0x770571: 785, + 0x770572: 786, + 0x770573: 787, + 0x770574: 788, + 0x770575: 789, + 0x770576: 790, + 0x770577: 832, + 0x770578: 833, + 0x770579: 834, + 0x77057a: 835, + 0x77057b: 836, + 0x77057c: 837, + 0x77057d: 838, + 0x77057e: 839, + 0x77057f: 840, + 0x770580: 841, + 0x770581: 842, + 0x770582: 843, + 0x770583: 844, + 0x770584: 845, + 0x770585: 846, + 0x770586: 847, + 0x770587: 848, + 0x770588: 849, + 0x770589: 850, + 0x77058a: 854, + 0x77058b: 855, + 0x77058c: 856, + 0x77058d: 857, + 0x77058e: 858, + 0x77058f: 859, + 0x770590: 860, + 0x770591: 861, + 0x770592: 862, + 0x770593: 863, + 0x770594: 864, + 0x770595: 865, + 0x770596: 866, + 0x770597: 867, + 0x770598: 868, + 0x770599: 869, + 0x77059a: 870, + 0x77059b: 871, + 0x77059c: 872, + 0x77059d: 873, + 0x77059e: 874, + 0x77059f: 875, + 0x7705a0: 876, + 0x7705a1: 877, + 0x7705a2: 878, + 0x7705a3: 879, + 0x7705a4: 880, + 0x7705a5: 881, + 0x7705a6: 882, + 0x7705a7: 896, + 0x7705a8: 897, + 0x7705a9: 898, + 0x7705aa: 899, + 0x7705ab: 900, + 0x7705ac: 901, + 0x7705ad: 902, + 0x7705ae: 903, + 0x7705af: 904, + 0x7705b0: 905, + 0x7705b1: 960, + 0x7705b2: 961, + 0x7705b3: 962, + 0x7705b4: 963, + 0x7705b5: 964, + 0x7705b6: 965, + 0x7705b7: 966, + 0x7705b8: 967, + 0x7705b9: 968, + 0x7705ba: 969, + 0x7705bb: 970, + 0x7705bc: 972, + 0x7705bd: 973, + 0x7705be: 974, + 0x7705bf: 975, + 0x7705c0: 977, + 0x7705c1: 978, + 0x7705c2: 979, + 0x7705c3: 980, + 0x7705c4: 981, + 0x7705c5: 982, + 0x7705c6: 983, + 0x7705c7: 984, + 0x7705c8: 1025, + 0x7705c9: 1026, + 0x7705ca: 1027, + 0x7705cb: 1028, + 0x7705cc: 1029, + 0x7705cd: 1030, + 0x7705ce: 1031, + 0x7705cf: 1032, + 0x7705d0: 1033, + 0x7705d1: 1034, + 0x7705d2: 1037, + 0x7705d3: 1040, + 0x7705d4: 1041, + 0x7705d5: 1042, + 0x7705d6: 1043, + 0x7705d7: 1044, + 0x7705d8: 1045, + 0x7705d9: 1046, + 0x7705da: 1049, + 0x7705db: 1050, + 0x7705dc: 1051, + 0x7705dd: 1052, + 0x7705de: 1053, + 0x7705df: 1054, + 0x7705e0: 1055, + 0x7705e1: 1056, + 0x7705e2: 1057, + 0x7705e3: 1058, + 0x7705e4: 1059, + 0x7705e5: 1060, + 0x7705e6: 1061, + 0x7705e7: 1062, + 0x7705e8: 1063, + 0x7705e9: 1064, + 0x7705ea: 1065, + 0x7705eb: 1066, + 0x7705ec: 1067, + 0x7705ed: 1068, + 0x7705ee: 1069, + 0x7705ef: 1070, + 0x7705f0: 1152, + 0x7705f1: 1154, + 0x7705f2: 1155, + 0x7705f3: 1156, + 0x7705f4: 1157, + 0x7705f5: 1158, + 0x7705f6: 1159, + 0x7705f7: 1160, + 0x7705f8: 1161, + 0x7705f9: 1162, + 0x7705fa: 1163, + 0x7705fb: 1164, + 0x7705fc: 1165, + 0x7705fd: 1166, + 0x7705fe: 1167, + 0x7705ff: 1168, + 0x770600: 1169, + 0x770601: 1173, + 0x770602: 1174, + 0x770603: 1175, + 0x770604: 1176, + 0x770605: 1177, + 0x770606: 1178, + 0x770607: 1216, + 0x770608: 1217, + 0x770609: 1218, + 0x77060a: 1219, + 0x77060b: 1220, + 0x77060c: 1221, + 0x77060d: 1222, + 0x77060e: 1223, + 0x77060f: 1224, + 0x770610: 1225, + 0x770611: 1226, + 0x770612: 1227, + 0x770613: 1228, + 0x770614: 1229, + 0x770615: 1230, + 0x770616: 1231, + 0x770617: 1232, + 0x770618: 1233, + 0x770619: 1234, + 0x77061a: 1235, + 0x77061b: 1236, + 0x77061c: 1237, + 0x77061d: 1238, + 0x77061e: 1239, + 0x77061f: 1240, + 0x770620: 1241, + 0x770621: 1242, + 0x770622: 1243, + 0x770623: 1244, + 0x770624: 1245, + 0x770625: 1246, + 0x770626: 1247, + 0x770627: 1248, + 0x770628: 1249, + 0x770629: 1250, + 0x77062a: 1251, + 0x77062b: 1252, + 0x77062c: 1253, + 0x77062d: 1254, + 0x77062e: 1255, + 0x77062f: 1256, + 0x770630: 1257, + 0x770631: 1258, + 0x770632: 1259, + 0x770633: 1260, + 0x770634: 1261, + 0x770635: 1262, + 0x770636: 1263, + 0x770637: 1264, + 0x770638: 1265, + 0x770639: 1266, + 0x77063a: 1267, + 0x77063b: 1268, + 0x77063c: 1269, + 0x77063d: 1280, + 0x77063e: 1281, + 0x77063f: 1282, + 0x770640: 1283, + 0x770641: 1284, + 0x770642: 1285, + 0x770643: 1286, + 0x770644: 1289, + 0x770645: 1290, + 0x770646: 1291, + 0x770647: 1292, + 0x770648: 1293, + 0x770649: 1294, + 0x77064a: 1295, + 0x77064b: 1296, + 0x77064c: 1297, + 0x77064d: 1298, + 0x77064e: 1299, + 0x77064f: 1300, + 0x770650: 1301, + 0x770651: 1302, + 0x770652: 1303, + 0x770653: 1344, + 0x770654: 1345, + 0x770655: 1346, + 0x770656: 1347, + 0x770657: 1348, + 0x770658: 1349, + 0x770659: 1350, + 0x77065a: 1351, + 0x77065b: 1352, + 0x77065c: 1354, + 0x77065d: 1355, + 0x77065e: 1356, + 0x77065f: 1357, + 0x770660: 1358, + 0x770661: 1359, + 0x770662: 1360, + 0x770663: 1361, + 0x770664: 1362, + 0x770665: 1363, + 0x770666: 1365, + 0x770667: 1366, + 0x770668: 1367, + 0x770669: 1368, + 0x77066a: 1369, + 0x77066b: 1370, + 0x77066c: 1371, + 0x77066d: 1372, + 0x77066e: 1374, + 0x77066f: 1375, + 0x770670: 1376, + 0x770671: 1379, + 0x770672: 1380, + 0x770673: 1381, + 0x770674: 1382, + 0x770675: 1383, + 0x770676: 1384, + 0x770677: 1385, + 0x770678: 1386, + 0x770679: 1387, + 0x77067a: 1388, + 0x77067b: 1389, + 0x77067c: 1390, + 0x77067d: 1391, + 0x77067e: 1392, + 0x77067f: 1393, + 0x770680: 1394, + 0x770681: 1395, + 0x770682: 1396, + 0x770683: 1397, + 0x770684: 1398, + 0x770685: 1408, + 0x770686: 1409, + 0x770687: 1410, + 0x770688: 1411, + 0x770689: 1412, + 0x77068a: 1414, + 0x77068b: 1472, + 0x77068c: 1473, + 0x77068d: 1474, + 0x77068e: 1475, + 0x77068f: 1476, + 0x770690: 1477, + 0x770691: 1478, + 0x770692: 1479, + 0x770693: 1480, + 0x770694: 1481, + 0x770695: 1482, + 0x770696: 1483, + 0x770697: 1484, + 0x770698: 1486, + 0x770699: 1487, + 0x77069a: 1488, + 0x77069b: 1489, + 0x77069c: 1490, + 0x77069d: 1491, + 0x77069e: 1495, + 0x77069f: 1496, + 0x7706a0: 1497, + 0x7706a1: 1498, + 0x7706a2: 1499, + 0x7706a3: 1500, + 0x7706a4: 1501, + 0x7706a5: 1502, + 0x7706a6: 1503, + 0x7706a7: 1504, + 0x7706a8: 1505, + 0x7706a9: 1506, + 0x7706aa: 1507, + 0x7706ab: 1508, + 0x7706ac: 1536, + 0x7706ad: 1537, + 0x7706ae: 1538, + 0x7706af: 1539, + 0x7706b0: 1540, + 0x7706b1: 1541, + 0x7706b2: 1600, + 0x7706b3: 1601, + 0x7706b4: 1602, + 0x7706b5: 1603, + 0x7706b6: 1604, + 0x7706b7: 1605, + 0x7706b8: 1606, + 0x7706b9: 1607, + 0x7706ba: 1612, + 0x7706bb: 1613, + 0x7706bc: 1614, + 0x7706bd: 1615, + 0x7706be: 1616, + 0x7706bf: 1617, + 0x7706c0: 1618, + 0x7706c1: 1619, + 0x7706c2: 1620, + 0x7706c3: 1621, + 0x7706c4: 1622, + 0x7706c5: 1664, + 0x7706c6: 1665, + 0x7706c7: 1667, + 0x7706c8: 1668, + 0x7706c9: 1670, + 0x7706ca: 1671, + 0x7706cb: 1672, + 0x7706cc: 1673, + 0x7706cd: 1674, + 0x7706ce: 1675, + 0x7706cf: 1676, + 0x7706d0: 1677, + 0x7706d1: 1678, + 0x7706d2: 1679, + 0x7706d3: 1680, + 0x7706d4: 1681, + 0x7706d5: 1682, + 0x7706d6: 1683, + 0x7706d7: 1684, + 0x7706d8: 1685, + 0x7706d9: 1686, + 0x7706da: 1730, + 0x7706db: 1732, + 0x7706dc: 1734, + 0x7706dd: 1792, + 0x7706de: 1793, + 0x7706df: 1794, + 0x7706e0: 1795, + 0x7706e1: 1796, + 0x7706e2: 1797, + 0x7706e3: 1798, + 0x7706e4: 1799, + 0x7706e5: 1800, + 0x7706e6: 1801, + 0x7706e7: 1802, + 0x7706e8: 1803, + 0x7706e9: 1804, + 0x7706ea: 1805, + 0x7706eb: 1810, + 0x7706ec: 1811, + 0x7706ed: 1812, + 0x7706ee: 1813, + 0x7706ef: 1814, + 0x7706f0: 1815, + 0x7706f1: 1817, + 0x7706f2: 1818, + 0x7706f3: 1819, + 0x7706f4: 1820, + 0x7706f5: 1821, + 0x7706f6: 1822, + 0x7706f7: 1823, + 0x7706f8: 1824, + 0x7706f9: 1825, + 0x7706fa: 1826, + 0x7706fb: 1827, + 0x7706fc: 1828, + 0x7706fd: 1831, + 0x7706fe: 1832, + 0x7706ff: 1858, +} diff --git a/worlds/kdl3/Compression.py b/worlds/kdl3/Compression.py new file mode 100644 index 000000000000..ec5461fbec75 --- /dev/null +++ b/worlds/kdl3/Compression.py @@ -0,0 +1,57 @@ +def hal_decompress(comp: bytes) -> bytes: + """ + HAL decompression based on exhal by devinacker + https://github.com/devinacker/exhal + """ + inpos = 0 + + inval = 0 + output = bytearray() + while inval != 0xFF: + remaining = 65536 - inpos + if remaining < 1: + return bytes() + inval = comp[inpos] + inpos += 1 + if inval == 0xFF: + break + if (inval & 0xE0) == 0xE0: + command = (inval >> 2) & 0x07 + length = (((inval & 0x03) << 8) | comp[inpos]) + 1 + inpos += 1 + else: + command = inval >> 5 + length = (inval & 0x1F) + 1 + if (command == 2 and ((len(output) + 2*length) > 65536)) or (len(output) + length) > 65536: + return bytes() + if command == 0: + output.extend(comp[inpos:inpos+length]) + inpos += length + elif command == 1: + output.extend([comp[inpos] for _ in range(length)]) + inpos += 1 + elif command == 2: + output.extend([comp[x] for _ in range(length) for x in (inpos, inpos+1)]) + inpos += 2 + elif command == 3: + output.extend([comp[inpos] + i for i in range(length)]) + inpos += 1 + elif command == 4 or command == 7: + offset = (comp[inpos] << 8) | comp[inpos + 1] + if (offset + length) > 65536: + return bytes() + output.extend(output[offset:offset+length]) + inpos += 2 + elif command == 5: + offset = (comp[inpos] << 8) | comp[inpos + 1] + if (offset + length) > 65536: + return bytes() + output.extend([int('{:08b}'.format(x)[::-1], 2) for x in output[offset:offset+length]]) + inpos += 2 + elif command == 6: + offset = (comp[inpos] << 8) | comp[inpos + 1] + if offset < length - 1: + return bytes() + output.extend([output[offset - x] for x in range(length)]) + inpos += 2 + return bytes(output) diff --git a/worlds/kdl3/Gifting.py b/worlds/kdl3/Gifting.py new file mode 100644 index 000000000000..8ccba7ec1ae6 --- /dev/null +++ b/worlds/kdl3/Gifting.py @@ -0,0 +1,282 @@ +# Small subfile to handle gifting info such as desired traits and giftbox management +import typing + + +async def update_object(ctx, key: str, value: typing.Dict): + await ctx.send_msgs([ + { + "cmd": "Set", + "key": key, + "default": {}, + "want_reply": False, + "operations": [ + {"operation": "update", "value": value} + ] + } + ]) + + +async def pop_object(ctx, key: str, value: str): + await ctx.send_msgs([ + { + "cmd": "Set", + "key": key, + "default": {}, + "want_reply": False, + "operations": [ + {"operation": "pop", "value": value} + ] + } + ]) + + +async def initialize_giftboxes(ctx, giftbox_key: str, motherbox_key: str, is_open: bool): + ctx.set_notify(motherbox_key, giftbox_key) + await update_object(ctx, f"Giftboxes;{ctx.team}", {f"{ctx.slot}": + { + "IsOpen": is_open, + **kdl3_gifting_options + }}) + ctx.gifting = is_open + + +kdl3_gifting_options = { + "AcceptsAnyGift": True, + "DesiredTraits": [ + "Consumable", "Food", "Drink", "Candy", "Tomato", + "Invincible", "Life", "Heal", "Health", "Trap", + "Goo", "Gel", "Slow", "Slowness", "Eject", "Removal" + ], + "MinimumGiftVersion": 2, +} + +kdl3_gifts = { + 1: { + "ItemName": "1-Up", + "Amount": 1, + "ItemValue": 400000, + "Traits": [ + { + "Trait": "Consumable", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Life", + "Quality": 1, + "Duration": 1 + } + ] + }, + 2: { + "ItemName": "Maxim Tomato", + "Amount": 1, + "ItemValue": 500000, + "Traits": [ + { + "Trait": "Consumable", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Heal", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Food", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Tomato", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Vegetable", + "Quality": 5, + "Duration": 1, + } + ] + }, + 3: { + "ItemName": "Energy Drink", + "Amount": 1, + "ItemValue": 100000, + "Traits": [ + { + "Trait": "Consumable", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Heal", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Drink", + "Quality": 1, + "Duration": 1, + }, + ] + }, + 5: { + "ItemName": "Small Star Piece", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Currency", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Money", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Star", + "Quality": 1, + "Duration": 1 + } + ] + }, + 6: { + "ItemName": "Medium Star Piece", + "Amount": 1, + "ItemValue": 30000, + "Traits": [ + { + "Trait": "Currency", + "Quality": 3, + "Duration": 1, + }, + { + "Trait": "Money", + "Quality": 3, + "Duration": 1, + }, + { + "Trait": "Star", + "Quality": 3, + "Duration": 1 + } + ] + }, + 7: { + "ItemName": "Large Star Piece", + "Amount": 1, + "ItemValue": 50000, + "Traits": [ + { + "Trait": "Currency", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Money", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Star", + "Quality": 5, + "Duration": 1 + } + ] + }, +} + +kdl3_trap_gifts = { + 0: { + "ItemName": "Gooey Bag", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Goo", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Gel", + "Quality": 1, + "Duration": 1 + } + ] + }, + 1: { + "ItemName": "Slowness", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Slow", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Slowness", + "Quality": 1, + "Duration": 1 + } + ] + }, + 2: { + "ItemName": "Eject Ability", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Eject", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Removal", + "Quality": 1, + "Duration": 1 + } + ] + }, + 3: { + "ItemName": "Bad Meal", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Damage", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Food", + "Quality": 1, + "Duration": 1 + } + ] + }, +} diff --git a/worlds/kdl3/Items.py b/worlds/kdl3/Items.py new file mode 100644 index 000000000000..66c7f8fee323 --- /dev/null +++ b/worlds/kdl3/Items.py @@ -0,0 +1,105 @@ +from BaseClasses import Item +import typing + + +class ItemData(typing.NamedTuple): + code: typing.Optional[int] + progression: bool + skip_balancing: bool = False + trap: bool = False + + +class KDL3Item(Item): + game = "Kirby's Dream Land 3" + + +copy_ability_table = { + "Burning": ItemData(0x770001, True), + "Stone": ItemData(0x770002, True), + "Ice": ItemData(0x770003, True), + "Needle": ItemData(0x770004, True), + "Clean": ItemData(0x770005, True), + "Parasol": ItemData(0x770006, True), + "Spark": ItemData(0x770007, True), + "Cutter": ItemData(0x770008, True) +} + +animal_friend_table = { + "Rick": ItemData(0x770010, True), + "Kine": ItemData(0x770011, True), + "Coo": ItemData(0x770012, True), + "Nago": ItemData(0x770013, True), + "ChuChu": ItemData(0x770014, True), + "Pitch": ItemData(0x770015, True) +} + +animal_friend_spawn_table = { + "Rick Spawn": ItemData(None, True), + "Kine Spawn": ItemData(None, True), + "Coo Spawn": ItemData(None, True), + "Nago Spawn": ItemData(None, True), + "ChuChu Spawn": ItemData(None, True), + "Pitch Spawn": ItemData(None, True) +} + +copy_ability_access_table = { + "No Ability": ItemData(None, False), + "Burning Ability": ItemData(None, True), + "Stone Ability": ItemData(None, True), + "Ice Ability": ItemData(None, True), + "Needle Ability": ItemData(None, True), + "Clean Ability": ItemData(None, True), + "Parasol Ability": ItemData(None, True), + "Spark Ability": ItemData(None, True), + "Cutter Ability": ItemData(None, True), +} + +misc_item_table = { + "Heart Star": ItemData(0x770020, True, True), + "1-Up": ItemData(0x770021, False), + "Maxim Tomato": ItemData(0x770022, False), + "Invincible Candy": ItemData(0x770023, False), + "Little Star": ItemData(0x770024, False), + "Medium Star": ItemData(0x770025, False), + "Big Star": ItemData(0x770026, False), +} + +trap_item_table = { + "Gooey Bag": ItemData(0x770040, False, False, True), + "Slowness": ItemData(0x770041, False, False, True), + "Eject Ability": ItemData(0x770042, False, False, True) +} + +filler_item_weights = { + "1-Up": 4, + "Maxim Tomato": 2, + "Invincible Candy": 2 +} + +star_item_weights = { + "Little Star": 4, + "Medium Star": 2, + "Big Star": 1 +} + +total_filler_weights = { + **filler_item_weights, + **star_item_weights +} + + +item_table = { + **copy_ability_table, + **copy_ability_access_table, + **animal_friend_table, + **animal_friend_spawn_table, + **misc_item_table, + **trap_item_table +} + +item_names = { + "Copy Ability": set(copy_ability_table), + "Animal Friend": set(animal_friend_table), +} + +lookup_name_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code} diff --git a/worlds/kdl3/Locations.py b/worlds/kdl3/Locations.py new file mode 100644 index 000000000000..4d039a13497c --- /dev/null +++ b/worlds/kdl3/Locations.py @@ -0,0 +1,940 @@ +import typing +from BaseClasses import Location, Region +from .Names import LocationName + +if typing.TYPE_CHECKING: + from .Room import KDL3Room + + +class KDL3Location(Location): + game: str = "Kirby's Dream Land 3" + room: typing.Optional["KDL3Room"] = None + + def __init__(self, player: int, name: str, address: typing.Optional[int], parent: typing.Union[Region, None]): + super().__init__(player, name, address, parent) + if not address: + self.show_in_spoiler = False + + +stage_locations = { + 0x770001: LocationName.grass_land_1, + 0x770002: LocationName.grass_land_2, + 0x770003: LocationName.grass_land_3, + 0x770004: LocationName.grass_land_4, + 0x770005: LocationName.grass_land_5, + 0x770006: LocationName.grass_land_6, + 0x770007: LocationName.ripple_field_1, + 0x770008: LocationName.ripple_field_2, + 0x770009: LocationName.ripple_field_3, + 0x77000A: LocationName.ripple_field_4, + 0x77000B: LocationName.ripple_field_5, + 0x77000C: LocationName.ripple_field_6, + 0x77000D: LocationName.sand_canyon_1, + 0x77000E: LocationName.sand_canyon_2, + 0x77000F: LocationName.sand_canyon_3, + 0x770010: LocationName.sand_canyon_4, + 0x770011: LocationName.sand_canyon_5, + 0x770012: LocationName.sand_canyon_6, + 0x770013: LocationName.cloudy_park_1, + 0x770014: LocationName.cloudy_park_2, + 0x770015: LocationName.cloudy_park_3, + 0x770016: LocationName.cloudy_park_4, + 0x770017: LocationName.cloudy_park_5, + 0x770018: LocationName.cloudy_park_6, + 0x770019: LocationName.iceberg_1, + 0x77001A: LocationName.iceberg_2, + 0x77001B: LocationName.iceberg_3, + 0x77001C: LocationName.iceberg_4, + 0x77001D: LocationName.iceberg_5, + 0x77001E: LocationName.iceberg_6, +} + +heart_star_locations = { + 0x770101: LocationName.grass_land_tulip, + 0x770102: LocationName.grass_land_muchi, + 0x770103: LocationName.grass_land_pitcherman, + 0x770104: LocationName.grass_land_chao, + 0x770105: LocationName.grass_land_mine, + 0x770106: LocationName.grass_land_pierre, + 0x770107: LocationName.ripple_field_kamuribana, + 0x770108: LocationName.ripple_field_bakasa, + 0x770109: LocationName.ripple_field_elieel, + 0x77010A: LocationName.ripple_field_toad, + 0x77010B: LocationName.ripple_field_mama_pitch, + 0x77010C: LocationName.ripple_field_hb002, + 0x77010D: LocationName.sand_canyon_mushrooms, + 0x77010E: LocationName.sand_canyon_auntie, + 0x77010F: LocationName.sand_canyon_caramello, + 0x770110: LocationName.sand_canyon_hikari, + 0x770111: LocationName.sand_canyon_nyupun, + 0x770112: LocationName.sand_canyon_rob, + 0x770113: LocationName.cloudy_park_hibanamodoki, + 0x770114: LocationName.cloudy_park_piyokeko, + 0x770115: LocationName.cloudy_park_mrball, + 0x770116: LocationName.cloudy_park_mikarin, + 0x770117: LocationName.cloudy_park_pick, + 0x770118: LocationName.cloudy_park_hb007, + 0x770119: LocationName.iceberg_kogoesou, + 0x77011A: LocationName.iceberg_samus, + 0x77011B: LocationName.iceberg_kawasaki, + 0x77011C: LocationName.iceberg_name, + 0x77011D: LocationName.iceberg_shiro, + 0x77011E: LocationName.iceberg_angel, +} + +boss_locations = { + 0x770200: LocationName.grass_land_whispy, + 0x770201: LocationName.ripple_field_acro, + 0x770202: LocationName.sand_canyon_poncon, + 0x770203: LocationName.cloudy_park_ado, + 0x770204: LocationName.iceberg_dedede, +} + +consumable_locations = { + 0x770300: LocationName.grass_land_1_u1, + 0x770301: LocationName.grass_land_1_m1, + 0x770302: LocationName.grass_land_2_u1, + 0x770303: LocationName.grass_land_3_u1, + 0x770304: LocationName.grass_land_3_m1, + 0x770305: LocationName.grass_land_4_m1, + 0x770306: LocationName.grass_land_4_u1, + 0x770307: LocationName.grass_land_4_m2, + 0x770308: LocationName.grass_land_4_m3, + 0x770309: LocationName.grass_land_6_u1, + 0x77030A: LocationName.grass_land_6_u2, + 0x77030B: LocationName.ripple_field_2_u1, + 0x77030C: LocationName.ripple_field_2_m1, + 0x77030D: LocationName.ripple_field_3_m1, + 0x77030E: LocationName.ripple_field_3_u1, + 0x77030F: LocationName.ripple_field_4_m2, + 0x770310: LocationName.ripple_field_4_u1, + 0x770311: LocationName.ripple_field_4_m1, + 0x770312: LocationName.ripple_field_5_u1, + 0x770313: LocationName.ripple_field_5_m2, + 0x770314: LocationName.ripple_field_5_m1, + 0x770315: LocationName.sand_canyon_1_u1, + 0x770316: LocationName.sand_canyon_2_u1, + 0x770317: LocationName.sand_canyon_2_m1, + 0x770318: LocationName.sand_canyon_4_m1, + 0x770319: LocationName.sand_canyon_4_u1, + 0x77031A: LocationName.sand_canyon_4_m2, + 0x77031B: LocationName.sand_canyon_5_u1, + 0x77031C: LocationName.sand_canyon_5_u3, + 0x77031D: LocationName.sand_canyon_5_m1, + 0x77031E: LocationName.sand_canyon_5_u4, + 0x77031F: LocationName.sand_canyon_5_u2, + 0x770320: LocationName.cloudy_park_1_m1, + 0x770321: LocationName.cloudy_park_1_u1, + 0x770322: LocationName.cloudy_park_4_u1, + 0x770323: LocationName.cloudy_park_4_m1, + 0x770324: LocationName.cloudy_park_5_m1, + 0x770325: LocationName.cloudy_park_6_u1, + 0x770326: LocationName.iceberg_3_m1, + 0x770327: LocationName.iceberg_5_u1, + 0x770328: LocationName.iceberg_5_u2, + 0x770329: LocationName.iceberg_5_u3, + 0x77032A: LocationName.iceberg_6_m1, + 0x77032B: LocationName.iceberg_6_u1, +} + +level_consumables = { + 1: [0, 1], + 2: [2], + 3: [3, 4], + 4: [5, 6, 7, 8], + 6: [9, 10], + 8: [11, 12], + 9: [13, 14], + 10: [15, 16, 17], + 11: [18, 19, 20], + 13: [21], + 14: [22, 23], + 16: [24, 25, 26], + 17: [27, 28, 29, 30, 31], + 19: [32, 33], + 22: [34, 35], + 23: [36], + 24: [37], + 27: [38], + 29: [39, 40, 41], + 30: [42, 43], +} + +star_locations = { + 0x770401: LocationName.grass_land_1_s1, + 0x770402: LocationName.grass_land_1_s2, + 0x770403: LocationName.grass_land_1_s3, + 0x770404: LocationName.grass_land_1_s4, + 0x770405: LocationName.grass_land_1_s5, + 0x770406: LocationName.grass_land_1_s6, + 0x770407: LocationName.grass_land_1_s7, + 0x770408: LocationName.grass_land_1_s8, + 0x770409: LocationName.grass_land_1_s9, + 0x77040a: LocationName.grass_land_1_s10, + 0x77040b: LocationName.grass_land_1_s11, + 0x77040c: LocationName.grass_land_1_s12, + 0x77040d: LocationName.grass_land_1_s13, + 0x77040e: LocationName.grass_land_1_s14, + 0x77040f: LocationName.grass_land_1_s15, + 0x770410: LocationName.grass_land_1_s16, + 0x770411: LocationName.grass_land_1_s17, + 0x770412: LocationName.grass_land_1_s18, + 0x770413: LocationName.grass_land_1_s19, + 0x770414: LocationName.grass_land_1_s20, + 0x770415: LocationName.grass_land_1_s21, + 0x770416: LocationName.grass_land_1_s22, + 0x770417: LocationName.grass_land_1_s23, + 0x770418: LocationName.grass_land_2_s1, + 0x770419: LocationName.grass_land_2_s2, + 0x77041a: LocationName.grass_land_2_s3, + 0x77041b: LocationName.grass_land_2_s4, + 0x77041c: LocationName.grass_land_2_s5, + 0x77041d: LocationName.grass_land_2_s6, + 0x77041e: LocationName.grass_land_2_s7, + 0x77041f: LocationName.grass_land_2_s8, + 0x770420: LocationName.grass_land_2_s9, + 0x770421: LocationName.grass_land_2_s10, + 0x770422: LocationName.grass_land_2_s11, + 0x770423: LocationName.grass_land_2_s12, + 0x770424: LocationName.grass_land_2_s13, + 0x770425: LocationName.grass_land_2_s14, + 0x770426: LocationName.grass_land_2_s15, + 0x770427: LocationName.grass_land_2_s16, + 0x770428: LocationName.grass_land_2_s17, + 0x770429: LocationName.grass_land_2_s18, + 0x77042a: LocationName.grass_land_2_s19, + 0x77042b: LocationName.grass_land_2_s20, + 0x77042c: LocationName.grass_land_2_s21, + 0x77042d: LocationName.grass_land_3_s1, + 0x77042e: LocationName.grass_land_3_s2, + 0x77042f: LocationName.grass_land_3_s3, + 0x770430: LocationName.grass_land_3_s4, + 0x770431: LocationName.grass_land_3_s5, + 0x770432: LocationName.grass_land_3_s6, + 0x770433: LocationName.grass_land_3_s7, + 0x770434: LocationName.grass_land_3_s8, + 0x770435: LocationName.grass_land_3_s9, + 0x770436: LocationName.grass_land_3_s10, + 0x770437: LocationName.grass_land_3_s11, + 0x770438: LocationName.grass_land_3_s12, + 0x770439: LocationName.grass_land_3_s13, + 0x77043a: LocationName.grass_land_3_s14, + 0x77043b: LocationName.grass_land_3_s15, + 0x77043c: LocationName.grass_land_3_s16, + 0x77043d: LocationName.grass_land_3_s17, + 0x77043e: LocationName.grass_land_3_s18, + 0x77043f: LocationName.grass_land_3_s19, + 0x770440: LocationName.grass_land_3_s20, + 0x770441: LocationName.grass_land_3_s21, + 0x770442: LocationName.grass_land_3_s22, + 0x770443: LocationName.grass_land_3_s23, + 0x770444: LocationName.grass_land_3_s24, + 0x770445: LocationName.grass_land_3_s25, + 0x770446: LocationName.grass_land_3_s26, + 0x770447: LocationName.grass_land_3_s27, + 0x770448: LocationName.grass_land_3_s28, + 0x770449: LocationName.grass_land_3_s29, + 0x77044a: LocationName.grass_land_3_s30, + 0x77044b: LocationName.grass_land_3_s31, + 0x77044c: LocationName.grass_land_4_s1, + 0x77044d: LocationName.grass_land_4_s2, + 0x77044e: LocationName.grass_land_4_s3, + 0x77044f: LocationName.grass_land_4_s4, + 0x770450: LocationName.grass_land_4_s5, + 0x770451: LocationName.grass_land_4_s6, + 0x770452: LocationName.grass_land_4_s7, + 0x770453: LocationName.grass_land_4_s8, + 0x770454: LocationName.grass_land_4_s9, + 0x770455: LocationName.grass_land_4_s10, + 0x770456: LocationName.grass_land_4_s11, + 0x770457: LocationName.grass_land_4_s12, + 0x770458: LocationName.grass_land_4_s13, + 0x770459: LocationName.grass_land_4_s14, + 0x77045a: LocationName.grass_land_4_s15, + 0x77045b: LocationName.grass_land_4_s16, + 0x77045c: LocationName.grass_land_4_s17, + 0x77045d: LocationName.grass_land_4_s18, + 0x77045e: LocationName.grass_land_4_s19, + 0x77045f: LocationName.grass_land_4_s20, + 0x770460: LocationName.grass_land_4_s21, + 0x770461: LocationName.grass_land_4_s22, + 0x770462: LocationName.grass_land_4_s23, + 0x770463: LocationName.grass_land_4_s24, + 0x770464: LocationName.grass_land_4_s25, + 0x770465: LocationName.grass_land_4_s26, + 0x770466: LocationName.grass_land_4_s27, + 0x770467: LocationName.grass_land_4_s28, + 0x770468: LocationName.grass_land_4_s29, + 0x770469: LocationName.grass_land_4_s30, + 0x77046a: LocationName.grass_land_4_s31, + 0x77046b: LocationName.grass_land_4_s32, + 0x77046c: LocationName.grass_land_4_s33, + 0x77046d: LocationName.grass_land_4_s34, + 0x77046e: LocationName.grass_land_4_s35, + 0x77046f: LocationName.grass_land_4_s36, + 0x770470: LocationName.grass_land_4_s37, + 0x770471: LocationName.grass_land_5_s1, + 0x770472: LocationName.grass_land_5_s2, + 0x770473: LocationName.grass_land_5_s3, + 0x770474: LocationName.grass_land_5_s4, + 0x770475: LocationName.grass_land_5_s5, + 0x770476: LocationName.grass_land_5_s6, + 0x770477: LocationName.grass_land_5_s7, + 0x770478: LocationName.grass_land_5_s8, + 0x770479: LocationName.grass_land_5_s9, + 0x77047a: LocationName.grass_land_5_s10, + 0x77047b: LocationName.grass_land_5_s11, + 0x77047c: LocationName.grass_land_5_s12, + 0x77047d: LocationName.grass_land_5_s13, + 0x77047e: LocationName.grass_land_5_s14, + 0x77047f: LocationName.grass_land_5_s15, + 0x770480: LocationName.grass_land_5_s16, + 0x770481: LocationName.grass_land_5_s17, + 0x770482: LocationName.grass_land_5_s18, + 0x770483: LocationName.grass_land_5_s19, + 0x770484: LocationName.grass_land_5_s20, + 0x770485: LocationName.grass_land_5_s21, + 0x770486: LocationName.grass_land_5_s22, + 0x770487: LocationName.grass_land_5_s23, + 0x770488: LocationName.grass_land_5_s24, + 0x770489: LocationName.grass_land_5_s25, + 0x77048a: LocationName.grass_land_5_s26, + 0x77048b: LocationName.grass_land_5_s27, + 0x77048c: LocationName.grass_land_5_s28, + 0x77048d: LocationName.grass_land_5_s29, + 0x77048e: LocationName.grass_land_6_s1, + 0x77048f: LocationName.grass_land_6_s2, + 0x770490: LocationName.grass_land_6_s3, + 0x770491: LocationName.grass_land_6_s4, + 0x770492: LocationName.grass_land_6_s5, + 0x770493: LocationName.grass_land_6_s6, + 0x770494: LocationName.grass_land_6_s7, + 0x770495: LocationName.grass_land_6_s8, + 0x770496: LocationName.grass_land_6_s9, + 0x770497: LocationName.grass_land_6_s10, + 0x770498: LocationName.grass_land_6_s11, + 0x770499: LocationName.grass_land_6_s12, + 0x77049a: LocationName.grass_land_6_s13, + 0x77049b: LocationName.grass_land_6_s14, + 0x77049c: LocationName.grass_land_6_s15, + 0x77049d: LocationName.grass_land_6_s16, + 0x77049e: LocationName.grass_land_6_s17, + 0x77049f: LocationName.grass_land_6_s18, + 0x7704a0: LocationName.grass_land_6_s19, + 0x7704a1: LocationName.grass_land_6_s20, + 0x7704a2: LocationName.grass_land_6_s21, + 0x7704a3: LocationName.grass_land_6_s22, + 0x7704a4: LocationName.grass_land_6_s23, + 0x7704a5: LocationName.grass_land_6_s24, + 0x7704a6: LocationName.grass_land_6_s25, + 0x7704a7: LocationName.grass_land_6_s26, + 0x7704a8: LocationName.grass_land_6_s27, + 0x7704a9: LocationName.grass_land_6_s28, + 0x7704aa: LocationName.grass_land_6_s29, + 0x7704ab: LocationName.ripple_field_1_s1, + 0x7704ac: LocationName.ripple_field_1_s2, + 0x7704ad: LocationName.ripple_field_1_s3, + 0x7704ae: LocationName.ripple_field_1_s4, + 0x7704af: LocationName.ripple_field_1_s5, + 0x7704b0: LocationName.ripple_field_1_s6, + 0x7704b1: LocationName.ripple_field_1_s7, + 0x7704b2: LocationName.ripple_field_1_s8, + 0x7704b3: LocationName.ripple_field_1_s9, + 0x7704b4: LocationName.ripple_field_1_s10, + 0x7704b5: LocationName.ripple_field_1_s11, + 0x7704b6: LocationName.ripple_field_1_s12, + 0x7704b7: LocationName.ripple_field_1_s13, + 0x7704b8: LocationName.ripple_field_1_s14, + 0x7704b9: LocationName.ripple_field_1_s15, + 0x7704ba: LocationName.ripple_field_1_s16, + 0x7704bb: LocationName.ripple_field_1_s17, + 0x7704bc: LocationName.ripple_field_1_s18, + 0x7704bd: LocationName.ripple_field_1_s19, + 0x7704be: LocationName.ripple_field_2_s1, + 0x7704bf: LocationName.ripple_field_2_s2, + 0x7704c0: LocationName.ripple_field_2_s3, + 0x7704c1: LocationName.ripple_field_2_s4, + 0x7704c2: LocationName.ripple_field_2_s5, + 0x7704c3: LocationName.ripple_field_2_s6, + 0x7704c4: LocationName.ripple_field_2_s7, + 0x7704c5: LocationName.ripple_field_2_s8, + 0x7704c6: LocationName.ripple_field_2_s9, + 0x7704c7: LocationName.ripple_field_2_s10, + 0x7704c8: LocationName.ripple_field_2_s11, + 0x7704c9: LocationName.ripple_field_2_s12, + 0x7704ca: LocationName.ripple_field_2_s13, + 0x7704cb: LocationName.ripple_field_2_s14, + 0x7704cc: LocationName.ripple_field_2_s15, + 0x7704cd: LocationName.ripple_field_2_s16, + 0x7704ce: LocationName.ripple_field_2_s17, + 0x7704cf: LocationName.ripple_field_3_s1, + 0x7704d0: LocationName.ripple_field_3_s2, + 0x7704d1: LocationName.ripple_field_3_s3, + 0x7704d2: LocationName.ripple_field_3_s4, + 0x7704d3: LocationName.ripple_field_3_s5, + 0x7704d4: LocationName.ripple_field_3_s6, + 0x7704d5: LocationName.ripple_field_3_s7, + 0x7704d6: LocationName.ripple_field_3_s8, + 0x7704d7: LocationName.ripple_field_3_s9, + 0x7704d8: LocationName.ripple_field_3_s10, + 0x7704d9: LocationName.ripple_field_3_s11, + 0x7704da: LocationName.ripple_field_3_s12, + 0x7704db: LocationName.ripple_field_3_s13, + 0x7704dc: LocationName.ripple_field_3_s14, + 0x7704dd: LocationName.ripple_field_3_s15, + 0x7704de: LocationName.ripple_field_3_s16, + 0x7704df: LocationName.ripple_field_3_s17, + 0x7704e0: LocationName.ripple_field_3_s18, + 0x7704e1: LocationName.ripple_field_3_s19, + 0x7704e2: LocationName.ripple_field_3_s20, + 0x7704e3: LocationName.ripple_field_3_s21, + 0x7704e4: LocationName.ripple_field_4_s1, + 0x7704e5: LocationName.ripple_field_4_s2, + 0x7704e6: LocationName.ripple_field_4_s3, + 0x7704e7: LocationName.ripple_field_4_s4, + 0x7704e8: LocationName.ripple_field_4_s5, + 0x7704e9: LocationName.ripple_field_4_s6, + 0x7704ea: LocationName.ripple_field_4_s7, + 0x7704eb: LocationName.ripple_field_4_s8, + 0x7704ec: LocationName.ripple_field_4_s9, + 0x7704ed: LocationName.ripple_field_4_s10, + 0x7704ee: LocationName.ripple_field_4_s11, + 0x7704ef: LocationName.ripple_field_4_s12, + 0x7704f0: LocationName.ripple_field_4_s13, + 0x7704f1: LocationName.ripple_field_4_s14, + 0x7704f2: LocationName.ripple_field_4_s15, + 0x7704f3: LocationName.ripple_field_4_s16, + 0x7704f4: LocationName.ripple_field_4_s17, + 0x7704f5: LocationName.ripple_field_4_s18, + 0x7704f6: LocationName.ripple_field_4_s19, + 0x7704f7: LocationName.ripple_field_4_s20, + 0x7704f8: LocationName.ripple_field_4_s21, + 0x7704f9: LocationName.ripple_field_4_s22, + 0x7704fa: LocationName.ripple_field_4_s23, + 0x7704fb: LocationName.ripple_field_4_s24, + 0x7704fc: LocationName.ripple_field_4_s25, + 0x7704fd: LocationName.ripple_field_4_s26, + 0x7704fe: LocationName.ripple_field_4_s27, + 0x7704ff: LocationName.ripple_field_4_s28, + 0x770500: LocationName.ripple_field_4_s29, + 0x770501: LocationName.ripple_field_4_s30, + 0x770502: LocationName.ripple_field_4_s31, + 0x770503: LocationName.ripple_field_4_s32, + 0x770504: LocationName.ripple_field_4_s33, + 0x770505: LocationName.ripple_field_4_s34, + 0x770506: LocationName.ripple_field_4_s35, + 0x770507: LocationName.ripple_field_4_s36, + 0x770508: LocationName.ripple_field_4_s37, + 0x770509: LocationName.ripple_field_4_s38, + 0x77050a: LocationName.ripple_field_4_s39, + 0x77050b: LocationName.ripple_field_4_s40, + 0x77050c: LocationName.ripple_field_4_s41, + 0x77050d: LocationName.ripple_field_4_s42, + 0x77050e: LocationName.ripple_field_4_s43, + 0x77050f: LocationName.ripple_field_4_s44, + 0x770510: LocationName.ripple_field_4_s45, + 0x770511: LocationName.ripple_field_4_s46, + 0x770512: LocationName.ripple_field_4_s47, + 0x770513: LocationName.ripple_field_4_s48, + 0x770514: LocationName.ripple_field_4_s49, + 0x770515: LocationName.ripple_field_4_s50, + 0x770516: LocationName.ripple_field_4_s51, + 0x770517: LocationName.ripple_field_5_s1, + 0x770518: LocationName.ripple_field_5_s2, + 0x770519: LocationName.ripple_field_5_s3, + 0x77051a: LocationName.ripple_field_5_s4, + 0x77051b: LocationName.ripple_field_5_s5, + 0x77051c: LocationName.ripple_field_5_s6, + 0x77051d: LocationName.ripple_field_5_s7, + 0x77051e: LocationName.ripple_field_5_s8, + 0x77051f: LocationName.ripple_field_5_s9, + 0x770520: LocationName.ripple_field_5_s10, + 0x770521: LocationName.ripple_field_5_s11, + 0x770522: LocationName.ripple_field_5_s12, + 0x770523: LocationName.ripple_field_5_s13, + 0x770524: LocationName.ripple_field_5_s14, + 0x770525: LocationName.ripple_field_5_s15, + 0x770526: LocationName.ripple_field_5_s16, + 0x770527: LocationName.ripple_field_5_s17, + 0x770528: LocationName.ripple_field_5_s18, + 0x770529: LocationName.ripple_field_5_s19, + 0x77052a: LocationName.ripple_field_5_s20, + 0x77052b: LocationName.ripple_field_5_s21, + 0x77052c: LocationName.ripple_field_5_s22, + 0x77052d: LocationName.ripple_field_5_s23, + 0x77052e: LocationName.ripple_field_5_s24, + 0x77052f: LocationName.ripple_field_5_s25, + 0x770530: LocationName.ripple_field_5_s26, + 0x770531: LocationName.ripple_field_5_s27, + 0x770532: LocationName.ripple_field_5_s28, + 0x770533: LocationName.ripple_field_5_s29, + 0x770534: LocationName.ripple_field_5_s30, + 0x770535: LocationName.ripple_field_5_s31, + 0x770536: LocationName.ripple_field_5_s32, + 0x770537: LocationName.ripple_field_5_s33, + 0x770538: LocationName.ripple_field_5_s34, + 0x770539: LocationName.ripple_field_5_s35, + 0x77053a: LocationName.ripple_field_5_s36, + 0x77053b: LocationName.ripple_field_5_s37, + 0x77053c: LocationName.ripple_field_5_s38, + 0x77053d: LocationName.ripple_field_5_s39, + 0x77053e: LocationName.ripple_field_5_s40, + 0x77053f: LocationName.ripple_field_5_s41, + 0x770540: LocationName.ripple_field_5_s42, + 0x770541: LocationName.ripple_field_5_s43, + 0x770542: LocationName.ripple_field_5_s44, + 0x770543: LocationName.ripple_field_5_s45, + 0x770544: LocationName.ripple_field_5_s46, + 0x770545: LocationName.ripple_field_5_s47, + 0x770546: LocationName.ripple_field_5_s48, + 0x770547: LocationName.ripple_field_5_s49, + 0x770548: LocationName.ripple_field_5_s50, + 0x770549: LocationName.ripple_field_5_s51, + 0x77054a: LocationName.ripple_field_6_s1, + 0x77054b: LocationName.ripple_field_6_s2, + 0x77054c: LocationName.ripple_field_6_s3, + 0x77054d: LocationName.ripple_field_6_s4, + 0x77054e: LocationName.ripple_field_6_s5, + 0x77054f: LocationName.ripple_field_6_s6, + 0x770550: LocationName.ripple_field_6_s7, + 0x770551: LocationName.ripple_field_6_s8, + 0x770552: LocationName.ripple_field_6_s9, + 0x770553: LocationName.ripple_field_6_s10, + 0x770554: LocationName.ripple_field_6_s11, + 0x770555: LocationName.ripple_field_6_s12, + 0x770556: LocationName.ripple_field_6_s13, + 0x770557: LocationName.ripple_field_6_s14, + 0x770558: LocationName.ripple_field_6_s15, + 0x770559: LocationName.ripple_field_6_s16, + 0x77055a: LocationName.ripple_field_6_s17, + 0x77055b: LocationName.ripple_field_6_s18, + 0x77055c: LocationName.ripple_field_6_s19, + 0x77055d: LocationName.ripple_field_6_s20, + 0x77055e: LocationName.ripple_field_6_s21, + 0x77055f: LocationName.ripple_field_6_s22, + 0x770560: LocationName.ripple_field_6_s23, + 0x770561: LocationName.sand_canyon_1_s1, + 0x770562: LocationName.sand_canyon_1_s2, + 0x770563: LocationName.sand_canyon_1_s3, + 0x770564: LocationName.sand_canyon_1_s4, + 0x770565: LocationName.sand_canyon_1_s5, + 0x770566: LocationName.sand_canyon_1_s6, + 0x770567: LocationName.sand_canyon_1_s7, + 0x770568: LocationName.sand_canyon_1_s8, + 0x770569: LocationName.sand_canyon_1_s9, + 0x77056a: LocationName.sand_canyon_1_s10, + 0x77056b: LocationName.sand_canyon_1_s11, + 0x77056c: LocationName.sand_canyon_1_s12, + 0x77056d: LocationName.sand_canyon_1_s13, + 0x77056e: LocationName.sand_canyon_1_s14, + 0x77056f: LocationName.sand_canyon_1_s15, + 0x770570: LocationName.sand_canyon_1_s16, + 0x770571: LocationName.sand_canyon_1_s17, + 0x770572: LocationName.sand_canyon_1_s18, + 0x770573: LocationName.sand_canyon_1_s19, + 0x770574: LocationName.sand_canyon_1_s20, + 0x770575: LocationName.sand_canyon_1_s21, + 0x770576: LocationName.sand_canyon_1_s22, + 0x770577: LocationName.sand_canyon_2_s1, + 0x770578: LocationName.sand_canyon_2_s2, + 0x770579: LocationName.sand_canyon_2_s3, + 0x77057a: LocationName.sand_canyon_2_s4, + 0x77057b: LocationName.sand_canyon_2_s5, + 0x77057c: LocationName.sand_canyon_2_s6, + 0x77057d: LocationName.sand_canyon_2_s7, + 0x77057e: LocationName.sand_canyon_2_s8, + 0x77057f: LocationName.sand_canyon_2_s9, + 0x770580: LocationName.sand_canyon_2_s10, + 0x770581: LocationName.sand_canyon_2_s11, + 0x770582: LocationName.sand_canyon_2_s12, + 0x770583: LocationName.sand_canyon_2_s13, + 0x770584: LocationName.sand_canyon_2_s14, + 0x770585: LocationName.sand_canyon_2_s15, + 0x770586: LocationName.sand_canyon_2_s16, + 0x770587: LocationName.sand_canyon_2_s17, + 0x770588: LocationName.sand_canyon_2_s18, + 0x770589: LocationName.sand_canyon_2_s19, + 0x77058a: LocationName.sand_canyon_2_s20, + 0x77058b: LocationName.sand_canyon_2_s21, + 0x77058c: LocationName.sand_canyon_2_s22, + 0x77058d: LocationName.sand_canyon_2_s23, + 0x77058e: LocationName.sand_canyon_2_s24, + 0x77058f: LocationName.sand_canyon_2_s25, + 0x770590: LocationName.sand_canyon_2_s26, + 0x770591: LocationName.sand_canyon_2_s27, + 0x770592: LocationName.sand_canyon_2_s28, + 0x770593: LocationName.sand_canyon_2_s29, + 0x770594: LocationName.sand_canyon_2_s30, + 0x770595: LocationName.sand_canyon_2_s31, + 0x770596: LocationName.sand_canyon_2_s32, + 0x770597: LocationName.sand_canyon_2_s33, + 0x770598: LocationName.sand_canyon_2_s34, + 0x770599: LocationName.sand_canyon_2_s35, + 0x77059a: LocationName.sand_canyon_2_s36, + 0x77059b: LocationName.sand_canyon_2_s37, + 0x77059c: LocationName.sand_canyon_2_s38, + 0x77059d: LocationName.sand_canyon_2_s39, + 0x77059e: LocationName.sand_canyon_2_s40, + 0x77059f: LocationName.sand_canyon_2_s41, + 0x7705a0: LocationName.sand_canyon_2_s42, + 0x7705a1: LocationName.sand_canyon_2_s43, + 0x7705a2: LocationName.sand_canyon_2_s44, + 0x7705a3: LocationName.sand_canyon_2_s45, + 0x7705a4: LocationName.sand_canyon_2_s46, + 0x7705a5: LocationName.sand_canyon_2_s47, + 0x7705a6: LocationName.sand_canyon_2_s48, + 0x7705a7: LocationName.sand_canyon_3_s1, + 0x7705a8: LocationName.sand_canyon_3_s2, + 0x7705a9: LocationName.sand_canyon_3_s3, + 0x7705aa: LocationName.sand_canyon_3_s4, + 0x7705ab: LocationName.sand_canyon_3_s5, + 0x7705ac: LocationName.sand_canyon_3_s6, + 0x7705ad: LocationName.sand_canyon_3_s7, + 0x7705ae: LocationName.sand_canyon_3_s8, + 0x7705af: LocationName.sand_canyon_3_s9, + 0x7705b0: LocationName.sand_canyon_3_s10, + 0x7705b1: LocationName.sand_canyon_4_s1, + 0x7705b2: LocationName.sand_canyon_4_s2, + 0x7705b3: LocationName.sand_canyon_4_s3, + 0x7705b4: LocationName.sand_canyon_4_s4, + 0x7705b5: LocationName.sand_canyon_4_s5, + 0x7705b6: LocationName.sand_canyon_4_s6, + 0x7705b7: LocationName.sand_canyon_4_s7, + 0x7705b8: LocationName.sand_canyon_4_s8, + 0x7705b9: LocationName.sand_canyon_4_s9, + 0x7705ba: LocationName.sand_canyon_4_s10, + 0x7705bb: LocationName.sand_canyon_4_s11, + 0x7705bc: LocationName.sand_canyon_4_s12, + 0x7705bd: LocationName.sand_canyon_4_s13, + 0x7705be: LocationName.sand_canyon_4_s14, + 0x7705bf: LocationName.sand_canyon_4_s15, + 0x7705c0: LocationName.sand_canyon_4_s16, + 0x7705c1: LocationName.sand_canyon_4_s17, + 0x7705c2: LocationName.sand_canyon_4_s18, + 0x7705c3: LocationName.sand_canyon_4_s19, + 0x7705c4: LocationName.sand_canyon_4_s20, + 0x7705c5: LocationName.sand_canyon_4_s21, + 0x7705c6: LocationName.sand_canyon_4_s22, + 0x7705c7: LocationName.sand_canyon_4_s23, + 0x7705c8: LocationName.sand_canyon_5_s1, + 0x7705c9: LocationName.sand_canyon_5_s2, + 0x7705ca: LocationName.sand_canyon_5_s3, + 0x7705cb: LocationName.sand_canyon_5_s4, + 0x7705cc: LocationName.sand_canyon_5_s5, + 0x7705cd: LocationName.sand_canyon_5_s6, + 0x7705ce: LocationName.sand_canyon_5_s7, + 0x7705cf: LocationName.sand_canyon_5_s8, + 0x7705d0: LocationName.sand_canyon_5_s9, + 0x7705d1: LocationName.sand_canyon_5_s10, + 0x7705d2: LocationName.sand_canyon_5_s11, + 0x7705d3: LocationName.sand_canyon_5_s12, + 0x7705d4: LocationName.sand_canyon_5_s13, + 0x7705d5: LocationName.sand_canyon_5_s14, + 0x7705d6: LocationName.sand_canyon_5_s15, + 0x7705d7: LocationName.sand_canyon_5_s16, + 0x7705d8: LocationName.sand_canyon_5_s17, + 0x7705d9: LocationName.sand_canyon_5_s18, + 0x7705da: LocationName.sand_canyon_5_s19, + 0x7705db: LocationName.sand_canyon_5_s20, + 0x7705dc: LocationName.sand_canyon_5_s21, + 0x7705dd: LocationName.sand_canyon_5_s22, + 0x7705de: LocationName.sand_canyon_5_s23, + 0x7705df: LocationName.sand_canyon_5_s24, + 0x7705e0: LocationName.sand_canyon_5_s25, + 0x7705e1: LocationName.sand_canyon_5_s26, + 0x7705e2: LocationName.sand_canyon_5_s27, + 0x7705e3: LocationName.sand_canyon_5_s28, + 0x7705e4: LocationName.sand_canyon_5_s29, + 0x7705e5: LocationName.sand_canyon_5_s30, + 0x7705e6: LocationName.sand_canyon_5_s31, + 0x7705e7: LocationName.sand_canyon_5_s32, + 0x7705e8: LocationName.sand_canyon_5_s33, + 0x7705e9: LocationName.sand_canyon_5_s34, + 0x7705ea: LocationName.sand_canyon_5_s35, + 0x7705eb: LocationName.sand_canyon_5_s36, + 0x7705ec: LocationName.sand_canyon_5_s37, + 0x7705ed: LocationName.sand_canyon_5_s38, + 0x7705ee: LocationName.sand_canyon_5_s39, + 0x7705ef: LocationName.sand_canyon_5_s40, + 0x7705f0: LocationName.cloudy_park_1_s1, + 0x7705f1: LocationName.cloudy_park_1_s2, + 0x7705f2: LocationName.cloudy_park_1_s3, + 0x7705f3: LocationName.cloudy_park_1_s4, + 0x7705f4: LocationName.cloudy_park_1_s5, + 0x7705f5: LocationName.cloudy_park_1_s6, + 0x7705f6: LocationName.cloudy_park_1_s7, + 0x7705f7: LocationName.cloudy_park_1_s8, + 0x7705f8: LocationName.cloudy_park_1_s9, + 0x7705f9: LocationName.cloudy_park_1_s10, + 0x7705fa: LocationName.cloudy_park_1_s11, + 0x7705fb: LocationName.cloudy_park_1_s12, + 0x7705fc: LocationName.cloudy_park_1_s13, + 0x7705fd: LocationName.cloudy_park_1_s14, + 0x7705fe: LocationName.cloudy_park_1_s15, + 0x7705ff: LocationName.cloudy_park_1_s16, + 0x770600: LocationName.cloudy_park_1_s17, + 0x770601: LocationName.cloudy_park_1_s18, + 0x770602: LocationName.cloudy_park_1_s19, + 0x770603: LocationName.cloudy_park_1_s20, + 0x770604: LocationName.cloudy_park_1_s21, + 0x770605: LocationName.cloudy_park_1_s22, + 0x770606: LocationName.cloudy_park_1_s23, + 0x770607: LocationName.cloudy_park_2_s1, + 0x770608: LocationName.cloudy_park_2_s2, + 0x770609: LocationName.cloudy_park_2_s3, + 0x77060a: LocationName.cloudy_park_2_s4, + 0x77060b: LocationName.cloudy_park_2_s5, + 0x77060c: LocationName.cloudy_park_2_s6, + 0x77060d: LocationName.cloudy_park_2_s7, + 0x77060e: LocationName.cloudy_park_2_s8, + 0x77060f: LocationName.cloudy_park_2_s9, + 0x770610: LocationName.cloudy_park_2_s10, + 0x770611: LocationName.cloudy_park_2_s11, + 0x770612: LocationName.cloudy_park_2_s12, + 0x770613: LocationName.cloudy_park_2_s13, + 0x770614: LocationName.cloudy_park_2_s14, + 0x770615: LocationName.cloudy_park_2_s15, + 0x770616: LocationName.cloudy_park_2_s16, + 0x770617: LocationName.cloudy_park_2_s17, + 0x770618: LocationName.cloudy_park_2_s18, + 0x770619: LocationName.cloudy_park_2_s19, + 0x77061a: LocationName.cloudy_park_2_s20, + 0x77061b: LocationName.cloudy_park_2_s21, + 0x77061c: LocationName.cloudy_park_2_s22, + 0x77061d: LocationName.cloudy_park_2_s23, + 0x77061e: LocationName.cloudy_park_2_s24, + 0x77061f: LocationName.cloudy_park_2_s25, + 0x770620: LocationName.cloudy_park_2_s26, + 0x770621: LocationName.cloudy_park_2_s27, + 0x770622: LocationName.cloudy_park_2_s28, + 0x770623: LocationName.cloudy_park_2_s29, + 0x770624: LocationName.cloudy_park_2_s30, + 0x770625: LocationName.cloudy_park_2_s31, + 0x770626: LocationName.cloudy_park_2_s32, + 0x770627: LocationName.cloudy_park_2_s33, + 0x770628: LocationName.cloudy_park_2_s34, + 0x770629: LocationName.cloudy_park_2_s35, + 0x77062a: LocationName.cloudy_park_2_s36, + 0x77062b: LocationName.cloudy_park_2_s37, + 0x77062c: LocationName.cloudy_park_2_s38, + 0x77062d: LocationName.cloudy_park_2_s39, + 0x77062e: LocationName.cloudy_park_2_s40, + 0x77062f: LocationName.cloudy_park_2_s41, + 0x770630: LocationName.cloudy_park_2_s42, + 0x770631: LocationName.cloudy_park_2_s43, + 0x770632: LocationName.cloudy_park_2_s44, + 0x770633: LocationName.cloudy_park_2_s45, + 0x770634: LocationName.cloudy_park_2_s46, + 0x770635: LocationName.cloudy_park_2_s47, + 0x770636: LocationName.cloudy_park_2_s48, + 0x770637: LocationName.cloudy_park_2_s49, + 0x770638: LocationName.cloudy_park_2_s50, + 0x770639: LocationName.cloudy_park_2_s51, + 0x77063a: LocationName.cloudy_park_2_s52, + 0x77063b: LocationName.cloudy_park_2_s53, + 0x77063c: LocationName.cloudy_park_2_s54, + 0x77063d: LocationName.cloudy_park_3_s1, + 0x77063e: LocationName.cloudy_park_3_s2, + 0x77063f: LocationName.cloudy_park_3_s3, + 0x770640: LocationName.cloudy_park_3_s4, + 0x770641: LocationName.cloudy_park_3_s5, + 0x770642: LocationName.cloudy_park_3_s6, + 0x770643: LocationName.cloudy_park_3_s7, + 0x770644: LocationName.cloudy_park_3_s8, + 0x770645: LocationName.cloudy_park_3_s9, + 0x770646: LocationName.cloudy_park_3_s10, + 0x770647: LocationName.cloudy_park_3_s11, + 0x770648: LocationName.cloudy_park_3_s12, + 0x770649: LocationName.cloudy_park_3_s13, + 0x77064a: LocationName.cloudy_park_3_s14, + 0x77064b: LocationName.cloudy_park_3_s15, + 0x77064c: LocationName.cloudy_park_3_s16, + 0x77064d: LocationName.cloudy_park_3_s17, + 0x77064e: LocationName.cloudy_park_3_s18, + 0x77064f: LocationName.cloudy_park_3_s19, + 0x770650: LocationName.cloudy_park_3_s20, + 0x770651: LocationName.cloudy_park_3_s21, + 0x770652: LocationName.cloudy_park_3_s22, + 0x770653: LocationName.cloudy_park_4_s1, + 0x770654: LocationName.cloudy_park_4_s2, + 0x770655: LocationName.cloudy_park_4_s3, + 0x770656: LocationName.cloudy_park_4_s4, + 0x770657: LocationName.cloudy_park_4_s5, + 0x770658: LocationName.cloudy_park_4_s6, + 0x770659: LocationName.cloudy_park_4_s7, + 0x77065a: LocationName.cloudy_park_4_s8, + 0x77065b: LocationName.cloudy_park_4_s9, + 0x77065c: LocationName.cloudy_park_4_s10, + 0x77065d: LocationName.cloudy_park_4_s11, + 0x77065e: LocationName.cloudy_park_4_s12, + 0x77065f: LocationName.cloudy_park_4_s13, + 0x770660: LocationName.cloudy_park_4_s14, + 0x770661: LocationName.cloudy_park_4_s15, + 0x770662: LocationName.cloudy_park_4_s16, + 0x770663: LocationName.cloudy_park_4_s17, + 0x770664: LocationName.cloudy_park_4_s18, + 0x770665: LocationName.cloudy_park_4_s19, + 0x770666: LocationName.cloudy_park_4_s20, + 0x770667: LocationName.cloudy_park_4_s21, + 0x770668: LocationName.cloudy_park_4_s22, + 0x770669: LocationName.cloudy_park_4_s23, + 0x77066a: LocationName.cloudy_park_4_s24, + 0x77066b: LocationName.cloudy_park_4_s25, + 0x77066c: LocationName.cloudy_park_4_s26, + 0x77066d: LocationName.cloudy_park_4_s27, + 0x77066e: LocationName.cloudy_park_4_s28, + 0x77066f: LocationName.cloudy_park_4_s29, + 0x770670: LocationName.cloudy_park_4_s30, + 0x770671: LocationName.cloudy_park_4_s31, + 0x770672: LocationName.cloudy_park_4_s32, + 0x770673: LocationName.cloudy_park_4_s33, + 0x770674: LocationName.cloudy_park_4_s34, + 0x770675: LocationName.cloudy_park_4_s35, + 0x770676: LocationName.cloudy_park_4_s36, + 0x770677: LocationName.cloudy_park_4_s37, + 0x770678: LocationName.cloudy_park_4_s38, + 0x770679: LocationName.cloudy_park_4_s39, + 0x77067a: LocationName.cloudy_park_4_s40, + 0x77067b: LocationName.cloudy_park_4_s41, + 0x77067c: LocationName.cloudy_park_4_s42, + 0x77067d: LocationName.cloudy_park_4_s43, + 0x77067e: LocationName.cloudy_park_4_s44, + 0x77067f: LocationName.cloudy_park_4_s45, + 0x770680: LocationName.cloudy_park_4_s46, + 0x770681: LocationName.cloudy_park_4_s47, + 0x770682: LocationName.cloudy_park_4_s48, + 0x770683: LocationName.cloudy_park_4_s49, + 0x770684: LocationName.cloudy_park_4_s50, + 0x770685: LocationName.cloudy_park_5_s1, + 0x770686: LocationName.cloudy_park_5_s2, + 0x770687: LocationName.cloudy_park_5_s3, + 0x770688: LocationName.cloudy_park_5_s4, + 0x770689: LocationName.cloudy_park_5_s5, + 0x77068a: LocationName.cloudy_park_5_s6, + 0x77068b: LocationName.cloudy_park_6_s1, + 0x77068c: LocationName.cloudy_park_6_s2, + 0x77068d: LocationName.cloudy_park_6_s3, + 0x77068e: LocationName.cloudy_park_6_s4, + 0x77068f: LocationName.cloudy_park_6_s5, + 0x770690: LocationName.cloudy_park_6_s6, + 0x770691: LocationName.cloudy_park_6_s7, + 0x770692: LocationName.cloudy_park_6_s8, + 0x770693: LocationName.cloudy_park_6_s9, + 0x770694: LocationName.cloudy_park_6_s10, + 0x770695: LocationName.cloudy_park_6_s11, + 0x770696: LocationName.cloudy_park_6_s12, + 0x770697: LocationName.cloudy_park_6_s13, + 0x770698: LocationName.cloudy_park_6_s14, + 0x770699: LocationName.cloudy_park_6_s15, + 0x77069a: LocationName.cloudy_park_6_s16, + 0x77069b: LocationName.cloudy_park_6_s17, + 0x77069c: LocationName.cloudy_park_6_s18, + 0x77069d: LocationName.cloudy_park_6_s19, + 0x77069e: LocationName.cloudy_park_6_s20, + 0x77069f: LocationName.cloudy_park_6_s21, + 0x7706a0: LocationName.cloudy_park_6_s22, + 0x7706a1: LocationName.cloudy_park_6_s23, + 0x7706a2: LocationName.cloudy_park_6_s24, + 0x7706a3: LocationName.cloudy_park_6_s25, + 0x7706a4: LocationName.cloudy_park_6_s26, + 0x7706a5: LocationName.cloudy_park_6_s27, + 0x7706a6: LocationName.cloudy_park_6_s28, + 0x7706a7: LocationName.cloudy_park_6_s29, + 0x7706a8: LocationName.cloudy_park_6_s30, + 0x7706a9: LocationName.cloudy_park_6_s31, + 0x7706aa: LocationName.cloudy_park_6_s32, + 0x7706ab: LocationName.cloudy_park_6_s33, + 0x7706ac: LocationName.iceberg_1_s1, + 0x7706ad: LocationName.iceberg_1_s2, + 0x7706ae: LocationName.iceberg_1_s3, + 0x7706af: LocationName.iceberg_1_s4, + 0x7706b0: LocationName.iceberg_1_s5, + 0x7706b1: LocationName.iceberg_1_s6, + 0x7706b2: LocationName.iceberg_2_s1, + 0x7706b3: LocationName.iceberg_2_s2, + 0x7706b4: LocationName.iceberg_2_s3, + 0x7706b5: LocationName.iceberg_2_s4, + 0x7706b6: LocationName.iceberg_2_s5, + 0x7706b7: LocationName.iceberg_2_s6, + 0x7706b8: LocationName.iceberg_2_s7, + 0x7706b9: LocationName.iceberg_2_s8, + 0x7706ba: LocationName.iceberg_2_s9, + 0x7706bb: LocationName.iceberg_2_s10, + 0x7706bc: LocationName.iceberg_2_s11, + 0x7706bd: LocationName.iceberg_2_s12, + 0x7706be: LocationName.iceberg_2_s13, + 0x7706bf: LocationName.iceberg_2_s14, + 0x7706c0: LocationName.iceberg_2_s15, + 0x7706c1: LocationName.iceberg_2_s16, + 0x7706c2: LocationName.iceberg_2_s17, + 0x7706c3: LocationName.iceberg_2_s18, + 0x7706c4: LocationName.iceberg_2_s19, + 0x7706c5: LocationName.iceberg_3_s1, + 0x7706c6: LocationName.iceberg_3_s2, + 0x7706c7: LocationName.iceberg_3_s3, + 0x7706c8: LocationName.iceberg_3_s4, + 0x7706c9: LocationName.iceberg_3_s5, + 0x7706ca: LocationName.iceberg_3_s6, + 0x7706cb: LocationName.iceberg_3_s7, + 0x7706cc: LocationName.iceberg_3_s8, + 0x7706cd: LocationName.iceberg_3_s9, + 0x7706ce: LocationName.iceberg_3_s10, + 0x7706cf: LocationName.iceberg_3_s11, + 0x7706d0: LocationName.iceberg_3_s12, + 0x7706d1: LocationName.iceberg_3_s13, + 0x7706d2: LocationName.iceberg_3_s14, + 0x7706d3: LocationName.iceberg_3_s15, + 0x7706d4: LocationName.iceberg_3_s16, + 0x7706d5: LocationName.iceberg_3_s17, + 0x7706d6: LocationName.iceberg_3_s18, + 0x7706d7: LocationName.iceberg_3_s19, + 0x7706d8: LocationName.iceberg_3_s20, + 0x7706d9: LocationName.iceberg_3_s21, + 0x7706da: LocationName.iceberg_4_s1, + 0x7706db: LocationName.iceberg_4_s2, + 0x7706dc: LocationName.iceberg_4_s3, + 0x7706dd: LocationName.iceberg_5_s1, + 0x7706de: LocationName.iceberg_5_s2, + 0x7706df: LocationName.iceberg_5_s3, + 0x7706e0: LocationName.iceberg_5_s4, + 0x7706e1: LocationName.iceberg_5_s5, + 0x7706e2: LocationName.iceberg_5_s6, + 0x7706e3: LocationName.iceberg_5_s7, + 0x7706e4: LocationName.iceberg_5_s8, + 0x7706e5: LocationName.iceberg_5_s9, + 0x7706e6: LocationName.iceberg_5_s10, + 0x7706e7: LocationName.iceberg_5_s11, + 0x7706e8: LocationName.iceberg_5_s12, + 0x7706e9: LocationName.iceberg_5_s13, + 0x7706ea: LocationName.iceberg_5_s14, + 0x7706eb: LocationName.iceberg_5_s15, + 0x7706ec: LocationName.iceberg_5_s16, + 0x7706ed: LocationName.iceberg_5_s17, + 0x7706ee: LocationName.iceberg_5_s18, + 0x7706ef: LocationName.iceberg_5_s19, + 0x7706f0: LocationName.iceberg_5_s20, + 0x7706f1: LocationName.iceberg_5_s21, + 0x7706f2: LocationName.iceberg_5_s22, + 0x7706f3: LocationName.iceberg_5_s23, + 0x7706f4: LocationName.iceberg_5_s24, + 0x7706f5: LocationName.iceberg_5_s25, + 0x7706f6: LocationName.iceberg_5_s26, + 0x7706f7: LocationName.iceberg_5_s27, + 0x7706f8: LocationName.iceberg_5_s28, + 0x7706f9: LocationName.iceberg_5_s29, + 0x7706fa: LocationName.iceberg_5_s30, + 0x7706fb: LocationName.iceberg_5_s31, + 0x7706fc: LocationName.iceberg_5_s32, + 0x7706fd: LocationName.iceberg_5_s33, + 0x7706fe: LocationName.iceberg_5_s34, + 0x7706ff: LocationName.iceberg_6_s1, + +} + +location_table = { + **stage_locations, + **heart_star_locations, + **boss_locations, + **consumable_locations, + **star_locations +} diff --git a/worlds/kdl3/Names/AnimalFriendSpawns.py b/worlds/kdl3/Names/AnimalFriendSpawns.py new file mode 100644 index 000000000000..4520cf143803 --- /dev/null +++ b/worlds/kdl3/Names/AnimalFriendSpawns.py @@ -0,0 +1,199 @@ +grass_land_1_a1 = "Grass Land 1 - Animal 1" # Nago +grass_land_1_a2 = "Grass Land 1 - Animal 2" # Rick +grass_land_2_a1 = "Grass Land 2 - Animal 1" # ChuChu +grass_land_2_a2 = "Grass Land 2 - Animal 2" # Pitch +grass_land_3_a1 = "Grass Land 3 - Animal 1" # Kine +grass_land_3_a2 = "Grass Land 3 - Animal 2" # Coo +grass_land_4_a1 = "Grass Land 4 - Animal 1" # ChuChu +grass_land_4_a2 = "Grass Land 4 - Animal 2" # Nago +grass_land_5_a1 = "Grass Land 5 - Animal 1" # Coo +grass_land_5_a2 = "Grass Land 5 - Animal 2" # Kine +grass_land_5_a3 = "Grass Land 5 - Animal 3" # Nago +grass_land_5_a4 = "Grass Land 5 - Animal 4" # Rick +grass_land_6_a1 = "Grass Land 6 - Animal 1" # Rick +grass_land_6_a2 = "Grass Land 6 - Animal 2" # ChuChu +grass_land_6_a3 = "Grass Land 6 - Animal 3" # Nago +grass_land_6_a4 = "Grass Land 6 - Animal 4" # Coo +ripple_field_1_a1 = "Ripple Field 1 - Animal 1" # Pitch +ripple_field_1_a2 = "Ripple Field 1 - Animal 2" # Nago +ripple_field_2_a1 = "Ripple Field 2 - Animal 1" # Kine +ripple_field_2_a2 = "Ripple Field 2 - Animal 2" # ChuChu +ripple_field_2_a3 = "Ripple Field 2 - Animal 3" # Rick +ripple_field_2_a4 = "Ripple Field 2 - Animal 4" # Coo +ripple_field_3_a1 = "Ripple Field 3 - Animal 1" # Kine +ripple_field_3_a2 = "Ripple Field 3 - Animal 2" # Rick +ripple_field_4_a1 = "Ripple Field 4 - Animal 1" # ChuChu +ripple_field_4_a2 = "Ripple Field 4 - Animal 2" # Kine +ripple_field_4_a3 = "Ripple Field 4 - Animal 3" # Nago +ripple_field_5_a1 = "Ripple Field 5 - Animal 1" # Kine +ripple_field_5_a2 = "Ripple Field 5 - Animal 2" # Pitch +ripple_field_6_a1 = "Ripple Field 6 - Animal 1" # Nago +ripple_field_6_a2 = "Ripple Field 6 - Animal 2" # Pitch +ripple_field_6_a3 = "Ripple Field 6 - Animal 3" # Rick +ripple_field_6_a4 = "Ripple Field 6 - Animal 4" # Coo +sand_canyon_1_a1 = "Sand Canyon 1 - Animal 1" # Rick +sand_canyon_1_a2 = "Sand Canyon 1 - Animal 2" # Pitch +sand_canyon_2_a1 = "Sand Canyon 2 - Animal 1" # ChuChu +sand_canyon_2_a2 = "Sand Canyon 2 - Animal 2" # Coo +sand_canyon_3_a1 = "Sand Canyon 3 - Animal 1" # Pitch +sand_canyon_3_a2 = "Sand Canyon 3 - Animal 2" # Coo +sand_canyon_3_a3 = "Sand Canyon 3 - Animal 3" # ChuChu +sand_canyon_4_a1 = "Sand Canyon 4 - Animal 1" # Rick +sand_canyon_4_a2 = "Sand Canyon 4 - Animal 2" # Pitch +sand_canyon_4_a3 = "Sand Canyon 4 - Animal 3" # Nago +sand_canyon_5_a1 = "Sand Canyon 5 - Animal 1" # Rick +sand_canyon_5_a2 = "Sand Canyon 5 - Animal 2" # ChuChu +sand_canyon_6_a1 = "Sand Canyon 6 - Animal 1" # Coo +sand_canyon_6_a2 = "Sand Canyon 6 - Animal 2" # Kine +sand_canyon_6_a3 = "Sand Canyon 6 - Animal 3" # Rick +sand_canyon_6_a4 = "Sand Canyon 6 - Animal 4" # ChuChu +sand_canyon_6_a5 = "Sand Canyon 6 - Animal 5" # Nago +sand_canyon_6_a6 = "Sand Canyon 6 - Animal 6" # Pitch +cloudy_park_1_a1 = "Cloudy Park 1 - Animal 1" # Rick +cloudy_park_1_a2 = "Cloudy Park 1 - Animal 2" # Nago +cloudy_park_1_a3 = "Cloudy Park 1 - Animal 3" # Coo +cloudy_park_1_a4 = "Cloudy Park 1 - Animal 4" # Kine +cloudy_park_1_a5 = "Cloudy Park 1 - Animal 5" # ChuChu +cloudy_park_1_a6 = "Cloudy Park 1 - Animal 6" # Pitch +cloudy_park_2_a1 = "Cloudy Park 2 - Animal 1" # Nago +cloudy_park_2_a2 = "Cloudy Park 2 - Animal 2" # Pitch +cloudy_park_2_a3 = "Cloudy Park 2 - Animal 3" # ChuChu +cloudy_park_3_a1 = "Cloudy Park 3 - Animal 1" # Kine +cloudy_park_3_a2 = "Cloudy Park 3 - Animal 2" # Rick +cloudy_park_3_a3 = "Cloudy Park 3 - Animal 3" # ChuChu +cloudy_park_4_a1 = "Cloudy Park 4 - Animal 1" # Coo +cloudy_park_4_a2 = "Cloudy Park 4 - Animal 2" # ChuChu +cloudy_park_5_a1 = "Cloudy Park 5 - Animal 1" # Rick +cloudy_park_5_a2 = "Cloudy Park 5 - Animal 2" # Coo +cloudy_park_6_a1 = "Cloudy Park 6 - Animal 1" # Nago +cloudy_park_6_a2 = "Cloudy Park 6 - Animal 2" # Coo +cloudy_park_6_a3 = "Cloudy Park 6 - Animal 3" # Rick +iceberg_1_a1 = "Iceberg 1 - Animal 1" # Pitch +iceberg_1_a2 = "Iceberg 1 - Animal 2" # Rick +iceberg_2_a1 = "Iceberg 2 - Animal 1" # Nago +iceberg_2_a2 = "Iceberg 2 - Animal 2" # Pitch +iceberg_3_a1 = "Iceberg 3 - Animal 1" # Pitch +iceberg_3_a2 = "Iceberg 3 - Animal 2" # Coo +iceberg_3_a3 = "Iceberg 3 - Animal 3" # Nago +iceberg_3_a4 = "Iceberg 3 - Animal 4" # Rick +iceberg_3_a5 = "Iceberg 3 - Animal 5" # Kine +iceberg_4_a1 = "Iceberg 4 - Animal 1" # ChuChu +iceberg_4_a2 = "Iceberg 4 - Animal 2" # Coo +iceberg_4_a3 = "Iceberg 4 - Animal 3" # Pitch +iceberg_4_a4 = "Iceberg 4 - Animal 4" # Coo +iceberg_4_a5 = "Iceberg 4 - Animal 5" # Rick +iceberg_5_a1 = "Iceberg 5 - Animal 1" # Kine +iceberg_5_a2 = "Iceberg 5 - Animal 2" # Rick +iceberg_5_a3 = "Iceberg 5 - Animal 3" # Pitch +iceberg_5_a4 = "Iceberg 5 - Animal 4" # ChuChu +iceberg_5_a5 = "Iceberg 5 - Animal 5" # Kine +iceberg_5_a6 = "Iceberg 5 - Animal 6" # Coo +iceberg_5_a7 = "Iceberg 5 - Animal 7" # Rick +iceberg_5_a8 = "Iceberg 5 - Animal 8" # ChuChu +iceberg_6_a1 = "Iceberg 6 - Animal 1" # Rick +iceberg_6_a2 = "Iceberg 6 - Animal 2" # Coo +iceberg_6_a3 = "Iceberg 6 - Animal 3" # Nago +iceberg_6_a4 = "Iceberg 6 - Animal 4" # Kine +iceberg_6_a5 = "Iceberg 6 - Animal 5" # ChuChu +iceberg_6_a6 = "Iceberg 6 - Animal 6" # Nago + +animal_friend_spawns = { + grass_land_1_a1: "Nago Spawn", + grass_land_1_a2: "Rick Spawn", + grass_land_2_a1: "ChuChu Spawn", + grass_land_2_a2: "Pitch Spawn", + grass_land_3_a1: "Kine Spawn", + grass_land_3_a2: "Coo Spawn", + grass_land_4_a1: "ChuChu Spawn", + grass_land_4_a2: "Nago Spawn", + grass_land_5_a1: "Coo Spawn", + grass_land_5_a2: "Kine Spawn", + grass_land_5_a3: "Nago Spawn", + grass_land_5_a4: "Rick Spawn", + grass_land_6_a1: "Rick Spawn", + grass_land_6_a2: "ChuChu Spawn", + grass_land_6_a3: "Nago Spawn", + grass_land_6_a4: "Coo Spawn", + ripple_field_1_a1: "Pitch Spawn", + ripple_field_1_a2: "Nago Spawn", + ripple_field_2_a1: "Kine Spawn", + ripple_field_2_a2: "ChuChu Spawn", + ripple_field_2_a3: "Rick Spawn", + ripple_field_2_a4: "Coo Spawn", + ripple_field_3_a1: "Kine Spawn", + ripple_field_3_a2: "Rick Spawn", + ripple_field_4_a1: "ChuChu Spawn", + ripple_field_4_a2: "Kine Spawn", + ripple_field_4_a3: "Nago Spawn", + ripple_field_5_a1: "Kine Spawn", + ripple_field_5_a2: "Pitch Spawn", + ripple_field_6_a1: "Nago Spawn", + ripple_field_6_a2: "Pitch Spawn", + ripple_field_6_a3: "Rick Spawn", + ripple_field_6_a4: "Coo Spawn", + sand_canyon_1_a1: "Rick Spawn", + sand_canyon_1_a2: "Pitch Spawn", + sand_canyon_2_a1: "ChuChu Spawn", + sand_canyon_2_a2: "Coo Spawn", + sand_canyon_3_a1: "Pitch Spawn", + sand_canyon_3_a2: "Coo Spawn", + sand_canyon_3_a3: "ChuChu Spawn", + sand_canyon_4_a1: "Rick Spawn", + sand_canyon_4_a2: "Pitch Spawn", + sand_canyon_4_a3: "Nago Spawn", + sand_canyon_5_a1: "Rick Spawn", + sand_canyon_5_a2: "ChuChu Spawn", + sand_canyon_6_a1: "Coo Spawn", + sand_canyon_6_a2: "Kine Spawn", + sand_canyon_6_a3: "Rick Spawn", + sand_canyon_6_a4: "ChuChu Spawn", + sand_canyon_6_a5: "Nago Spawn", + sand_canyon_6_a6: "Pitch Spawn", + cloudy_park_1_a1: "Rick Spawn", + cloudy_park_1_a2: "Nago Spawn", + cloudy_park_1_a3: "Coo Spawn", + cloudy_park_1_a4: "Kine Spawn", + cloudy_park_1_a5: "ChuChu Spawn", + cloudy_park_1_a6: "Pitch Spawn", + cloudy_park_2_a1: "Nago Spawn", + cloudy_park_2_a2: "Pitch Spawn", + cloudy_park_2_a3: "ChuChu Spawn", + cloudy_park_3_a1: "Kine Spawn", + cloudy_park_3_a2: "Rick Spawn", + cloudy_park_3_a3: "ChuChu Spawn", + cloudy_park_4_a1: "Coo Spawn", + cloudy_park_4_a2: "ChuChu Spawn", + cloudy_park_5_a1: "Rick Spawn", + cloudy_park_5_a2: "Coo Spawn", + cloudy_park_6_a1: "Nago Spawn", + cloudy_park_6_a2: "Coo Spawn", + cloudy_park_6_a3: "Rick Spawn", + iceberg_1_a1: "Pitch Spawn", + iceberg_1_a2: "Rick Spawn", + iceberg_2_a1: "Nago Spawn", + iceberg_2_a2: "Pitch Spawn", + iceberg_3_a1: "Pitch Spawn", + iceberg_3_a2: "Coo Spawn", + iceberg_3_a3: "Nago Spawn", + iceberg_3_a4: "Rick Spawn", + iceberg_3_a5: "Kine Spawn", + iceberg_4_a1: "ChuChu Spawn", + iceberg_4_a2: "Coo Spawn", + iceberg_4_a3: "Pitch Spawn", + iceberg_4_a4: "Coo Spawn", + iceberg_4_a5: "Rick Spawn", + iceberg_5_a1: "Kine Spawn", + iceberg_5_a2: "Rick Spawn", + iceberg_5_a3: "Pitch Spawn", + iceberg_5_a4: "ChuChu Spawn", + iceberg_5_a5: "Kine Spawn", + iceberg_5_a6: "Coo Spawn", + iceberg_5_a7: "Rick Spawn", + iceberg_5_a8: "ChuChu Spawn", + iceberg_6_a1: "Rick Spawn", + iceberg_6_a2: "Coo Spawn", + iceberg_6_a3: "Nago Spawn", + iceberg_6_a4: "Kine Spawn", + iceberg_6_a5: "ChuChu Spawn", + iceberg_6_a6: "Nago Spawn", +} diff --git a/worlds/kdl3/Names/EnemyAbilities.py b/worlds/kdl3/Names/EnemyAbilities.py new file mode 100644 index 000000000000..016e3033ab25 --- /dev/null +++ b/worlds/kdl3/Names/EnemyAbilities.py @@ -0,0 +1,822 @@ +from typing import List, Tuple, Set + +Grass_Land_1_E1 = "Grass Land 1 - Enemy 1 (Waddle Dee)" +Grass_Land_1_E2 = "Grass Land 1 - Enemy 2 (Sir Kibble)" +Grass_Land_1_E3 = "Grass Land 1 - Enemy 3 (Cappy)" +Grass_Land_1_E4 = "Grass Land 1 - Enemy 4 (Sparky)" +Grass_Land_1_E5 = "Grass Land 1 - Enemy 5 (Bronto Burt)" +Grass_Land_1_E6 = "Grass Land 1 - Enemy 6 (Sasuke)" +Grass_Land_1_E7 = "Grass Land 1 - Enemy 7 (Poppy Bros Jr.)" +Grass_Land_2_E1 = "Grass Land 2 - Enemy 1 (Rocky)" +Grass_Land_2_E2 = "Grass Land 2 - Enemy 2 (KeKe)" +Grass_Land_2_E3 = "Grass Land 2 - Enemy 3 (Bobo)" +Grass_Land_2_E4 = "Grass Land 2 - Enemy 4 (Poppy Bros Jr.)" +Grass_Land_2_E5 = "Grass Land 2 - Enemy 5 (Waddle Dee)" +Grass_Land_2_E6 = "Grass Land 2 - Enemy 6 (Popon Ball)" +Grass_Land_2_E7 = "Grass Land 2 - Enemy 7 (Bouncy)" +Grass_Land_2_E8 = "Grass Land 2 - Enemy 8 (Tick)" +Grass_Land_2_E9 = "Grass Land 2 - Enemy 9 (Bronto Burt)" +Grass_Land_2_E10 = "Grass Land 2 - Enemy 10 (Nruff)" +Grass_Land_3_E1 = "Grass Land 3 - Enemy 1 (Sparky)" +Grass_Land_3_E2 = "Grass Land 3 - Enemy 2 (Rocky)" +Grass_Land_3_E3 = "Grass Land 3 - Enemy 3 (Nruff)" +Grass_Land_3_E4 = "Grass Land 3 - Enemy 4 (Bouncy)" +Grass_Land_4_E1 = "Grass Land 4 - Enemy 1 (Loud)" +Grass_Land_4_E2 = "Grass Land 4 - Enemy 2 (Babut)" +Grass_Land_4_E3 = "Grass Land 4 - Enemy 3 (Rocky)" +Grass_Land_4_E4 = "Grass Land 4 - Enemy 4 (Kapar)" +Grass_Land_4_E5 = "Grass Land 4 - Enemy 5 (Glunk)" +Grass_Land_4_E6 = "Grass Land 4 - Enemy 6 (Oro)" +Grass_Land_4_E7 = "Grass Land 4 - Enemy 7 (Peran)" +Grass_Land_5_E1 = "Grass Land 5 - Enemy 1 (Propeller)" +Grass_Land_5_E2 = "Grass Land 5 - Enemy 2 (Broom Hatter)" +Grass_Land_5_E3 = "Grass Land 5 - Enemy 3 (Bouncy)" +Grass_Land_5_E4 = "Grass Land 5 - Enemy 4 (Sir Kibble)" +Grass_Land_5_E5 = "Grass Land 5 - Enemy 5 (Waddle Dee)" +Grass_Land_5_E6 = "Grass Land 5 - Enemy 6 (Sasuke)" +Grass_Land_5_E7 = "Grass Land 5 - Enemy 7 (Nruff)" +Grass_Land_5_E8 = "Grass Land 5 - Enemy 8 (Tick)" +Grass_Land_6_E1 = "Grass Land 6 - Enemy 1 (Como)" +Grass_Land_6_E2 = "Grass Land 6 - Enemy 2 (Togezo)" +Grass_Land_6_E3 = "Grass Land 6 - Enemy 3 (Bronto Burt)" +Grass_Land_6_E4 = "Grass Land 6 - Enemy 4 (Cappy)" +Grass_Land_6_E5 = "Grass Land 6 - Enemy 5 (Bobo)" +Grass_Land_6_E6 = "Grass Land 6 - Enemy 6 (Mariel)" +Grass_Land_6_E7 = "Grass Land 6 - Enemy 7 (Yaban)" +Grass_Land_6_E8 = "Grass Land 6 - Enemy 8 (Broom Hatter)" +Grass_Land_6_E9 = "Grass Land 6 - Enemy 9 (Apolo)" +Grass_Land_6_E10 = "Grass Land 6 - Enemy 10 (Sasuke)" +Grass_Land_6_E11 = "Grass Land 6 - Enemy 11 (Rocky)" +Ripple_Field_1_E1 = "Ripple Field 1 - Enemy 1 (Waddle Dee)" +Ripple_Field_1_E2 = "Ripple Field 1 - Enemy 2 (Glunk)" +Ripple_Field_1_E3 = "Ripple Field 1 - Enemy 3 (Broom Hatter)" +Ripple_Field_1_E4 = "Ripple Field 1 - Enemy 4 (Cappy)" +Ripple_Field_1_E5 = "Ripple Field 1 - Enemy 5 (Bronto Burt)" +Ripple_Field_1_E6 = "Ripple Field 1 - Enemy 6 (Rocky)" +Ripple_Field_1_E7 = "Ripple Field 1 - Enemy 7 (Poppy Bros Jr.)" +Ripple_Field_1_E8 = "Ripple Field 1 - Enemy 8 (Bobin)" +Ripple_Field_2_E1 = "Ripple Field 2 - Enemy 1 (Togezo)" +Ripple_Field_2_E2 = "Ripple Field 2 - Enemy 2 (Coconut)" +Ripple_Field_2_E3 = "Ripple Field 2 - Enemy 3 (Blipper)" +Ripple_Field_2_E4 = "Ripple Field 2 - Enemy 4 (Sasuke)" +Ripple_Field_2_E5 = "Ripple Field 2 - Enemy 5 (Kany)" +Ripple_Field_2_E6 = "Ripple Field 2 - Enemy 6 (Glunk)" +Ripple_Field_3_E1 = "Ripple Field 3 - Enemy 1 (Raft Waddle Dee)" +Ripple_Field_3_E2 = "Ripple Field 3 - Enemy 2 (Kapar)" +Ripple_Field_3_E3 = "Ripple Field 3 - Enemy 3 (Blipper)" +Ripple_Field_3_E4 = "Ripple Field 3 - Enemy 4 (Sparky)" +Ripple_Field_3_E5 = "Ripple Field 3 - Enemy 5 (Glunk)" +Ripple_Field_3_E6 = "Ripple Field 3 - Enemy 6 (Joe)" +Ripple_Field_3_E7 = "Ripple Field 3 - Enemy 7 (Bobo)" +Ripple_Field_4_E1 = "Ripple Field 4 - Enemy 1 (Bukiset (Stone))" +Ripple_Field_4_E2 = "Ripple Field 4 - Enemy 2 (Bukiset (Needle))" +Ripple_Field_4_E3 = "Ripple Field 4 - Enemy 3 (Bukiset (Clean))" +Ripple_Field_4_E4 = "Ripple Field 4 - Enemy 4 (Bukiset (Parasol))" +Ripple_Field_4_E5 = "Ripple Field 4 - Enemy 5 (Mony)" +Ripple_Field_4_E6 = "Ripple Field 4 - Enemy 6 (Bukiset (Burning))" +Ripple_Field_4_E7 = "Ripple Field 4 - Enemy 7 (Bobin)" +Ripple_Field_4_E8 = "Ripple Field 4 - Enemy 8 (Blipper)" +Ripple_Field_4_E9 = "Ripple Field 4 - Enemy 9 (Como)" +Ripple_Field_4_E10 = "Ripple Field 4 - Enemy 10 (Oro)" +Ripple_Field_4_E11 = "Ripple Field 4 - Enemy 11 (Gansan)" +Ripple_Field_4_E12 = "Ripple Field 4 - Enemy 12 (Waddle Dee)" +Ripple_Field_4_E13 = "Ripple Field 4 - Enemy 13 (Kapar)" +Ripple_Field_4_E14 = "Ripple Field 4 - Enemy 14 (Squishy)" +Ripple_Field_4_E15 = "Ripple Field 4 - Enemy 15 (Nidoo)" +Ripple_Field_5_E1 = "Ripple Field 5 - Enemy 1 (Glunk)" +Ripple_Field_5_E2 = "Ripple Field 5 - Enemy 2 (Joe)" +Ripple_Field_5_E3 = "Ripple Field 5 - Enemy 3 (Bobin)" +Ripple_Field_5_E4 = "Ripple Field 5 - Enemy 4 (Mony)" +Ripple_Field_5_E5 = "Ripple Field 5 - Enemy 5 (Squishy)" +Ripple_Field_5_E6 = "Ripple Field 5 - Enemy 6 (Yaban)" +Ripple_Field_5_E7 = "Ripple Field 5 - Enemy 7 (Broom Hatter)" +Ripple_Field_5_E8 = "Ripple Field 5 - Enemy 8 (Bouncy)" +Ripple_Field_5_E9 = "Ripple Field 5 - Enemy 9 (Sparky)" +Ripple_Field_5_E10 = "Ripple Field 5 - Enemy 10 (Rocky)" +Ripple_Field_5_E11 = "Ripple Field 5 - Enemy 11 (Babut)" +Ripple_Field_5_E12 = "Ripple Field 5 - Enemy 12 (Galbo)" +Ripple_Field_6_E1 = "Ripple Field 6 - Enemy 1 (Kany)" +Ripple_Field_6_E2 = "Ripple Field 6 - Enemy 2 (KeKe)" +Ripple_Field_6_E3 = "Ripple Field 6 - Enemy 3 (Kapar)" +Ripple_Field_6_E4 = "Ripple Field 6 - Enemy 4 (Rocky)" +Ripple_Field_6_E5 = "Ripple Field 6 - Enemy 5 (Poppy Bros Jr.)" +Ripple_Field_6_E6 = "Ripple Field 6 - Enemy 6 (Propeller)" +Ripple_Field_6_E7 = "Ripple Field 6 - Enemy 7 (Coconut)" +Ripple_Field_6_E8 = "Ripple Field 6 - Enemy 8 (Sasuke)" +Ripple_Field_6_E9 = "Ripple Field 6 - Enemy 9 (Nruff)" +Sand_Canyon_1_E1 = "Sand Canyon 1 - Enemy 1 (Bronto Burt)" +Sand_Canyon_1_E2 = "Sand Canyon 1 - Enemy 2 (Galbo)" +Sand_Canyon_1_E3 = "Sand Canyon 1 - Enemy 3 (Oro)" +Sand_Canyon_1_E4 = "Sand Canyon 1 - Enemy 4 (Sparky)" +Sand_Canyon_1_E5 = "Sand Canyon 1 - Enemy 5 (Propeller)" +Sand_Canyon_1_E6 = "Sand Canyon 1 - Enemy 6 (Gansan)" +Sand_Canyon_1_E7 = "Sand Canyon 1 - Enemy 7 (Babut)" +Sand_Canyon_1_E8 = "Sand Canyon 1 - Enemy 8 (Loud)" +Sand_Canyon_1_E9 = "Sand Canyon 1 - Enemy 9 (Dogon)" +Sand_Canyon_1_E10 = "Sand Canyon 1 - Enemy 10 (Bouncy)" +Sand_Canyon_1_E11 = "Sand Canyon 1 - Enemy 11 (Pteran)" +Sand_Canyon_1_E12 = "Sand Canyon 1 - Enemy 12 (Polof)" +Sand_Canyon_2_E1 = "Sand Canyon 2 - Enemy 1 (KeKe)" +Sand_Canyon_2_E2 = "Sand Canyon 2 - Enemy 2 (Doka)" +Sand_Canyon_2_E3 = "Sand Canyon 2 - Enemy 3 (Boten)" +Sand_Canyon_2_E4 = "Sand Canyon 2 - Enemy 4 (Propeller)" +Sand_Canyon_2_E5 = "Sand Canyon 2 - Enemy 5 (Waddle Dee)" +Sand_Canyon_2_E6 = "Sand Canyon 2 - Enemy 6 (Sparky)" +Sand_Canyon_2_E7 = "Sand Canyon 2 - Enemy 7 (Sasuke)" +Sand_Canyon_2_E8 = "Sand Canyon 2 - Enemy 8 (Como)" +Sand_Canyon_2_E9 = "Sand Canyon 2 - Enemy 9 (Bukiset (Ice))" +Sand_Canyon_2_E10 = "Sand Canyon 2 - Enemy 10 (Bukiset (Needle))" +Sand_Canyon_2_E11 = "Sand Canyon 2 - Enemy 11 (Bukiset (Clean))" +Sand_Canyon_2_E12 = "Sand Canyon 2 - Enemy 12 (Bukiset (Parasol))" +Sand_Canyon_2_E13 = "Sand Canyon 2 - Enemy 13 (Bukiset (Spark))" +Sand_Canyon_2_E14 = "Sand Canyon 2 - Enemy 14 (Bukiset (Cutter))" +Sand_Canyon_2_E15 = "Sand Canyon 2 - Enemy 15 (Nidoo)" +Sand_Canyon_2_E16 = "Sand Canyon 2 - Enemy 16 (Mariel)" +Sand_Canyon_2_E17 = "Sand Canyon 2 - Enemy 17 (Yaban)" +Sand_Canyon_2_E18 = "Sand Canyon 2 - Enemy 18 (Wapod)" +Sand_Canyon_2_E19 = "Sand Canyon 2 - Enemy 19 (Squishy)" +Sand_Canyon_2_E20 = "Sand Canyon 2 - Enemy 20 (Pteran)" +Sand_Canyon_3_E1 = "Sand Canyon 3 - Enemy 1 (Sir Kibble)" +Sand_Canyon_3_E2 = "Sand Canyon 3 - Enemy 2 (Broom Hatter)" +Sand_Canyon_3_E3 = "Sand Canyon 3 - Enemy 3 (Rocky)" +Sand_Canyon_3_E4 = "Sand Canyon 3 - Enemy 4 (Gabon)" +Sand_Canyon_3_E5 = "Sand Canyon 3 - Enemy 5 (Kany)" +Sand_Canyon_3_E6 = "Sand Canyon 3 - Enemy 6 (Galbo)" +Sand_Canyon_3_E7 = "Sand Canyon 3 - Enemy 7 (Propeller)" +Sand_Canyon_3_E8 = "Sand Canyon 3 - Enemy 8 (Sasuke)" +Sand_Canyon_3_E9 = "Sand Canyon 3 - Enemy 9 (Wapod)" +Sand_Canyon_3_E10 = "Sand Canyon 3 - Enemy 10 (Bobo)" +Sand_Canyon_3_E11 = "Sand Canyon 3 - Enemy 11 (Babut)" +Sand_Canyon_3_E12 = "Sand Canyon 3 - Enemy 12 (Magoo)" +Sand_Canyon_4_E1 = "Sand Canyon 4 - Enemy 1 (Popon Ball)" +Sand_Canyon_4_E2 = "Sand Canyon 4 - Enemy 2 (Mariel)" +Sand_Canyon_4_E3 = "Sand Canyon 4 - Enemy 3 (Chilly)" +Sand_Canyon_4_E4 = "Sand Canyon 4 - Enemy 4 (Tick)" +Sand_Canyon_4_E5 = "Sand Canyon 4 - Enemy 5 (Bronto Burt)" +Sand_Canyon_4_E6 = "Sand Canyon 4 - Enemy 6 (Babut)" +Sand_Canyon_4_E7 = "Sand Canyon 4 - Enemy 7 (Bobin)" +Sand_Canyon_4_E8 = "Sand Canyon 4 - Enemy 8 (Joe)" +Sand_Canyon_4_E9 = "Sand Canyon 4 - Enemy 9 (Mony)" +Sand_Canyon_4_E10 = "Sand Canyon 4 - Enemy 10 (Blipper)" +Sand_Canyon_4_E11 = "Sand Canyon 4 - Enemy 11 (Togezo)" +Sand_Canyon_4_E12 = "Sand Canyon 4 - Enemy 12 (Rocky)" +Sand_Canyon_4_E13 = "Sand Canyon 4 - Enemy 13 (Bobo)" +Sand_Canyon_5_E1 = "Sand Canyon 5 - Enemy 1 (Wapod)" +Sand_Canyon_5_E2 = "Sand Canyon 5 - Enemy 2 (Dogon)" +Sand_Canyon_5_E3 = "Sand Canyon 5 - Enemy 3 (Tick)" +Sand_Canyon_5_E4 = "Sand Canyon 5 - Enemy 4 (Rocky)" +Sand_Canyon_5_E5 = "Sand Canyon 5 - Enemy 5 (Bobo)" +Sand_Canyon_5_E6 = "Sand Canyon 5 - Enemy 6 (Chilly)" +Sand_Canyon_5_E7 = "Sand Canyon 5 - Enemy 7 (Sparky)" +Sand_Canyon_5_E8 = "Sand Canyon 5 - Enemy 8 (Togezo)" +Sand_Canyon_5_E9 = "Sand Canyon 5 - Enemy 9 (Bronto Burt)" +Sand_Canyon_5_E10 = "Sand Canyon 5 - Enemy 10 (Sasuke)" +Sand_Canyon_5_E11 = "Sand Canyon 5 - Enemy 11 (Oro)" +Sand_Canyon_5_E12 = "Sand Canyon 5 - Enemy 12 (Galbo)" +Sand_Canyon_5_E13 = "Sand Canyon 5 - Enemy 13 (Nidoo)" +Sand_Canyon_5_E14 = "Sand Canyon 5 - Enemy 14 (Propeller)" +Sand_Canyon_5_E15 = "Sand Canyon 5 - Enemy 15 (Sir Kibble)" +Sand_Canyon_5_E16 = "Sand Canyon 5 - Enemy 16 (KeKe)" +Sand_Canyon_5_E17 = "Sand Canyon 5 - Enemy 17 (Kabu)" +Sand_Canyon_6_E1 = "Sand Canyon 6 - Enemy 1 (Sparky)" +Sand_Canyon_6_E2 = "Sand Canyon 6 - Enemy 2 (Doka)" +Sand_Canyon_6_E3 = "Sand Canyon 6 - Enemy 3 (Cappy)" +Sand_Canyon_6_E4 = "Sand Canyon 6 - Enemy 4 (Pteran)" +Sand_Canyon_6_E5 = "Sand Canyon 6 - Enemy 5 (Bukiset (Parasol))" +Sand_Canyon_6_E6 = "Sand Canyon 6 - Enemy 6 (Bukiset (Cutter))" +Sand_Canyon_6_E7 = "Sand Canyon 6 - Enemy 7 (Bukiset (Clean))" +Sand_Canyon_6_E8 = "Sand Canyon 6 - Enemy 8 (Bukiset (Spark))" +Sand_Canyon_6_E9 = "Sand Canyon 6 - Enemy 9 (Bukiset (Ice))" +Sand_Canyon_6_E10 = "Sand Canyon 6 - Enemy 10 (Bukiset (Needle))" +Sand_Canyon_6_E11 = "Sand Canyon 6 - Enemy 11 (Bukiset (Burning))" +Sand_Canyon_6_E12 = "Sand Canyon 6 - Enemy 12 (Bukiset (Stone))" +Sand_Canyon_6_E13 = "Sand Canyon 6 - Enemy 13 (Nidoo)" +Cloudy_Park_1_E1 = "Cloudy Park 1 - Enemy 1 (Waddle Dee)" +Cloudy_Park_1_E2 = "Cloudy Park 1 - Enemy 2 (KeKe)" +Cloudy_Park_1_E3 = "Cloudy Park 1 - Enemy 3 (Cappy)" +Cloudy_Park_1_E4 = "Cloudy Park 1 - Enemy 4 (Yaban)" +Cloudy_Park_1_E5 = "Cloudy Park 1 - Enemy 5 (Togezo)" +Cloudy_Park_1_E6 = "Cloudy Park 1 - Enemy 6 (Galbo)" +Cloudy_Park_1_E7 = "Cloudy Park 1 - Enemy 7 (Sparky)" +Cloudy_Park_1_E8 = "Cloudy Park 1 - Enemy 8 (Como)" +Cloudy_Park_1_E9 = "Cloudy Park 1 - Enemy 9 (Bronto Burt)" +Cloudy_Park_1_E10 = "Cloudy Park 1 - Enemy 10 (Gabon)" +Cloudy_Park_1_E11 = "Cloudy Park 1 - Enemy 11 (Sir Kibble)" +Cloudy_Park_1_E12 = "Cloudy Park 1 - Enemy 12 (Mariel)" +Cloudy_Park_1_E13 = "Cloudy Park 1 - Enemy 13 (Nruff)" +Cloudy_Park_2_E1 = "Cloudy Park 2 - Enemy 1 (Chilly)" +Cloudy_Park_2_E2 = "Cloudy Park 2 - Enemy 2 (Sasuke)" +Cloudy_Park_2_E3 = "Cloudy Park 2 - Enemy 3 (Waddle Dee)" +Cloudy_Park_2_E4 = "Cloudy Park 2 - Enemy 4 (Sparky)" +Cloudy_Park_2_E5 = "Cloudy Park 2 - Enemy 5 (Broom Hatter)" +Cloudy_Park_2_E6 = "Cloudy Park 2 - Enemy 6 (Sir Kibble)" +Cloudy_Park_2_E7 = "Cloudy Park 2 - Enemy 7 (Pteran)" +Cloudy_Park_2_E8 = "Cloudy Park 2 - Enemy 8 (Propeller)" +Cloudy_Park_2_E9 = "Cloudy Park 2 - Enemy 9 (Dogon)" +Cloudy_Park_2_E10 = "Cloudy Park 2 - Enemy 10 (Togezo)" +Cloudy_Park_2_E11 = "Cloudy Park 2 - Enemy 11 (Oro)" +Cloudy_Park_2_E12 = "Cloudy Park 2 - Enemy 12 (Bronto Burt)" +Cloudy_Park_2_E13 = "Cloudy Park 2 - Enemy 13 (Rocky)" +Cloudy_Park_2_E14 = "Cloudy Park 2 - Enemy 14 (Galbo)" +Cloudy_Park_2_E15 = "Cloudy Park 2 - Enemy 15 (Kapar)" +Cloudy_Park_3_E1 = "Cloudy Park 3 - Enemy 1 (Bronto Burt)" +Cloudy_Park_3_E2 = "Cloudy Park 3 - Enemy 2 (Mopoo)" +Cloudy_Park_3_E3 = "Cloudy Park 3 - Enemy 3 (Poppy Bros Jr.)" +Cloudy_Park_3_E4 = "Cloudy Park 3 - Enemy 4 (Como)" +Cloudy_Park_3_E5 = "Cloudy Park 3 - Enemy 5 (Glunk)" +Cloudy_Park_3_E6 = "Cloudy Park 3 - Enemy 6 (Bobin)" +Cloudy_Park_3_E7 = "Cloudy Park 3 - Enemy 7 (Loud)" +Cloudy_Park_3_E8 = "Cloudy Park 3 - Enemy 8 (Kapar)" +Cloudy_Park_3_E9 = "Cloudy Park 3 - Enemy 9 (Galbo)" +Cloudy_Park_3_E10 = "Cloudy Park 3 - Enemy 10 (Batamon)" +Cloudy_Park_3_E11 = "Cloudy Park 3 - Enemy 11 (Bouncy)" +Cloudy_Park_4_E1 = "Cloudy Park 4 - Enemy 1 (Gabon)" +Cloudy_Park_4_E2 = "Cloudy Park 4 - Enemy 2 (Como)" +Cloudy_Park_4_E3 = "Cloudy Park 4 - Enemy 3 (Wapod)" +Cloudy_Park_4_E4 = "Cloudy Park 4 - Enemy 4 (Cappy)" +Cloudy_Park_4_E5 = "Cloudy Park 4 - Enemy 5 (Sparky)" +Cloudy_Park_4_E6 = "Cloudy Park 4 - Enemy 6 (Togezo)" +Cloudy_Park_4_E7 = "Cloudy Park 4 - Enemy 7 (Bronto Burt)" +Cloudy_Park_4_E8 = "Cloudy Park 4 - Enemy 8 (KeKe)" +Cloudy_Park_4_E9 = "Cloudy Park 4 - Enemy 9 (Bouncy)" +Cloudy_Park_4_E10 = "Cloudy Park 4 - Enemy 10 (Sir Kibble)" +Cloudy_Park_4_E11 = "Cloudy Park 4 - Enemy 11 (Mariel)" +Cloudy_Park_4_E12 = "Cloudy Park 4 - Enemy 12 (Kabu)" +Cloudy_Park_4_E13 = "Cloudy Park 4 - Enemy 13 (Wappa)" +Cloudy_Park_5_E1 = "Cloudy Park 5 - Enemy 1 (Yaban)" +Cloudy_Park_5_E2 = "Cloudy Park 5 - Enemy 2 (Sir Kibble)" +Cloudy_Park_5_E3 = "Cloudy Park 5 - Enemy 3 (Cappy)" +Cloudy_Park_5_E4 = "Cloudy Park 5 - Enemy 4 (Wappa)" +Cloudy_Park_5_E5 = "Cloudy Park 5 - Enemy 5 (Galbo)" +Cloudy_Park_5_E6 = "Cloudy Park 5 - Enemy 6 (Bronto Burt)" +Cloudy_Park_5_E7 = "Cloudy Park 5 - Enemy 7 (KeKe)" +Cloudy_Park_5_E8 = "Cloudy Park 5 - Enemy 8 (Propeller)" +Cloudy_Park_5_E9 = "Cloudy Park 5 - Enemy 9 (Klinko)" +Cloudy_Park_5_E10 = "Cloudy Park 5 - Enemy 10 (Wapod)" +Cloudy_Park_5_E11 = "Cloudy Park 5 - Enemy 11 (Pteran)" +Cloudy_Park_6_E1 = "Cloudy Park 6 - Enemy 1 (Madoo)" +Cloudy_Park_6_E2 = "Cloudy Park 6 - Enemy 2 (Tick)" +Cloudy_Park_6_E3 = "Cloudy Park 6 - Enemy 3 (Como)" +Cloudy_Park_6_E4 = "Cloudy Park 6 - Enemy 4 (Waddle Dee Drawing)" +Cloudy_Park_6_E5 = "Cloudy Park 6 - Enemy 5 (Bronto Burt Drawing)" +Cloudy_Park_6_E6 = "Cloudy Park 6 - Enemy 6 (Bouncy Drawing)" +Cloudy_Park_6_E7 = "Cloudy Park 6 - Enemy 7 (Propeller)" +Cloudy_Park_6_E8 = "Cloudy Park 6 - Enemy 8 (Mopoo)" +Cloudy_Park_6_E9 = "Cloudy Park 6 - Enemy 9 (Bukiset (Burning))" +Cloudy_Park_6_E10 = "Cloudy Park 6 - Enemy 10 (Bukiset (Ice))" +Cloudy_Park_6_E11 = "Cloudy Park 6 - Enemy 11 (Bukiset (Needle))" +Cloudy_Park_6_E12 = "Cloudy Park 6 - Enemy 12 (Bukiset (Clean))" +Cloudy_Park_6_E13 = "Cloudy Park 6 - Enemy 13 (Bukiset (Cutter))" +Iceberg_1_E1 = "Iceberg 1 - Enemy 1 (Waddle Dee)" +Iceberg_1_E2 = "Iceberg 1 - Enemy 2 (Klinko)" +Iceberg_1_E3 = "Iceberg 1 - Enemy 3 (KeKe)" +Iceberg_1_E4 = "Iceberg 1 - Enemy 4 (Como)" +Iceberg_1_E5 = "Iceberg 1 - Enemy 5 (Galbo)" +Iceberg_1_E6 = "Iceberg 1 - Enemy 6 (Rocky)" +Iceberg_1_E7 = "Iceberg 1 - Enemy 7 (Kapar)" +Iceberg_1_E8 = "Iceberg 1 - Enemy 8 (Mopoo)" +Iceberg_1_E9 = "Iceberg 1 - Enemy 9 (Babut)" +Iceberg_1_E10 = "Iceberg 1 - Enemy 10 (Wappa)" +Iceberg_1_E11 = "Iceberg 1 - Enemy 11 (Bronto Burt)" +Iceberg_1_E12 = "Iceberg 1 - Enemy 12 (Chilly)" +Iceberg_1_E13 = "Iceberg 1 - Enemy 13 (Poppy Bros Jr.)" +Iceberg_2_E1 = "Iceberg 2 - Enemy 1 (Gabon)" +Iceberg_2_E2 = "Iceberg 2 - Enemy 2 (Nruff)" +Iceberg_2_E3 = "Iceberg 2 - Enemy 3 (Waddle Dee)" +Iceberg_2_E4 = "Iceberg 2 - Enemy 4 (Chilly)" +Iceberg_2_E5 = "Iceberg 2 - Enemy 5 (Pteran)" +Iceberg_2_E6 = "Iceberg 2 - Enemy 6 (Glunk)" +Iceberg_2_E7 = "Iceberg 2 - Enemy 7 (Galbo)" +Iceberg_2_E8 = "Iceberg 2 - Enemy 8 (Babut)" +Iceberg_2_E9 = "Iceberg 2 - Enemy 9 (Magoo)" +Iceberg_2_E10 = "Iceberg 2 - Enemy 10 (Propeller)" +Iceberg_2_E11 = "Iceberg 2 - Enemy 11 (Nidoo)" +Iceberg_2_E12 = "Iceberg 2 - Enemy 12 (Oro)" +Iceberg_2_E13 = "Iceberg 2 - Enemy 13 (Klinko)" +Iceberg_2_E14 = "Iceberg 2 - Enemy 14 (Bronto Burt)" +Iceberg_3_E1 = "Iceberg 3 - Enemy 1 (Corori)" +Iceberg_3_E2 = "Iceberg 3 - Enemy 2 (Bouncy)" +Iceberg_3_E3 = "Iceberg 3 - Enemy 3 (Chilly)" +Iceberg_3_E4 = "Iceberg 3 - Enemy 4 (Pteran)" +Iceberg_3_E5 = "Iceberg 3 - Enemy 5 (Raft Waddle Dee)" +Iceberg_3_E6 = "Iceberg 3 - Enemy 6 (Kapar)" +Iceberg_3_E7 = "Iceberg 3 - Enemy 7 (Blipper)" +Iceberg_3_E8 = "Iceberg 3 - Enemy 8 (Wapod)" +Iceberg_3_E9 = "Iceberg 3 - Enemy 9 (Glunk)" +Iceberg_3_E10 = "Iceberg 3 - Enemy 10 (Icicle)" +Iceberg_4_E1 = "Iceberg 4 - Enemy 1 (Bronto Burt)" +Iceberg_4_E2 = "Iceberg 4 - Enemy 2 (Galbo)" +Iceberg_4_E3 = "Iceberg 4 - Enemy 3 (Klinko)" +Iceberg_4_E4 = "Iceberg 4 - Enemy 4 (Chilly)" +Iceberg_4_E5 = "Iceberg 4 - Enemy 5 (Babut)" +Iceberg_4_E6 = "Iceberg 4 - Enemy 6 (Wappa)" +Iceberg_4_E7 = "Iceberg 4 - Enemy 7 (Icicle)" +Iceberg_4_E8 = "Iceberg 4 - Enemy 8 (Corori)" +Iceberg_4_E9 = "Iceberg 4 - Enemy 9 (Gabon)" +Iceberg_4_E10 = "Iceberg 4 - Enemy 10 (Kabu)" +Iceberg_4_E11 = "Iceberg 4 - Enemy 11 (Broom Hatter)" +Iceberg_4_E12 = "Iceberg 4 - Enemy 12 (Sasuke)" +Iceberg_4_E13 = "Iceberg 4 - Enemy 13 (Nruff)" +Iceberg_5_E1 = "Iceberg 5 - Enemy 1 (Bukiset (Burning))" +Iceberg_5_E2 = "Iceberg 5 - Enemy 2 (Bukiset (Stone))" +Iceberg_5_E3 = "Iceberg 5 - Enemy 3 (Bukiset (Ice))" +Iceberg_5_E4 = "Iceberg 5 - Enemy 4 (Bukiset (Needle))" +Iceberg_5_E5 = "Iceberg 5 - Enemy 5 (Bukiset (Clean))" +Iceberg_5_E6 = "Iceberg 5 - Enemy 6 (Bukiset (Parasol))" +Iceberg_5_E7 = "Iceberg 5 - Enemy 7 (Bukiset (Spark))" +Iceberg_5_E8 = "Iceberg 5 - Enemy 8 (Bukiset (Cutter))" +Iceberg_5_E9 = "Iceberg 5 - Enemy 9 (Glunk)" +Iceberg_5_E10 = "Iceberg 5 - Enemy 10 (Wapod)" +Iceberg_5_E11 = "Iceberg 5 - Enemy 11 (Tick)" +Iceberg_5_E12 = "Iceberg 5 - Enemy 12 (Madoo)" +Iceberg_5_E13 = "Iceberg 5 - Enemy 13 (Yaban)" +Iceberg_5_E14 = "Iceberg 5 - Enemy 14 (Propeller)" +Iceberg_5_E15 = "Iceberg 5 - Enemy 15 (Mariel)" +Iceberg_5_E16 = "Iceberg 5 - Enemy 16 (Pteran)" +Iceberg_5_E17 = "Iceberg 5 - Enemy 17 (Galbo)" +Iceberg_5_E18 = "Iceberg 5 - Enemy 18 (KeKe)" +Iceberg_5_E19 = "Iceberg 5 - Enemy 19 (Nidoo)" +Iceberg_5_E20 = "Iceberg 5 - Enemy 20 (Waddle Dee Drawing)" +Iceberg_5_E21 = "Iceberg 5 - Enemy 21 (Bronto Burt Drawing)" +Iceberg_5_E22 = "Iceberg 5 - Enemy 22 (Bouncy Drawing)" +Iceberg_5_E23 = "Iceberg 5 - Enemy 23 (Joe)" +Iceberg_5_E24 = "Iceberg 5 - Enemy 24 (Kapar)" +Iceberg_5_E25 = "Iceberg 5 - Enemy 25 (Gansan)" +Iceberg_5_E26 = "Iceberg 5 - Enemy 26 (Sasuke)" +Iceberg_5_E27 = "Iceberg 5 - Enemy 27 (Togezo)" +Iceberg_5_E28 = "Iceberg 5 - Enemy 28 (Sparky)" +Iceberg_5_E29 = "Iceberg 5 - Enemy 29 (Bobin)" +Iceberg_5_E30 = "Iceberg 5 - Enemy 30 (Chilly)" +Iceberg_5_E31 = "Iceberg 5 - Enemy 31 (Peran)" +Iceberg_6_E1 = "Iceberg 6 - Enemy 1 (Nruff)" +Iceberg_6_E2 = "Iceberg 6 - Enemy 2 (Nidoo)" +Iceberg_6_E3 = "Iceberg 6 - Enemy 3 (Sparky)" +Iceberg_6_E4 = "Iceberg 6 - Enemy 4 (Sir Kibble)" +Grass_Land_4_M1 = "Grass Land 4 - Miniboss 1 (Boboo)" +Ripple_Field_4_M1 = "Ripple Field 4 - Miniboss 1 (Captain Stitch)" +Sand_Canyon_4_M1 = "Sand Canyon 4 - Miniboss 1 (Haboki)" +Cloudy_Park_4_M1 = "Cloudy Park 4 - Miniboss 1 (Jumper Shoot)" +Iceberg_4_M1 = "Iceberg 4 - Miniboss 1 (Yuki)" +Iceberg_6_M1 = "Iceberg 6 - Miniboss 1 (Blocky)" +Iceberg_6_M2 = "Iceberg 6 - Miniboss 2 (Jumper Shoot)" +Iceberg_6_M3 = "Iceberg 6 - Miniboss 3 (Yuki)" +Iceberg_6_M4 = "Iceberg 6 - Miniboss 4 (Haboki)" +Iceberg_6_M5 = "Iceberg 6 - Miniboss 5 (Boboo)" +Iceberg_6_M6 = "Iceberg 6 - Miniboss 6 (Captain Stitch)" + + +enemy_mapping = { + Grass_Land_1_E1: "Waddle Dee", + Grass_Land_1_E2: "Sir Kibble", + Grass_Land_1_E3: "Cappy", + Grass_Land_1_E4: "Sparky", + Grass_Land_1_E5: "Bronto Burt", + Grass_Land_1_E6: "Sasuke", + Grass_Land_1_E7: "Poppy Bros Jr.", + Grass_Land_2_E1: "Rocky", + Grass_Land_2_E2: "KeKe", + Grass_Land_2_E3: "Bobo", + Grass_Land_2_E4: "Poppy Bros Jr.", + Grass_Land_2_E5: "Waddle Dee", + Grass_Land_2_E6: "Popon Ball", + Grass_Land_2_E7: "Bouncy", + Grass_Land_2_E8: "Tick", + Grass_Land_2_E9: "Bronto Burt", + Grass_Land_2_E10: "Nruff", + Grass_Land_3_E1: "Sparky", + Grass_Land_3_E2: "Rocky", + Grass_Land_3_E3: "Nruff", + Grass_Land_3_E4: "Bouncy", + Grass_Land_4_E1: "Loud", + Grass_Land_4_E2: "Babut", + Grass_Land_4_E3: "Rocky", + Grass_Land_4_E4: "Kapar", + Grass_Land_4_E5: "Glunk", + Grass_Land_4_E6: "Oro", + Grass_Land_4_E7: "Peran", + Grass_Land_5_E1: "Propeller", + Grass_Land_5_E2: "Broom Hatter", + Grass_Land_5_E3: "Bouncy", + Grass_Land_5_E4: "Sir Kibble", + Grass_Land_5_E5: "Waddle Dee", + Grass_Land_5_E6: "Sasuke", + Grass_Land_5_E7: "Nruff", + Grass_Land_5_E8: "Tick", + Grass_Land_6_E1: "Como", + Grass_Land_6_E2: "Togezo", + Grass_Land_6_E3: "Bronto Burt", + Grass_Land_6_E4: "Cappy", + Grass_Land_6_E5: "Bobo", + Grass_Land_6_E6: "Mariel", + Grass_Land_6_E7: "Yaban", + Grass_Land_6_E8: "Broom Hatter", + Grass_Land_6_E9: "Apolo", + Grass_Land_6_E10: "Sasuke", + Grass_Land_6_E11: "Rocky", + Ripple_Field_1_E1: "Waddle Dee", + Ripple_Field_1_E2: "Glunk", + Ripple_Field_1_E3: "Broom Hatter", + Ripple_Field_1_E4: "Cappy", + Ripple_Field_1_E5: "Bronto Burt", + Ripple_Field_1_E6: "Rocky", + Ripple_Field_1_E7: "Poppy Bros Jr.", + Ripple_Field_1_E8: "Bobin", + Ripple_Field_2_E1: "Togezo", + Ripple_Field_2_E2: "Coconut", + Ripple_Field_2_E3: "Blipper", + Ripple_Field_2_E4: "Sasuke", + Ripple_Field_2_E5: "Kany", + Ripple_Field_2_E6: "Glunk", + Ripple_Field_3_E1: "Raft Waddle Dee", + Ripple_Field_3_E2: "Kapar", + Ripple_Field_3_E3: "Blipper", + Ripple_Field_3_E4: "Sparky", + Ripple_Field_3_E5: "Glunk", + Ripple_Field_3_E6: "Joe", + Ripple_Field_3_E7: "Bobo", + Ripple_Field_4_E1: "Bukiset (Stone)", + Ripple_Field_4_E2: "Bukiset (Needle)", + Ripple_Field_4_E3: "Bukiset (Clean)", + Ripple_Field_4_E4: "Bukiset (Parasol)", + Ripple_Field_4_E5: "Mony", + Ripple_Field_4_E6: "Bukiset (Burning)", + Ripple_Field_4_E7: "Bobin", + Ripple_Field_4_E8: "Blipper", + Ripple_Field_4_E9: "Como", + Ripple_Field_4_E10: "Oro", + Ripple_Field_4_E11: "Gansan", + Ripple_Field_4_E12: "Waddle Dee", + Ripple_Field_4_E13: "Kapar", + Ripple_Field_4_E14: "Squishy", + Ripple_Field_4_E15: "Nidoo", + Ripple_Field_5_E1: "Glunk", + Ripple_Field_5_E2: "Joe", + Ripple_Field_5_E3: "Bobin", + Ripple_Field_5_E4: "Mony", + Ripple_Field_5_E5: "Squishy", + Ripple_Field_5_E6: "Yaban", + Ripple_Field_5_E7: "Broom Hatter", + Ripple_Field_5_E8: "Bouncy", + Ripple_Field_5_E9: "Sparky", + Ripple_Field_5_E10: "Rocky", + Ripple_Field_5_E11: "Babut", + Ripple_Field_5_E12: "Galbo", + Ripple_Field_6_E1: "Kany", + Ripple_Field_6_E2: "KeKe", + Ripple_Field_6_E3: "Kapar", + Ripple_Field_6_E4: "Rocky", + Ripple_Field_6_E5: "Poppy Bros Jr.", + Ripple_Field_6_E6: "Propeller", + Ripple_Field_6_E7: "Coconut", + Ripple_Field_6_E8: "Sasuke", + Ripple_Field_6_E9: "Nruff", + Sand_Canyon_1_E1: "Bronto Burt", + Sand_Canyon_1_E2: "Galbo", + Sand_Canyon_1_E3: "Oro", + Sand_Canyon_1_E4: "Sparky", + Sand_Canyon_1_E5: "Propeller", + Sand_Canyon_1_E6: "Gansan", + Sand_Canyon_1_E7: "Babut", + Sand_Canyon_1_E8: "Loud", + Sand_Canyon_1_E9: "Dogon", + Sand_Canyon_1_E10: "Bouncy", + Sand_Canyon_1_E11: "Pteran", + Sand_Canyon_1_E12: "Polof", + Sand_Canyon_2_E1: "KeKe", + Sand_Canyon_2_E2: "Doka", + Sand_Canyon_2_E3: "Boten", + Sand_Canyon_2_E4: "Propeller", + Sand_Canyon_2_E5: "Waddle Dee", + Sand_Canyon_2_E6: "Sparky", + Sand_Canyon_2_E7: "Sasuke", + Sand_Canyon_2_E8: "Como", + Sand_Canyon_2_E9: "Bukiset (Ice)", + Sand_Canyon_2_E10: "Bukiset (Needle)", + Sand_Canyon_2_E11: "Bukiset (Clean)", + Sand_Canyon_2_E12: "Bukiset (Parasol)", + Sand_Canyon_2_E13: "Bukiset (Spark)", + Sand_Canyon_2_E14: "Bukiset (Cutter)", + Sand_Canyon_2_E15: "Nidoo", + Sand_Canyon_2_E16: "Mariel", + Sand_Canyon_2_E17: "Yaban", + Sand_Canyon_2_E18: "Wapod", + Sand_Canyon_2_E19: "Squishy", + Sand_Canyon_2_E20: "Pteran", + Sand_Canyon_3_E1: "Sir Kibble", + Sand_Canyon_3_E2: "Broom Hatter", + Sand_Canyon_3_E3: "Rocky", + Sand_Canyon_3_E4: "Gabon", + Sand_Canyon_3_E5: "Kany", + Sand_Canyon_3_E6: "Galbo", + Sand_Canyon_3_E7: "Propeller", + Sand_Canyon_3_E8: "Sasuke", + Sand_Canyon_3_E9: "Wapod", + Sand_Canyon_3_E10: "Bobo", + Sand_Canyon_3_E11: "Babut", + Sand_Canyon_3_E12: "Magoo", + Sand_Canyon_4_E1: "Popon Ball", + Sand_Canyon_4_E2: "Mariel", + Sand_Canyon_4_E3: "Chilly", + Sand_Canyon_4_E4: "Tick", + Sand_Canyon_4_E5: "Bronto Burt", + Sand_Canyon_4_E6: "Babut", + Sand_Canyon_4_E7: "Bobin", + Sand_Canyon_4_E8: "Joe", + Sand_Canyon_4_E9: "Mony", + Sand_Canyon_4_E10: "Blipper", + Sand_Canyon_4_E11: "Togezo", + Sand_Canyon_4_E12: "Rocky", + Sand_Canyon_4_E13: "Bobo", + Sand_Canyon_5_E1: "Wapod", + Sand_Canyon_5_E2: "Dogon", + Sand_Canyon_5_E3: "Tick", + Sand_Canyon_5_E4: "Rocky", + Sand_Canyon_5_E5: "Bobo", + Sand_Canyon_5_E6: "Chilly", + Sand_Canyon_5_E7: "Sparky", + Sand_Canyon_5_E8: "Togezo", + Sand_Canyon_5_E9: "Bronto Burt", + Sand_Canyon_5_E10: "Sasuke", + Sand_Canyon_5_E11: "Oro", + Sand_Canyon_5_E12: "Galbo", + Sand_Canyon_5_E13: "Nidoo", + Sand_Canyon_5_E14: "Propeller", + Sand_Canyon_5_E15: "Sir Kibble", + Sand_Canyon_5_E16: "KeKe", + Sand_Canyon_5_E17: "Kabu", + Sand_Canyon_6_E1: "Sparky", + Sand_Canyon_6_E2: "Doka", + Sand_Canyon_6_E3: "Cappy", + Sand_Canyon_6_E4: "Pteran", + Sand_Canyon_6_E5: "Bukiset (Parasol)", + Sand_Canyon_6_E6: "Bukiset (Cutter)", + Sand_Canyon_6_E7: "Bukiset (Clean)", + Sand_Canyon_6_E8: "Bukiset (Spark)", + Sand_Canyon_6_E9: "Bukiset (Ice)", + Sand_Canyon_6_E10: "Bukiset (Needle)", + Sand_Canyon_6_E11: "Bukiset (Burning)", + Sand_Canyon_6_E12: "Bukiset (Stone)", + Sand_Canyon_6_E13: "Nidoo", + Cloudy_Park_1_E1: "Waddle Dee", + Cloudy_Park_1_E2: "KeKe", + Cloudy_Park_1_E3: "Cappy", + Cloudy_Park_1_E4: "Yaban", + Cloudy_Park_1_E5: "Togezo", + Cloudy_Park_1_E6: "Galbo", + Cloudy_Park_1_E7: "Sparky", + Cloudy_Park_1_E8: "Como", + Cloudy_Park_1_E9: "Bronto Burt", + Cloudy_Park_1_E10: "Gabon", + Cloudy_Park_1_E11: "Sir Kibble", + Cloudy_Park_1_E12: "Mariel", + Cloudy_Park_1_E13: "Nruff", + Cloudy_Park_2_E1: "Chilly", + Cloudy_Park_2_E2: "Sasuke", + Cloudy_Park_2_E3: "Waddle Dee", + Cloudy_Park_2_E4: "Sparky", + Cloudy_Park_2_E5: "Broom Hatter", + Cloudy_Park_2_E6: "Sir Kibble", + Cloudy_Park_2_E7: "Pteran", + Cloudy_Park_2_E8: "Propeller", + Cloudy_Park_2_E9: "Dogon", + Cloudy_Park_2_E10: "Togezo", + Cloudy_Park_2_E11: "Oro", + Cloudy_Park_2_E12: "Bronto Burt", + Cloudy_Park_2_E13: "Rocky", + Cloudy_Park_2_E14: "Galbo", + Cloudy_Park_2_E15: "Kapar", + Cloudy_Park_3_E1: "Bronto Burt", + Cloudy_Park_3_E2: "Mopoo", + Cloudy_Park_3_E3: "Poppy Bros Jr.", + Cloudy_Park_3_E4: "Como", + Cloudy_Park_3_E5: "Glunk", + Cloudy_Park_3_E6: "Bobin", + Cloudy_Park_3_E7: "Loud", + Cloudy_Park_3_E8: "Kapar", + Cloudy_Park_3_E9: "Galbo", + Cloudy_Park_3_E10: "Batamon", + Cloudy_Park_3_E11: "Bouncy", + Cloudy_Park_4_E1: "Gabon", + Cloudy_Park_4_E2: "Como", + Cloudy_Park_4_E3: "Wapod", + Cloudy_Park_4_E4: "Cappy", + Cloudy_Park_4_E5: "Sparky", + Cloudy_Park_4_E6: "Togezo", + Cloudy_Park_4_E7: "Bronto Burt", + Cloudy_Park_4_E8: "KeKe", + Cloudy_Park_4_E9: "Bouncy", + Cloudy_Park_4_E10: "Sir Kibble", + Cloudy_Park_4_E11: "Mariel", + Cloudy_Park_4_E12: "Kabu", + Cloudy_Park_4_E13: "Wappa", + Cloudy_Park_5_E1: "Yaban", + Cloudy_Park_5_E2: "Sir Kibble", + Cloudy_Park_5_E3: "Cappy", + Cloudy_Park_5_E4: "Wappa", + Cloudy_Park_5_E5: "Galbo", + Cloudy_Park_5_E6: "Bronto Burt", + Cloudy_Park_5_E7: "KeKe", + Cloudy_Park_5_E8: "Propeller", + Cloudy_Park_5_E9: "Klinko", + Cloudy_Park_5_E10: "Wapod", + Cloudy_Park_5_E11: "Pteran", + Cloudy_Park_6_E1: "Madoo", + Cloudy_Park_6_E2: "Tick", + Cloudy_Park_6_E3: "Como", + Cloudy_Park_6_E4: "Waddle Dee Drawing", + Cloudy_Park_6_E5: "Bronto Burt Drawing", + Cloudy_Park_6_E6: "Bouncy Drawing", + Cloudy_Park_6_E7: "Propeller", + Cloudy_Park_6_E8: "Mopoo", + Cloudy_Park_6_E9: "Bukiset (Burning)", + Cloudy_Park_6_E10: "Bukiset (Ice)", + Cloudy_Park_6_E11: "Bukiset (Needle)", + Cloudy_Park_6_E12: "Bukiset (Clean)", + Cloudy_Park_6_E13: "Bukiset (Cutter)", + Iceberg_1_E1: "Waddle Dee", + Iceberg_1_E2: "Klinko", + Iceberg_1_E3: "KeKe", + Iceberg_1_E4: "Como", + Iceberg_1_E5: "Galbo", + Iceberg_1_E6: "Rocky", + Iceberg_1_E7: "Kapar", + Iceberg_1_E8: "Mopoo", + Iceberg_1_E9: "Babut", + Iceberg_1_E10: "Wappa", + Iceberg_1_E11: "Bronto Burt", + Iceberg_1_E12: "Chilly", + Iceberg_1_E13: "Poppy Bros Jr.", + Iceberg_2_E1: "Gabon", + Iceberg_2_E2: "Nruff", + Iceberg_2_E3: "Waddle Dee", + Iceberg_2_E4: "Chilly", + Iceberg_2_E5: "Pteran", + Iceberg_2_E6: "Glunk", + Iceberg_2_E7: "Galbo", + Iceberg_2_E8: "Babut", + Iceberg_2_E9: "Magoo", + Iceberg_2_E10: "Propeller", + Iceberg_2_E11: "Nidoo", + Iceberg_2_E12: "Oro", + Iceberg_2_E13: "Klinko", + Iceberg_2_E14: "Bronto Burt", + Iceberg_3_E1: "Corori", + Iceberg_3_E2: "Bouncy", + Iceberg_3_E3: "Chilly", + Iceberg_3_E4: "Pteran", + Iceberg_3_E5: "Raft Waddle Dee", + Iceberg_3_E6: "Kapar", + Iceberg_3_E7: "Blipper", + Iceberg_3_E8: "Wapod", + Iceberg_3_E9: "Glunk", + Iceberg_3_E10: "Icicle", + Iceberg_4_E1: "Bronto Burt", + Iceberg_4_E2: "Galbo", + Iceberg_4_E3: "Klinko", + Iceberg_4_E4: "Chilly", + Iceberg_4_E5: "Babut", + Iceberg_4_E6: "Wappa", + Iceberg_4_E7: "Icicle", + Iceberg_4_E8: "Corori", + Iceberg_4_E9: "Gabon", + Iceberg_4_E10: "Kabu", + Iceberg_4_E11: "Broom Hatter", + Iceberg_4_E12: "Sasuke", + Iceberg_4_E13: "Nruff", + Iceberg_5_E1: "Bukiset (Burning)", + Iceberg_5_E2: "Bukiset (Stone)", + Iceberg_5_E3: "Bukiset (Ice)", + Iceberg_5_E4: "Bukiset (Needle)", + Iceberg_5_E5: "Bukiset (Clean)", + Iceberg_5_E6: "Bukiset (Parasol)", + Iceberg_5_E7: "Bukiset (Spark)", + Iceberg_5_E8: "Bukiset (Cutter)", + Iceberg_5_E9: "Glunk", + Iceberg_5_E10: "Wapod", + Iceberg_5_E11: "Tick", + Iceberg_5_E12: "Madoo", + Iceberg_5_E13: "Yaban", + Iceberg_5_E14: "Propeller", + Iceberg_5_E15: "Mariel", + Iceberg_5_E16: "Pteran", + Iceberg_5_E17: "Galbo", + Iceberg_5_E18: "KeKe", + Iceberg_5_E19: "Nidoo", + Iceberg_5_E20: "Waddle Dee Drawing", + Iceberg_5_E21: "Bronto Burt Drawing", + Iceberg_5_E22: "Bouncy Drawing", + Iceberg_5_E23: "Joe", + Iceberg_5_E24: "Kapar", + Iceberg_5_E25: "Gansan", + Iceberg_5_E26: "Sasuke", + Iceberg_5_E27: "Togezo", + Iceberg_5_E28: "Sparky", + Iceberg_5_E29: "Bobin", + Iceberg_5_E30: "Chilly", + Iceberg_5_E31: "Peran", + Iceberg_6_E1: "Nruff", + Iceberg_6_E2: "Nidoo", + Iceberg_6_E3: "Sparky", + Iceberg_6_E4: "Sir Kibble", + Grass_Land_4_M1: "Boboo", + Ripple_Field_4_M1: "Captain Stitch", + Sand_Canyon_4_M1: "Haboki", + Cloudy_Park_4_M1: "Jumper Shoot", + Iceberg_4_M1: "Yuki", + Iceberg_6_M1: "Blocky", + Iceberg_6_M2: "Jumper Shoot", + Iceberg_6_M3: "Yuki", + Iceberg_6_M4: "Haboki", + Iceberg_6_M5: "Boboo", + Iceberg_6_M6: "Captain Stitch", + +} + +vanilla_enemies = {'Waddle Dee': 'No Ability', + 'Bronto Burt': 'No Ability', + 'Rocky': 'Stone Ability', + 'Bobo': 'Burning Ability', + 'Chilly': 'Ice Ability', + 'Poppy Bros Jr.': 'No Ability', + 'Sparky': 'Spark Ability', + 'Polof': 'No Ability', + 'Broom Hatter': 'Clean Ability', + 'Cappy': 'No Ability', + 'Bouncy': 'No Ability', + 'Nruff': 'No Ability', + 'Glunk': 'No Ability', + 'Togezo': 'Needle Ability', + 'Kabu': 'No Ability', + 'Mony': 'No Ability', + 'Blipper': 'No Ability', + 'Squishy': 'No Ability', + 'Gabon': 'No Ability', + 'Oro': 'No Ability', + 'Galbo': 'Burning Ability', + 'Sir Kibble': 'Cutter Ability', + 'Nidoo': 'No Ability', + 'Kany': 'No Ability', + 'Sasuke': 'Parasol Ability', + 'Yaban': 'No Ability', + 'Boten': 'Needle Ability', + 'Coconut': 'No Ability', + 'Doka': 'No Ability', + 'Icicle': 'No Ability', + 'Pteran': 'No Ability', + 'Loud': 'No Ability', + 'Como': 'No Ability', + 'Klinko': 'Parasol Ability', + 'Babut': 'No Ability', + 'Wappa': 'Ice Ability', + 'Mariel': 'No Ability', + 'Tick': 'Needle Ability', + 'Apolo': 'No Ability', + 'Popon Ball': 'No Ability', + 'KeKe': 'Clean Ability', + 'Magoo': 'Burning Ability', + 'Raft Waddle Dee': 'No Ability', + 'Madoo': 'No Ability', + 'Corori': 'No Ability', + 'Kapar': 'Cutter Ability', + 'Batamon': 'No Ability', + 'Peran': 'No Ability', + 'Bobin': 'Spark Ability', + 'Mopoo': 'No Ability', + 'Gansan': 'Stone Ability', + 'Bukiset (Burning)': 'Burning Ability', + 'Bukiset (Stone)': 'Stone Ability', + 'Bukiset (Ice)': 'Ice Ability', + 'Bukiset (Needle)': 'Needle Ability', + 'Bukiset (Clean)': 'Clean Ability', + 'Bukiset (Parasol)': 'Parasol Ability', + 'Bukiset (Spark)': 'Spark Ability', + 'Bukiset (Cutter)': 'Cutter Ability', + 'Waddle Dee Drawing': 'No Ability', + 'Bronto Burt Drawing': 'No Ability', + 'Bouncy Drawing': 'No Ability', + 'Kabu (Dekabu)': 'No Ability', + 'Wapod': 'No Ability', + 'Propeller': 'No Ability', + 'Dogon': 'No Ability', + 'Joe': 'No Ability', + 'Captain Stitch': 'Needle Ability', + 'Yuki': 'Ice Ability', + 'Blocky': 'Stone Ability', + 'Jumper Shoot': 'Parasol Ability', + 'Boboo': 'Burning Ability', + 'Haboki': 'Clean Ability', + } + +enemy_restrictive: List[Tuple[List[str], List[str]]] = [ + # abilities, enemies, set_all (False to set any) + (["Burning Ability", "Stone Ability"], ["Rocky", "Sparky", "Babut", "Squishy", ]), # Ribbon Field 5 - 7 + # Sand Canyon 6 + (["Parasol Ability", "Cutter Ability"], ['Bukiset (Parasol)', 'Bukiset (Cutter)']), + (["Spark Ability", "Clean Ability"], ['Bukiset (Spark)', 'Bukiset (Clean)']), + (["Ice Ability", "Needle Ability"], ['Bukiset (Ice)', 'Bukiset (Needle)']), + (["Stone Ability", "Burning Ability"], ['Bukiset (Stone)', 'Bukiset (Burning)']), + (["Stone Ability"], ['Bukiset (Burning)', 'Bukiset (Stone)', 'Bukiset (Ice)', 'Bukiset (Needle)', + 'Bukiset (Clean)', 'Bukiset (Spark)', 'Bukiset (Parasol)', 'Bukiset (Cutter)']), + (["Parasol Ability"], ['Bukiset (Burning)', 'Bukiset (Stone)', 'Bukiset (Ice)', 'Bukiset (Needle)', + 'Bukiset (Clean)', 'Bukiset (Spark)', 'Bukiset (Parasol)', 'Bukiset (Cutter)']), +] diff --git a/worlds/kdl3/Names/LocationName.py b/worlds/kdl3/Names/LocationName.py new file mode 100644 index 000000000000..59a0a1d690f9 --- /dev/null +++ b/worlds/kdl3/Names/LocationName.py @@ -0,0 +1,928 @@ +# Level 1 +grass_land_1 = "Grass Land 1 - Complete" +grass_land_2 = "Grass Land 2 - Complete" +grass_land_3 = "Grass Land 3 - Complete" +grass_land_4 = "Grass Land 4 - Complete" +grass_land_5 = "Grass Land 5 - Complete" +grass_land_6 = "Grass Land 6 - Complete" +grass_land_tulip = "Grass Land 1 - Tulip" +grass_land_muchi = "Grass Land 2 - Muchimuchi" +grass_land_pitcherman = "Grass Land 3 - Pitcherman" +grass_land_chao = "Grass Land 4 - Chao & Goku" +grass_land_mine = "Grass Land 5 - Mine" +grass_land_pierre = "Grass Land 6 - Pierre" +grass_land_whispy = "Grass Land - Boss (Whispy Woods) Purified" + +# Level 2 +ripple_field_1 = "Ripple Field 1 - Complete" +ripple_field_2 = "Ripple Field 2 - Complete" +ripple_field_3 = "Ripple Field 3 - Complete" +ripple_field_4 = "Ripple Field 4 - Complete" +ripple_field_5 = "Ripple Field 5 - Complete" +ripple_field_6 = "Ripple Field 6 - Complete" +ripple_field_kamuribana = "Ripple Field 1 - Kamuribana" +ripple_field_bakasa = "Ripple Field 2 - Bakasa" +ripple_field_elieel = "Ripple Field 3 - Elieel" +ripple_field_toad = "Ripple Field 4 - Toad & Little Toad" +ripple_field_mama_pitch = "Ripple Field 5 - Mama Pitch" +ripple_field_hb002 = "Ripple Field 6 - HB-002" +ripple_field_acro = "Ripple Field - Boss (Acro) Purified" + +# Level 3 +sand_canyon_1 = "Sand Canyon 1 - Complete" +sand_canyon_2 = "Sand Canyon 2 - Complete" +sand_canyon_3 = "Sand Canyon 3 - Complete" +sand_canyon_4 = "Sand Canyon 4 - Complete" +sand_canyon_5 = "Sand Canyon 5 - Complete" +sand_canyon_6 = "Sand Canyon 6 - Complete" +sand_canyon_mushrooms = "Sand Canyon 1 - Geromuzudake" +sand_canyon_auntie = "Sand Canyon 2 - Auntie" +sand_canyon_caramello = "Sand Canyon 3 - Caramello" +sand_canyon_hikari = "Sand Canyon 4 - Donbe & Hikari" +sand_canyon_nyupun = "Sand Canyon 5 - Nyupun" +sand_canyon_rob = "Sand Canyon 6 - Professor Hector & R.O.B" +sand_canyon_poncon = "Sand Canyon - Boss (Pon & Con) Purified" + +# Level 4 +cloudy_park_1 = "Cloudy Park 1 - Complete" +cloudy_park_2 = "Cloudy Park 2 - Complete" +cloudy_park_3 = "Cloudy Park 3 - Complete" +cloudy_park_4 = "Cloudy Park 4 - Complete" +cloudy_park_5 = "Cloudy Park 5 - Complete" +cloudy_park_6 = "Cloudy Park 6 - Complete" +cloudy_park_hibanamodoki = "Cloudy Park 1 - Hibanamodoki" +cloudy_park_piyokeko = "Cloudy Park 2 - Piyo & Keko" +cloudy_park_mrball = "Cloudy Park 3 - Mr. Ball" +cloudy_park_mikarin = "Cloudy Park 4 - Mikarin & Kagami Mocchi" +cloudy_park_pick = "Cloudy Park 5 - Pick" +cloudy_park_hb007 = "Cloudy Park 6 - HB-007" +cloudy_park_ado = "Cloudy Park - Boss (Ado) Purified" + +# Level 5 +iceberg_1 = "Iceberg 1 - Complete" +iceberg_2 = "Iceberg 2 - Complete" +iceberg_3 = "Iceberg 3 - Complete" +iceberg_4 = "Iceberg 4 - Complete" +iceberg_5 = "Iceberg 5 - Complete" +iceberg_6 = "Iceberg 6 - Complete" +iceberg_kogoesou = "Iceberg 1 - Kogoesou" +iceberg_samus = "Iceberg 2 - Samus" +iceberg_kawasaki = "Iceberg 3 - Chef Kawasaki" +iceberg_name = "Iceberg 4 - Name" +iceberg_shiro = "Iceberg 5 - Shiro" +iceberg_angel = "Iceberg 6 - Angel" +iceberg_dedede = "Iceberg - Boss (Dedede) Purified" + +# Level 6 +hyper_zone = "Hyper Zone - Zero" + +# Extras +boss_butch = "Boss Butch" +mg5_p = "Minigame 5 - Perfect" +jumping_clear = "Jumping - Target Score Reached" + +# 1-Ups +grass_land_1_u1 = "Grass Land 1 - 1-Up (Parasol)" # Parasol +grass_land_2_u1 = "Grass Land 2 - 1-Up (Needle)" # Needle +grass_land_3_u1 = "Grass Land 3 - 1-Up (Climb)" # None +grass_land_4_u1 = "Grass Land 4 - 1-Up (Gordo)" # None +grass_land_6_u1 = "Grass Land 6 - 1-Up (Tower)" # None +grass_land_6_u2 = "Grass Land 6 - 1-Up (Falling)" # None +ripple_field_2_u1 = "Ripple Field 2 - 1-Up (Currents)" # Kine +ripple_field_3_u1 = "Ripple Field 3 - 1-Up (Cutter/Spark)" # Cutter or Spark +ripple_field_4_u1 = "Ripple Field 4 - 1-Up (Stone)" # Stone +ripple_field_5_u1 = "Ripple Field 5 - 1-Up (Currents)" # Kine, Burning, Stone +sand_canyon_1_u1 = "Sand Canyon 1 - 1-Up (Polof)" # None +sand_canyon_2_u1 = "Sand Canyon 2 - 1-Up (Enclave)" # None +sand_canyon_4_u1 = "Sand Canyon 4 - 1-Up (Clean)" # Clean +sand_canyon_5_u1 = "Sand Canyon 5 - 1-Up (Falling Block)" # None +sand_canyon_5_u2 = "Sand Canyon 5 - 1-Up (Ice 1)" # Ice +sand_canyon_5_u3 = "Sand Canyon 5 - 1-Up (Ice 2)" # Ice +sand_canyon_5_u4 = "Sand Canyon 5 - 1-Up (Ice 3)" # Ice +cloudy_park_1_u1 = "Cloudy Park 1 - 1-Up (Shotzo)" # None +cloudy_park_4_u1 = "Cloudy Park 4 - 1-Up (Windy)" # Coo +cloudy_park_6_u1 = "Cloudy Park 6 - 1-Up (Cutter)" # Cutter +iceberg_5_u1 = "Iceberg 5 - 1-Up (Boulder)" # None +iceberg_5_u2 = "Iceberg 5 - 1-Up (Floor)" # None +iceberg_5_u3 = "Iceberg 5 - 1-Up (Peloo)" # None, just let yourself get eaten by the Peloo +iceberg_6_u1 = "Iceberg 6 - 1-Up (Middle)" # None + +# Maxim Tomatoes +grass_land_1_m1 = "Grass Land 1 - Maxim Tomato (Spark)" # Spark +grass_land_3_m1 = "Grass Land 3 - Maxim Tomato (Climb)" # None +grass_land_4_m1 = "Grass Land 4 - Maxim Tomato (Zebon Right)" # None +grass_land_4_m2 = "Grass Land 4 - Maxim Tomato (Gordo)" # None +grass_land_4_m3 = "Grass Land 4 - Maxim Tomato (Cliff)" # None +ripple_field_2_m1 = "Ripple Field 2 - Maxim Tomato (Currents)" # Kine +ripple_field_3_m1 = "Ripple Field 3 - Maxim Tomato (Cove)" # None +ripple_field_4_m1 = "Ripple Field 4 - Maxim Tomato (Dark)" # None (maybe Spark?) +ripple_field_4_m2 = "Ripple Field 4 - Maxim Tomato (Stone)" # Stone +ripple_field_5_m1 = "Ripple Field 5 - Maxim Tomato (Exit)" # Kine +ripple_field_5_m2 = "Ripple Field 5 - Maxim Tomato (Currents)" # Kine, Burning, Stone +sand_canyon_2_m1 = "Sand Canyon 2 - Maxim Tomato (Underwater)" # None +sand_canyon_4_m1 = "Sand Canyon 4 - Maxim Tomato (Pacto)" # None +sand_canyon_4_m2 = "Sand Canyon 4 - Maxim Tomato (Needle)" # Needle +sand_canyon_5_m1 = "Sand Canyon 5 - Maxim Tomato (Pit)" # None +cloudy_park_1_m1 = "Cloudy Park 1 - Maxim Tomato (Mariel)" # None +cloudy_park_4_m1 = "Cloudy Park 4 - Maxim Tomato (Windy)" # Coo +cloudy_park_5_m1 = "Cloudy Park 5 - Maxim Tomato (Pillars)" # None +iceberg_3_m1 = "Iceberg 3 - Maxim Tomato (Ceiling)" # None +iceberg_6_m1 = "Iceberg 6 - Maxim Tomato (Left)" # None + +# Level Names +level_names = { + "Grass Land": 1, + "Ripple Field": 2, + "Sand Canyon": 3, + "Cloudy Park": 4, + "Iceberg": 5, +} + +level_names_inverse = { + level_names[level]: level for level in level_names +} + +# Boss Names +boss_names = { + "Whispy Woods": 0x770200, + "Acro": 0x770201, + "Pon & Con": 0x770202, + "Ado": 0x770203, + "King Dedede": 0x770204 +} + +# Goal Mapping +goals = { + 0: hyper_zone, + 1: boss_butch, + 2: mg5_p, + 3: jumping_clear +} + +grass_land_1_s1 = "Grass Land 1 - Star 1" +grass_land_1_s2 = "Grass Land 1 - Star 2" +grass_land_1_s3 = "Grass Land 1 - Star 3" +grass_land_1_s4 = "Grass Land 1 - Star 4" +grass_land_1_s5 = "Grass Land 1 - Star 5" +grass_land_1_s6 = "Grass Land 1 - Star 6" +grass_land_1_s7 = "Grass Land 1 - Star 7" +grass_land_1_s8 = "Grass Land 1 - Star 8" +grass_land_1_s9 = "Grass Land 1 - Star 9" +grass_land_1_s10 = "Grass Land 1 - Star 10" +grass_land_1_s11 = "Grass Land 1 - Star 11" +grass_land_1_s12 = "Grass Land 1 - Star 12" +grass_land_1_s13 = "Grass Land 1 - Star 13" +grass_land_1_s14 = "Grass Land 1 - Star 14" +grass_land_1_s15 = "Grass Land 1 - Star 15" +grass_land_1_s16 = "Grass Land 1 - Star 16" +grass_land_1_s17 = "Grass Land 1 - Star 17" +grass_land_1_s18 = "Grass Land 1 - Star 18" +grass_land_1_s19 = "Grass Land 1 - Star 19" +grass_land_1_s20 = "Grass Land 1 - Star 20" +grass_land_1_s21 = "Grass Land 1 - Star 21" +grass_land_1_s22 = "Grass Land 1 - Star 22" +grass_land_1_s23 = "Grass Land 1 - Star 23" +grass_land_2_s1 = "Grass Land 2 - Star 1" +grass_land_2_s2 = "Grass Land 2 - Star 2" +grass_land_2_s3 = "Grass Land 2 - Star 3" +grass_land_2_s4 = "Grass Land 2 - Star 4" +grass_land_2_s5 = "Grass Land 2 - Star 5" +grass_land_2_s6 = "Grass Land 2 - Star 6" +grass_land_2_s7 = "Grass Land 2 - Star 7" +grass_land_2_s8 = "Grass Land 2 - Star 8" +grass_land_2_s9 = "Grass Land 2 - Star 9" +grass_land_2_s10 = "Grass Land 2 - Star 10" +grass_land_2_s11 = "Grass Land 2 - Star 11" +grass_land_2_s12 = "Grass Land 2 - Star 12" +grass_land_2_s13 = "Grass Land 2 - Star 13" +grass_land_2_s14 = "Grass Land 2 - Star 14" +grass_land_2_s15 = "Grass Land 2 - Star 15" +grass_land_2_s16 = "Grass Land 2 - Star 16" +grass_land_2_s17 = "Grass Land 2 - Star 17" +grass_land_2_s18 = "Grass Land 2 - Star 18" +grass_land_2_s19 = "Grass Land 2 - Star 19" +grass_land_2_s20 = "Grass Land 2 - Star 20" +grass_land_2_s21 = "Grass Land 2 - Star 21" +grass_land_3_s1 = "Grass Land 3 - Star 1" +grass_land_3_s2 = "Grass Land 3 - Star 2" +grass_land_3_s3 = "Grass Land 3 - Star 3" +grass_land_3_s4 = "Grass Land 3 - Star 4" +grass_land_3_s5 = "Grass Land 3 - Star 5" +grass_land_3_s6 = "Grass Land 3 - Star 6" +grass_land_3_s7 = "Grass Land 3 - Star 7" +grass_land_3_s8 = "Grass Land 3 - Star 8" +grass_land_3_s9 = "Grass Land 3 - Star 9" +grass_land_3_s10 = "Grass Land 3 - Star 10" +grass_land_3_s11 = "Grass Land 3 - Star 11" +grass_land_3_s12 = "Grass Land 3 - Star 12" +grass_land_3_s13 = "Grass Land 3 - Star 13" +grass_land_3_s14 = "Grass Land 3 - Star 14" +grass_land_3_s15 = "Grass Land 3 - Star 15" +grass_land_3_s16 = "Grass Land 3 - Star 16" +grass_land_3_s17 = "Grass Land 3 - Star 17" +grass_land_3_s18 = "Grass Land 3 - Star 18" +grass_land_3_s19 = "Grass Land 3 - Star 19" +grass_land_3_s20 = "Grass Land 3 - Star 20" +grass_land_3_s21 = "Grass Land 3 - Star 21" +grass_land_3_s22 = "Grass Land 3 - Star 22" +grass_land_3_s23 = "Grass Land 3 - Star 23" +grass_land_3_s24 = "Grass Land 3 - Star 24" +grass_land_3_s25 = "Grass Land 3 - Star 25" +grass_land_3_s26 = "Grass Land 3 - Star 26" +grass_land_3_s27 = "Grass Land 3 - Star 27" +grass_land_3_s28 = "Grass Land 3 - Star 28" +grass_land_3_s29 = "Grass Land 3 - Star 29" +grass_land_3_s30 = "Grass Land 3 - Star 30" +grass_land_3_s31 = "Grass Land 3 - Star 31" +grass_land_4_s1 = "Grass Land 4 - Star 1" +grass_land_4_s2 = "Grass Land 4 - Star 2" +grass_land_4_s3 = "Grass Land 4 - Star 3" +grass_land_4_s4 = "Grass Land 4 - Star 4" +grass_land_4_s5 = "Grass Land 4 - Star 5" +grass_land_4_s6 = "Grass Land 4 - Star 6" +grass_land_4_s7 = "Grass Land 4 - Star 7" +grass_land_4_s8 = "Grass Land 4 - Star 8" +grass_land_4_s9 = "Grass Land 4 - Star 9" +grass_land_4_s10 = "Grass Land 4 - Star 10" +grass_land_4_s11 = "Grass Land 4 - Star 11" +grass_land_4_s12 = "Grass Land 4 - Star 12" +grass_land_4_s13 = "Grass Land 4 - Star 13" +grass_land_4_s14 = "Grass Land 4 - Star 14" +grass_land_4_s15 = "Grass Land 4 - Star 15" +grass_land_4_s16 = "Grass Land 4 - Star 16" +grass_land_4_s17 = "Grass Land 4 - Star 17" +grass_land_4_s18 = "Grass Land 4 - Star 18" +grass_land_4_s19 = "Grass Land 4 - Star 19" +grass_land_4_s20 = "Grass Land 4 - Star 20" +grass_land_4_s21 = "Grass Land 4 - Star 21" +grass_land_4_s22 = "Grass Land 4 - Star 22" +grass_land_4_s23 = "Grass Land 4 - Star 23" +grass_land_4_s24 = "Grass Land 4 - Star 24" +grass_land_4_s25 = "Grass Land 4 - Star 25" +grass_land_4_s26 = "Grass Land 4 - Star 26" +grass_land_4_s27 = "Grass Land 4 - Star 27" +grass_land_4_s28 = "Grass Land 4 - Star 28" +grass_land_4_s29 = "Grass Land 4 - Star 29" +grass_land_4_s30 = "Grass Land 4 - Star 30" +grass_land_4_s31 = "Grass Land 4 - Star 31" +grass_land_4_s32 = "Grass Land 4 - Star 32" +grass_land_4_s33 = "Grass Land 4 - Star 33" +grass_land_4_s34 = "Grass Land 4 - Star 34" +grass_land_4_s35 = "Grass Land 4 - Star 35" +grass_land_4_s36 = "Grass Land 4 - Star 36" +grass_land_4_s37 = "Grass Land 4 - Star 37" +grass_land_5_s1 = "Grass Land 5 - Star 1" +grass_land_5_s2 = "Grass Land 5 - Star 2" +grass_land_5_s3 = "Grass Land 5 - Star 3" +grass_land_5_s4 = "Grass Land 5 - Star 4" +grass_land_5_s5 = "Grass Land 5 - Star 5" +grass_land_5_s6 = "Grass Land 5 - Star 6" +grass_land_5_s7 = "Grass Land 5 - Star 7" +grass_land_5_s8 = "Grass Land 5 - Star 8" +grass_land_5_s9 = "Grass Land 5 - Star 9" +grass_land_5_s10 = "Grass Land 5 - Star 10" +grass_land_5_s11 = "Grass Land 5 - Star 11" +grass_land_5_s12 = "Grass Land 5 - Star 12" +grass_land_5_s13 = "Grass Land 5 - Star 13" +grass_land_5_s14 = "Grass Land 5 - Star 14" +grass_land_5_s15 = "Grass Land 5 - Star 15" +grass_land_5_s16 = "Grass Land 5 - Star 16" +grass_land_5_s17 = "Grass Land 5 - Star 17" +grass_land_5_s18 = "Grass Land 5 - Star 18" +grass_land_5_s19 = "Grass Land 5 - Star 19" +grass_land_5_s20 = "Grass Land 5 - Star 20" +grass_land_5_s21 = "Grass Land 5 - Star 21" +grass_land_5_s22 = "Grass Land 5 - Star 22" +grass_land_5_s23 = "Grass Land 5 - Star 23" +grass_land_5_s24 = "Grass Land 5 - Star 24" +grass_land_5_s25 = "Grass Land 5 - Star 25" +grass_land_5_s26 = "Grass Land 5 - Star 26" +grass_land_5_s27 = "Grass Land 5 - Star 27" +grass_land_5_s28 = "Grass Land 5 - Star 28" +grass_land_5_s29 = "Grass Land 5 - Star 29" +grass_land_6_s1 = "Grass Land 6 - Star 1" +grass_land_6_s2 = "Grass Land 6 - Star 2" +grass_land_6_s3 = "Grass Land 6 - Star 3" +grass_land_6_s4 = "Grass Land 6 - Star 4" +grass_land_6_s5 = "Grass Land 6 - Star 5" +grass_land_6_s6 = "Grass Land 6 - Star 6" +grass_land_6_s7 = "Grass Land 6 - Star 7" +grass_land_6_s8 = "Grass Land 6 - Star 8" +grass_land_6_s9 = "Grass Land 6 - Star 9" +grass_land_6_s10 = "Grass Land 6 - Star 10" +grass_land_6_s11 = "Grass Land 6 - Star 11" +grass_land_6_s12 = "Grass Land 6 - Star 12" +grass_land_6_s13 = "Grass Land 6 - Star 13" +grass_land_6_s14 = "Grass Land 6 - Star 14" +grass_land_6_s15 = "Grass Land 6 - Star 15" +grass_land_6_s16 = "Grass Land 6 - Star 16" +grass_land_6_s17 = "Grass Land 6 - Star 17" +grass_land_6_s18 = "Grass Land 6 - Star 18" +grass_land_6_s19 = "Grass Land 6 - Star 19" +grass_land_6_s20 = "Grass Land 6 - Star 20" +grass_land_6_s21 = "Grass Land 6 - Star 21" +grass_land_6_s22 = "Grass Land 6 - Star 22" +grass_land_6_s23 = "Grass Land 6 - Star 23" +grass_land_6_s24 = "Grass Land 6 - Star 24" +grass_land_6_s25 = "Grass Land 6 - Star 25" +grass_land_6_s26 = "Grass Land 6 - Star 26" +grass_land_6_s27 = "Grass Land 6 - Star 27" +grass_land_6_s28 = "Grass Land 6 - Star 28" +grass_land_6_s29 = "Grass Land 6 - Star 29" +ripple_field_1_s1 = "Ripple Field 1 - Star 1" +ripple_field_1_s2 = "Ripple Field 1 - Star 2" +ripple_field_1_s3 = "Ripple Field 1 - Star 3" +ripple_field_1_s4 = "Ripple Field 1 - Star 4" +ripple_field_1_s5 = "Ripple Field 1 - Star 5" +ripple_field_1_s6 = "Ripple Field 1 - Star 6" +ripple_field_1_s7 = "Ripple Field 1 - Star 7" +ripple_field_1_s8 = "Ripple Field 1 - Star 8" +ripple_field_1_s9 = "Ripple Field 1 - Star 9" +ripple_field_1_s10 = "Ripple Field 1 - Star 10" +ripple_field_1_s11 = "Ripple Field 1 - Star 11" +ripple_field_1_s12 = "Ripple Field 1 - Star 12" +ripple_field_1_s13 = "Ripple Field 1 - Star 13" +ripple_field_1_s14 = "Ripple Field 1 - Star 14" +ripple_field_1_s15 = "Ripple Field 1 - Star 15" +ripple_field_1_s16 = "Ripple Field 1 - Star 16" +ripple_field_1_s17 = "Ripple Field 1 - Star 17" +ripple_field_1_s18 = "Ripple Field 1 - Star 18" +ripple_field_1_s19 = "Ripple Field 1 - Star 19" +ripple_field_2_s1 = "Ripple Field 2 - Star 1" +ripple_field_2_s2 = "Ripple Field 2 - Star 2" +ripple_field_2_s3 = "Ripple Field 2 - Star 3" +ripple_field_2_s4 = "Ripple Field 2 - Star 4" +ripple_field_2_s5 = "Ripple Field 2 - Star 5" +ripple_field_2_s6 = "Ripple Field 2 - Star 6" +ripple_field_2_s7 = "Ripple Field 2 - Star 7" +ripple_field_2_s8 = "Ripple Field 2 - Star 8" +ripple_field_2_s9 = "Ripple Field 2 - Star 9" +ripple_field_2_s10 = "Ripple Field 2 - Star 10" +ripple_field_2_s11 = "Ripple Field 2 - Star 11" +ripple_field_2_s12 = "Ripple Field 2 - Star 12" +ripple_field_2_s13 = "Ripple Field 2 - Star 13" +ripple_field_2_s14 = "Ripple Field 2 - Star 14" +ripple_field_2_s15 = "Ripple Field 2 - Star 15" +ripple_field_2_s16 = "Ripple Field 2 - Star 16" +ripple_field_2_s17 = "Ripple Field 2 - Star 17" +ripple_field_3_s1 = "Ripple Field 3 - Star 1" +ripple_field_3_s2 = "Ripple Field 3 - Star 2" +ripple_field_3_s3 = "Ripple Field 3 - Star 3" +ripple_field_3_s4 = "Ripple Field 3 - Star 4" +ripple_field_3_s5 = "Ripple Field 3 - Star 5" +ripple_field_3_s6 = "Ripple Field 3 - Star 6" +ripple_field_3_s7 = "Ripple Field 3 - Star 7" +ripple_field_3_s8 = "Ripple Field 3 - Star 8" +ripple_field_3_s9 = "Ripple Field 3 - Star 9" +ripple_field_3_s10 = "Ripple Field 3 - Star 10" +ripple_field_3_s11 = "Ripple Field 3 - Star 11" +ripple_field_3_s12 = "Ripple Field 3 - Star 12" +ripple_field_3_s13 = "Ripple Field 3 - Star 13" +ripple_field_3_s14 = "Ripple Field 3 - Star 14" +ripple_field_3_s15 = "Ripple Field 3 - Star 15" +ripple_field_3_s16 = "Ripple Field 3 - Star 16" +ripple_field_3_s17 = "Ripple Field 3 - Star 17" +ripple_field_3_s18 = "Ripple Field 3 - Star 18" +ripple_field_3_s19 = "Ripple Field 3 - Star 19" +ripple_field_3_s20 = "Ripple Field 3 - Star 20" +ripple_field_3_s21 = "Ripple Field 3 - Star 21" +ripple_field_4_s1 = "Ripple Field 4 - Star 1" +ripple_field_4_s2 = "Ripple Field 4 - Star 2" +ripple_field_4_s3 = "Ripple Field 4 - Star 3" +ripple_field_4_s4 = "Ripple Field 4 - Star 4" +ripple_field_4_s5 = "Ripple Field 4 - Star 5" +ripple_field_4_s6 = "Ripple Field 4 - Star 6" +ripple_field_4_s7 = "Ripple Field 4 - Star 7" +ripple_field_4_s8 = "Ripple Field 4 - Star 8" +ripple_field_4_s9 = "Ripple Field 4 - Star 9" +ripple_field_4_s10 = "Ripple Field 4 - Star 10" +ripple_field_4_s11 = "Ripple Field 4 - Star 11" +ripple_field_4_s12 = "Ripple Field 4 - Star 12" +ripple_field_4_s13 = "Ripple Field 4 - Star 13" +ripple_field_4_s14 = "Ripple Field 4 - Star 14" +ripple_field_4_s15 = "Ripple Field 4 - Star 15" +ripple_field_4_s16 = "Ripple Field 4 - Star 16" +ripple_field_4_s17 = "Ripple Field 4 - Star 17" +ripple_field_4_s18 = "Ripple Field 4 - Star 18" +ripple_field_4_s19 = "Ripple Field 4 - Star 19" +ripple_field_4_s20 = "Ripple Field 4 - Star 20" +ripple_field_4_s21 = "Ripple Field 4 - Star 21" +ripple_field_4_s22 = "Ripple Field 4 - Star 22" +ripple_field_4_s23 = "Ripple Field 4 - Star 23" +ripple_field_4_s24 = "Ripple Field 4 - Star 24" +ripple_field_4_s25 = "Ripple Field 4 - Star 25" +ripple_field_4_s26 = "Ripple Field 4 - Star 26" +ripple_field_4_s27 = "Ripple Field 4 - Star 27" +ripple_field_4_s28 = "Ripple Field 4 - Star 28" +ripple_field_4_s29 = "Ripple Field 4 - Star 29" +ripple_field_4_s30 = "Ripple Field 4 - Star 30" +ripple_field_4_s31 = "Ripple Field 4 - Star 31" +ripple_field_4_s32 = "Ripple Field 4 - Star 32" +ripple_field_4_s33 = "Ripple Field 4 - Star 33" +ripple_field_4_s34 = "Ripple Field 4 - Star 34" +ripple_field_4_s35 = "Ripple Field 4 - Star 35" +ripple_field_4_s36 = "Ripple Field 4 - Star 36" +ripple_field_4_s37 = "Ripple Field 4 - Star 37" +ripple_field_4_s38 = "Ripple Field 4 - Star 38" +ripple_field_4_s39 = "Ripple Field 4 - Star 39" +ripple_field_4_s40 = "Ripple Field 4 - Star 40" +ripple_field_4_s41 = "Ripple Field 4 - Star 41" +ripple_field_4_s42 = "Ripple Field 4 - Star 42" +ripple_field_4_s43 = "Ripple Field 4 - Star 43" +ripple_field_4_s44 = "Ripple Field 4 - Star 44" +ripple_field_4_s45 = "Ripple Field 4 - Star 45" +ripple_field_4_s46 = "Ripple Field 4 - Star 46" +ripple_field_4_s47 = "Ripple Field 4 - Star 47" +ripple_field_4_s48 = "Ripple Field 4 - Star 48" +ripple_field_4_s49 = "Ripple Field 4 - Star 49" +ripple_field_4_s50 = "Ripple Field 4 - Star 50" +ripple_field_4_s51 = "Ripple Field 4 - Star 51" +ripple_field_5_s1 = "Ripple Field 5 - Star 1" +ripple_field_5_s2 = "Ripple Field 5 - Star 2" +ripple_field_5_s3 = "Ripple Field 5 - Star 3" +ripple_field_5_s4 = "Ripple Field 5 - Star 4" +ripple_field_5_s5 = "Ripple Field 5 - Star 5" +ripple_field_5_s6 = "Ripple Field 5 - Star 6" +ripple_field_5_s7 = "Ripple Field 5 - Star 7" +ripple_field_5_s8 = "Ripple Field 5 - Star 8" +ripple_field_5_s9 = "Ripple Field 5 - Star 9" +ripple_field_5_s10 = "Ripple Field 5 - Star 10" +ripple_field_5_s11 = "Ripple Field 5 - Star 11" +ripple_field_5_s12 = "Ripple Field 5 - Star 12" +ripple_field_5_s13 = "Ripple Field 5 - Star 13" +ripple_field_5_s14 = "Ripple Field 5 - Star 14" +ripple_field_5_s15 = "Ripple Field 5 - Star 15" +ripple_field_5_s16 = "Ripple Field 5 - Star 16" +ripple_field_5_s17 = "Ripple Field 5 - Star 17" +ripple_field_5_s18 = "Ripple Field 5 - Star 18" +ripple_field_5_s19 = "Ripple Field 5 - Star 19" +ripple_field_5_s20 = "Ripple Field 5 - Star 20" +ripple_field_5_s21 = "Ripple Field 5 - Star 21" +ripple_field_5_s22 = "Ripple Field 5 - Star 22" +ripple_field_5_s23 = "Ripple Field 5 - Star 23" +ripple_field_5_s24 = "Ripple Field 5 - Star 24" +ripple_field_5_s25 = "Ripple Field 5 - Star 25" +ripple_field_5_s26 = "Ripple Field 5 - Star 26" +ripple_field_5_s27 = "Ripple Field 5 - Star 27" +ripple_field_5_s28 = "Ripple Field 5 - Star 28" +ripple_field_5_s29 = "Ripple Field 5 - Star 29" +ripple_field_5_s30 = "Ripple Field 5 - Star 30" +ripple_field_5_s31 = "Ripple Field 5 - Star 31" +ripple_field_5_s32 = "Ripple Field 5 - Star 32" +ripple_field_5_s33 = "Ripple Field 5 - Star 33" +ripple_field_5_s34 = "Ripple Field 5 - Star 34" +ripple_field_5_s35 = "Ripple Field 5 - Star 35" +ripple_field_5_s36 = "Ripple Field 5 - Star 36" +ripple_field_5_s37 = "Ripple Field 5 - Star 37" +ripple_field_5_s38 = "Ripple Field 5 - Star 38" +ripple_field_5_s39 = "Ripple Field 5 - Star 39" +ripple_field_5_s40 = "Ripple Field 5 - Star 40" +ripple_field_5_s41 = "Ripple Field 5 - Star 41" +ripple_field_5_s42 = "Ripple Field 5 - Star 42" +ripple_field_5_s43 = "Ripple Field 5 - Star 43" +ripple_field_5_s44 = "Ripple Field 5 - Star 44" +ripple_field_5_s45 = "Ripple Field 5 - Star 45" +ripple_field_5_s46 = "Ripple Field 5 - Star 46" +ripple_field_5_s47 = "Ripple Field 5 - Star 47" +ripple_field_5_s48 = "Ripple Field 5 - Star 48" +ripple_field_5_s49 = "Ripple Field 5 - Star 49" +ripple_field_5_s50 = "Ripple Field 5 - Star 50" +ripple_field_5_s51 = "Ripple Field 5 - Star 51" +ripple_field_6_s1 = "Ripple Field 6 - Star 1" +ripple_field_6_s2 = "Ripple Field 6 - Star 2" +ripple_field_6_s3 = "Ripple Field 6 - Star 3" +ripple_field_6_s4 = "Ripple Field 6 - Star 4" +ripple_field_6_s5 = "Ripple Field 6 - Star 5" +ripple_field_6_s6 = "Ripple Field 6 - Star 6" +ripple_field_6_s7 = "Ripple Field 6 - Star 7" +ripple_field_6_s8 = "Ripple Field 6 - Star 8" +ripple_field_6_s9 = "Ripple Field 6 - Star 9" +ripple_field_6_s10 = "Ripple Field 6 - Star 10" +ripple_field_6_s11 = "Ripple Field 6 - Star 11" +ripple_field_6_s12 = "Ripple Field 6 - Star 12" +ripple_field_6_s13 = "Ripple Field 6 - Star 13" +ripple_field_6_s14 = "Ripple Field 6 - Star 14" +ripple_field_6_s15 = "Ripple Field 6 - Star 15" +ripple_field_6_s16 = "Ripple Field 6 - Star 16" +ripple_field_6_s17 = "Ripple Field 6 - Star 17" +ripple_field_6_s18 = "Ripple Field 6 - Star 18" +ripple_field_6_s19 = "Ripple Field 6 - Star 19" +ripple_field_6_s20 = "Ripple Field 6 - Star 20" +ripple_field_6_s21 = "Ripple Field 6 - Star 21" +ripple_field_6_s22 = "Ripple Field 6 - Star 22" +ripple_field_6_s23 = "Ripple Field 6 - Star 23" +sand_canyon_1_s1 = "Sand Canyon 1 - Star 1" +sand_canyon_1_s2 = "Sand Canyon 1 - Star 2" +sand_canyon_1_s3 = "Sand Canyon 1 - Star 3" +sand_canyon_1_s4 = "Sand Canyon 1 - Star 4" +sand_canyon_1_s5 = "Sand Canyon 1 - Star 5" +sand_canyon_1_s6 = "Sand Canyon 1 - Star 6" +sand_canyon_1_s7 = "Sand Canyon 1 - Star 7" +sand_canyon_1_s8 = "Sand Canyon 1 - Star 8" +sand_canyon_1_s9 = "Sand Canyon 1 - Star 9" +sand_canyon_1_s10 = "Sand Canyon 1 - Star 10" +sand_canyon_1_s11 = "Sand Canyon 1 - Star 11" +sand_canyon_1_s12 = "Sand Canyon 1 - Star 12" +sand_canyon_1_s13 = "Sand Canyon 1 - Star 13" +sand_canyon_1_s14 = "Sand Canyon 1 - Star 14" +sand_canyon_1_s15 = "Sand Canyon 1 - Star 15" +sand_canyon_1_s16 = "Sand Canyon 1 - Star 16" +sand_canyon_1_s17 = "Sand Canyon 1 - Star 17" +sand_canyon_1_s18 = "Sand Canyon 1 - Star 18" +sand_canyon_1_s19 = "Sand Canyon 1 - Star 19" +sand_canyon_1_s20 = "Sand Canyon 1 - Star 20" +sand_canyon_1_s21 = "Sand Canyon 1 - Star 21" +sand_canyon_1_s22 = "Sand Canyon 1 - Star 22" +sand_canyon_2_s1 = "Sand Canyon 2 - Star 1" +sand_canyon_2_s2 = "Sand Canyon 2 - Star 2" +sand_canyon_2_s3 = "Sand Canyon 2 - Star 3" +sand_canyon_2_s4 = "Sand Canyon 2 - Star 4" +sand_canyon_2_s5 = "Sand Canyon 2 - Star 5" +sand_canyon_2_s6 = "Sand Canyon 2 - Star 6" +sand_canyon_2_s7 = "Sand Canyon 2 - Star 7" +sand_canyon_2_s8 = "Sand Canyon 2 - Star 8" +sand_canyon_2_s9 = "Sand Canyon 2 - Star 9" +sand_canyon_2_s10 = "Sand Canyon 2 - Star 10" +sand_canyon_2_s11 = "Sand Canyon 2 - Star 11" +sand_canyon_2_s12 = "Sand Canyon 2 - Star 12" +sand_canyon_2_s13 = "Sand Canyon 2 - Star 13" +sand_canyon_2_s14 = "Sand Canyon 2 - Star 14" +sand_canyon_2_s15 = "Sand Canyon 2 - Star 15" +sand_canyon_2_s16 = "Sand Canyon 2 - Star 16" +sand_canyon_2_s17 = "Sand Canyon 2 - Star 17" +sand_canyon_2_s18 = "Sand Canyon 2 - Star 18" +sand_canyon_2_s19 = "Sand Canyon 2 - Star 19" +sand_canyon_2_s20 = "Sand Canyon 2 - Star 20" +sand_canyon_2_s21 = "Sand Canyon 2 - Star 21" +sand_canyon_2_s22 = "Sand Canyon 2 - Star 22" +sand_canyon_2_s23 = "Sand Canyon 2 - Star 23" +sand_canyon_2_s24 = "Sand Canyon 2 - Star 24" +sand_canyon_2_s25 = "Sand Canyon 2 - Star 25" +sand_canyon_2_s26 = "Sand Canyon 2 - Star 26" +sand_canyon_2_s27 = "Sand Canyon 2 - Star 27" +sand_canyon_2_s28 = "Sand Canyon 2 - Star 28" +sand_canyon_2_s29 = "Sand Canyon 2 - Star 29" +sand_canyon_2_s30 = "Sand Canyon 2 - Star 30" +sand_canyon_2_s31 = "Sand Canyon 2 - Star 31" +sand_canyon_2_s32 = "Sand Canyon 2 - Star 32" +sand_canyon_2_s33 = "Sand Canyon 2 - Star 33" +sand_canyon_2_s34 = "Sand Canyon 2 - Star 34" +sand_canyon_2_s35 = "Sand Canyon 2 - Star 35" +sand_canyon_2_s36 = "Sand Canyon 2 - Star 36" +sand_canyon_2_s37 = "Sand Canyon 2 - Star 37" +sand_canyon_2_s38 = "Sand Canyon 2 - Star 38" +sand_canyon_2_s39 = "Sand Canyon 2 - Star 39" +sand_canyon_2_s40 = "Sand Canyon 2 - Star 40" +sand_canyon_2_s41 = "Sand Canyon 2 - Star 41" +sand_canyon_2_s42 = "Sand Canyon 2 - Star 42" +sand_canyon_2_s43 = "Sand Canyon 2 - Star 43" +sand_canyon_2_s44 = "Sand Canyon 2 - Star 44" +sand_canyon_2_s45 = "Sand Canyon 2 - Star 45" +sand_canyon_2_s46 = "Sand Canyon 2 - Star 46" +sand_canyon_2_s47 = "Sand Canyon 2 - Star 47" +sand_canyon_2_s48 = "Sand Canyon 2 - Star 48" +sand_canyon_3_s1 = "Sand Canyon 3 - Star 1" +sand_canyon_3_s2 = "Sand Canyon 3 - Star 2" +sand_canyon_3_s3 = "Sand Canyon 3 - Star 3" +sand_canyon_3_s4 = "Sand Canyon 3 - Star 4" +sand_canyon_3_s5 = "Sand Canyon 3 - Star 5" +sand_canyon_3_s6 = "Sand Canyon 3 - Star 6" +sand_canyon_3_s7 = "Sand Canyon 3 - Star 7" +sand_canyon_3_s8 = "Sand Canyon 3 - Star 8" +sand_canyon_3_s9 = "Sand Canyon 3 - Star 9" +sand_canyon_3_s10 = "Sand Canyon 3 - Star 10" +sand_canyon_4_s1 = "Sand Canyon 4 - Star 1" +sand_canyon_4_s2 = "Sand Canyon 4 - Star 2" +sand_canyon_4_s3 = "Sand Canyon 4 - Star 3" +sand_canyon_4_s4 = "Sand Canyon 4 - Star 4" +sand_canyon_4_s5 = "Sand Canyon 4 - Star 5" +sand_canyon_4_s6 = "Sand Canyon 4 - Star 6" +sand_canyon_4_s7 = "Sand Canyon 4 - Star 7" +sand_canyon_4_s8 = "Sand Canyon 4 - Star 8" +sand_canyon_4_s9 = "Sand Canyon 4 - Star 9" +sand_canyon_4_s10 = "Sand Canyon 4 - Star 10" +sand_canyon_4_s11 = "Sand Canyon 4 - Star 11" +sand_canyon_4_s12 = "Sand Canyon 4 - Star 12" +sand_canyon_4_s13 = "Sand Canyon 4 - Star 13" +sand_canyon_4_s14 = "Sand Canyon 4 - Star 14" +sand_canyon_4_s15 = "Sand Canyon 4 - Star 15" +sand_canyon_4_s16 = "Sand Canyon 4 - Star 16" +sand_canyon_4_s17 = "Sand Canyon 4 - Star 17" +sand_canyon_4_s18 = "Sand Canyon 4 - Star 18" +sand_canyon_4_s19 = "Sand Canyon 4 - Star 19" +sand_canyon_4_s20 = "Sand Canyon 4 - Star 20" +sand_canyon_4_s21 = "Sand Canyon 4 - Star 21" +sand_canyon_4_s22 = "Sand Canyon 4 - Star 22" +sand_canyon_4_s23 = "Sand Canyon 4 - Star 23" +sand_canyon_5_s1 = "Sand Canyon 5 - Star 1" +sand_canyon_5_s2 = "Sand Canyon 5 - Star 2" +sand_canyon_5_s3 = "Sand Canyon 5 - Star 3" +sand_canyon_5_s4 = "Sand Canyon 5 - Star 4" +sand_canyon_5_s5 = "Sand Canyon 5 - Star 5" +sand_canyon_5_s6 = "Sand Canyon 5 - Star 6" +sand_canyon_5_s7 = "Sand Canyon 5 - Star 7" +sand_canyon_5_s8 = "Sand Canyon 5 - Star 8" +sand_canyon_5_s9 = "Sand Canyon 5 - Star 9" +sand_canyon_5_s10 = "Sand Canyon 5 - Star 10" +sand_canyon_5_s11 = "Sand Canyon 5 - Star 11" +sand_canyon_5_s12 = "Sand Canyon 5 - Star 12" +sand_canyon_5_s13 = "Sand Canyon 5 - Star 13" +sand_canyon_5_s14 = "Sand Canyon 5 - Star 14" +sand_canyon_5_s15 = "Sand Canyon 5 - Star 15" +sand_canyon_5_s16 = "Sand Canyon 5 - Star 16" +sand_canyon_5_s17 = "Sand Canyon 5 - Star 17" +sand_canyon_5_s18 = "Sand Canyon 5 - Star 18" +sand_canyon_5_s19 = "Sand Canyon 5 - Star 19" +sand_canyon_5_s20 = "Sand Canyon 5 - Star 20" +sand_canyon_5_s21 = "Sand Canyon 5 - Star 21" +sand_canyon_5_s22 = "Sand Canyon 5 - Star 22" +sand_canyon_5_s23 = "Sand Canyon 5 - Star 23" +sand_canyon_5_s24 = "Sand Canyon 5 - Star 24" +sand_canyon_5_s25 = "Sand Canyon 5 - Star 25" +sand_canyon_5_s26 = "Sand Canyon 5 - Star 26" +sand_canyon_5_s27 = "Sand Canyon 5 - Star 27" +sand_canyon_5_s28 = "Sand Canyon 5 - Star 28" +sand_canyon_5_s29 = "Sand Canyon 5 - Star 29" +sand_canyon_5_s30 = "Sand Canyon 5 - Star 30" +sand_canyon_5_s31 = "Sand Canyon 5 - Star 31" +sand_canyon_5_s32 = "Sand Canyon 5 - Star 32" +sand_canyon_5_s33 = "Sand Canyon 5 - Star 33" +sand_canyon_5_s34 = "Sand Canyon 5 - Star 34" +sand_canyon_5_s35 = "Sand Canyon 5 - Star 35" +sand_canyon_5_s36 = "Sand Canyon 5 - Star 36" +sand_canyon_5_s37 = "Sand Canyon 5 - Star 37" +sand_canyon_5_s38 = "Sand Canyon 5 - Star 38" +sand_canyon_5_s39 = "Sand Canyon 5 - Star 39" +sand_canyon_5_s40 = "Sand Canyon 5 - Star 40" +cloudy_park_1_s1 = "Cloudy Park 1 - Star 1" +cloudy_park_1_s2 = "Cloudy Park 1 - Star 2" +cloudy_park_1_s3 = "Cloudy Park 1 - Star 3" +cloudy_park_1_s4 = "Cloudy Park 1 - Star 4" +cloudy_park_1_s5 = "Cloudy Park 1 - Star 5" +cloudy_park_1_s6 = "Cloudy Park 1 - Star 6" +cloudy_park_1_s7 = "Cloudy Park 1 - Star 7" +cloudy_park_1_s8 = "Cloudy Park 1 - Star 8" +cloudy_park_1_s9 = "Cloudy Park 1 - Star 9" +cloudy_park_1_s10 = "Cloudy Park 1 - Star 10" +cloudy_park_1_s11 = "Cloudy Park 1 - Star 11" +cloudy_park_1_s12 = "Cloudy Park 1 - Star 12" +cloudy_park_1_s13 = "Cloudy Park 1 - Star 13" +cloudy_park_1_s14 = "Cloudy Park 1 - Star 14" +cloudy_park_1_s15 = "Cloudy Park 1 - Star 15" +cloudy_park_1_s16 = "Cloudy Park 1 - Star 16" +cloudy_park_1_s17 = "Cloudy Park 1 - Star 17" +cloudy_park_1_s18 = "Cloudy Park 1 - Star 18" +cloudy_park_1_s19 = "Cloudy Park 1 - Star 19" +cloudy_park_1_s20 = "Cloudy Park 1 - Star 20" +cloudy_park_1_s21 = "Cloudy Park 1 - Star 21" +cloudy_park_1_s22 = "Cloudy Park 1 - Star 22" +cloudy_park_1_s23 = "Cloudy Park 1 - Star 23" +cloudy_park_2_s1 = "Cloudy Park 2 - Star 1" +cloudy_park_2_s2 = "Cloudy Park 2 - Star 2" +cloudy_park_2_s3 = "Cloudy Park 2 - Star 3" +cloudy_park_2_s4 = "Cloudy Park 2 - Star 4" +cloudy_park_2_s5 = "Cloudy Park 2 - Star 5" +cloudy_park_2_s6 = "Cloudy Park 2 - Star 6" +cloudy_park_2_s7 = "Cloudy Park 2 - Star 7" +cloudy_park_2_s8 = "Cloudy Park 2 - Star 8" +cloudy_park_2_s9 = "Cloudy Park 2 - Star 9" +cloudy_park_2_s10 = "Cloudy Park 2 - Star 10" +cloudy_park_2_s11 = "Cloudy Park 2 - Star 11" +cloudy_park_2_s12 = "Cloudy Park 2 - Star 12" +cloudy_park_2_s13 = "Cloudy Park 2 - Star 13" +cloudy_park_2_s14 = "Cloudy Park 2 - Star 14" +cloudy_park_2_s15 = "Cloudy Park 2 - Star 15" +cloudy_park_2_s16 = "Cloudy Park 2 - Star 16" +cloudy_park_2_s17 = "Cloudy Park 2 - Star 17" +cloudy_park_2_s18 = "Cloudy Park 2 - Star 18" +cloudy_park_2_s19 = "Cloudy Park 2 - Star 19" +cloudy_park_2_s20 = "Cloudy Park 2 - Star 20" +cloudy_park_2_s21 = "Cloudy Park 2 - Star 21" +cloudy_park_2_s22 = "Cloudy Park 2 - Star 22" +cloudy_park_2_s23 = "Cloudy Park 2 - Star 23" +cloudy_park_2_s24 = "Cloudy Park 2 - Star 24" +cloudy_park_2_s25 = "Cloudy Park 2 - Star 25" +cloudy_park_2_s26 = "Cloudy Park 2 - Star 26" +cloudy_park_2_s27 = "Cloudy Park 2 - Star 27" +cloudy_park_2_s28 = "Cloudy Park 2 - Star 28" +cloudy_park_2_s29 = "Cloudy Park 2 - Star 29" +cloudy_park_2_s30 = "Cloudy Park 2 - Star 30" +cloudy_park_2_s31 = "Cloudy Park 2 - Star 31" +cloudy_park_2_s32 = "Cloudy Park 2 - Star 32" +cloudy_park_2_s33 = "Cloudy Park 2 - Star 33" +cloudy_park_2_s34 = "Cloudy Park 2 - Star 34" +cloudy_park_2_s35 = "Cloudy Park 2 - Star 35" +cloudy_park_2_s36 = "Cloudy Park 2 - Star 36" +cloudy_park_2_s37 = "Cloudy Park 2 - Star 37" +cloudy_park_2_s38 = "Cloudy Park 2 - Star 38" +cloudy_park_2_s39 = "Cloudy Park 2 - Star 39" +cloudy_park_2_s40 = "Cloudy Park 2 - Star 40" +cloudy_park_2_s41 = "Cloudy Park 2 - Star 41" +cloudy_park_2_s42 = "Cloudy Park 2 - Star 42" +cloudy_park_2_s43 = "Cloudy Park 2 - Star 43" +cloudy_park_2_s44 = "Cloudy Park 2 - Star 44" +cloudy_park_2_s45 = "Cloudy Park 2 - Star 45" +cloudy_park_2_s46 = "Cloudy Park 2 - Star 46" +cloudy_park_2_s47 = "Cloudy Park 2 - Star 47" +cloudy_park_2_s48 = "Cloudy Park 2 - Star 48" +cloudy_park_2_s49 = "Cloudy Park 2 - Star 49" +cloudy_park_2_s50 = "Cloudy Park 2 - Star 50" +cloudy_park_2_s51 = "Cloudy Park 2 - Star 51" +cloudy_park_2_s52 = "Cloudy Park 2 - Star 52" +cloudy_park_2_s53 = "Cloudy Park 2 - Star 53" +cloudy_park_2_s54 = "Cloudy Park 2 - Star 54" +cloudy_park_3_s1 = "Cloudy Park 3 - Star 1" +cloudy_park_3_s2 = "Cloudy Park 3 - Star 2" +cloudy_park_3_s3 = "Cloudy Park 3 - Star 3" +cloudy_park_3_s4 = "Cloudy Park 3 - Star 4" +cloudy_park_3_s5 = "Cloudy Park 3 - Star 5" +cloudy_park_3_s6 = "Cloudy Park 3 - Star 6" +cloudy_park_3_s7 = "Cloudy Park 3 - Star 7" +cloudy_park_3_s8 = "Cloudy Park 3 - Star 8" +cloudy_park_3_s9 = "Cloudy Park 3 - Star 9" +cloudy_park_3_s10 = "Cloudy Park 3 - Star 10" +cloudy_park_3_s11 = "Cloudy Park 3 - Star 11" +cloudy_park_3_s12 = "Cloudy Park 3 - Star 12" +cloudy_park_3_s13 = "Cloudy Park 3 - Star 13" +cloudy_park_3_s14 = "Cloudy Park 3 - Star 14" +cloudy_park_3_s15 = "Cloudy Park 3 - Star 15" +cloudy_park_3_s16 = "Cloudy Park 3 - Star 16" +cloudy_park_3_s17 = "Cloudy Park 3 - Star 17" +cloudy_park_3_s18 = "Cloudy Park 3 - Star 18" +cloudy_park_3_s19 = "Cloudy Park 3 - Star 19" +cloudy_park_3_s20 = "Cloudy Park 3 - Star 20" +cloudy_park_3_s21 = "Cloudy Park 3 - Star 21" +cloudy_park_3_s22 = "Cloudy Park 3 - Star 22" +cloudy_park_4_s1 = "Cloudy Park 4 - Star 1" +cloudy_park_4_s2 = "Cloudy Park 4 - Star 2" +cloudy_park_4_s3 = "Cloudy Park 4 - Star 3" +cloudy_park_4_s4 = "Cloudy Park 4 - Star 4" +cloudy_park_4_s5 = "Cloudy Park 4 - Star 5" +cloudy_park_4_s6 = "Cloudy Park 4 - Star 6" +cloudy_park_4_s7 = "Cloudy Park 4 - Star 7" +cloudy_park_4_s8 = "Cloudy Park 4 - Star 8" +cloudy_park_4_s9 = "Cloudy Park 4 - Star 9" +cloudy_park_4_s10 = "Cloudy Park 4 - Star 10" +cloudy_park_4_s11 = "Cloudy Park 4 - Star 11" +cloudy_park_4_s12 = "Cloudy Park 4 - Star 12" +cloudy_park_4_s13 = "Cloudy Park 4 - Star 13" +cloudy_park_4_s14 = "Cloudy Park 4 - Star 14" +cloudy_park_4_s15 = "Cloudy Park 4 - Star 15" +cloudy_park_4_s16 = "Cloudy Park 4 - Star 16" +cloudy_park_4_s17 = "Cloudy Park 4 - Star 17" +cloudy_park_4_s18 = "Cloudy Park 4 - Star 18" +cloudy_park_4_s19 = "Cloudy Park 4 - Star 19" +cloudy_park_4_s20 = "Cloudy Park 4 - Star 20" +cloudy_park_4_s21 = "Cloudy Park 4 - Star 21" +cloudy_park_4_s22 = "Cloudy Park 4 - Star 22" +cloudy_park_4_s23 = "Cloudy Park 4 - Star 23" +cloudy_park_4_s24 = "Cloudy Park 4 - Star 24" +cloudy_park_4_s25 = "Cloudy Park 4 - Star 25" +cloudy_park_4_s26 = "Cloudy Park 4 - Star 26" +cloudy_park_4_s27 = "Cloudy Park 4 - Star 27" +cloudy_park_4_s28 = "Cloudy Park 4 - Star 28" +cloudy_park_4_s29 = "Cloudy Park 4 - Star 29" +cloudy_park_4_s30 = "Cloudy Park 4 - Star 30" +cloudy_park_4_s31 = "Cloudy Park 4 - Star 31" +cloudy_park_4_s32 = "Cloudy Park 4 - Star 32" +cloudy_park_4_s33 = "Cloudy Park 4 - Star 33" +cloudy_park_4_s34 = "Cloudy Park 4 - Star 34" +cloudy_park_4_s35 = "Cloudy Park 4 - Star 35" +cloudy_park_4_s36 = "Cloudy Park 4 - Star 36" +cloudy_park_4_s37 = "Cloudy Park 4 - Star 37" +cloudy_park_4_s38 = "Cloudy Park 4 - Star 38" +cloudy_park_4_s39 = "Cloudy Park 4 - Star 39" +cloudy_park_4_s40 = "Cloudy Park 4 - Star 40" +cloudy_park_4_s41 = "Cloudy Park 4 - Star 41" +cloudy_park_4_s42 = "Cloudy Park 4 - Star 42" +cloudy_park_4_s43 = "Cloudy Park 4 - Star 43" +cloudy_park_4_s44 = "Cloudy Park 4 - Star 44" +cloudy_park_4_s45 = "Cloudy Park 4 - Star 45" +cloudy_park_4_s46 = "Cloudy Park 4 - Star 46" +cloudy_park_4_s47 = "Cloudy Park 4 - Star 47" +cloudy_park_4_s48 = "Cloudy Park 4 - Star 48" +cloudy_park_4_s49 = "Cloudy Park 4 - Star 49" +cloudy_park_4_s50 = "Cloudy Park 4 - Star 50" +cloudy_park_5_s1 = "Cloudy Park 5 - Star 1" +cloudy_park_5_s2 = "Cloudy Park 5 - Star 2" +cloudy_park_5_s3 = "Cloudy Park 5 - Star 3" +cloudy_park_5_s4 = "Cloudy Park 5 - Star 4" +cloudy_park_5_s5 = "Cloudy Park 5 - Star 5" +cloudy_park_5_s6 = "Cloudy Park 5 - Star 6" +cloudy_park_6_s1 = "Cloudy Park 6 - Star 1" +cloudy_park_6_s2 = "Cloudy Park 6 - Star 2" +cloudy_park_6_s3 = "Cloudy Park 6 - Star 3" +cloudy_park_6_s4 = "Cloudy Park 6 - Star 4" +cloudy_park_6_s5 = "Cloudy Park 6 - Star 5" +cloudy_park_6_s6 = "Cloudy Park 6 - Star 6" +cloudy_park_6_s7 = "Cloudy Park 6 - Star 7" +cloudy_park_6_s8 = "Cloudy Park 6 - Star 8" +cloudy_park_6_s9 = "Cloudy Park 6 - Star 9" +cloudy_park_6_s10 = "Cloudy Park 6 - Star 10" +cloudy_park_6_s11 = "Cloudy Park 6 - Star 11" +cloudy_park_6_s12 = "Cloudy Park 6 - Star 12" +cloudy_park_6_s13 = "Cloudy Park 6 - Star 13" +cloudy_park_6_s14 = "Cloudy Park 6 - Star 14" +cloudy_park_6_s15 = "Cloudy Park 6 - Star 15" +cloudy_park_6_s16 = "Cloudy Park 6 - Star 16" +cloudy_park_6_s17 = "Cloudy Park 6 - Star 17" +cloudy_park_6_s18 = "Cloudy Park 6 - Star 18" +cloudy_park_6_s19 = "Cloudy Park 6 - Star 19" +cloudy_park_6_s20 = "Cloudy Park 6 - Star 20" +cloudy_park_6_s21 = "Cloudy Park 6 - Star 21" +cloudy_park_6_s22 = "Cloudy Park 6 - Star 22" +cloudy_park_6_s23 = "Cloudy Park 6 - Star 23" +cloudy_park_6_s24 = "Cloudy Park 6 - Star 24" +cloudy_park_6_s25 = "Cloudy Park 6 - Star 25" +cloudy_park_6_s26 = "Cloudy Park 6 - Star 26" +cloudy_park_6_s27 = "Cloudy Park 6 - Star 27" +cloudy_park_6_s28 = "Cloudy Park 6 - Star 28" +cloudy_park_6_s29 = "Cloudy Park 6 - Star 29" +cloudy_park_6_s30 = "Cloudy Park 6 - Star 30" +cloudy_park_6_s31 = "Cloudy Park 6 - Star 31" +cloudy_park_6_s32 = "Cloudy Park 6 - Star 32" +cloudy_park_6_s33 = "Cloudy Park 6 - Star 33" +iceberg_1_s1 = "Iceberg 1 - Star 1" +iceberg_1_s2 = "Iceberg 1 - Star 2" +iceberg_1_s3 = "Iceberg 1 - Star 3" +iceberg_1_s4 = "Iceberg 1 - Star 4" +iceberg_1_s5 = "Iceberg 1 - Star 5" +iceberg_1_s6 = "Iceberg 1 - Star 6" +iceberg_2_s1 = "Iceberg 2 - Star 1" +iceberg_2_s2 = "Iceberg 2 - Star 2" +iceberg_2_s3 = "Iceberg 2 - Star 3" +iceberg_2_s4 = "Iceberg 2 - Star 4" +iceberg_2_s5 = "Iceberg 2 - Star 5" +iceberg_2_s6 = "Iceberg 2 - Star 6" +iceberg_2_s7 = "Iceberg 2 - Star 7" +iceberg_2_s8 = "Iceberg 2 - Star 8" +iceberg_2_s9 = "Iceberg 2 - Star 9" +iceberg_2_s10 = "Iceberg 2 - Star 10" +iceberg_2_s11 = "Iceberg 2 - Star 11" +iceberg_2_s12 = "Iceberg 2 - Star 12" +iceberg_2_s13 = "Iceberg 2 - Star 13" +iceberg_2_s14 = "Iceberg 2 - Star 14" +iceberg_2_s15 = "Iceberg 2 - Star 15" +iceberg_2_s16 = "Iceberg 2 - Star 16" +iceberg_2_s17 = "Iceberg 2 - Star 17" +iceberg_2_s18 = "Iceberg 2 - Star 18" +iceberg_2_s19 = "Iceberg 2 - Star 19" +iceberg_3_s1 = "Iceberg 3 - Star 1" +iceberg_3_s2 = "Iceberg 3 - Star 2" +iceberg_3_s3 = "Iceberg 3 - Star 3" +iceberg_3_s4 = "Iceberg 3 - Star 4" +iceberg_3_s5 = "Iceberg 3 - Star 5" +iceberg_3_s6 = "Iceberg 3 - Star 6" +iceberg_3_s7 = "Iceberg 3 - Star 7" +iceberg_3_s8 = "Iceberg 3 - Star 8" +iceberg_3_s9 = "Iceberg 3 - Star 9" +iceberg_3_s10 = "Iceberg 3 - Star 10" +iceberg_3_s11 = "Iceberg 3 - Star 11" +iceberg_3_s12 = "Iceberg 3 - Star 12" +iceberg_3_s13 = "Iceberg 3 - Star 13" +iceberg_3_s14 = "Iceberg 3 - Star 14" +iceberg_3_s15 = "Iceberg 3 - Star 15" +iceberg_3_s16 = "Iceberg 3 - Star 16" +iceberg_3_s17 = "Iceberg 3 - Star 17" +iceberg_3_s18 = "Iceberg 3 - Star 18" +iceberg_3_s19 = "Iceberg 3 - Star 19" +iceberg_3_s20 = "Iceberg 3 - Star 20" +iceberg_3_s21 = "Iceberg 3 - Star 21" +iceberg_4_s1 = "Iceberg 4 - Star 1" +iceberg_4_s2 = "Iceberg 4 - Star 2" +iceberg_4_s3 = "Iceberg 4 - Star 3" +iceberg_5_s1 = "Iceberg 5 - Star 1" +iceberg_5_s2 = "Iceberg 5 - Star 2" +iceberg_5_s3 = "Iceberg 5 - Star 3" +iceberg_5_s4 = "Iceberg 5 - Star 4" +iceberg_5_s5 = "Iceberg 5 - Star 5" +iceberg_5_s6 = "Iceberg 5 - Star 6" +iceberg_5_s7 = "Iceberg 5 - Star 7" +iceberg_5_s8 = "Iceberg 5 - Star 8" +iceberg_5_s9 = "Iceberg 5 - Star 9" +iceberg_5_s10 = "Iceberg 5 - Star 10" +iceberg_5_s11 = "Iceberg 5 - Star 11" +iceberg_5_s12 = "Iceberg 5 - Star 12" +iceberg_5_s13 = "Iceberg 5 - Star 13" +iceberg_5_s14 = "Iceberg 5 - Star 14" +iceberg_5_s15 = "Iceberg 5 - Star 15" +iceberg_5_s16 = "Iceberg 5 - Star 16" +iceberg_5_s17 = "Iceberg 5 - Star 17" +iceberg_5_s18 = "Iceberg 5 - Star 18" +iceberg_5_s19 = "Iceberg 5 - Star 19" +iceberg_5_s20 = "Iceberg 5 - Star 20" +iceberg_5_s21 = "Iceberg 5 - Star 21" +iceberg_5_s22 = "Iceberg 5 - Star 22" +iceberg_5_s23 = "Iceberg 5 - Star 23" +iceberg_5_s24 = "Iceberg 5 - Star 24" +iceberg_5_s25 = "Iceberg 5 - Star 25" +iceberg_5_s26 = "Iceberg 5 - Star 26" +iceberg_5_s27 = "Iceberg 5 - Star 27" +iceberg_5_s28 = "Iceberg 5 - Star 28" +iceberg_5_s29 = "Iceberg 5 - Star 29" +iceberg_5_s30 = "Iceberg 5 - Star 30" +iceberg_5_s31 = "Iceberg 5 - Star 31" +iceberg_5_s32 = "Iceberg 5 - Star 32" +iceberg_5_s33 = "Iceberg 5 - Star 33" +iceberg_5_s34 = "Iceberg 5 - Star 34" +iceberg_6_s1 = "Iceberg 6 - Star 1" diff --git a/worlds/kdl3/Names/__init__.py b/worlds/kdl3/Names/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/kdl3/Options.py b/worlds/kdl3/Options.py new file mode 100644 index 000000000000..336bd33bc583 --- /dev/null +++ b/worlds/kdl3/Options.py @@ -0,0 +1,432 @@ +import random +from dataclasses import dataclass + +from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \ + PerGameCommonOptions +from .Names import LocationName + + +class Goal(Choice): + """ + Zero: collect the Heart Stars, and defeat Zero in the Hyper Zone. + Boss Butch: collect the Heart Stars, and then complete the boss rematches in the Boss Butch mode. + MG5: collect the Heart Stars, and then complete a perfect run through the minigame gauntlet within the Super MG5 + Jumping: collect the Heart Stars, and then reach a designated score within the Jumping sub-game + """ + display_name = "Goal" + option_zero = 0 + option_boss_butch = 1 + option_MG5 = 2 + option_jumping = 3 + default = 0 + + @classmethod + def get_option_name(cls, value: int) -> str: + if value == 2: + return cls.name_lookup[value].upper() + return super().get_option_name(value) + +class GoalSpeed(Choice): + """ + Normal: the goal is unlocked after purifying the five bosses + Fast: the goal is unlocked after acquiring the target number of Heart Stars + """ + display_name = "Goal Speed" + option_normal = 0 + option_fast = 1 + + +class TotalHeartStars(Range): + """ + Maximum number of heart stars to include in the pool of items. + """ + display_name = "Max Heart Stars" + range_start = 5 # set to 5 so strict bosses does not degrade + range_end = 50 # 30 default locations + 30 stage clears + 5 bosses - 14 progression items = 51, so round down + default = 30 + + +class HeartStarsRequired(Range): + """ + Percentage of heart stars required to purify the five bosses and reach Zero. + Each boss will require a differing amount of heart stars to purify. + """ + display_name = "Required Heart Stars" + range_start = 1 + range_end = 100 + default = 50 + + +class LevelShuffle(Choice): + """ + None: No stage shuffling. + Same World: shuffles stages around their world. + Pattern: shuffles stages according to the stage pattern (stage 3 will always be a minigame stage, etc.) + Shuffled: shuffles stages across all worlds. + """ + display_name = "Stage Shuffle" + option_none = 0 + option_same_world = 1 + option_pattern = 2 + option_shuffled = 3 + default = 0 + + +class BossShuffle(PlandoBosses): + """ + None: Bosses will remain in their vanilla locations + Shuffled: Bosses will be shuffled amongst each other + Full: Bosses will be randomized + Singularity: All (non-Zero) bosses will be replaced with a single boss + Supports plando placement. + """ + bosses = frozenset(LocationName.boss_names.keys()) + + locations = frozenset(LocationName.level_names.keys()) + + duplicate_bosses = True + + @classmethod + def can_place_boss(cls, boss: str, location: str) -> bool: + # Kirby has no logic about requiring bosses in specific locations (since we load in their stage) + return True + + display_name = "Boss Shuffle" + option_none = 0 + option_shuffled = 1 + option_full = 2 + option_singularity = 3 + + +class BossShuffleAllowBB(Choice): + """ + Allow Boss Butch variants of bosses in Boss Shuffle. + Enabled: any boss placed will have a 50% chance of being the Boss Butch variant, including bosses not present + Enforced: all bosses will be their Boss Butch variant. + Boss Butch boss changes are only visual. + """ + display_name = "Allow Boss Butch Bosses" + option_disabled = 0 + option_enabled = 1 + option_enforced = 2 + default = 0 + + +class AnimalRandomization(Choice): + """ + Disabled: all animal positions will be vanilla. + Shuffled: all animal positions will be shuffled amongst each other. + Full: random animals will be placed across the levels. At least one of each animal is guaranteed. + """ + display_name = "Animal Randomization" + option_disabled = 0 + option_shuffled = 1 + option_full = 2 + default = 0 + + +class CopyAbilityRandomization(Choice): + """ + Disabled: enemies give regular copy abilities and health. + Enabled: all enemies will have the copy ability received from them randomized. + Enabled Plus Minus: enemies (except minibosses) can additionally give you anywhere from +2 health to -1 health when eaten. + """ + display_name = "Copy Ability Randomization" + option_disabled = 0 + option_enabled = 1 + option_enabled_plus_minus = 2 + + +class StrictBosses(DefaultOnToggle): + """ + If enabled, one will not be able to move onto the next world until the previous world's boss has been purified. + """ + display_name = "Strict Bosses" + + +class OpenWorld(DefaultOnToggle): + """ + If enabled, all 6 stages will be unlocked upon entering a world for the first time. A certain amount of stages + will need to be completed in order to unlock the bosses + """ + display_name = "Open World" + + +class OpenWorldBossRequirement(Range): + """ + The amount of stages completed needed to unlock the boss of a world when Open World is turned on. + """ + display_name = "Open World Boss Requirement" + range_start = 1 + range_end = 6 + default = 3 + + +class BossRequirementRandom(Toggle): + """ + If enabled, boss purification will require a random amount of Heart Stars. Depending on options, this may have + boss purification unlock in a random order. + """ + display_name = "Randomize Purification Requirement" + + +class JumpingTarget(Range): + """ + The required score needed to complete the Jumping minigame. + """ + display_name = "Jumping Target Score" + range_start = 1 + range_end = 25 + default = 10 + + +class GameLanguage(Choice): + """ + The language that the game should display. This does not have to match the given rom. + """ + display_name = "Game Language" + option_japanese = 0 + option_english = 1 + default = 1 + + +class FillerPercentage(Range): + """ + Percentage of non-required Heart Stars to be converted to filler items (1-Ups, Maxim Tomatoes, Invincibility Candy). + """ + display_name = "Filler Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class TrapPercentage(Range): + """ + Percentage of filler items to be converted to trap items (Gooey Bags, Slowness, Eject Ability). + """ + display_name = "Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class GooeyTrapPercentage(Range): + """ + Chance that any given trap is a Gooey Bag (spawns Gooey when you receive it). + """ + display_name = "Gooey Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class SlowTrapPercentage(Range): + """ + Chance that any given trap is Slowness (halves your max speed for 15 seconds when you receive it). + """ + display_name = "Slowness Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class AbilityTrapPercentage(Range): + """ + Chance that any given trap is an Eject Ability (ejects your ability when you receive it). + """ + display_name = "Ability Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class ConsumableChecks(Toggle): + """ + When enabled, adds all 1-Ups and Maxim Tomatoes as possible locations. + """ + display_name = "Consumable-sanity" + + +class StarChecks(Toggle): + """ + When enabled, every star in a given stage will become a check. + Will increase the possible filler pool to include 1/3/5 stars. + """ + display_name = "Starsanity" + + +class KirbyFlavorPreset(Choice): + """ + The color of Kirby, from a list of presets. + """ + display_name = "Kirby Flavor" + option_default = 0 + option_bubblegum = 1 + option_cherry = 2 + option_blueberry = 3 + option_lemon = 4 + option_kiwi = 5 + option_grape = 6 + option_chocolate = 7 + option_marshmallow = 8 + option_licorice = 9 + option_watermelon = 10 + option_orange = 11 + option_lime = 12 + option_lavender = 13 + option_custom = 14 + default = 0 + + @classmethod + def from_text(cls, text: str) -> Choice: + text = text.lower() + if text == "random": + choice_list = list(cls.name_lookup) + choice_list.remove(14) + return cls(random.choice(choice_list)) + return super().from_text(text) + + +class KirbyFlavor(OptionDict): + """ + A custom color for Kirby. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to + "15", with their values being an HTML hex color. + """ + default = { + "1": "B01810", + "2": "F0E0E8", + "3": "C8A0A8", + "4": "A87070", + "5": "E02018", + "6": "F0A0B8", + "7": "D07880", + "8": "A85048", + "9": "E8D0D0", + "10": "E85048", + "11": "D0C0C0", + "12": "B08888", + "13": "E87880", + "14": "F8F8F8", + "15": "B03830", + } + + +class GooeyFlavorPreset(Choice): + """ + The color of Gooey, from a list of presets. + """ + display_name = "Gooey Flavor" + option_default = 0 + option_bubblegum = 1 + option_cherry = 2 + option_blueberry = 3 + option_lemon = 4 + option_kiwi = 5 + option_grape = 6 + option_chocolate = 7 + option_marshmallow = 8 + option_licorice = 9 + option_watermelon = 10 + option_orange = 11 + option_lime = 12 + option_lavender = 13 + option_custom = 14 + default = 0 + + @classmethod + def from_text(cls, text: str) -> Choice: + text = text.lower() + if text == "random": + choice_list = list(cls.name_lookup) + choice_list.remove(14) + return cls(random.choice(choice_list)) + return super().from_text(text) + + +class GooeyFlavor(OptionDict): + """ + A custom color for Gooey. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to + "15", with their values being an HTML hex color. + """ + default = { + "1": "000808", + "2": "102838", + "3": "183048", + "4": "183878", + "5": "1838A0", + "6": "B01810", + "7": "E85048", + "8": "D0C0C0", + "9": "F8F8F8", + } + + +class MusicShuffle(Choice): + """ + None: default music will play + Shuffled: music will be shuffled amongst each other + Full: random music will play in each room + Note that certain songs will not be chosen in shuffled or full + """ + display_name = "Music Randomization" + option_none = 0 + option_shuffled = 1 + option_full = 2 + default = 0 + + +class VirtualConsoleChanges(Choice): + """ + Adds the ability to enable 2 of the Virtual Console changes. + Flash Reduction: reduces the flashing during the Zero battle. + Color Changes: changes the color of the background within the Zero Boss Butch rematch. + """ + display_name = "Virtual Console Changes" + option_none = 0 + option_flash_reduction = 1 + option_color_changes = 2 + option_both = 3 + default = 1 + + +class Gifting(Toggle): + """ + When enabled, the goal game item will be sent to other compatible games as a gift, + and you can receive gifts from other players. This can be enabled during gameplay + using the client. + """ + display_name = "Gifting" + + +@dataclass +class KDL3Options(PerGameCommonOptions): + death_link: DeathLink + game_language: GameLanguage + goal: Goal + goal_speed: GoalSpeed + total_heart_stars: TotalHeartStars + heart_stars_required: HeartStarsRequired + filler_percentage: FillerPercentage + trap_percentage: TrapPercentage + gooey_trap_weight: GooeyTrapPercentage + slow_trap_weight: SlowTrapPercentage + ability_trap_weight: AbilityTrapPercentage + jumping_target: JumpingTarget + stage_shuffle: LevelShuffle + boss_shuffle: BossShuffle + allow_bb: BossShuffleAllowBB + animal_randomization: AnimalRandomization + copy_ability_randomization: CopyAbilityRandomization + strict_bosses: StrictBosses + open_world: OpenWorld + ow_boss_requirement: OpenWorldBossRequirement + boss_requirement_random: BossRequirementRandom + consumables: ConsumableChecks + starsanity: StarChecks + gifting: Gifting + kirby_flavor_preset: KirbyFlavorPreset + kirby_flavor: KirbyFlavor + gooey_flavor_preset: GooeyFlavorPreset + gooey_flavor: GooeyFlavor + music_shuffle: MusicShuffle + virtual_console: VirtualConsoleChanges diff --git a/worlds/kdl3/Presets.py b/worlds/kdl3/Presets.py new file mode 100644 index 000000000000..d3a7146ded5f --- /dev/null +++ b/worlds/kdl3/Presets.py @@ -0,0 +1,56 @@ +from typing import Dict, Any + +all_random = { + "progression_balancing": "random", + "accessibility": "random", + "death_link": "random", + "game_language": "random", + "goal": "random", + "goal_speed": "random", + "total_heart_stars": "random", + "heart_stars_required": "random", + "filler_percentage": "random", + "trap_percentage": "random", + "gooey_trap_weight": "random", + "slow_trap_weight": "random", + "ability_trap_weight": "random", + "jumping_target": "random", + "stage_shuffle": "random", + "boss_shuffle": "random", + "allow_bb": "random", + "animal_randomization": "random", + "copy_ability_randomization": "random", + "strict_bosses": "random", + "open_world": "random", + "ow_boss_requirement": "random", + "boss_requirement_random": "random", + "consumables": "random", + "kirby_flavor_preset": "random", + "gooey_flavor_preset": "random", + "music_shuffle": "random", +} + +beginner = { + "goal": "zero", + "goal_speed": "normal", + "total_heart_stars": 50, + "heart_stars_required": 30, + "filler_percentage": 25, + "trap_percentage": 0, + "gooey_trap_weight": "random", + "slow_trap_weight": "random", + "ability_trap_weight": "random", + "jumping_target": 5, + "stage_shuffle": "pattern", + "boss_shuffle": "shuffled", + "allow_bb": "random", + "strict_bosses": True, + "open_world": True, + "ow_boss_requirement": 3, +} + + +kdl3_options_presets: Dict[str, Dict[str, Any]] = { + "All Random": all_random, + "Beginner": beginner, +} diff --git a/worlds/kdl3/Regions.py b/worlds/kdl3/Regions.py new file mode 100644 index 000000000000..ed0d86586615 --- /dev/null +++ b/worlds/kdl3/Regions.py @@ -0,0 +1,231 @@ +import orjson +import os +import typing +from pkgutil import get_data + +from BaseClasses import Region +from worlds.generic.Rules import add_item_rule +from .Locations import KDL3Location +from .Names import LocationName +from .Options import BossShuffle +from .Room import KDL3Room + +if typing.TYPE_CHECKING: + from . import KDL3World + +default_levels = { + 1: [0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770006, 0x770200], + 2: [0x770007, 0x770008, 0x770009, 0x77000A, 0x77000B, 0x77000C, 0x770201], + 3: [0x77000D, 0x77000E, 0x77000F, 0x770010, 0x770011, 0x770012, 0x770202], + 4: [0x770013, 0x770014, 0x770015, 0x770016, 0x770017, 0x770018, 0x770203], + 5: [0x770019, 0x77001A, 0x77001B, 0x77001C, 0x77001D, 0x77001E, 0x770204], +} + +first_stage_blacklist = { + # We want to confirm that the first stage can be completed without any items + 0x77000B, # 2-5 needs Kine + 0x770011, # 3-5 needs Cutter + 0x77001C, # 5-4 needs Burning +} + + +def generate_valid_level(level, stage, possible_stages, slot_random): + new_stage = slot_random.choice(possible_stages) + if level == 1 and stage == 0 and new_stage in first_stage_blacklist: + return generate_valid_level(level, stage, possible_stages, slot_random) + else: + return new_stage + + +def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing.Dict[int, Region]): + level_names = {LocationName.level_names[level]: level for level in LocationName.level_names} + room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json"))) + rooms: typing.Dict[str, KDL3Room] = dict() + for room_entry in room_data: + room = KDL3Room(room_entry["name"], world.player, world.multiworld, None, room_entry["level"], + room_entry["stage"], room_entry["room"], room_entry["pointer"], room_entry["music"], + room_entry["default_exits"], room_entry["animal_pointers"], room_entry["enemies"], + room_entry["entity_load"], room_entry["consumables"], room_entry["consumables_pointer"]) + room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else + None for location in room_entry["locations"] + if (not any(x in location for x in ["1-Up", "Maxim"]) or + world.options.consumables.value) and ("Star" not in location + or world.options.starsanity.value)}, + KDL3Location) + rooms[room.name] = room + for location in room.locations: + if "Animal" in location.name: + add_item_rule(location, lambda item: item.name in { + "Rick Spawn", "Kine Spawn", "Coo Spawn", "Nago Spawn", "ChuChu Spawn", "Pitch Spawn" + }) + world.rooms = list(rooms.values()) + world.multiworld.regions.extend(world.rooms) + + first_rooms: typing.Dict[int, KDL3Room] = dict() + if door_shuffle: + # first, we need to generate the notable edge cases + # 5-6 is the first, being the most restrictive + # half of its rooms are required to be vanilla, but can be in different orders + # the room before it *must* contain the copy ability required to unlock the room's goal + + raise NotImplementedError() + else: + for name, room in rooms.items(): + if room.room == 0: + if room.stage == 7: + first_rooms[0x770200 + room.level - 1] = room + else: + first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room + exits = dict() + for def_exit in room.default_exits: + target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}" + access_rule = tuple(def_exit["access_rule"]) + exits[target] = lambda state, rule=access_rule: state.has_all(rule, world.player) + room.add_exits( + exits.keys(), + exits + ) + if world.options.open_world: + if any("Complete" in location.name for location in room.locations): + room.add_locations({f"{level_names[room.level]} {room.stage} - Stage Completion": None}, + KDL3Location) + + for level in world.player_levels: + for stage in range(6): + proper_stage = world.player_levels[level][stage] + stage_name = world.multiworld.get_location(world.location_id_to_name[proper_stage], + world.player).name.replace(" - Complete", "") + stage_regions = [rooms[room] for room in rooms if stage_name in rooms[room].name] + for region in stage_regions: + region.level = level + region.stage = stage + if world.options.open_world or stage == 0: + level_regions[level].add_exits([first_rooms[proper_stage].name]) + else: + world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][stage-1]], + world.player).parent_region.add_exits([first_rooms[proper_stage].name]) + level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name]) + + +def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_pattern: bool) -> dict: + levels: typing.Dict[int, typing.List[typing.Optional[int]]] = { + 1: [None] * 7, + 2: [None] * 7, + 3: [None] * 7, + 4: [None] * 7, + 5: [None] * 7, + } + + possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)] + if world.multiworld.plando_connections[world.player]: + for connection in world.multiworld.plando_connections[world.player]: + try: + entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1) + stage_world, stage_stage = connection.exit.rsplit(" ", 1) + new_stage = default_levels[LocationName.level_names[stage_world.strip()]][int(stage_stage) - 1] + levels[LocationName.level_names[entrance_world.strip()]][int(entrance_stage) - 1] = new_stage + possible_stages.remove(new_stage) + + except Exception: + raise Exception( + f"Invalid connection: {connection.entrance} =>" + f" {connection.exit} for player {world.player} ({world.player_name})") + + for level in range(1, 6): + for stage in range(6): + # Randomize bosses separately + try: + if levels[level][stage] is None: + stage_candidates = [candidate for candidate in possible_stages + if (enforce_world and candidate in default_levels[level]) + or (enforce_pattern and ((candidate - 1) & 0x00FFFF) % 6 == stage) + or (enforce_pattern == enforce_world) + ] + new_stage = generate_valid_level(level, stage, stage_candidates, + world.random) + possible_stages.remove(new_stage) + levels[level][stage] = new_stage + except Exception: + raise Exception(f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}") + + # now handle bosses + boss_shuffle: typing.Union[int, str] = world.options.boss_shuffle.value + plando_bosses = [] + if isinstance(boss_shuffle, str): + # boss plando + options = boss_shuffle.split(";") + boss_shuffle = BossShuffle.options[options.pop()] + for option in options: + if "-" in option: + loc, boss = option.split("-") + loc = loc.title() + boss = boss.title() + levels[LocationName.level_names[loc]][6] = LocationName.boss_names[boss] + plando_bosses.append(LocationName.boss_names[boss]) + else: + option = option.title() + for level in levels: + if levels[level][6] is None: + levels[level][6] = LocationName.boss_names[option] + plando_bosses.append(LocationName.boss_names[option]) + + if boss_shuffle > 0: + if boss_shuffle == BossShuffle.option_full: + possible_bosses = [default_levels[world.random.randint(1, 5)][6] + for _ in range(5 - len(plando_bosses))] + elif boss_shuffle == BossShuffle.option_singularity: + boss = world.random.randint(1, 5) + possible_bosses = [default_levels[boss][6] for _ in range(5 - len(plando_bosses))] + else: + possible_bosses = [default_levels[level][6] for level in default_levels + if default_levels[level][6] not in plando_bosses] + for level in levels: + if levels[level][6] is None: + boss = world.random.choice(possible_bosses) + levels[level][6] = boss + possible_bosses.remove(boss) + else: + for level in levels: + if levels[level][6] is None: + levels[level][6] = default_levels[level][6] + + for level in levels: + for stage in range(7): + assert levels[level][stage] is not None, "Level tried to be sent with a None stage, incorrect plando?" + + return levels + + +def create_levels(world: "KDL3World") -> None: + menu = Region("Menu", world.player, world.multiworld) + level1 = Region("Grass Land", world.player, world.multiworld) + level2 = Region("Ripple Field", world.player, world.multiworld) + level3 = Region("Sand Canyon", world.player, world.multiworld) + level4 = Region("Cloudy Park", world.player, world.multiworld) + level5 = Region("Iceberg", world.player, world.multiworld) + level6 = Region("Hyper Zone", world.player, world.multiworld) + levels = { + 1: level1, + 2: level2, + 3: level3, + 4: level4, + 5: level5, + } + level_shuffle = world.options.stage_shuffle.value + if level_shuffle != 0: + world.player_levels = generate_valid_levels( + world, + level_shuffle == 1, + level_shuffle == 2) + + generate_rooms(world, False, levels) + + level6.add_locations({LocationName.goals[world.options.goal]: None}, KDL3Location) + + menu.connect(level1, "Start Game") + level1.connect(level2, "To Level 2") + level2.connect(level3, "To Level 3") + level3.connect(level4, "To Level 4") + level4.connect(level5, "To Level 5") + menu.connect(level6, "To Level 6") # put the connection on menu, since you can reach it before level 5 on fast goal + world.multiworld.regions += [menu, level1, level2, level3, level4, level5, level6] diff --git a/worlds/kdl3/Rom.py b/worlds/kdl3/Rom.py new file mode 100644 index 000000000000..5a846ab8be5e --- /dev/null +++ b/worlds/kdl3/Rom.py @@ -0,0 +1,577 @@ +import typing +from pkgutil import get_data + +import Utils +from typing import Optional, TYPE_CHECKING +import hashlib +import os +import struct + +import settings +from worlds.Files import APDeltaPatch +from .Aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \ + get_gooey_palette +from .Compression import hal_decompress +import bsdiff4 + +if TYPE_CHECKING: + from . import KDL3World + +KDL3UHASH = "201e7658f6194458a3869dde36bf8ec2" +KDL3JHASH = "b2f2d004ea640c3db66df958fce122b2" + +level_pointers = { + 0x770001: 0x0084, + 0x770002: 0x009C, + 0x770003: 0x00B8, + 0x770004: 0x00D8, + 0x770005: 0x0104, + 0x770006: 0x0124, + 0x770007: 0x014C, + 0x770008: 0x0170, + 0x770009: 0x0190, + 0x77000A: 0x01B0, + 0x77000B: 0x01E8, + 0x77000C: 0x0218, + 0x77000D: 0x024C, + 0x77000E: 0x0270, + 0x77000F: 0x02A0, + 0x770010: 0x02C4, + 0x770011: 0x02EC, + 0x770012: 0x0314, + 0x770013: 0x03CC, + 0x770014: 0x0404, + 0x770015: 0x042C, + 0x770016: 0x044C, + 0x770017: 0x0478, + 0x770018: 0x049C, + 0x770019: 0x04E4, + 0x77001A: 0x0504, + 0x77001B: 0x0530, + 0x77001C: 0x0554, + 0x77001D: 0x05A8, + 0x77001E: 0x0640, + 0x770200: 0x0148, + 0x770201: 0x0248, + 0x770202: 0x03C8, + 0x770203: 0x04E0, + 0x770204: 0x06A4, + 0x770205: 0x06A8, +} + +bb_bosses = { + 0x770200: 0xED85F1, + 0x770201: 0xF01360, + 0x770202: 0xEDA3DF, + 0x770203: 0xEDC2B9, + 0x770204: 0xED7C3F, + 0x770205: 0xEC29D2, +} + +level_sprites = { + 0x19B2C6: 1827, + 0x1A195C: 1584, + 0x19F6F3: 1679, + 0x19DC8B: 1717, + 0x197900: 1872 +} + +stage_tiles = { + 0: [ + 0, 1, 2, + 16, 17, 18, + 32, 33, 34, + 48, 49, 50 + ], + 1: [ + 3, 4, 5, + 19, 20, 21, + 35, 36, 37, + 51, 52, 53 + ], + 2: [ + 6, 7, 8, + 22, 23, 24, + 38, 39, 40, + 54, 55, 56 + ], + 3: [ + 9, 10, 11, + 25, 26, 27, + 41, 42, 43, + 57, 58, 59, + ], + 4: [ + 12, 13, 64, + 28, 29, 65, + 44, 45, 66, + 60, 61, 67 + ], + 5: [ + 14, 15, 68, + 30, 31, 69, + 46, 47, 70, + 62, 63, 71 + ] +} + +heart_star_address = 0x2D0000 +heart_star_size = 456 +consumable_address = 0x2F91DD +consumable_size = 698 + +stage_palettes = [0x60964, 0x60B64, 0x60D64, 0x60F64, 0x61164] + +music_choices = [ + 2, # Boss 1 + 3, # Boss 2 (Unused) + 4, # Boss 3 (Miniboss) + 7, # Dedede + 9, # Event 2 (used once) + 10, # Field 1 + 11, # Field 2 + 12, # Field 3 + 13, # Field 4 + 14, # Field 5 + 15, # Field 6 + 16, # Field 7 + 17, # Field 8 + 18, # Field 9 + 19, # Field 10 + 20, # Field 11 + 21, # Field 12 (Gourmet Race) + 23, # Dark Matter in the Hyper Zone + 24, # Zero + 25, # Level 1 + 26, # Level 2 + 27, # Level 4 + 28, # Level 3 + 29, # Heart Star Failed + 30, # Level 5 + 31, # Minigame + 38, # Animal Friend 1 + 39, # Animal Friend 2 + 40, # Animal Friend 3 +] +# extra room pointers we don't want to track other than for music +room_pointers = [ + 3079990, # Zero + 2983409, # BB Whispy + 3150688, # BB Acro + 2991071, # BB PonCon + 2998969, # BB Ado + 2980927, # BB Dedede + 2894290 # BB Zero +] + +enemy_remap = { + "Waddle Dee": 0, + "Bronto Burt": 2, + "Rocky": 3, + "Bobo": 5, + "Chilly": 6, + "Poppy Bros Jr.": 7, + "Sparky": 8, + "Polof": 9, + "Broom Hatter": 11, + "Cappy": 12, + "Bouncy": 13, + "Nruff": 15, + "Glunk": 16, + "Togezo": 18, + "Kabu": 19, + "Mony": 20, + "Blipper": 21, + "Squishy": 22, + "Gabon": 24, + "Oro": 25, + "Galbo": 26, + "Sir Kibble": 27, + "Nidoo": 28, + "Kany": 29, + "Sasuke": 30, + "Yaban": 32, + "Boten": 33, + "Coconut": 34, + "Doka": 35, + "Icicle": 36, + "Pteran": 39, + "Loud": 40, + "Como": 41, + "Klinko": 42, + "Babut": 43, + "Wappa": 44, + "Mariel": 45, + "Tick": 48, + "Apolo": 49, + "Popon Ball": 50, + "KeKe": 51, + "Magoo": 53, + "Raft Waddle Dee": 57, + "Madoo": 58, + "Corori": 60, + "Kapar": 67, + "Batamon": 68, + "Peran": 72, + "Bobin": 73, + "Mopoo": 74, + "Gansan": 75, + "Bukiset (Burning)": 76, + "Bukiset (Stone)": 77, + "Bukiset (Ice)": 78, + "Bukiset (Needle)": 79, + "Bukiset (Clean)": 80, + "Bukiset (Parasol)": 81, + "Bukiset (Spark)": 82, + "Bukiset (Cutter)": 83, + "Waddle Dee Drawing": 84, + "Bronto Burt Drawing": 85, + "Bouncy Drawing": 86, + "Kabu (Dekabu)": 87, + "Wapod": 88, + "Propeller": 89, + "Dogon": 90, + "Joe": 91 +} + +miniboss_remap = { + "Captain Stitch": 0, + "Yuki": 1, + "Blocky": 2, + "Jumper Shoot": 3, + "Boboo": 4, + "Haboki": 5 +} + +ability_remap = { + "No Ability": 0, + "Burning Ability": 1, + "Stone Ability": 2, + "Ice Ability": 3, + "Needle Ability": 4, + "Clean Ability": 5, + "Parasol Ability": 6, + "Spark Ability": 7, + "Cutter Ability": 8, +} + + +class RomData: + def __init__(self, file: str, name: typing.Optional[str] = None): + self.file = bytearray() + self.read_from_file(file) + self.name = name + + def read_byte(self, offset: int): + return self.file[offset] + + def read_bytes(self, offset: int, length: int): + return self.file[offset:offset + length] + + def write_byte(self, offset: int, value: int): + self.file[offset] = value + + def write_bytes(self, offset: int, values: typing.Sequence) -> None: + self.file[offset:offset + len(values)] = values + + def write_to_file(self, file: str): + with open(file, 'wb') as outfile: + outfile.write(self.file) + + def read_from_file(self, file: str): + with open(file, 'rb') as stream: + self.file = bytearray(stream.read()) + + def apply_patch(self, patch: bytes): + self.file = bytearray(bsdiff4.patch(bytes(self.file), patch)) + + def write_crc(self): + crc = (sum(self.file[:0x7FDC] + self.file[0x7FE0:]) + 0x01FE) & 0xFFFF + inv = crc ^ 0xFFFF + self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF]) + + +def handle_level_sprites(stages, sprites, palettes): + palette_by_level = list() + for palette in palettes: + palette_by_level.extend(palette[10:16]) + for i in range(5): + for j in range(6): + palettes[i][10 + j] = palette_by_level[stages[i][j] - 1] + palettes[i] = [x for palette in palettes[i] for x in palette] + tiles_by_level = list() + for spritesheet in sprites: + decompressed = hal_decompress(spritesheet) + tiles = [decompressed[i:i + 32] for i in range(0, 2304, 32)] + tiles_by_level.extend([[tiles[x] for x in stage_tiles[stage]] for stage in stage_tiles]) + for world in range(5): + levels = [stages[world][x] - 1 for x in range(6)] + world_tiles: typing.List[typing.Optional[bytes]] = [None for _ in range(72)] + for i in range(6): + for x in range(12): + world_tiles[stage_tiles[i][x]] = tiles_by_level[levels[i]][x] + sprites[world] = list() + for tile in world_tiles: + sprites[world].extend(tile) + # insert our fake compression + sprites[world][0:0] = [0xe3, 0xff] + sprites[world][1026:1026] = [0xe3, 0xff] + sprites[world][2052:2052] = [0xe0, 0xff] + sprites[world].append(0xff) + return sprites, palettes + + +def write_heart_star_sprites(rom: RomData): + compressed = rom.read_bytes(heart_star_address, heart_star_size) + decompressed = hal_decompress(compressed) + patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4")) + patched = bytearray(bsdiff4.patch(decompressed, patch)) + rom.write_bytes(0x1AF7DF, patched) + patched[0:0] = [0xE3, 0xFF] + patched.append(0xFF) + rom.write_bytes(0x1CD000, patched) + rom.write_bytes(0x3F0EBF, [0x00, 0xD0, 0x39]) + + +def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool): + compressed = rom.read_bytes(consumable_address, consumable_size) + decompressed = hal_decompress(compressed) + patched = bytearray(decompressed) + if consumables: + patch = get_data(__name__, os.path.join("data", "APConsumable.bsdiff4")) + patched = bytearray(bsdiff4.patch(bytes(patched), patch)) + if stars: + patch = get_data(__name__, os.path.join("data", "APStars.bsdiff4")) + patched = bytearray(bsdiff4.patch(bytes(patched), patch)) + patched[0:0] = [0xE3, 0xFF] + patched.append(0xFF) + rom.write_bytes(0x1CD500, patched) + rom.write_bytes(0x3F0DAE, [0x00, 0xD5, 0x39]) + + +class KDL3DeltaPatch(APDeltaPatch): + hash = [KDL3UHASH, KDL3JHASH] + game = "Kirby's Dream Land 3" + patch_file_ending = ".apkdl3" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + def patch(self, target: str): + super().patch(target) + rom = RomData(target) + target_language = rom.read_byte(0x3C020) + rom.write_byte(0x7FD9, target_language) + write_heart_star_sprites(rom) + if rom.read_bytes(0x3D014, 1)[0] > 0: + stages = [struct.unpack("HHHHHHH", rom.read_bytes(0x3D020 + x * 14, 14)) for x in range(5)] + palettes = [rom.read_bytes(full_pal, 512) for full_pal in stage_palettes] + palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes] + sprites = [rom.read_bytes(offset, level_sprites[offset]) for offset in level_sprites] + sprites, palettes = handle_level_sprites(stages, sprites, palettes) + for addr, palette in zip(stage_palettes, palettes): + rom.write_bytes(addr, palette) + for addr, level_sprite in zip([0x1CA000, 0x1CA920, 0x1CB230, 0x1CBB40, 0x1CC450], sprites): + rom.write_bytes(addr, level_sprite) + rom.write_bytes(0x460A, [0x00, 0xA0, 0x39, 0x20, 0xA9, 0x39, 0x30, 0xB2, 0x39, 0x40, 0xBB, 0x39, + 0x50, 0xC4, 0x39]) + write_consumable_sprites(rom, rom.read_byte(0x3D018) > 0, rom.read_byte(0x3D01A) > 0) + rom_name = rom.read_bytes(0x3C000, 21) + rom.write_bytes(0x7FC0, rom_name) + rom.write_crc() + rom.write_to_file(target) + + +def patch_rom(world: "KDL3World", rom: RomData): + rom.apply_patch(get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4"))) + tiles = get_data(__name__, os.path.join("data", "APPauseIcons.dat")) + rom.write_bytes(0x3F000, tiles) + + # Write open world patch + if world.options.open_world: + rom.write_bytes(0x143C7, [0xAD, 0xC1, 0x5A, 0xCD, 0xC1, 0x5A, ]) + # changes the stage flag function to compare $5AC1 to $5AC1, + # always running the "new stage" function + # This has further checks present for bosses already, so we just + # need to handle regular stages + # write check for boss to be unlocked + + if world.options.consumables: + # reroute maxim tomatoes to use the 1-UP function, then null out the function + rom.write_bytes(0x3002F, [0x37, 0x00]) + rom.write_bytes(0x30037, [0xA9, 0x26, 0x00, # LDA #$0026 + 0x22, 0x27, 0xD9, 0x00, # JSL $00D927 + 0xA4, 0xD2, # LDY $D2 + 0x6B, # RTL + 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, # NOP #10 + ]) + + # stars handling is built into the rom, so no changes there + + rooms = world.rooms + if world.options.music_shuffle > 0: + if world.options.music_shuffle == 1: + shuffled_music = music_choices.copy() + world.random.shuffle(shuffled_music) + music_map = dict(zip(music_choices, shuffled_music)) + # Avoid putting star twinkle in the pool + music_map[5] = world.random.choice(music_choices) + # Heart Star music doesn't work on regular stages + music_map[8] = world.random.choice(music_choices) + for room in rooms: + room.music = music_map[room.music] + for room in room_pointers: + old_music = rom.read_byte(room + 2) + rom.write_byte(room + 2, music_map[old_music]) + for i in range(5): + # level themes + old_music = rom.read_byte(0x133F2 + i) + rom.write_byte(0x133F2 + i, music_map[old_music]) + # Zero + rom.write_byte(0x9AE79, music_map[0x18]) + # Heart Star success and fail + rom.write_byte(0x4A388, music_map[0x08]) + rom.write_byte(0x4A38D, music_map[0x1D]) + elif world.options.music_shuffle == 2: + for room in rooms: + room.music = world.random.choice(music_choices) + for room in room_pointers: + rom.write_byte(room + 2, world.random.choice(music_choices)) + for i in range(5): + # level themes + rom.write_byte(0x133F2 + i, world.random.choice(music_choices)) + # Zero + rom.write_byte(0x9AE79, world.random.choice(music_choices)) + # Heart Star success and fail + rom.write_byte(0x4A388, world.random.choice(music_choices)) + rom.write_byte(0x4A38D, world.random.choice(music_choices)) + + for room in rooms: + room.patch(rom) + + if world.options.virtual_console in [1, 3]: + # Flash Reduction + rom.write_byte(0x9AE68, 0x10) + rom.write_bytes(0x9AE8E, [0x08, 0x00, 0x22, 0x5D, 0xF7, 0x00, 0xA2, 0x08, ]) + rom.write_byte(0x9AEA1, 0x08) + rom.write_byte(0x9AEC9, 0x01) + rom.write_bytes(0x9AED2, [0xA9, 0x1F]) + rom.write_byte(0x9AEE1, 0x08) + + if world.options.virtual_console in [2, 3]: + # Hyper Zone BB colors + rom.write_bytes(0x2C5E16, [0xEE, 0x1B, 0x18, 0x5B, 0xD3, 0x4A, 0xF4, 0x3B, ]) + rom.write_bytes(0x2C8217, [0xFF, 0x1E, ]) + + # boss requirements + rom.write_bytes(0x3D000, struct.pack("HHHHH", world.boss_requirements[0], world.boss_requirements[1], + world.boss_requirements[2], world.boss_requirements[3], + world.boss_requirements[4])) + rom.write_bytes(0x3D00A, struct.pack("H", world.required_heart_stars if world.options.goal_speed == 1 else 0xFFFF)) + rom.write_byte(0x3D00C, world.options.goal_speed.value) + rom.write_byte(0x3D00E, world.options.open_world.value) + rom.write_byte(0x3D010, world.options.death_link.value) + rom.write_byte(0x3D012, world.options.goal.value) + rom.write_byte(0x3D014, world.options.stage_shuffle.value) + rom.write_byte(0x3D016, world.options.ow_boss_requirement.value) + rom.write_byte(0x3D018, world.options.consumables.value) + rom.write_byte(0x3D01A, world.options.starsanity.value) + rom.write_byte(0x3D01C, world.options.gifting.value if world.multiworld.players > 1 else 0) + rom.write_byte(0x3D01E, world.options.strict_bosses.value) + # don't write gifting for solo game, since there's no one to send anything to + + for level in world.player_levels: + for i in range(len(world.player_levels[level])): + rom.write_bytes(0x3F002E + ((level - 1) * 14) + (i * 2), + struct.pack("H", level_pointers[world.player_levels[level][i]])) + rom.write_bytes(0x3D020 + (level - 1) * 14 + (i * 2), + struct.pack("H", world.player_levels[level][i] & 0x00FFFF)) + if (i == 0) or (i > 0 and i % 6 != 0): + rom.write_bytes(0x3D080 + (level - 1) * 12 + (i * 2), + struct.pack("H", (world.player_levels[level][i] & 0x00FFFF) % 6)) + + for i in range(6): + if world.boss_butch_bosses[i]: + rom.write_bytes(0x3F0000 + (level_pointers[0x770200 + i]), struct.pack("I", bb_bosses[0x770200 + i])) + + # copy ability shuffle + if world.options.copy_ability_randomization.value > 0: + for enemy in world.copy_abilities: + if enemy in miniboss_remap: + rom.write_bytes(0xB417E + (miniboss_remap[enemy] << 1), + struct.pack("H", ability_remap[world.copy_abilities[enemy]])) + else: + rom.write_bytes(0xB3CAC + (enemy_remap[enemy] << 1), + struct.pack("H", ability_remap[world.copy_abilities[enemy]])) + # following only needs done on non-door rando + # incredibly lucky this follows the same order (including 5E == star block) + rom.write_byte(0x2F77EA, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)) + rom.write_byte(0x2F7811, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)) + rom.write_byte(0x2F9BC4, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)) + rom.write_byte(0x2F9BEB, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)) + rom.write_byte(0x2FAC06, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)) + rom.write_byte(0x2FAC2D, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)) + rom.write_byte(0x2F9E7B, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)) + rom.write_byte(0x2F9EA2, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)) + rom.write_byte(0x2FA951, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)) + rom.write_byte(0x2FA978, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)) + rom.write_byte(0x2FA132, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)) + rom.write_byte(0x2FA159, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)) + rom.write_byte(0x2FA3E8, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)) + rom.write_byte(0x2FA40F, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)) + rom.write_byte(0x2F90E2, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)) + rom.write_byte(0x2F9109, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)) + + if world.options.copy_ability_randomization == 2: + for enemy in enemy_remap: + # we just won't include it for minibosses + rom.write_bytes(0xB3E40 + (enemy_remap[enemy] << 1), struct.pack("h", world.random.randint(-1, 2))) + + # write jumping goal + rom.write_bytes(0x94F8, struct.pack("H", world.options.jumping_target)) + rom.write_bytes(0x944E, struct.pack("H", world.options.jumping_target)) + + from Utils import __version__ + rom.name = bytearray( + f'KDL3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] + rom.name.extend([0] * (21 - len(rom.name))) + rom.write_bytes(0x3C000, rom.name) + rom.write_byte(0x3C020, world.options.game_language.value) + + # handle palette + if world.options.kirby_flavor_preset.value != 0: + for addr in kirby_target_palettes: + target = kirby_target_palettes[addr] + palette = get_kirby_palette(world) + rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2])) + + if world.options.gooey_flavor_preset.value != 0: + for addr in gooey_target_palettes: + target = gooey_target_palettes[addr] + palette = get_gooey_palette(world) + rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2])) + + +def get_base_rom_bytes() -> bytes: + rom_file: str = get_base_rom_path() + base_rom_bytes: Optional[bytes] = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + base_rom_bytes = bytes(Utils.read_snes_rom(open(rom_file, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if basemd5.hexdigest() not in {KDL3UHASH, KDL3JHASH}: + raise Exception("Supplied Base Rom does not match known MD5 for US or JP release. " + "Get the correct game and version, then dump it") + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + options: settings.Settings = settings.get_settings() + if not file_name: + file_name = options["kdl3_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/kdl3/Room.py b/worlds/kdl3/Room.py new file mode 100644 index 000000000000..256955b924ab --- /dev/null +++ b/worlds/kdl3/Room.py @@ -0,0 +1,95 @@ +import struct +import typing +from BaseClasses import Region, ItemClassification + +if typing.TYPE_CHECKING: + from .Rom import RomData + +animal_map = { + "Rick Spawn": 0, + "Kine Spawn": 1, + "Coo Spawn": 2, + "Nago Spawn": 3, + "ChuChu Spawn": 4, + "Pitch Spawn": 5 +} + + +class KDL3Room(Region): + pointer: int = 0 + level: int = 0 + stage: int = 0 + room: int = 0 + music: int = 0 + default_exits: typing.List[typing.Dict[str, typing.Union[int, typing.List[str]]]] + animal_pointers: typing.List[int] + enemies: typing.List[str] + entity_load: typing.List[typing.List[int]] + consumables: typing.List[typing.Dict[str, typing.Union[int, str]]] + + def __init__(self, name, player, multiworld, hint, level, stage, room, pointer, music, default_exits, + animal_pointers, enemies, entity_load, consumables, consumable_pointer): + super().__init__(name, player, multiworld, hint) + self.level = level + self.stage = stage + self.room = room + self.pointer = pointer + self.music = music + self.default_exits = default_exits + self.animal_pointers = animal_pointers + self.enemies = enemies + self.entity_load = entity_load + self.consumables = consumables + self.consumable_pointer = consumable_pointer + + def patch(self, rom: "RomData"): + rom.write_byte(self.pointer + 2, self.music) + animals = [x.item.name for x in self.locations if "Animal" in x.name] + if len(animals) > 0: + for current_animal, address in zip(animals, self.animal_pointers): + rom.write_byte(self.pointer + address + 7, animal_map[current_animal]) + if self.multiworld.worlds[self.player].options.consumables: + load_len = len(self.entity_load) + for consumable in self.consumables: + location = next(x for x in self.locations if x.name == consumable["name"]) + assert location.item + is_progression = location.item.classification & ItemClassification.progression + if load_len == 8: + # edge case, there is exactly 1 room with 8 entities and only 1 consumable among them + if not (any(x in self.entity_load for x in [[0, 22], [1, 22]]) + and any(x in self.entity_load for x in [[2, 22], [3, 22]])): + replacement_target = self.entity_load.index( + next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]])) + if is_progression: + vtype = 0 + else: + vtype = 2 + rom.write_byte(self.pointer + 88 + (replacement_target * 2), vtype) + self.entity_load[replacement_target] = [vtype, 22] + else: + if is_progression: + # we need to see if 1-ups are in our load list + if any(x not in self.entity_load for x in [[0, 22], [1, 22]]): + self.entity_load.append([0, 22]) + else: + if any(x not in self.entity_load for x in [[2, 22], [3, 22]]): + # edge case: if (1, 22) is in, we need to load (3, 22) instead + if [1, 22] in self.entity_load: + self.entity_load.append([3, 22]) + else: + self.entity_load.append([2, 22]) + if load_len < len(self.entity_load): + rom.write_bytes(self.pointer + 88 + (load_len * 2), bytes(self.entity_load[load_len])) + rom.write_bytes(self.pointer + 104 + (load_len * 2), + bytes(struct.pack("H", self.consumable_pointer))) + if is_progression: + if [1, 22] in self.entity_load: + vtype = 1 + else: + vtype = 0 + else: + if [3, 22] in self.entity_load: + vtype = 3 + else: + vtype = 2 + rom.write_byte(self.pointer + consumable["pointer"] + 7, vtype) diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/Rules.py new file mode 100644 index 000000000000..81ad8f1f1fc0 --- /dev/null +++ b/worlds/kdl3/Rules.py @@ -0,0 +1,332 @@ +from worlds.generic.Rules import set_rule, add_rule +from .Names import LocationName, EnemyAbilities +from .Locations import location_table +from .Options import GoalSpeed +import typing + +if typing.TYPE_CHECKING: + from . import KDL3World + from BaseClasses import CollectionState + + +def can_reach_boss(state: "CollectionState", player: int, level: int, open_world: int, + ow_boss_req: int, player_levels: typing.Dict[int, typing.Dict[int, int]]): + if open_world: + return state.has(f"{LocationName.level_names_inverse[level]} - Stage Completion", player, ow_boss_req) + else: + return state.can_reach(location_table[player_levels[level][5]], "Location", player) + + +def can_reach_rick(state: "CollectionState", player: int) -> bool: + return state.has("Rick", player) and state.has("Rick Spawn", player) + + +def can_reach_kine(state: "CollectionState", player: int) -> bool: + return state.has("Kine", player) and state.has("Kine Spawn", player) + + +def can_reach_coo(state: "CollectionState", player: int) -> bool: + return state.has("Coo", player) and state.has("Coo Spawn", player) + + +def can_reach_nago(state: "CollectionState", player: int) -> bool: + return state.has("Nago", player) and state.has("Nago Spawn", player) + + +def can_reach_chuchu(state: "CollectionState", player: int) -> bool: + return state.has("ChuChu", player) and state.has("ChuChu Spawn", player) + + +def can_reach_pitch(state: "CollectionState", player: int) -> bool: + return state.has("Pitch", player) and state.has("Pitch Spawn", player) + + +def can_reach_burning(state: "CollectionState", player: int) -> bool: + return state.has("Burning", player) and state.has("Burning Ability", player) + + +def can_reach_stone(state: "CollectionState", player: int) -> bool: + return state.has("Stone", player) and state.has("Stone Ability", player) + + +def can_reach_ice(state: "CollectionState", player: int) -> bool: + return state.has("Ice", player) and state.has("Ice Ability", player) + + +def can_reach_needle(state: "CollectionState", player: int) -> bool: + return state.has("Needle", player) and state.has("Needle Ability", player) + + +def can_reach_clean(state: "CollectionState", player: int) -> bool: + return state.has("Clean", player) and state.has("Clean Ability", player) + + +def can_reach_parasol(state: "CollectionState", player: int) -> bool: + return state.has("Parasol", player) and state.has("Parasol Ability", player) + + +def can_reach_spark(state: "CollectionState", player: int) -> bool: + return state.has("Spark", player) and state.has("Spark Ability", player) + + +def can_reach_cutter(state: "CollectionState", player: int) -> bool: + return state.has("Cutter", player) and state.has("Cutter Ability", player) + + +ability_map: typing.Dict[str, typing.Callable[["CollectionState", int], bool]] = { + "No Ability": lambda state, player: True, + "Burning Ability": can_reach_burning, + "Stone Ability": can_reach_stone, + "Ice Ability": can_reach_ice, + "Needle Ability": can_reach_needle, + "Clean Ability": can_reach_clean, + "Parasol Ability": can_reach_parasol, + "Spark Ability": can_reach_spark, + "Cutter Ability": can_reach_cutter, +} + + +def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): + # check animal requirements + if not (can_reach_coo(state, player) and can_reach_kine(state, player)): + return False + for abilities, bukisets in EnemyAbilities.enemy_restrictive[1:5]: + iterator = iter(x for x in bukisets if copy_abilities[x] in abilities) + target_bukiset = next(iterator, None) + can_reach = False + while target_bukiset is not None: + can_reach = can_reach | ability_map[copy_abilities[target_bukiset]](state, player) + target_bukiset = next(iterator, None) + if not can_reach: + return False + # now the known needed abilities + return can_reach_parasol(state, player) and can_reach_stone(state, player) + + +def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): + can_reach = True + for enemy in {"Sparky", "Blocky", "Jumper Shoot", "Yuki", "Sir Kibble", "Haboki", "Boboo", "Captain Stitch"}: + can_reach = can_reach & ability_map[copy_abilities[enemy]](state, player) + return can_reach + + +def set_rules(world: "KDL3World") -> None: + # Level 1 + set_rule(world.multiworld.get_location(LocationName.grass_land_muchi, world.player), + lambda state: can_reach_chuchu(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_chao, world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_mine, world.player), + lambda state: can_reach_kine(state, world.player)) + + # Level 2 + set_rule(world.multiworld.get_location(LocationName.ripple_field_5, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_kamuribana, world.player), + lambda state: can_reach_pitch(state, world.player) and can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_bakasa, world.player), + lambda state: can_reach_kine(state, world.player) and can_reach_parasol(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_toad, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_mama_pitch, world.player), + lambda state: (can_reach_pitch(state, world.player) and + can_reach_kine(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_stone(state, world.player))) + + # Level 3 + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5, world.player), + lambda state: can_reach_cutter(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_auntie, world.player), + lambda state: can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_nyupun, world.player), + lambda state: can_reach_chuchu(state, world.player) and can_reach_cutter(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_rob, world.player), + lambda state: can_assemble_rob(state, world.player, world.copy_abilities) + ) + + # Level 4 + set_rule(world.multiworld.get_location(LocationName.cloudy_park_hibanamodoki, world.player), + lambda state: can_reach_coo(state, world.player) and can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_piyokeko, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_mikarin, world.player), + lambda state: can_reach_coo(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_pick, world.player), + lambda state: can_reach_rick(state, world.player)) + + # Level 5 + set_rule(world.multiworld.get_location(LocationName.iceberg_4, world.player), + lambda state: can_reach_burning(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_kogoesou, world.player), + lambda state: can_reach_burning(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_samus, world.player), + lambda state: can_reach_ice(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_name, world.player), + lambda state: (can_reach_coo(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_chuchu(state, world.player))) + # ChuChu is guaranteed here, but we use this for consistency + set_rule(world.multiworld.get_location(LocationName.iceberg_shiro, world.player), + lambda state: can_reach_nago(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_angel, world.player), + lambda state: can_fix_angel_wings(state, world.player, world.copy_abilities)) + + # Consumables + if world.options.consumables: + set_rule(world.multiworld.get_location(LocationName.grass_land_1_u1, world.player), + lambda state: can_reach_parasol(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_1_m1, world.player), + lambda state: can_reach_spark(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_2_u1, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_2_u1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_2_m1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_3_u1, world.player), + lambda state: can_reach_cutter(state, world.player) or can_reach_spark(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_4_u1, world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_4_m2, world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_5_u1, world.player), + lambda state: (can_reach_kine(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_stone(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m2, world.player), + lambda state: (can_reach_kine(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_stone(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_u1, world.player), + lambda state: can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_m2, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u3, world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u4, world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_6_u1, world.player), + lambda state: can_reach_cutter(state, world.player)) + + if world.options.starsanity: + # ranges are our friend + for i in range(7, 11): + set_rule(world.multiworld.get_location(f"Grass Land 1 - Star {i}", world.player), + lambda state: can_reach_cutter(state, world.player)) + for i in range(11, 14): + set_rule(world.multiworld.get_location(f"Grass Land 1 - Star {i}", world.player), + lambda state: can_reach_parasol(state, world.player)) + for i in [1, 3, 4, 9, 10]: + set_rule(world.multiworld.get_location(f"Grass Land 2 - Star {i}", world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location("Grass Land 2 - Star 2", world.player), + lambda state: can_reach_burning(state, world.player)) + set_rule(world.multiworld.get_location("Ripple Field 2 - Star 17", world.player), + lambda state: can_reach_kine(state, world.player)) + for i in range(41, 43): + # any star past this point also needs kine, but so does the exit + set_rule(world.multiworld.get_location(f"Ripple Field 5 - Star {i}", world.player), + lambda state: can_reach_kine(state, world.player)) + for i in range(46, 49): + # also requires kine, but only for access from the prior room + set_rule(world.multiworld.get_location(f"Ripple Field 5 - Star {i}", world.player), + lambda state: can_reach_burning(state, world.player) and can_reach_stone(state, world.player)) + for i in range(12, 18): + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + for i in range(21, 23): + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_chuchu(state, world.player)) + for r in [range(19, 21), range(23, 31)]: + for i in r: + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_clean(state, world.player)) + for i in range(31, 41): + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_burning(state, world.player)) + for r in [range(1, 31), range(44, 51)]: + for i in r: + set_rule(world.multiworld.get_location(f"Cloudy Park 4 - Star {i}", world.player), + lambda state: can_reach_clean(state, world.player)) + for i in [18, *list(range(20, 25))]: + set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player), + lambda state: can_reach_ice(state, world.player)) + for i in [19, *list(range(25, 30))]: + set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player), + lambda state: can_reach_ice(state, world.player)) + # copy ability access edge cases + # Kirby cannot eat enemies fully submerged in water. Vast majority of cases, the enemy can be brought to the surface + # and eaten by inhaling while falling on top of them + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_2_E3, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_3_E6, world.player), + lambda state: can_reach_kine(state, world.player)) + # Ripple Field 4 E5, E7, and E8 are doable, but too strict to leave in logic + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E5, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E7, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E8, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E2, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E3, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E4, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E7, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E8, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E9, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E10, world.player), + lambda state: can_reach_kine(state, world.player)) + + for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified", + "Level 3 Boss - Purified", "Level 4 Boss - Purified", + "Level 5 Boss - Purified"], + [LocationName.grass_land_whispy, LocationName.ripple_field_acro, + LocationName.sand_canyon_poncon, LocationName.cloudy_park_ado, + LocationName.iceberg_dedede], + range(1, 6)): + set_rule(world.multiworld.get_location(boss_flag, world.player), + lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) + and can_reach_boss(state, world.player, i, + world.options.open_world.value, + world.options.ow_boss_requirement.value, + world.player_levels))) + set_rule(world.multiworld.get_location(purification, world.player), + lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) + and can_reach_boss(state, world.player, i, + world.options.open_world.value, + world.options.ow_boss_requirement.value, + world.player_levels))) + + set_rule(world.multiworld.get_entrance("To Level 6", world.player), + lambda state: state.has("Heart Star", world.player, world.required_heart_stars)) + + for level in range(2, 6): + set_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), + lambda state, i=level: state.has(f"Level {i - 1} Boss Defeated", world.player)) + + if world.options.strict_bosses: + for level in range(2, 6): + add_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), + lambda state, i=level: state.has(f"Level {i - 1} Boss Purified", world.player)) + + if world.options.goal_speed == GoalSpeed.option_normal: + add_rule(world.multiworld.get_entrance("To Level 6", world.player), + lambda state: state.has_all(["Level 1 Boss Purified", "Level 2 Boss Purified", "Level 3 Boss Purified", + "Level 4 Boss Purified", "Level 5 Boss Purified"], world.player)) diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py new file mode 100644 index 000000000000..66c9b17b84f4 --- /dev/null +++ b/worlds/kdl3/__init__.py @@ -0,0 +1,350 @@ +import logging +import typing + +from BaseClasses import Tutorial, ItemClassification, MultiWorld +from Fill import fill_restrictive +from Options import PerGameCommonOptions +from worlds.AutoWorld import World, WebWorld +from .Items import item_table, item_names, copy_ability_table, animal_friend_table, filler_item_weights, KDL3Item, \ + trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights +from .Locations import location_table, KDL3Location, level_consumables, consumable_locations, star_locations +from .Names.AnimalFriendSpawns import animal_friend_spawns +from .Names.EnemyAbilities import vanilla_enemies, enemy_mapping, enemy_restrictive +from .Regions import create_levels, default_levels +from .Options import KDL3Options +from .Presets import kdl3_options_presets +from .Names import LocationName +from .Room import KDL3Room +from .Rules import set_rules +from .Rom import KDL3DeltaPatch, get_base_rom_path, RomData, patch_rom, KDL3JHASH, KDL3UHASH +from .Client import KDL3SNIClient + +from typing import Dict, TextIO, Optional, List +import os +import math +import threading +import base64 +import settings + +logger = logging.getLogger("Kirby's Dream Land 3") + + +class KDL3Settings(settings.Group): + class RomFile(settings.SNESRomPath): + """File name of the KDL3 JP or EN rom""" + description = "Kirby's Dream Land 3 ROM File" + copy_to = "Kirby's Dream Land 3.sfc" + md5s = [KDL3JHASH, KDL3UHASH] + + rom_file: RomFile = RomFile(RomFile.copy_to) + + +class KDL3WebWorld(WebWorld): + theme = "partyTime" + tutorials = [ + + Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Kirby's Dream Land 3 randomizer connected to an Archipelago Multiworld.", + "English", + "setup_en.md", + "setup/en", + ["Silvris"] + ) + ] + options_presets = kdl3_options_presets + + +class KDL3World(World): + """ + Join Kirby and his Animal Friends on an adventure to collect Heart Stars and drive Dark Matter away from Dream Land! + """ + + game = "Kirby's Dream Land 3" + options_dataclass: typing.ClassVar[typing.Type[PerGameCommonOptions]] = KDL3Options + options: KDL3Options + item_name_to_id = {item: item_table[item].code for item in item_table} + location_name_to_id = {location_table[location]: location for location in location_table} + item_name_groups = item_names + web = KDL3WebWorld() + settings: typing.ClassVar[KDL3Settings] + + def __init__(self, multiworld: MultiWorld, player: int): + self.rom_name = None + self.rom_name_available_event = threading.Event() + super().__init__(multiworld, player) + self.copy_abilities: Dict[str, str] = vanilla_enemies.copy() + self.required_heart_stars: int = 0 # we fill this during create_items + self.boss_requirements: Dict[int, int] = dict() + self.player_levels = default_levels.copy() + self.stage_shuffle_enabled = False + self.boss_butch_bosses: List[Optional[bool]] = list() + self.rooms: Optional[List[KDL3Room]] = None + + @classmethod + def stage_assert_generate(cls, multiworld: MultiWorld) -> None: + rom_file: str = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(f"Could not find base ROM for {cls.game}: {rom_file}") + + create_regions = create_levels + + def create_item(self, name: str, force_non_progression=False) -> KDL3Item: + item = item_table[name] + classification = ItemClassification.filler + if item.progression and not force_non_progression: + classification = ItemClassification.progression_skip_balancing \ + if item.skip_balancing else ItemClassification.progression + elif item.trap: + classification = ItemClassification.trap + return KDL3Item(name, classification, item.code, self.player) + + def get_filler_item_name(self, include_stars=True) -> str: + if include_stars: + return self.random.choices(list(total_filler_weights.keys()), + weights=list(total_filler_weights.values()))[0] + return self.random.choices(list(filler_item_weights.keys()), + weights=list(filler_item_weights.values()))[0] + + def get_trap_item_name(self) -> str: + return self.random.choices(["Gooey Bag", "Slowness", "Eject Ability"], + weights=[self.options.gooey_trap_weight.value, + self.options.slow_trap_weight.value, + self.options.ability_trap_weight.value])[0] + + def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: typing.List[str], + level: int, stage: int): + valid_rooms = [room for room in self.rooms if (room.level < level) + or (room.level == level and room.stage < stage)] # leave out the stage in question to avoid edge + valid_enemies = set() + for room in valid_rooms: + valid_enemies.update(room.enemies) + placed_enemies = [enemy for enemy in valid_enemies if enemy not in enemies_to_set] + if any(self.copy_abilities[enemy] == copy_ability for enemy in placed_enemies): + return None # a valid enemy got placed by a more restrictive placement + return self.random.choice(sorted([enemy for enemy in valid_enemies if enemy not in placed_enemies])) + + def pre_fill(self) -> None: + if self.options.copy_ability_randomization: + # randomize copy abilities + valid_abilities = list(copy_ability_access_table.keys()) + enemies_to_set = list(self.copy_abilities.keys()) + # now for the edge cases + for abilities, enemies in enemy_restrictive: + available_enemies = list() + for enemy in enemies: + if enemy not in enemies_to_set: + if self.copy_abilities[enemy] in abilities: + break + else: + available_enemies.append(enemy) + else: + chosen_enemy = self.random.choice(available_enemies) + chosen_ability = self.random.choice(abilities) + self.copy_abilities[chosen_enemy] = chosen_ability + enemies_to_set.remove(chosen_enemy) + # two less restrictive ones, we need to ensure Cutter and Burning appear before their required stages + sand_canyon_5 = self.get_region("Sand Canyon 5 - 9") + # this is primarily for typing, but if this ever hits it's fine to crash + assert isinstance(sand_canyon_5, KDL3Room) + cutter_enemy = self.get_restrictive_copy_ability_placement("Cutter Ability", enemies_to_set, + sand_canyon_5.level, sand_canyon_5.stage) + if cutter_enemy: + self.copy_abilities[cutter_enemy] = "Cutter Ability" + enemies_to_set.remove(cutter_enemy) + iceberg_4 = self.get_region("Iceberg 4 - 7") + # this is primarily for typing, but if this ever hits it's fine to crash + assert isinstance(iceberg_4, KDL3Room) + burning_enemy = self.get_restrictive_copy_ability_placement("Burning Ability", enemies_to_set, + iceberg_4.level, iceberg_4.stage) + if burning_enemy: + self.copy_abilities[burning_enemy] = "Burning Ability" + enemies_to_set.remove(burning_enemy) + # place remaining + for enemy in enemies_to_set: + self.copy_abilities[enemy] = self.random.choice(valid_abilities) + for enemy in enemy_mapping: + self.multiworld.get_location(enemy, self.player) \ + .place_locked_item(self.create_item(self.copy_abilities[enemy_mapping[enemy]])) + # fill animals + if self.options.animal_randomization != 0: + spawns = [animal for animal in animal_friend_spawns.keys() if + animal not in ["Ripple Field 5 - Animal 2", "Sand Canyon 6 - Animal 1", "Iceberg 4 - Animal 1"]] + self.multiworld.get_location("Iceberg 4 - Animal 1", self.player) \ + .place_locked_item(self.create_item("ChuChu Spawn")) + # Not having ChuChu here makes the room impossible (since only she has vertical burning) + self.multiworld.get_location("Ripple Field 5 - Animal 2", self.player) \ + .place_locked_item(self.create_item("Pitch Spawn")) + guaranteed_animal = self.random.choice(["Kine Spawn", "Coo Spawn"]) + self.multiworld.get_location("Sand Canyon 6 - Animal 1", self.player) \ + .place_locked_item(self.create_item(guaranteed_animal)) + # Ripple Field 5 - Animal 2 needs to be Pitch to ensure accessibility on non-door rando + if self.options.animal_randomization == 1: + animal_pool = [animal_friend_spawns[spawn] for spawn in animal_friend_spawns + if spawn not in ["Ripple Field 5 - Animal 2", "Sand Canyon 6 - Animal 1", + "Iceberg 4 - Animal 1"]] + else: + animal_base = ["Rick Spawn", "Kine Spawn", "Coo Spawn", "Nago Spawn", "ChuChu Spawn", "Pitch Spawn"] + animal_pool = [self.random.choice(animal_base) + for _ in range(len(animal_friend_spawns) - 9)] + # have to guarantee one of each animal + animal_pool.extend(animal_base) + if guaranteed_animal == "Kine Spawn": + animal_pool.append("Coo Spawn") + else: + animal_pool.append("Kine Spawn") + locations = [self.multiworld.get_location(spawn, self.player) for spawn in spawns] + items = [self.create_item(animal) for animal in animal_pool] + allstate = self.multiworld.get_all_state(False) + fill_restrictive(self.multiworld, allstate, locations, items, True, True) + else: + animal_friends = animal_friend_spawns.copy() + for animal in animal_friends: + self.multiworld.get_location(animal, self.player) \ + .place_locked_item(self.create_item(animal_friends[animal])) + + def create_items(self) -> None: + itempool = [] + itempool.extend([self.create_item(name) for name in copy_ability_table]) + itempool.extend([self.create_item(name) for name in animal_friend_table]) + required_percentage = self.options.heart_stars_required / 100.0 + remaining_items = len(location_table) - len(itempool) + if not self.options.consumables: + remaining_items -= len(consumable_locations) + remaining_items -= len(star_locations) + if self.options.starsanity: + # star fill, keep consumable pool locked to consumable and fill 767 stars specifically + star_items = list(star_item_weights.keys()) + star_weights = list(star_item_weights.values()) + itempool.extend([self.create_item(item) for item in self.random.choices(star_items, weights=star_weights, + k=767)]) + total_heart_stars = self.options.total_heart_stars + # ensure at least 1 heart star required per world + required_heart_stars = max(int(total_heart_stars * required_percentage), 5) + filler_items = total_heart_stars - required_heart_stars + filler_amount = math.floor(filler_items * (self.options.filler_percentage / 100.0)) + trap_amount = math.floor(filler_amount * (self.options.trap_percentage / 100.0)) + filler_amount -= trap_amount + non_required_heart_stars = filler_items - filler_amount - trap_amount + self.required_heart_stars = required_heart_stars + # handle boss requirements here + requirements = [required_heart_stars] + quotient = required_heart_stars // 5 # since we set the last manually, we can afford imperfect rounding + if self.options.boss_requirement_random: + for i in range(1, 5): + if self.options.strict_bosses: + max_stars = quotient * i + else: + max_stars = required_heart_stars + requirements.insert(i, self.random.randint( + min(1, max_stars), max_stars)) + if self.options.strict_bosses: + requirements.sort() + else: + self.random.shuffle(requirements) + else: + for i in range(1, 5): + requirements.insert(i - 1, quotient * i) + self.boss_requirements = requirements + itempool.extend([self.create_item("Heart Star") for _ in range(required_heart_stars)]) + itempool.extend([self.create_item(self.get_filler_item_name(False)) + for _ in range(filler_amount + (remaining_items - total_heart_stars))]) + itempool.extend([self.create_item(self.get_trap_item_name()) + for _ in range(trap_amount)]) + itempool.extend([self.create_item("Heart Star", True) for _ in range(non_required_heart_stars)]) + self.multiworld.itempool += itempool + if self.options.open_world: + for level in self.player_levels: + for stage in range(0, 6): + self.multiworld.get_location(location_table[self.player_levels[level][stage]] + .replace("Complete", "Stage Completion"), self.player) \ + .place_locked_item(KDL3Item( + f"{LocationName.level_names_inverse[level]} - Stage Completion", + ItemClassification.progression, None, self.player)) + + set_rules = set_rules + + def generate_basic(self) -> None: + self.stage_shuffle_enabled = self.options.stage_shuffle > 0 + goal = self.options.goal + goal_location = self.multiworld.get_location(LocationName.goals[goal], self.player) + goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, self.player)) + for level in range(1, 6): + self.multiworld.get_location(f"Level {level} Boss - Defeated", self.player) \ + .place_locked_item( + KDL3Item(f"Level {level} Boss Defeated", ItemClassification.progression, None, self.player)) + self.multiworld.get_location(f"Level {level} Boss - Purified", self.player) \ + .place_locked_item( + KDL3Item(f"Level {level} Boss Purified", ItemClassification.progression, None, self.player)) + self.multiworld.completion_condition[self.player] = lambda state: state.has("Love-Love Rod", self.player) + # this can technically be done at any point before generate_output + if self.options.allow_bb: + if self.options.allow_bb == self.options.allow_bb.option_enforced: + self.boss_butch_bosses = [True for _ in range(6)] + else: + self.boss_butch_bosses = [self.random.choice([True, False]) for _ in range(6)] + + def generate_output(self, output_directory: str): + rom_path = "" + try: + rom = RomData(get_base_rom_path()) + patch_rom(self, rom) + + rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") + rom.write_to_file(rom_path) + self.rom_name = rom.name + + patch = KDL3DeltaPatch(os.path.splitext(rom_path)[0] + KDL3DeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=rom_path) + patch.write() + except Exception: + raise + finally: + self.rom_name_available_event.set() # make sure threading continues and errors are collected + if os.path.exists(rom_path): + os.unlink(rom_path) + + def modify_multidata(self, multidata: dict): + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + # we skip in case of error, so that the original error in the output thread is the one that gets raised + if rom_name: + new_name = base64.b64encode(bytes(self.rom_name)).decode() + multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] + + def write_spoiler(self, spoiler_handle: TextIO) -> None: + if self.stage_shuffle_enabled: + spoiler_handle.write(f"\nLevel Layout ({self.multiworld.get_player_name(self.player)}):\n") + for level in LocationName.level_names: + for stage, i in zip(self.player_levels[LocationName.level_names[level]], range(1, 7)): + spoiler_handle.write(f"{level} {i}: {location_table[stage].replace(' - Complete', '')}\n") + if self.options.animal_randomization: + spoiler_handle.write(f"\nAnimal Friends ({self.multiworld.get_player_name(self.player)}):\n") + for level in self.player_levels: + for stage in range(6): + rooms = [room for room in self.rooms if room.level == level and room.stage == stage] + animals = [] + for room in rooms: + animals.extend([location.item.name.replace(" Spawn", "") + for location in room.locations if "Animal" in location.name]) + spoiler_handle.write(f"{location_table[self.player_levels[level][stage]].replace(' - Complete','')}" + f": {', '.join(animals)}\n") + if self.options.copy_ability_randomization: + spoiler_handle.write(f"\nCopy Abilities ({self.multiworld.get_player_name(self.player)}):\n") + for enemy in self.copy_abilities: + spoiler_handle.write(f"{enemy}: {self.copy_abilities[enemy].replace('No Ability', 'None').replace(' Ability', '')}\n") + + def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): + if self.stage_shuffle_enabled: + regions = {LocationName.level_names[level]: level for level in LocationName.level_names} + level_hint_data = {} + for level in regions: + for stage in range(7): + stage_name = self.multiworld.get_location(self.location_id_to_name[self.player_levels[level][stage]], + self.player).name.replace(" - Complete", "") + stage_regions = [room for room in self.rooms if stage_name in room.name] + for region in stage_regions: + for location in [location for location in region.locations if location.address]: + level_hint_data[location.address] = f"{regions[level]} {stage + 1 if stage < 6 else 'Boss'}" + hint_data[self.player] = level_hint_data diff --git a/worlds/kdl3/data/APConsumable.bsdiff4 b/worlds/kdl3/data/APConsumable.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..e930a4e2b30ca2094f8bced437275ff9c89bec27 GIT binary patch literal 585 zcmZ;fqqXZvIz|GR`Xd zxMfr=>|8J_$#6;9WTvFFWj>5X2{UH6xe0bQFto88G+;T%;;WQ2Z*iurFXOVdnQU&e zGT0hB3>q#j6EMk;GD=~aWn}BdwxlO)tHTx3Qx2>wQ+T$P&U>83#UQ52K7Vk0>e0*0a8Nm^AH{i^a-mkq4f9cCE8fYV2m|xtMalHh1PKrA2*9ESy8_CSNTn zIQgz%yZF@853L2*^=7;lQ1*Ytq5J&$wY>LnXPx4e4(N)xOuRO6o0O2j$_59SpSBYp zyX?2pd8v`7WH*12!pZsd_FE)H=H;KQdh?xizO34b2ah@ox+EI*UMS;|)L6CFcGk&L zq8Gm}bx2*Vm&LyI($jm_y%y)RG(9_3utaL{m9#ZyY!$iqnGF`ChD69XOJ*&$|8PKs z;or6Q@m>voz1yEjzt27^nBrmaqB-uG#r``tnpC4+3mS+WxWMD2v?)(2_3nnP|H41| zT>2q4(Oj;lr?=F&GfB@Vyn6=g*9zAoN)PfE)e0Uqkm@>pF~l+WW_|Zgr8ycN)_pHu NHGtw867!&>0swph^2z`J literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/APHeartStar.bsdiff4 b/worlds/kdl3/data/APHeartStar.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..e442604f26009c5981c79d4cdd497dee2d6003d0 GIT binary patch literal 603 zcmZmL5CI1U0R{yIMUBiC z25e<~?OS3`iad03%==um2cdjcgFy)c1JD2e|38PMUJFSGzq`PJ-M@i>?a{`o0eKGs z6zhLDupg_?&fmaTu_EBAf0n8LMgL&6Hn(0WBMIkADVYlx7cMZ$oM6Ii()eU{@TrUf z23{b{U|_h|!5{^4oD_qpky^`9<6viJw^__rG8mYB86>5oB-t7m8041btnpzjI+V36 z`QfR!Q(K-FYCNjmGuLYoyW@gR#vjLpd9SKYW0>VQw{6lzjiwXfZx2jinV>YuL-OhE zssbTZi)>Y!q?IfJ#)5Jdj*_klMQ4u84mr(lCM0%8Gd0LIbjjvFON)E7JU|aRq zT`;8PkCn>gBr(yPBW4R7_W$LO_|hBZlIi`^OlnqqSyI|VuhK(FoeD;=Of4Vpd{S5= zt;^P7u=r=6km{ABTJcNDahEQwwEh*cG^NAf@T;1u&Kea9cv?@ydPQ2UsTF2s*_j;})-8WayNt+3cvE{&ktw!P|w` zOm1&=;ykp)OZet8$)s6%YBTT5xhh@Q^n~9bvf%K;*mqm^m2gO`l5=zw{69k^D<;mT eBv$yotM?yp>^o&K9J~OEeeoh!i+~BB_yGVyZ1K|o literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/APPauseIcons.dat b/worlds/kdl3/data/APPauseIcons.dat new file mode 100644 index 0000000000000000000000000000000000000000..e7773a0e6718d35360e9c250119c2bc329b50379 GIT binary patch literal 448 zcmZuts|v$F5UlHGMfAhU5lV=vs0tOKBBCF0BCH5ip(>OUAw;Z*sE7!qgz4PTxZB}a z_I79XvEqXPH40chLClo@hKOe`xG{4=j%SRFFJ@vS;{25r4KnO;MjufyoNgdqoG@ks zbu;E?DBh8RlIot{pp0Zk)MMUWdrNi8PT-vF+HaK)K-H;D@5ZL0%fgsQd5x5Q!9#W=E_g4?{ EKW{sg`v3p{ literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/APStars.bsdiff4 b/worlds/kdl3/data/APStars.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..c335bae9cadaa0297ecaac90e06568a2bfd37ebb GIT binary patch literal 250 zcmZnt=tvaEi(>HFS(r3y$2Sqj83Tf%$+#h(G{?0)wJP<_iP1 zGQRdLu_r|yIyt@Au-E{hc(IVuLk0%Mzuo^jSaokP2)G?!Z~$sI*WiVloVq8$f_IlAwjdNSf8~ZLi%@Q;a)a-mv!ar-iLuaFd z>crW#j5qIlzC9OeIO$2!M(-`}!%vH>cQx4g^1j@H>+AjqmFjPCJDj!LL?O;eu*V`| egZPW4e|k^43KlB5skj~jIZwRE)goX5$Zr4uwN>Z< literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/Rooms.json b/worlds/kdl3/data/Rooms.json new file mode 100644 index 000000000000..47fe76534c30 --- /dev/null +++ b/worlds/kdl3/data/Rooms.json @@ -0,0 +1 @@ +[{"name": "Grass Land 1 - 0", "level": 1, "stage": 1, "room": 0, "pointer": 3434257, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sir Kibble", "Cappy"], "default_exits": [{"room": 1, "unkn1": 205, "unkn2": 8, "x": 72, "y": 200, "name": "Grass Land 1 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 87, "unkn2": 9, "x": 104, "y": 152, "name": "Grass Land 1 - 0 Exit 1", "access_rule": []}], "entity_load": [[0, 16], [1, 23], [0, 23], [14, 23], [27, 16], [12, 16], [4, 22]], "locations": ["Grass Land 1 - Enemy 1 (Waddle Dee)", "Grass Land 1 - Enemy 2 (Sir Kibble)", "Grass Land 1 - Enemy 3 (Cappy)", "Grass Land 1 - Star 1", "Grass Land 1 - Star 2", "Grass Land 1 - Star 3", "Grass Land 1 - Star 4", "Grass Land 1 - Star 5", "Grass Land 1 - Star 6", "Grass Land 1 - Star 7", "Grass Land 1 - Star 8", "Grass Land 1 - Star 9", "Grass Land 1 - Star 10"], "music": 20}, {"name": "Grass Land 1 - 1", "level": 1, "stage": 1, "room": 1, "pointer": 3368373, "animal_pointers": [], "consumables": [{"idx": 14, "pointer": 264, "x": 928, "y": 160, "etype": 22, "vtype": 0, "name": "Grass Land 1 - 1-Up (Parasol)"}, {"idx": 15, "pointer": 312, "x": 1456, "y": 176, "etype": 22, "vtype": 2, "name": "Grass Land 1 - Maxim Tomato (Spark)"}], "consumables_pointer": 304, "enemies": ["Sparky", "Bronto Burt", "Sasuke"], "default_exits": [{"room": 3, "unkn1": 143, "unkn2": 6, "x": 56, "y": 152, "name": "Grass Land 1 - 1 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [30, 16], [12, 16], [14, 23], [8, 16], [0, 22], [2, 22]], "locations": ["Grass Land 1 - Enemy 4 (Sparky)", "Grass Land 1 - Enemy 5 (Bronto Burt)", "Grass Land 1 - Enemy 6 (Sasuke)", "Grass Land 1 - Star 11", "Grass Land 1 - Star 12", "Grass Land 1 - Star 13", "Grass Land 1 - Star 14", "Grass Land 1 - Star 15", "Grass Land 1 - 1-Up (Parasol)", "Grass Land 1 - Maxim Tomato (Spark)"], "music": 20}, {"name": "Grass Land 1 - 2", "level": 1, "stage": 1, "room": 2, "pointer": 2960650, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 5, "unkn2": 9, "x": 1416, "y": 152, "name": "Grass Land 1 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 1 - Animal 1", "Grass Land 1 - Animal 2"], "music": 38}, {"name": "Grass Land 1 - 3", "level": 1, "stage": 1, "room": 3, "pointer": 3478442, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Poppy Bros Jr."], "default_exits": [{"room": 4, "unkn1": 179, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 1 - 3 Exit 0", "access_rule": []}], "entity_load": [[0, 19], [7, 16], [0, 23], [6, 22], [14, 23], [8, 16], [1, 23]], "locations": ["Grass Land 1 - Enemy 7 (Poppy Bros Jr.)", "Grass Land 1 - Star 16", "Grass Land 1 - Star 17", "Grass Land 1 - Star 18", "Grass Land 1 - Star 19", "Grass Land 1 - Star 20", "Grass Land 1 - Star 21", "Grass Land 1 - Star 22", "Grass Land 1 - Star 23"], "music": 20}, {"name": "Grass Land 1 - 4", "level": 1, "stage": 1, "room": 4, "pointer": 2978390, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[0, 19], [42, 19]], "locations": ["Grass Land 1 - Tulip"], "music": 8}, {"name": "Grass Land 1 - 5", "level": 1, "stage": 1, "room": 5, "pointer": 2890835, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 1 - Complete"], "music": 5}, {"name": "Grass Land 2 - 0", "level": 1, "stage": 2, "room": 0, "pointer": 3293347, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Rocky", "KeKe", "Bobo", "Poppy Bros Jr."], "default_exits": [{"room": 1, "unkn1": 112, "unkn2": 9, "x": 72, "y": 152, "name": "Grass Land 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[3, 16], [7, 16], [5, 16], [4, 22], [51, 16], [14, 23]], "locations": ["Grass Land 2 - Enemy 1 (Rocky)", "Grass Land 2 - Enemy 2 (KeKe)", "Grass Land 2 - Enemy 3 (Bobo)", "Grass Land 2 - Enemy 4 (Poppy Bros Jr.)", "Grass Land 2 - Star 1", "Grass Land 2 - Star 2", "Grass Land 2 - Star 3", "Grass Land 2 - Star 4", "Grass Land 2 - Star 5", "Grass Land 2 - Star 6", "Grass Land 2 - Star 7", "Grass Land 2 - Star 8", "Grass Land 2 - Star 9", "Grass Land 2 - Star 10"], "music": 11}, {"name": "Grass Land 2 - 1", "level": 1, "stage": 2, "room": 1, "pointer": 3059685, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 20, "unkn2": 9, "x": 56, "y": 136, "name": "Grass Land 2 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 2 - Animal 1", "Grass Land 2 - Animal 2"], "music": 39}, {"name": "Grass Land 2 - 2", "level": 1, "stage": 2, "room": 2, "pointer": 3432109, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Popon Ball", "Bouncy"], "default_exits": [{"room": 4, "unkn1": 133, "unkn2": 11, "x": 72, "y": 200, "name": "Grass Land 2 - 2 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 52, "unkn2": 12, "x": 56, "y": 152, "name": "Grass Land 2 - 2 Exit 1", "access_rule": []}], "entity_load": [[13, 16], [50, 16], [4, 22], [3, 16], [0, 16], [14, 23]], "locations": ["Grass Land 2 - Enemy 5 (Waddle Dee)", "Grass Land 2 - Enemy 6 (Popon Ball)", "Grass Land 2 - Enemy 7 (Bouncy)", "Grass Land 2 - Star 11", "Grass Land 2 - Star 12", "Grass Land 2 - Star 13"], "music": 11}, {"name": "Grass Land 2 - 3", "level": 1, "stage": 2, "room": 3, "pointer": 2970029, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 2, "unkn2": 9, "x": 840, "y": 168, "name": "Grass Land 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[1, 19]], "locations": [], "music": 11}, {"name": "Grass Land 2 - 4", "level": 1, "stage": 2, "room": 4, "pointer": 3578022, "animal_pointers": [], "consumables": [{"idx": 20, "pointer": 272, "x": 992, "y": 192, "etype": 22, "vtype": 0, "name": "Grass Land 2 - 1-Up (Needle)"}], "consumables_pointer": 352, "enemies": ["Tick", "Bronto Burt", "Nruff"], "default_exits": [{"room": 5, "unkn1": 154, "unkn2": 12, "x": 72, "y": 152, "name": "Grass Land 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[15, 16], [5, 16], [2, 16], [48, 16], [14, 23], [0, 22]], "locations": ["Grass Land 2 - Enemy 8 (Tick)", "Grass Land 2 - Enemy 9 (Bronto Burt)", "Grass Land 2 - Enemy 10 (Nruff)", "Grass Land 2 - Star 14", "Grass Land 2 - Star 15", "Grass Land 2 - Star 16", "Grass Land 2 - Star 17", "Grass Land 2 - Star 18", "Grass Land 2 - Star 19", "Grass Land 2 - Star 20", "Grass Land 2 - Star 21", "Grass Land 2 - 1-Up (Needle)"], "music": 11}, {"name": "Grass Land 2 - 5", "level": 1, "stage": 2, "room": 5, "pointer": 2966057, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 2 - 5 Exit 0", "access_rule": []}], "entity_load": [[1, 19], [42, 19]], "locations": ["Grass Land 2 - Muchimuchi"], "music": 8}, {"name": "Grass Land 2 - 6", "level": 1, "stage": 2, "room": 6, "pointer": 2887461, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 2 - Complete"], "music": 5}, {"name": "Grass Land 3 - 0", "level": 1, "stage": 3, "room": 0, "pointer": 3149707, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Rocky", "Nruff"], "default_exits": [{"room": 1, "unkn1": 107, "unkn2": 7, "x": 72, "y": 840, "name": "Grass Land 3 - 0 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 46, "unkn2": 9, "x": 152, "y": 152, "name": "Grass Land 3 - 0 Exit 1", "access_rule": []}], "entity_load": [[3, 16], [14, 23], [15, 16], [0, 16], [8, 16]], "locations": ["Grass Land 3 - Enemy 1 (Sparky)", "Grass Land 3 - Enemy 2 (Rocky)", "Grass Land 3 - Enemy 3 (Nruff)", "Grass Land 3 - Star 1", "Grass Land 3 - Star 2", "Grass Land 3 - Star 3", "Grass Land 3 - Star 4", "Grass Land 3 - Star 5", "Grass Land 3 - Star 6", "Grass Land 3 - Star 7", "Grass Land 3 - Star 8", "Grass Land 3 - Star 9", "Grass Land 3 - Star 10"], "music": 19}, {"name": "Grass Land 3 - 1", "level": 1, "stage": 3, "room": 1, "pointer": 3204939, "animal_pointers": [], "consumables": [{"idx": 10, "pointer": 360, "x": 208, "y": 344, "etype": 22, "vtype": 0, "name": "Grass Land 3 - 1-Up (Climb)"}, {"idx": 11, "pointer": 376, "x": 224, "y": 568, "etype": 22, "vtype": 2, "name": "Grass Land 3 - Maxim Tomato (Climb)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 3, "unkn1": 9, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 3 - 1 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [6, 23], [2, 22], [5, 23], [14, 23], [1, 23], [0, 23], [31, 16]], "locations": ["Grass Land 3 - Star 11", "Grass Land 3 - Star 12", "Grass Land 3 - Star 13", "Grass Land 3 - Star 14", "Grass Land 3 - Star 15", "Grass Land 3 - Star 16", "Grass Land 3 - 1-Up (Climb)", "Grass Land 3 - Maxim Tomato (Climb)"], "music": 19}, {"name": "Grass Land 3 - 2", "level": 1, "stage": 3, "room": 2, "pointer": 3200066, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 13, "unkn2": 55, "x": 56, "y": 152, "name": "Grass Land 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[15, 16], [0, 16], [14, 23]], "locations": ["Grass Land 3 - Star 17", "Grass Land 3 - Star 18", "Grass Land 3 - Star 19", "Grass Land 3 - Star 20", "Grass Land 3 - Star 21", "Grass Land 3 - Star 22", "Grass Land 3 - Star 23", "Grass Land 3 - Star 24", "Grass Land 3 - Star 25", "Grass Land 3 - Star 26"], "music": 19}, {"name": "Grass Land 3 - 3", "level": 1, "stage": 3, "room": 3, "pointer": 2959784, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 15, "unkn2": 9, "x": 56, "y": 120, "name": "Grass Land 3 - 3 Exit 0", "access_rule": []}], "entity_load": [[2, 19]], "locations": [], "music": 31}, {"name": "Grass Land 3 - 4", "level": 1, "stage": 3, "room": 4, "pointer": 2979121, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 8, "unkn2": 9, "x": 760, "y": 152, "name": "Grass Land 3 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 3 - Animal 1", "Grass Land 3 - Animal 2"], "music": 40}, {"name": "Grass Land 3 - 5", "level": 1, "stage": 3, "room": 5, "pointer": 2997811, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[2, 19]], "locations": [], "music": 8}, {"name": "Grass Land 3 - 6", "level": 1, "stage": 3, "room": 6, "pointer": 3084876, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bouncy"], "default_exits": [{"room": 5, "unkn1": 96, "unkn2": 9, "x": 40, "y": 152, "name": "Grass Land 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[13, 16], [14, 16], [1, 23], [59, 16], [14, 23]], "locations": ["Grass Land 3 - Pitcherman", "Grass Land 3 - Enemy 4 (Bouncy)", "Grass Land 3 - Star 27", "Grass Land 3 - Star 28", "Grass Land 3 - Star 29", "Grass Land 3 - Star 30", "Grass Land 3 - Star 31"], "music": 19}, {"name": "Grass Land 3 - 7", "level": 1, "stage": 3, "room": 7, "pointer": 2891317, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 3 - Complete"], "music": 5}, {"name": "Grass Land 4 - 0", "level": 1, "stage": 4, "room": 0, "pointer": 3471284, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Loud", "Babut", "Rocky"], "default_exits": [{"room": 1, "unkn1": 145, "unkn2": 13, "x": 72, "y": 136, "name": "Grass Land 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[3, 16], [43, 16], [14, 23], [40, 16], [61, 16], [4, 22]], "locations": ["Grass Land 4 - Enemy 1 (Loud)", "Grass Land 4 - Enemy 2 (Babut)", "Grass Land 4 - Enemy 3 (Rocky)", "Grass Land 4 - Star 1", "Grass Land 4 - Star 2", "Grass Land 4 - Star 3", "Grass Land 4 - Star 4", "Grass Land 4 - Star 5", "Grass Land 4 - Star 6", "Grass Land 4 - Star 7", "Grass Land 4 - Star 8", "Grass Land 4 - Star 9"], "music": 10}, {"name": "Grass Land 4 - 1", "level": 1, "stage": 4, "room": 1, "pointer": 3436401, "animal_pointers": [], "consumables": [{"idx": 12, "pointer": 290, "x": 1008, "y": 144, "etype": 22, "vtype": 2, "name": "Grass Land 4 - Maxim Tomato (Zebon Right)"}], "consumables_pointer": 368, "enemies": ["Kapar"], "default_exits": [{"room": 5, "unkn1": 58, "unkn2": 5, "x": 184, "y": 312, "name": "Grass Land 4 - 1 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 42, "unkn2": 18, "x": 168, "y": 88, "name": "Grass Land 4 - 1 Exit 1", "access_rule": []}], "entity_load": [[43, 16], [10, 23], [6, 22], [14, 23], [2, 22], [67, 16]], "locations": ["Grass Land 4 - Enemy 4 (Kapar)", "Grass Land 4 - Star 10", "Grass Land 4 - Maxim Tomato (Zebon Right)"], "music": 10}, {"name": "Grass Land 4 - 2", "level": 1, "stage": 4, "room": 2, "pointer": 3039401, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 4, "x": 56, "y": 72, "name": "Grass Land 4 - 2 Exit 0", "access_rule": []}], "entity_load": [[4, 27]], "locations": [], "music": 9}, {"name": "Grass Land 4 - 3", "level": 1, "stage": 4, "room": 3, "pointer": 3722714, "animal_pointers": [], "consumables": [{"idx": 23, "pointer": 280, "x": 856, "y": 224, "etype": 22, "vtype": 2, "name": "Grass Land 4 - Maxim Tomato (Gordo)"}, {"idx": 22, "pointer": 480, "x": 1352, "y": 112, "etype": 22, "vtype": 0, "name": "Grass Land 4 - 1-Up (Gordo)"}], "consumables_pointer": 288, "enemies": ["Glunk", "Oro"], "default_exits": [{"room": 4, "unkn1": 95, "unkn2": 5, "x": 72, "y": 200, "name": "Grass Land 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [55, 16], [16, 16], [25, 16], [14, 23], [0, 22], [2, 22], [4, 22]], "locations": ["Grass Land 4 - Enemy 5 (Glunk)", "Grass Land 4 - Enemy 6 (Oro)", "Grass Land 4 - Star 11", "Grass Land 4 - Star 12", "Grass Land 4 - Star 13", "Grass Land 4 - Star 14", "Grass Land 4 - Star 15", "Grass Land 4 - Star 16", "Grass Land 4 - Star 17", "Grass Land 4 - Star 18", "Grass Land 4 - Star 19", "Grass Land 4 - Star 20", "Grass Land 4 - Star 21", "Grass Land 4 - Star 22", "Grass Land 4 - Star 23", "Grass Land 4 - Star 24", "Grass Land 4 - Star 25", "Grass Land 4 - Star 26", "Grass Land 4 - Maxim Tomato (Gordo)", "Grass Land 4 - 1-Up (Gordo)"], "music": 10}, {"name": "Grass Land 4 - 4", "level": 1, "stage": 4, "room": 4, "pointer": 3304980, "animal_pointers": [], "consumables": [{"idx": 32, "pointer": 208, "x": 488, "y": 64, "etype": 22, "vtype": 2, "name": "Grass Land 4 - Maxim Tomato (Cliff)"}], "consumables_pointer": 160, "enemies": [], "default_exits": [{"room": 8, "unkn1": 94, "unkn2": 9, "x": 40, "y": 152, "name": "Grass Land 4 - 4 Exit 0", "access_rule": []}], "entity_load": [[43, 16], [2, 22], [54, 16], [1, 16], [40, 16], [14, 23]], "locations": ["Grass Land 4 - Star 27", "Grass Land 4 - Maxim Tomato (Cliff)"], "music": 10}, {"name": "Grass Land 4 - 5", "level": 1, "stage": 4, "room": 5, "pointer": 3498127, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Peran"], "default_exits": [{"room": 2, "unkn1": 61, "unkn2": 13, "x": 56, "y": 72, "name": "Grass Land 4 - 5 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 61, "unkn2": 18, "x": 56, "y": 200, "name": "Grass Land 4 - 5 Exit 1", "access_rule": ["Stone", "Stone Ability"]}], "entity_load": [[72, 16], [43, 16], [4, 22], [14, 23], [10, 23], [3, 16]], "locations": ["Grass Land 4 - Enemy 7 (Peran)", "Grass Land 4 - Star 28", "Grass Land 4 - Star 29", "Grass Land 4 - Star 30", "Grass Land 4 - Star 31", "Grass Land 4 - Star 32", "Grass Land 4 - Star 33", "Grass Land 4 - Star 34", "Grass Land 4 - Star 35", "Grass Land 4 - Star 36", "Grass Land 4 - Star 37"], "music": 10}, {"name": "Grass Land 4 - 6", "level": 1, "stage": 4, "room": 6, "pointer": 3160191, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 28, "unkn2": 4, "x": 72, "y": 376, "name": "Grass Land 4 - 6 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 28, "unkn2": 12, "x": 72, "y": 440, "name": "Grass Land 4 - 6 Exit 1", "access_rule": []}], "entity_load": [[3, 19], [6, 23]], "locations": [], "music": 10}, {"name": "Grass Land 4 - 7", "level": 1, "stage": 4, "room": 7, "pointer": 3035801, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 12, "x": 56, "y": 200, "name": "Grass Land 4 - 7 Exit 0", "access_rule": []}], "entity_load": [[4, 27]], "locations": ["Grass Land 4 - Miniboss 1 (Boboo)"], "music": 4}, {"name": "Grass Land 4 - 8", "level": 1, "stage": 4, "room": 8, "pointer": 2989794, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[3, 19], [42, 19]], "locations": ["Grass Land 4 - Chao & Goku"], "music": 8}, {"name": "Grass Land 4 - 9", "level": 1, "stage": 4, "room": 9, "pointer": 3043518, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 9, "unkn2": 5, "x": 696, "y": 296, "name": "Grass Land 4 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 4 - Animal 1", "Grass Land 4 - Animal 2"], "music": 38}, {"name": "Grass Land 4 - 10", "level": 1, "stage": 4, "room": 10, "pointer": 2888425, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 4 - Complete"], "music": 5}, {"name": "Grass Land 5 - 0", "level": 1, "stage": 5, "room": 0, "pointer": 3303565, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Broom Hatter", "Bouncy"], "default_exits": [{"room": 1, "unkn1": 120, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[13, 16], [4, 22], [14, 23], [6, 23], [11, 16], [89, 16]], "locations": ["Grass Land 5 - Enemy 1 (Propeller)", "Grass Land 5 - Enemy 2 (Broom Hatter)", "Grass Land 5 - Enemy 3 (Bouncy)", "Grass Land 5 - Star 1", "Grass Land 5 - Star 2", "Grass Land 5 - Star 3", "Grass Land 5 - Star 4", "Grass Land 5 - Star 5", "Grass Land 5 - Star 6", "Grass Land 5 - Star 7", "Grass Land 5 - Star 8"], "music": 11}, {"name": "Grass Land 5 - 1", "level": 1, "stage": 5, "room": 1, "pointer": 3048718, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble"], "default_exits": [{"room": 3, "unkn1": 18, "unkn2": 4, "x": 184, "y": 152, "name": "Grass Land 5 - 1 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 21, "unkn2": 4, "x": 184, "y": 152, "name": "Grass Land 5 - 1 Exit 1", "access_rule": []}, {"room": 2, "unkn1": 36, "unkn2": 9, "x": 56, "y": 88, "name": "Grass Land 5 - 1 Exit 2", "access_rule": []}], "entity_load": [[27, 16], [14, 23]], "locations": ["Grass Land 5 - Enemy 4 (Sir Kibble)", "Grass Land 5 - Star 9", "Grass Land 5 - Star 10"], "music": 11}, {"name": "Grass Land 5 - 2", "level": 1, "stage": 5, "room": 2, "pointer": 3327019, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sasuke", "Nruff"], "default_exits": [{"room": 7, "unkn1": 121, "unkn2": 6, "x": 56, "y": 72, "name": "Grass Land 5 - 2 Exit 0", "access_rule": []}], "entity_load": [[0, 16], [14, 23], [4, 22], [30, 16], [15, 16], [1, 16]], "locations": ["Grass Land 5 - Enemy 5 (Waddle Dee)", "Grass Land 5 - Enemy 6 (Sasuke)", "Grass Land 5 - Enemy 7 (Nruff)", "Grass Land 5 - Star 11", "Grass Land 5 - Star 12", "Grass Land 5 - Star 13", "Grass Land 5 - Star 14", "Grass Land 5 - Star 15", "Grass Land 5 - Star 16"], "music": 11}, {"name": "Grass Land 5 - 3", "level": 1, "stage": 5, "room": 3, "pointer": 2966459, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 10, "unkn2": 9, "x": 312, "y": 72, "name": "Grass Land 5 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 5 - Animal 1", "Grass Land 5 - Animal 2"], "music": 38}, {"name": "Grass Land 5 - 4", "level": 1, "stage": 5, "room": 4, "pointer": 2973509, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 10, "unkn2": 9, "x": 360, "y": 72, "name": "Grass Land 5 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 5 - Animal 3", "Grass Land 5 - Animal 4"], "music": 38}, {"name": "Grass Land 5 - 5", "level": 1, "stage": 5, "room": 5, "pointer": 2962351, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 5 - 5 Exit 0", "access_rule": []}], "entity_load": [[4, 19], [42, 19]], "locations": ["Grass Land 5 - Mine"], "music": 8}, {"name": "Grass Land 5 - 6", "level": 1, "stage": 5, "room": 6, "pointer": 2886738, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 5 - Complete"], "music": 5}, {"name": "Grass Land 5 - 7", "level": 1, "stage": 5, "room": 7, "pointer": 3255423, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Tick"], "default_exits": [{"room": 5, "unkn1": 96, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[48, 16], [4, 22], [14, 23]], "locations": ["Grass Land 5 - Enemy 8 (Tick)", "Grass Land 5 - Star 17", "Grass Land 5 - Star 18", "Grass Land 5 - Star 19", "Grass Land 5 - Star 20", "Grass Land 5 - Star 21", "Grass Land 5 - Star 22", "Grass Land 5 - Star 23", "Grass Land 5 - Star 24", "Grass Land 5 - Star 25", "Grass Land 5 - Star 26", "Grass Land 5 - Star 27", "Grass Land 5 - Star 28", "Grass Land 5 - Star 29"], "music": 11}, {"name": "Grass Land 6 - 0", "level": 1, "stage": 6, "room": 0, "pointer": 3376872, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como", "Togezo", "Bronto Burt", "Cappy"], "default_exits": [{"room": 6, "unkn1": 51, "unkn2": 9, "x": 216, "y": 152, "name": "Grass Land 6 - 0 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 96, "unkn2": 9, "x": 216, "y": 1144, "name": "Grass Land 6 - 0 Exit 1", "access_rule": []}], "entity_load": [[12, 16], [18, 16], [2, 16], [41, 16], [4, 22], [14, 23]], "locations": ["Grass Land 6 - Enemy 1 (Como)", "Grass Land 6 - Enemy 2 (Togezo)", "Grass Land 6 - Enemy 3 (Bronto Burt)", "Grass Land 6 - Enemy 4 (Cappy)", "Grass Land 6 - Star 1", "Grass Land 6 - Star 2", "Grass Land 6 - Star 3", "Grass Land 6 - Star 4", "Grass Land 6 - Star 5", "Grass Land 6 - Star 6", "Grass Land 6 - Star 7", "Grass Land 6 - Star 8", "Grass Land 6 - Star 9"], "music": 20}, {"name": "Grass Land 6 - 1", "level": 1, "stage": 6, "room": 1, "pointer": 3395125, "animal_pointers": [], "consumables": [{"idx": 10, "pointer": 192, "x": 104, "y": 1144, "etype": 22, "vtype": 0, "name": "Grass Land 6 - 1-Up (Tower)"}], "consumables_pointer": 256, "enemies": ["Bobo", "Mariel"], "default_exits": [{"room": 2, "unkn1": 16, "unkn2": 5, "x": 72, "y": 88, "name": "Grass Land 6 - 1 Exit 0", "access_rule": []}], "entity_load": [[5, 16], [5, 19], [45, 16], [0, 22], [4, 22], [14, 23], [55, 16]], "locations": ["Grass Land 6 - Enemy 5 (Bobo)", "Grass Land 6 - Enemy 6 (Mariel)", "Grass Land 6 - Star 10", "Grass Land 6 - Star 11", "Grass Land 6 - Star 12", "Grass Land 6 - Star 13", "Grass Land 6 - Star 14", "Grass Land 6 - 1-Up (Tower)"], "music": 20}, {"name": "Grass Land 6 - 2", "level": 1, "stage": 6, "room": 2, "pointer": 3375177, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Broom Hatter"], "default_exits": [{"room": 3, "unkn1": 93, "unkn2": 6, "x": 200, "y": 152, "name": "Grass Land 6 - 2 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 49, "unkn2": 7, "x": 216, "y": 104, "name": "Grass Land 6 - 2 Exit 1", "access_rule": []}], "entity_load": [[11, 16], [45, 16], [41, 16], [4, 22], [32, 16], [14, 23]], "locations": ["Grass Land 6 - Enemy 7 (Yaban)", "Grass Land 6 - Enemy 8 (Broom Hatter)", "Grass Land 6 - Star 15", "Grass Land 6 - Star 16"], "music": 20}, {"name": "Grass Land 6 - 3", "level": 1, "stage": 6, "room": 3, "pointer": 3322977, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Apolo", "Sasuke"], "default_exits": [{"room": 4, "unkn1": 12, "unkn2": 52, "x": 72, "y": 104, "name": "Grass Land 6 - 3 Exit 0", "access_rule": []}], "entity_load": [[41, 16], [49, 16], [30, 16], [14, 23], [4, 22]], "locations": ["Grass Land 6 - Enemy 9 (Apolo)", "Grass Land 6 - Enemy 10 (Sasuke)", "Grass Land 6 - Star 17", "Grass Land 6 - Star 18", "Grass Land 6 - Star 19", "Grass Land 6 - Star 20", "Grass Land 6 - Star 21", "Grass Land 6 - Star 22", "Grass Land 6 - Star 23", "Grass Land 6 - Star 24", "Grass Land 6 - Star 25"], "music": 20}, {"name": "Grass Land 6 - 4", "level": 1, "stage": 6, "room": 4, "pointer": 3490819, "animal_pointers": [], "consumables": [{"idx": 33, "pointer": 192, "x": 40, "y": 104, "etype": 22, "vtype": 1, "name": "Grass Land 6 - 1-Up (Falling)"}], "consumables_pointer": 176, "enemies": ["Rocky"], "default_exits": [{"room": 5, "unkn1": 145, "unkn2": 6, "x": 56, "y": 152, "name": "Grass Land 6 - 4 Exit 0", "access_rule": []}], "entity_load": [[5, 16], [4, 22], [49, 16], [61, 16], [3, 16], [1, 22], [14, 23]], "locations": ["Grass Land 6 - Enemy 11 (Rocky)", "Grass Land 6 - Star 26", "Grass Land 6 - Star 27", "Grass Land 6 - Star 28", "Grass Land 6 - Star 29", "Grass Land 6 - 1-Up (Falling)"], "music": 20}, {"name": "Grass Land 6 - 5", "level": 1, "stage": 6, "room": 5, "pointer": 3076769, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 6 - 5 Exit 0", "access_rule": []}], "entity_load": [[5, 19], [42, 19]], "locations": ["Grass Land 6 - Pierre"], "music": 8}, {"name": "Grass Land 6 - 6", "level": 1, "stage": 6, "room": 6, "pointer": 3047576, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 12, "unkn2": 9, "x": 840, "y": 152, "name": "Grass Land 6 - 6 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 6 - Animal 1", "Grass Land 6 - Animal 2"], "music": 39}, {"name": "Grass Land 6 - 7", "level": 1, "stage": 6, "room": 7, "pointer": 3022909, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 12, "unkn2": 6, "x": 808, "y": 120, "name": "Grass Land 6 - 7 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 6 - Animal 3", "Grass Land 6 - Animal 4"], "music": 38}, {"name": "Grass Land 6 - 8", "level": 1, "stage": 6, "room": 8, "pointer": 2884569, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 6 - Complete"], "music": 5}, {"name": "Grass Land Boss - 0", "level": 1, "stage": 7, "room": 0, "pointer": 2984105, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[0, 18]], "locations": ["Grass Land - Boss (Whispy Woods) Purified", "Level 1 Boss - Defeated", "Level 1 Boss - Purified"], "music": 2}, {"name": "Ripple Field 1 - 0", "level": 2, "stage": 1, "room": 0, "pointer": 3279855, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Glunk", "Broom Hatter", "Cappy"], "default_exits": [{"room": 2, "unkn1": 102, "unkn2": 8, "x": 56, "y": 152, "name": "Ripple Field 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[16, 16], [0, 16], [12, 16], [11, 16], [14, 23]], "locations": ["Ripple Field 1 - Enemy 1 (Waddle Dee)", "Ripple Field 1 - Enemy 2 (Glunk)", "Ripple Field 1 - Enemy 3 (Broom Hatter)", "Ripple Field 1 - Enemy 4 (Cappy)", "Ripple Field 1 - Star 1", "Ripple Field 1 - Star 2", "Ripple Field 1 - Star 3", "Ripple Field 1 - Star 4", "Ripple Field 1 - Star 5", "Ripple Field 1 - Star 6"], "music": 15}, {"name": "Ripple Field 1 - 1", "level": 2, "stage": 1, "room": 1, "pointer": 3588688, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Rocky", "Poppy Bros Jr."], "default_exits": [{"room": 3, "unkn1": 146, "unkn2": 11, "x": 40, "y": 232, "name": "Ripple Field 1 - 1 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 73, "unkn2": 16, "x": 200, "y": 184, "name": "Ripple Field 1 - 1 Exit 1", "access_rule": []}, {"room": 6, "unkn1": 108, "unkn2": 16, "x": 200, "y": 184, "name": "Ripple Field 1 - 1 Exit 2", "access_rule": []}, {"room": 7, "unkn1": 138, "unkn2": 16, "x": 200, "y": 184, "name": "Ripple Field 1 - 1 Exit 3", "access_rule": []}], "entity_load": [[11, 16], [2, 16], [3, 16], [7, 16]], "locations": ["Ripple Field 1 - Enemy 5 (Bronto Burt)", "Ripple Field 1 - Enemy 6 (Rocky)", "Ripple Field 1 - Enemy 7 (Poppy Bros Jr.)"], "music": 15}, {"name": "Ripple Field 1 - 2", "level": 2, "stage": 1, "room": 2, "pointer": 2955848, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 18, "unkn2": 9, "x": 56, "y": 168, "name": "Ripple Field 1 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 1 - Animal 1", "Ripple Field 1 - Animal 2"], "music": 40}, {"name": "Ripple Field 1 - 3", "level": 2, "stage": 1, "room": 3, "pointer": 3558828, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobin"], "default_exits": [{"room": 4, "unkn1": 171, "unkn2": 5, "x": 40, "y": 152, "name": "Ripple Field 1 - 3 Exit 0", "access_rule": []}], "entity_load": [[73, 16], [6, 22], [14, 23], [4, 22], [0, 16], [10, 23]], "locations": ["Ripple Field 1 - Enemy 8 (Bobin)", "Ripple Field 1 - Star 7", "Ripple Field 1 - Star 8", "Ripple Field 1 - Star 9", "Ripple Field 1 - Star 10", "Ripple Field 1 - Star 11", "Ripple Field 1 - Star 12", "Ripple Field 1 - Star 13", "Ripple Field 1 - Star 14", "Ripple Field 1 - Star 15", "Ripple Field 1 - Star 16"], "music": 15}, {"name": "Ripple Field 1 - 4", "level": 2, "stage": 1, "room": 4, "pointer": 2974271, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[7, 19], [42, 19]], "locations": ["Ripple Field 1 - Kamuribana"], "music": 8}, {"name": "Ripple Field 1 - 5", "level": 2, "stage": 1, "room": 5, "pointer": 3051513, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 11, "x": 1192, "y": 264, "name": "Ripple Field 1 - 5 Exit 0", "access_rule": []}], "entity_load": [[7, 19], [14, 23]], "locations": ["Ripple Field 1 - Star 17", "Ripple Field 1 - Star 18"], "music": 15}, {"name": "Ripple Field 1 - 6", "level": 2, "stage": 1, "room": 6, "pointer": 3049838, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 11, "x": 1752, "y": 264, "name": "Ripple Field 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[7, 19], [14, 23]], "locations": ["Ripple Field 1 - Star 19"], "music": 15}, {"name": "Ripple Field 1 - 7", "level": 2, "stage": 1, "room": 7, "pointer": 3066407, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 11, "x": 2232, "y": 264, "name": "Ripple Field 1 - 7 Exit 0", "access_rule": []}], "entity_load": [[7, 19]], "locations": [], "music": 15}, {"name": "Ripple Field 1 - 8", "level": 2, "stage": 1, "room": 8, "pointer": 2889148, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 1 - Complete"], "music": 5}, {"name": "Ripple Field 2 - 0", "level": 2, "stage": 2, "room": 0, "pointer": 3342336, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Togezo", "Coconut", "Blipper", "Sasuke"], "default_exits": [{"room": 1, "unkn1": 103, "unkn2": 15, "x": 56, "y": 104, "name": "Ripple Field 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[5, 22], [34, 16], [30, 16], [21, 16], [14, 23], [4, 22], [18, 16]], "locations": ["Ripple Field 2 - Enemy 1 (Togezo)", "Ripple Field 2 - Enemy 2 (Coconut)", "Ripple Field 2 - Enemy 3 (Blipper)", "Ripple Field 2 - Enemy 4 (Sasuke)", "Ripple Field 2 - Star 1", "Ripple Field 2 - Star 2", "Ripple Field 2 - Star 3", "Ripple Field 2 - Star 4"], "music": 10}, {"name": "Ripple Field 2 - 1", "level": 2, "stage": 2, "room": 1, "pointer": 3084099, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 23, "unkn2": 8, "x": 72, "y": 248, "name": "Ripple Field 2 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 2 - Animal 1", "Ripple Field 2 - Animal 2"], "music": 39}, {"name": "Ripple Field 2 - 2", "level": 2, "stage": 2, "room": 2, "pointer": 3451207, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kany"], "default_exits": [{"room": 4, "unkn1": 31, "unkn2": 5, "x": 72, "y": 152, "name": "Ripple Field 2 - 2 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 96, "unkn2": 6, "x": 56, "y": 152, "name": "Ripple Field 2 - 2 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 56, "unkn2": 17, "x": 136, "y": 264, "name": "Ripple Field 2 - 2 Exit 2", "access_rule": []}], "entity_load": [[29, 16], [47, 16], [1, 16], [46, 16], [14, 23]], "locations": ["Ripple Field 2 - Enemy 5 (Kany)", "Ripple Field 2 - Star 5", "Ripple Field 2 - Star 6", "Ripple Field 2 - Star 7", "Ripple Field 2 - Star 8"], "music": 10}, {"name": "Ripple Field 2 - 3", "level": 2, "stage": 2, "room": 3, "pointer": 3674327, "animal_pointers": [], "consumables": [{"idx": 11, "pointer": 480, "x": 1384, "y": 200, "etype": 22, "vtype": 2, "name": "Ripple Field 2 - Maxim Tomato (Currents)"}, {"idx": 10, "pointer": 520, "x": 1456, "y": 200, "etype": 22, "vtype": 0, "name": "Ripple Field 2 - 1-Up (Currents)"}], "consumables_pointer": 128, "enemies": ["Glunk"], "default_exits": [{"room": 2, "unkn1": 134, "unkn2": 5, "x": 40, "y": 136, "name": "Ripple Field 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [2, 22], [14, 23], [16, 16], [21, 16], [4, 22]], "locations": ["Ripple Field 2 - Enemy 6 (Glunk)", "Ripple Field 2 - Star 9", "Ripple Field 2 - Star 10", "Ripple Field 2 - Star 11", "Ripple Field 2 - Star 12", "Ripple Field 2 - Star 13", "Ripple Field 2 - Star 14", "Ripple Field 2 - Star 15", "Ripple Field 2 - Star 16", "Ripple Field 2 - Star 17", "Ripple Field 2 - Maxim Tomato (Currents)", "Ripple Field 2 - 1-Up (Currents)"], "music": 10}, {"name": "Ripple Field 2 - 4", "level": 2, "stage": 2, "room": 4, "pointer": 2972744, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 3, "unkn2": 9, "x": 520, "y": 88, "name": "Ripple Field 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[8, 19]], "locations": [], "music": 10}, {"name": "Ripple Field 2 - 5", "level": 2, "stage": 2, "room": 5, "pointer": 3109710, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 16, "unkn2": 16, "x": 1048, "y": 280, "name": "Ripple Field 2 - 5 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 2 - Animal 3", "Ripple Field 2 - Animal 4"], "music": 38}, {"name": "Ripple Field 2 - 6", "level": 2, "stage": 2, "room": 6, "pointer": 2973127, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 2 - 6 Exit 0", "access_rule": []}], "entity_load": [[8, 19], [42, 19]], "locations": ["Ripple Field 2 - Bakasa"], "music": 8}, {"name": "Ripple Field 2 - 7", "level": 2, "stage": 2, "room": 7, "pointer": 2890353, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 2 - Complete"], "music": 5}, {"name": "Ripple Field 3 - 0", "level": 2, "stage": 3, "room": 0, "pointer": 3517254, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Raft Waddle Dee", "Kapar", "Blipper"], "default_exits": [{"room": 3, "unkn1": 105, "unkn2": 8, "x": 40, "y": 104, "name": "Ripple Field 3 - 0 Exit 0", "access_rule": []}], "entity_load": [[21, 16], [57, 16], [62, 16], [67, 16], [4, 22], [14, 23]], "locations": ["Ripple Field 3 - Enemy 1 (Raft Waddle Dee)", "Ripple Field 3 - Enemy 2 (Kapar)", "Ripple Field 3 - Enemy 3 (Blipper)", "Ripple Field 3 - Star 1", "Ripple Field 3 - Star 2", "Ripple Field 3 - Star 3", "Ripple Field 3 - Star 4"], "music": 18}, {"name": "Ripple Field 3 - 1", "level": 2, "stage": 3, "room": 1, "pointer": 3604480, "animal_pointers": [], "consumables": [{"idx": 10, "pointer": 320, "x": 832, "y": 152, "etype": 22, "vtype": 2, "name": "Ripple Field 3 - Maxim Tomato (Cove)"}, {"idx": 13, "pointer": 424, "x": 1128, "y": 384, "etype": 22, "vtype": 0, "name": "Ripple Field 3 - 1-Up (Cutter/Spark)"}], "consumables_pointer": 160, "enemies": ["Sparky", "Glunk", "Joe"], "default_exits": [{"room": 7, "unkn1": 80, "unkn2": 24, "x": 104, "y": 328, "name": "Ripple Field 3 - 1 Exit 0", "access_rule": []}], "entity_load": [[13, 23], [14, 23], [91, 16], [16, 16], [2, 22], [8, 16], [0, 22]], "locations": ["Ripple Field 3 - Enemy 4 (Sparky)", "Ripple Field 3 - Enemy 5 (Glunk)", "Ripple Field 3 - Enemy 6 (Joe)", "Ripple Field 3 - Star 5", "Ripple Field 3 - Star 6", "Ripple Field 3 - Star 7", "Ripple Field 3 - Star 8", "Ripple Field 3 - Star 9", "Ripple Field 3 - Star 10", "Ripple Field 3 - Star 11", "Ripple Field 3 - Maxim Tomato (Cove)", "Ripple Field 3 - 1-Up (Cutter/Spark)"], "music": 18}, {"name": "Ripple Field 3 - 2", "level": 2, "stage": 3, "room": 2, "pointer": 3715428, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobo"], "default_exits": [{"room": 4, "unkn1": 118, "unkn2": 9, "x": 56, "y": 152, "name": "Ripple Field 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[91, 16], [16, 16], [21, 16], [4, 22], [46, 16], [47, 16], [5, 16], [14, 23]], "locations": ["Ripple Field 3 - Enemy 7 (Bobo)", "Ripple Field 3 - Star 12", "Ripple Field 3 - Star 13", "Ripple Field 3 - Star 14", "Ripple Field 3 - Star 15", "Ripple Field 3 - Star 16", "Ripple Field 3 - Star 17", "Ripple Field 3 - Star 18", "Ripple Field 3 - Star 19", "Ripple Field 3 - Star 20", "Ripple Field 3 - Star 21"], "music": 18}, {"name": "Ripple Field 3 - 3", "level": 2, "stage": 3, "room": 3, "pointer": 3071919, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 15, "unkn2": 6, "x": 56, "y": 104, "name": "Ripple Field 3 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 3 - Animal 1", "Ripple Field 3 - Animal 2"], "music": 39}, {"name": "Ripple Field 3 - 4", "level": 2, "stage": 3, "room": 4, "pointer": 2970810, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 3 - 4 Exit 0", "access_rule": []}], "entity_load": [[9, 19]], "locations": ["Ripple Field 3 - Elieel"], "music": 8}, {"name": "Ripple Field 3 - 5", "level": 2, "stage": 3, "room": 5, "pointer": 2987502, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 15, "unkn2": 9, "x": 232, "y": 88, "name": "Ripple Field 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[9, 19]], "locations": [], "music": 31}, {"name": "Ripple Field 3 - 6", "level": 2, "stage": 3, "room": 6, "pointer": 2888666, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 3 - Complete"], "music": 5}, {"name": "Ripple Field 3 - 7", "level": 2, "stage": 3, "room": 7, "pointer": 3161120, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 3, "unkn2": 5, "x": 40, "y": 152, "name": "Ripple Field 3 - 7 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 11, "unkn2": 20, "x": 56, "y": 216, "name": "Ripple Field 3 - 7 Exit 1", "access_rule": []}], "entity_load": [[57, 16]], "locations": [], "music": 18}, {"name": "Ripple Field 4 - 0", "level": 2, "stage": 4, "room": 0, "pointer": 3082540, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Stone)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Parasol)", "Mony"], "default_exits": [{"room": 6, "unkn1": 4, "unkn2": 16, "x": 72, "y": 232, "name": "Ripple Field 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [79, 16], [80, 16], [81, 16], [4, 22], [20, 16], [77, 16]], "locations": ["Ripple Field 4 - Enemy 1 (Bukiset (Stone))", "Ripple Field 4 - Enemy 2 (Bukiset (Needle))", "Ripple Field 4 - Enemy 3 (Bukiset (Clean))", "Ripple Field 4 - Enemy 4 (Bukiset (Parasol))", "Ripple Field 4 - Enemy 5 (Mony)", "Ripple Field 4 - Star 1", "Ripple Field 4 - Star 2", "Ripple Field 4 - Star 3", "Ripple Field 4 - Star 4", "Ripple Field 4 - Star 5"], "music": 15}, {"name": "Ripple Field 4 - 1", "level": 2, "stage": 4, "room": 1, "pointer": 2964846, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 14, "unkn2": 8, "x": 72, "y": 88, "name": "Ripple Field 4 - 1 Exit 0", "access_rule": []}], "entity_load": [[0, 27]], "locations": ["Ripple Field 4 - Miniboss 1 (Captain Stitch)"], "music": 4}, {"name": "Ripple Field 4 - 2", "level": 2, "stage": 4, "room": 2, "pointer": 3018503, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Burning)"], "default_exits": [{"room": 11, "unkn1": 25, "unkn2": 5, "x": 56, "y": 88, "name": "Ripple Field 4 - 2 Exit 0", "access_rule": []}, {"room": 11, "unkn1": 25, "unkn2": 15, "x": 56, "y": 216, "name": "Ripple Field 4 - 2 Exit 1", "access_rule": []}], "entity_load": [[10, 19], [76, 16]], "locations": ["Ripple Field 4 - Enemy 6 (Bukiset (Burning))"], "music": 15}, {"name": "Ripple Field 4 - 3", "level": 2, "stage": 4, "room": 3, "pointer": 2988166, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[10, 19], [42, 19]], "locations": ["Ripple Field 4 - Toad & Little Toad"], "music": 8}, {"name": "Ripple Field 4 - 4", "level": 2, "stage": 4, "room": 4, "pointer": 2885533, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 4 - Complete"], "music": 5}, {"name": "Ripple Field 4 - 5", "level": 2, "stage": 4, "room": 5, "pointer": 3042349, "animal_pointers": [222, 230, 238], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 5, "unkn2": 5, "x": 360, "y": 120, "name": "Ripple Field 4 - 5 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 15, "unkn2": 5, "x": 488, "y": 120, "name": "Ripple Field 4 - 5 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 16, "unkn2": 5, "x": 104, "y": 88, "name": "Ripple Field 4 - 5 Exit 2", "access_rule": []}, {"room": 6, "unkn1": 10, "unkn2": 11, "x": 440, "y": 216, "name": "Ripple Field 4 - 5 Exit 3", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 4 - Animal 1", "Ripple Field 4 - Animal 2", "Ripple Field 4 - Animal 3"], "music": 40}, {"name": "Ripple Field 4 - 6", "level": 2, "stage": 4, "room": 6, "pointer": 3234805, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobin", "Blipper"], "default_exits": [{"room": 5, "unkn1": 21, "unkn2": 7, "x": 104, "y": 88, "name": "Ripple Field 4 - 6 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 31, "unkn2": 7, "x": 232, "y": 88, "name": "Ripple Field 4 - 6 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 26, "unkn2": 13, "x": 184, "y": 184, "name": "Ripple Field 4 - 6 Exit 2", "access_rule": []}, {"room": 12, "unkn1": 48, "unkn2": 15, "x": 88, "y": 216, "name": "Ripple Field 4 - 6 Exit 3", "access_rule": []}], "entity_load": [[73, 16], [14, 23], [21, 16], [13, 23]], "locations": ["Ripple Field 4 - Enemy 7 (Bobin)", "Ripple Field 4 - Enemy 8 (Blipper)", "Ripple Field 4 - Star 6", "Ripple Field 4 - Star 7"], "music": 15}, {"name": "Ripple Field 4 - 7", "level": 2, "stage": 4, "room": 7, "pointer": 3155468, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 6, "x": 104, "y": 136, "name": "Ripple Field 4 - 7 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 18, "unkn2": 9, "x": 72, "y": 248, "name": "Ripple Field 4 - 7 Exit 1", "access_rule": []}], "entity_load": [[14, 23], [1, 16], [0, 23]], "locations": ["Ripple Field 4 - Star 8", "Ripple Field 4 - Star 9", "Ripple Field 4 - Star 10", "Ripple Field 4 - Star 11", "Ripple Field 4 - Star 12", "Ripple Field 4 - Star 13", "Ripple Field 4 - Star 14", "Ripple Field 4 - Star 15", "Ripple Field 4 - Star 16", "Ripple Field 4 - Star 17", "Ripple Field 4 - Star 18", "Ripple Field 4 - Star 19", "Ripple Field 4 - Star 20", "Ripple Field 4 - Star 21"], "music": 15}, {"name": "Ripple Field 4 - 8", "level": 2, "stage": 4, "room": 8, "pointer": 3350031, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como", "Oro"], "default_exits": [{"room": 7, "unkn1": 24, "unkn2": 22, "x": 184, "y": 440, "name": "Ripple Field 4 - 8 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 34, "unkn2": 22, "x": 296, "y": 440, "name": "Ripple Field 4 - 8 Exit 1", "access_rule": []}, {"room": 9, "unkn1": 16, "unkn2": 72, "x": 168, "y": 152, "name": "Ripple Field 4 - 8 Exit 2", "access_rule": []}, {"room": 10, "unkn1": 23, "unkn2": 72, "x": 120, "y": 152, "name": "Ripple Field 4 - 8 Exit 3", "access_rule": []}], "entity_load": [[41, 16], [68, 16], [14, 23], [25, 16], [6, 23]], "locations": ["Ripple Field 4 - Enemy 9 (Como)", "Ripple Field 4 - Enemy 10 (Oro)", "Ripple Field 4 - Star 22", "Ripple Field 4 - Star 23", "Ripple Field 4 - Star 24", "Ripple Field 4 - Star 25", "Ripple Field 4 - Star 26", "Ripple Field 4 - Star 27", "Ripple Field 4 - Star 28"], "music": 15}, {"name": "Ripple Field 4 - 9", "level": 2, "stage": 4, "room": 9, "pointer": 3050397, "animal_pointers": [], "consumables": [{"idx": 29, "pointer": 200, "x": 88, "y": 200, "etype": 22, "vtype": 2, "name": "Ripple Field 4 - Maxim Tomato (Stone)"}], "consumables_pointer": 176, "enemies": ["Gansan"], "default_exits": [{"room": 8, "unkn1": 11, "unkn2": 9, "x": 264, "y": 1144, "name": "Ripple Field 4 - 9 Exit 0", "access_rule": []}], "entity_load": [[75, 16], [2, 22]], "locations": ["Ripple Field 4 - Enemy 11 (Gansan)", "Ripple Field 4 - Maxim Tomato (Stone)"], "music": 15}, {"name": "Ripple Field 4 - 10", "level": 2, "stage": 4, "room": 10, "pointer": 3052069, "animal_pointers": [], "consumables": [{"idx": 30, "pointer": 192, "x": 200, "y": 200, "etype": 22, "vtype": 0, "name": "Ripple Field 4 - 1-Up (Stone)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 8, "unkn1": 6, "unkn2": 9, "x": 376, "y": 1144, "name": "Ripple Field 4 - 10 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [75, 16]], "locations": ["Ripple Field 4 - 1-Up (Stone)"], "music": 15}, {"name": "Ripple Field 4 - 11", "level": 2, "stage": 4, "room": 11, "pointer": 3386974, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Kapar", "Squishy"], "default_exits": [{"room": 3, "unkn1": 146, "unkn2": 13, "x": 72, "y": 152, "name": "Ripple Field 4 - 11 Exit 0", "access_rule": []}], "entity_load": [[22, 16], [67, 16], [14, 23], [62, 16], [0, 16]], "locations": ["Ripple Field 4 - Enemy 12 (Waddle Dee)", "Ripple Field 4 - Enemy 13 (Kapar)", "Ripple Field 4 - Enemy 14 (Squishy)", "Ripple Field 4 - Star 29", "Ripple Field 4 - Star 30", "Ripple Field 4 - Star 31", "Ripple Field 4 - Star 32", "Ripple Field 4 - Star 33", "Ripple Field 4 - Star 34", "Ripple Field 4 - Star 35", "Ripple Field 4 - Star 36", "Ripple Field 4 - Star 37", "Ripple Field 4 - Star 38", "Ripple Field 4 - Star 39", "Ripple Field 4 - Star 40", "Ripple Field 4 - Star 41", "Ripple Field 4 - Star 42", "Ripple Field 4 - Star 43"], "music": 15}, {"name": "Ripple Field 4 - 12", "level": 2, "stage": 4, "room": 12, "pointer": 3168339, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 8, "unkn1": 67, "unkn2": 7, "x": 88, "y": 1224, "name": "Ripple Field 4 - 12 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 75, "unkn2": 7, "x": 88, "y": 136, "name": "Ripple Field 4 - 12 Exit 1", "access_rule": []}], "entity_load": [[59, 16], [13, 23], [28, 16]], "locations": ["Ripple Field 4 - Enemy 15 (Nidoo)"], "music": 15}, {"name": "Ripple Field 4 - 13", "level": 2, "stage": 4, "room": 13, "pointer": 2958478, "animal_pointers": [], "consumables": [{"idx": 54, "pointer": 264, "x": 216, "y": 136, "etype": 22, "vtype": 2, "name": "Ripple Field 4 - Maxim Tomato (Dark)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 12, "unkn1": 4, "unkn2": 8, "x": 1192, "y": 120, "name": "Ripple Field 4 - 13 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [2, 22], [59, 16]], "locations": ["Ripple Field 4 - Star 44", "Ripple Field 4 - Star 45", "Ripple Field 4 - Star 46", "Ripple Field 4 - Star 47", "Ripple Field 4 - Star 48", "Ripple Field 4 - Star 49", "Ripple Field 4 - Star 50", "Ripple Field 4 - Star 51", "Ripple Field 4 - Maxim Tomato (Dark)"], "music": 15}, {"name": "Ripple Field 5 - 0", "level": 2, "stage": 5, "room": 0, "pointer": 3240369, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 9, "unkn2": 43, "x": 88, "y": 344, "name": "Ripple Field 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 23]], "locations": ["Ripple Field 5 - Star 1", "Ripple Field 5 - Star 2", "Ripple Field 5 - Star 3", "Ripple Field 5 - Star 4", "Ripple Field 5 - Star 5", "Ripple Field 5 - Star 6"], "music": 16}, {"name": "Ripple Field 5 - 1", "level": 2, "stage": 5, "room": 1, "pointer": 3547528, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 15, "unkn2": 4, "x": 184, "y": 344, "name": "Ripple Field 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [14, 23]], "locations": ["Ripple Field 5 - Star 7", "Ripple Field 5 - Star 8", "Ripple Field 5 - Star 9", "Ripple Field 5 - Star 10", "Ripple Field 5 - Star 11", "Ripple Field 5 - Star 12", "Ripple Field 5 - Star 13", "Ripple Field 5 - Star 14", "Ripple Field 5 - Star 15", "Ripple Field 5 - Star 16", "Ripple Field 5 - Star 17"], "music": 16}, {"name": "Ripple Field 5 - 2", "level": 2, "stage": 5, "room": 2, "pointer": 3611327, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Joe"], "default_exits": [{"room": 4, "unkn1": 95, "unkn2": 21, "x": 56, "y": 184, "name": "Ripple Field 5 - 2 Exit 0", "access_rule": []}], "entity_load": [[91, 16], [16, 16], [14, 23]], "locations": ["Ripple Field 5 - Enemy 1 (Glunk)", "Ripple Field 5 - Enemy 2 (Joe)", "Ripple Field 5 - Star 18", "Ripple Field 5 - Star 19", "Ripple Field 5 - Star 20", "Ripple Field 5 - Star 21", "Ripple Field 5 - Star 22", "Ripple Field 5 - Star 23", "Ripple Field 5 - Star 24", "Ripple Field 5 - Star 25", "Ripple Field 5 - Star 26", "Ripple Field 5 - Star 27", "Ripple Field 5 - Star 28", "Ripple Field 5 - Star 29", "Ripple Field 5 - Star 30", "Ripple Field 5 - Star 31"], "music": 16}, {"name": "Ripple Field 5 - 3", "level": 2, "stage": 5, "room": 3, "pointer": 3926157, "animal_pointers": [], "consumables": [{"idx": 32, "pointer": 1258, "x": 1488, "y": 192, "etype": 22, "vtype": 2, "name": "Ripple Field 5 - Maxim Tomato (Currents)"}, {"idx": 31, "pointer": 1290, "x": 1520, "y": 192, "etype": 22, "vtype": 0, "name": "Ripple Field 5 - 1-Up (Currents)"}], "consumables_pointer": 128, "enemies": ["Bobin", "Mony", "Squishy"], "default_exits": [{"room": 8, "unkn1": 4, "unkn2": 38, "x": 152, "y": 152, "name": "Ripple Field 5 - 3 Exit 0", "access_rule": ["Kine", "Kine Spawn"]}, {"room": 1, "unkn1": 95, "unkn2": 38, "x": 248, "y": 1064, "name": "Ripple Field 5 - 3 Exit 1", "access_rule": []}], "entity_load": [[0, 22], [2, 22], [6, 22], [14, 23], [1, 16], [73, 16], [22, 16], [20, 16]], "locations": ["Ripple Field 5 - Enemy 3 (Bobin)", "Ripple Field 5 - Enemy 4 (Mony)", "Ripple Field 5 - Enemy 5 (Squishy)", "Ripple Field 5 - Star 32", "Ripple Field 5 - Star 33", "Ripple Field 5 - Star 34", "Ripple Field 5 - Star 35", "Ripple Field 5 - Star 36", "Ripple Field 5 - Star 37", "Ripple Field 5 - Maxim Tomato (Currents)", "Ripple Field 5 - 1-Up (Currents)"], "music": 16}, {"name": "Ripple Field 5 - 4", "level": 2, "stage": 5, "room": 4, "pointer": 3026639, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 14, "unkn2": 4, "x": 232, "y": 152, "name": "Ripple Field 5 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 5 - Animal 1"], "music": 40}, {"name": "Ripple Field 5 - 5", "level": 2, "stage": 5, "room": 5, "pointer": 3207333, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 4, "unkn2": 9, "x": 56, "y": 72, "name": "Ripple Field 5 - 5 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 24, "unkn2": 9, "x": 120, "y": 552, "name": "Ripple Field 5 - 5 Exit 1", "access_rule": ["Kine", "Kine Spawn"]}], "entity_load": [[14, 23]], "locations": ["Ripple Field 5 - Star 38", "Ripple Field 5 - Star 39", "Ripple Field 5 - Star 40", "Ripple Field 5 - Star 41", "Ripple Field 5 - Star 42"], "music": 16}, {"name": "Ripple Field 5 - 6", "level": 2, "stage": 5, "room": 6, "pointer": 3485896, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Broom Hatter", "Bouncy"], "default_exits": [{"room": 9, "unkn1": 121, "unkn2": 11, "x": 56, "y": 152, "name": "Ripple Field 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[6, 22], [22, 16], [14, 23], [13, 16], [11, 16], [32, 16]], "locations": ["Ripple Field 5 - Enemy 6 (Yaban)", "Ripple Field 5 - Enemy 7 (Broom Hatter)", "Ripple Field 5 - Enemy 8 (Bouncy)", "Ripple Field 5 - Star 43", "Ripple Field 5 - Star 44", "Ripple Field 5 - Star 45"], "music": 16}, {"name": "Ripple Field 5 - 7", "level": 2, "stage": 5, "room": 7, "pointer": 3752698, "animal_pointers": [], "consumables": [{"idx": 53, "pointer": 418, "x": 1512, "y": 608, "etype": 22, "vtype": 2, "name": "Ripple Field 5 - Maxim Tomato (Exit)"}], "consumables_pointer": 352, "enemies": ["Sparky", "Rocky", "Babut"], "default_exits": [{"room": 10, "unkn1": 45, "unkn2": 31, "x": 152, "y": 152, "name": "Ripple Field 5 - 7 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 94, "unkn2": 40, "x": 88, "y": 200, "name": "Ripple Field 5 - 7 Exit 1", "access_rule": []}], "entity_load": [[3, 16], [43, 16], [8, 16], [22, 16], [14, 23], [2, 22]], "locations": ["Ripple Field 5 - Enemy 9 (Sparky)", "Ripple Field 5 - Enemy 10 (Rocky)", "Ripple Field 5 - Enemy 11 (Babut)", "Ripple Field 5 - Star 46", "Ripple Field 5 - Star 47", "Ripple Field 5 - Star 48", "Ripple Field 5 - Star 49", "Ripple Field 5 - Maxim Tomato (Exit)"], "music": 16}, {"name": "Ripple Field 5 - 8", "level": 2, "stage": 5, "room": 8, "pointer": 3044682, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 8, "unkn2": 9, "x": 88, "y": 616, "name": "Ripple Field 5 - 8 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 5 - Animal 2"], "music": 39}, {"name": "Ripple Field 5 - 9", "level": 2, "stage": 5, "room": 9, "pointer": 2963193, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 5 - 9 Exit 0", "access_rule": []}], "entity_load": [[11, 19], [42, 19]], "locations": ["Ripple Field 5 - Mama Pitch"], "music": 8}, {"name": "Ripple Field 5 - 10", "level": 2, "stage": 5, "room": 10, "pointer": 3042934, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 7, "unkn1": 8, "unkn2": 9, "x": 712, "y": 504, "name": "Ripple Field 5 - 10 Exit 0", "access_rule": []}], "entity_load": [[26, 16], [14, 23]], "locations": ["Ripple Field 5 - Enemy 12 (Galbo)", "Ripple Field 5 - Star 50", "Ripple Field 5 - Star 51"], "music": 16}, {"name": "Ripple Field 5 - 11", "level": 2, "stage": 5, "room": 11, "pointer": 2886256, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 5 - Complete"], "music": 5}, {"name": "Ripple Field 6 - 0", "level": 2, "stage": 6, "room": 0, "pointer": 2949576, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kany"], "default_exits": [{"room": 1, "unkn1": 56, "unkn2": 7, "x": 40, "y": 152, "name": "Ripple Field 6 - 0 Exit 0", "access_rule": []}], "entity_load": [[29, 16], [13, 23]], "locations": ["Ripple Field 6 - Enemy 1 (Kany)"], "music": 15}, {"name": "Ripple Field 6 - 1", "level": 2, "stage": 6, "room": 1, "pointer": 2971200, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 23, "unkn2": 9, "x": 56, "y": 264, "name": "Ripple Field 6 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 6 - Animal 1", "Ripple Field 6 - Animal 2", "Ripple Field 6 - Animal 3"], "music": 38}, {"name": "Ripple Field 6 - 2", "level": 2, "stage": 6, "room": 2, "pointer": 3637749, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 95, "unkn2": 9, "x": 104, "y": 872, "name": "Ripple Field 6 - 2 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 50, "unkn2": 22, "x": 184, "y": 88, "name": "Ripple Field 6 - 2 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 45, "unkn2": 26, "x": 88, "y": 88, "name": "Ripple Field 6 - 2 Exit 2", "access_rule": []}, {"room": 3, "unkn1": 55, "unkn2": 26, "x": 248, "y": 88, "name": "Ripple Field 6 - 2 Exit 3", "access_rule": []}], "entity_load": [[52, 16], [13, 23]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 3", "level": 2, "stage": 6, "room": 3, "pointer": 3092564, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 4, "unkn2": 5, "x": 744, "y": 424, "name": "Ripple Field 6 - 3 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 16, "unkn2": 5, "x": 872, "y": 424, "name": "Ripple Field 6 - 3 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 4", "level": 2, "stage": 6, "room": 4, "pointer": 3133247, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 10, "unkn2": 5, "x": 824, "y": 360, "name": "Ripple Field 6 - 4 Exit 0", "access_rule": []}], "entity_load": [[12, 19]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 5", "level": 2, "stage": 6, "room": 5, "pointer": 3507762, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 76, "unkn2": 4, "x": 680, "y": 72, "name": "Ripple Field 6 - 5 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 49, "unkn2": 6, "x": 440, "y": 104, "name": "Ripple Field 6 - 5 Exit 1", "access_rule": []}, {"room": 6, "unkn1": 49, "unkn2": 10, "x": 440, "y": 168, "name": "Ripple Field 6 - 5 Exit 2", "access_rule": []}, {"room": 2, "unkn1": 88, "unkn2": 10, "x": 104, "y": 152, "name": "Ripple Field 6 - 5 Exit 3", "access_rule": []}, {"room": 6, "unkn1": 22, "unkn2": 12, "x": 200, "y": 200, "name": "Ripple Field 6 - 5 Exit 4", "access_rule": []}, {"room": 6, "unkn1": 76, "unkn2": 12, "x": 680, "y": 200, "name": "Ripple Field 6 - 5 Exit 5", "access_rule": []}, {"room": 6, "unkn1": 76, "unkn2": 16, "x": 680, "y": 264, "name": "Ripple Field 6 - 5 Exit 6", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 6", "level": 2, "stage": 6, "room": 6, "pointer": 3211264, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 4, "unkn2": 10, "x": 72, "y": 168, "name": "Ripple Field 6 - 6 Exit 0", "access_rule": []}], "entity_load": [[6, 23]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 7", "level": 2, "stage": 6, "room": 7, "pointer": 3586039, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["KeKe", "Kapar", "Rocky", "Poppy Bros Jr."], "default_exits": [{"room": 5, "unkn1": 134, "unkn2": 16, "x": 72, "y": 168, "name": "Ripple Field 6 - 7 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [3, 16], [67, 16], [51, 16], [7, 16]], "locations": ["Ripple Field 6 - Enemy 2 (KeKe)", "Ripple Field 6 - Enemy 3 (Kapar)", "Ripple Field 6 - Enemy 4 (Rocky)", "Ripple Field 6 - Enemy 5 (Poppy Bros Jr.)", "Ripple Field 6 - Star 1", "Ripple Field 6 - Star 2", "Ripple Field 6 - Star 3", "Ripple Field 6 - Star 4", "Ripple Field 6 - Star 5", "Ripple Field 6 - Star 6", "Ripple Field 6 - Star 7", "Ripple Field 6 - Star 8", "Ripple Field 6 - Star 9", "Ripple Field 6 - Star 10", "Ripple Field 6 - Star 11", "Ripple Field 6 - Star 12", "Ripple Field 6 - Star 13", "Ripple Field 6 - Star 14"], "music": 15}, {"name": "Ripple Field 6 - 8", "level": 2, "stage": 6, "room": 8, "pointer": 3621483, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Coconut", "Sasuke", "Nruff"], "default_exits": [{"room": 10, "unkn1": 70, "unkn2": 11, "x": 56, "y": 152, "name": "Ripple Field 6 - 8 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 26, "unkn2": 54, "x": 72, "y": 152, "name": "Ripple Field 6 - 8 Exit 1", "access_rule": []}], "entity_load": [[89, 16], [15, 16], [30, 16], [34, 16], [14, 23]], "locations": ["Ripple Field 6 - Enemy 6 (Propeller)", "Ripple Field 6 - Enemy 7 (Coconut)", "Ripple Field 6 - Enemy 8 (Sasuke)", "Ripple Field 6 - Enemy 9 (Nruff)", "Ripple Field 6 - Star 15", "Ripple Field 6 - Star 16", "Ripple Field 6 - Star 17", "Ripple Field 6 - Star 18", "Ripple Field 6 - Star 19", "Ripple Field 6 - Star 20", "Ripple Field 6 - Star 21", "Ripple Field 6 - Star 22", "Ripple Field 6 - Star 23"], "music": 15}, {"name": "Ripple Field 6 - 9", "level": 2, "stage": 6, "room": 9, "pointer": 2954523, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 3, "unkn2": 9, "x": 408, "y": 872, "name": "Ripple Field 6 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 6 - Animal 4"], "music": 39}, {"name": "Ripple Field 6 - 10", "level": 2, "stage": 6, "room": 10, "pointer": 3069438, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 6 - 10 Exit 0", "access_rule": []}], "entity_load": [[12, 19], [42, 19]], "locations": ["Ripple Field 6 - HB-002"], "music": 8}, {"name": "Ripple Field 6 - 11", "level": 2, "stage": 6, "room": 11, "pointer": 2886497, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 6 - Complete"], "music": 5}, {"name": "Ripple Field Boss - 0", "level": 2, "stage": 7, "room": 0, "pointer": 3157370, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[7, 18]], "locations": ["Ripple Field - Boss (Acro) Purified", "Level 2 Boss - Defeated", "Level 2 Boss - Purified"], "music": 2}, {"name": "Sand Canyon 1 - 0", "level": 3, "stage": 1, "room": 0, "pointer": 3524267, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Galbo"], "default_exits": [{"room": 5, "unkn1": 196, "unkn2": 7, "x": 72, "y": 152, "name": "Sand Canyon 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 19], [1, 16], [26, 16], [2, 16]], "locations": ["Sand Canyon 1 - Enemy 1 (Bronto Burt)", "Sand Canyon 1 - Enemy 2 (Galbo)"], "music": 13}, {"name": "Sand Canyon 1 - 1", "level": 3, "stage": 1, "room": 1, "pointer": 3163860, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 34, "unkn2": 5, "x": 104, "y": 408, "name": "Sand Canyon 1 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 1 - Animal 1", "Sand Canyon 1 - Animal 2"], "music": 38}, {"name": "Sand Canyon 1 - 2", "level": 3, "stage": 1, "room": 2, "pointer": 3512532, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Oro"], "default_exits": [{"room": 3, "unkn1": 73, "unkn2": 6, "x": 56, "y": 120, "name": "Sand Canyon 1 - 2 Exit 0", "access_rule": []}], "entity_load": [[14, 16], [26, 16], [25, 16], [4, 22], [14, 23], [1, 23], [0, 23], [4, 23]], "locations": ["Sand Canyon 1 - Enemy 3 (Oro)", "Sand Canyon 1 - Star 1", "Sand Canyon 1 - Star 2", "Sand Canyon 1 - Star 3", "Sand Canyon 1 - Star 4"], "music": 13}, {"name": "Sand Canyon 1 - 3", "level": 3, "stage": 1, "room": 3, "pointer": 3719146, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Propeller", "Gansan", "Babut"], "default_exits": [{"room": 4, "unkn1": 25, "unkn2": 73, "x": 72, "y": 280, "name": "Sand Canyon 1 - 3 Exit 0", "access_rule": []}], "entity_load": [[89, 16], [75, 16], [43, 16], [8, 16]], "locations": ["Sand Canyon 1 - Enemy 4 (Sparky)", "Sand Canyon 1 - Enemy 5 (Propeller)", "Sand Canyon 1 - Enemy 6 (Gansan)", "Sand Canyon 1 - Enemy 7 (Babut)"], "music": 13}, {"name": "Sand Canyon 1 - 4", "level": 3, "stage": 1, "room": 4, "pointer": 3421212, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Loud", "Dogon", "Bouncy", "Pteran"], "default_exits": [{"room": 7, "unkn1": 196, "unkn2": 8, "x": 56, "y": 152, "name": "Sand Canyon 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[90, 16], [13, 16], [40, 16], [14, 23], [39, 16]], "locations": ["Sand Canyon 1 - Enemy 8 (Loud)", "Sand Canyon 1 - Enemy 9 (Dogon)", "Sand Canyon 1 - Enemy 10 (Bouncy)", "Sand Canyon 1 - Enemy 11 (Pteran)", "Sand Canyon 1 - Star 5", "Sand Canyon 1 - Star 6", "Sand Canyon 1 - Star 7", "Sand Canyon 1 - Star 8", "Sand Canyon 1 - Star 9", "Sand Canyon 1 - Star 10"], "music": 13}, {"name": "Sand Canyon 1 - 5", "level": 3, "stage": 1, "room": 5, "pointer": 3203325, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Polof"], "default_exits": [{"room": 6, "unkn1": 32, "unkn2": 9, "x": 104, "y": 152, "name": "Sand Canyon 1 - 5 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 46, "unkn2": 9, "x": 56, "y": 344, "name": "Sand Canyon 1 - 5 Exit 1", "access_rule": []}], "entity_load": [[9, 16]], "locations": ["Sand Canyon 1 - Enemy 12 (Polof)"], "music": 13}, {"name": "Sand Canyon 1 - 6", "level": 3, "stage": 1, "room": 6, "pointer": 3138524, "animal_pointers": [], "consumables": [{"idx": 23, "pointer": 248, "x": 168, "y": 104, "etype": 22, "vtype": 0, "name": "Sand Canyon 1 - 1-Up (Polof)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 5, "unkn1": 5, "unkn2": 9, "x": 536, "y": 152, "name": "Sand Canyon 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [0, 22]], "locations": ["Sand Canyon 1 - Star 11", "Sand Canyon 1 - Star 12", "Sand Canyon 1 - Star 13", "Sand Canyon 1 - Star 14", "Sand Canyon 1 - Star 15", "Sand Canyon 1 - Star 16", "Sand Canyon 1 - Star 17", "Sand Canyon 1 - Star 18", "Sand Canyon 1 - Star 19", "Sand Canyon 1 - Star 20", "Sand Canyon 1 - Star 21", "Sand Canyon 1 - Star 22", "Sand Canyon 1 - 1-Up (Polof)"], "music": 13}, {"name": "Sand Canyon 1 - 7", "level": 3, "stage": 1, "room": 7, "pointer": 2988822, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 1 - 7 Exit 0", "access_rule": []}], "entity_load": [[14, 19], [42, 19]], "locations": ["Sand Canyon 1 - Geromuzudake"], "music": 8}, {"name": "Sand Canyon 1 - 8", "level": 3, "stage": 1, "room": 8, "pointer": 2885292, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 1 - Complete"], "music": 5}, {"name": "Sand Canyon 2 - 0", "level": 3, "stage": 2, "room": 0, "pointer": 3668370, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["KeKe", "Doka", "Boten"], "default_exits": [{"room": 1, "unkn1": 178, "unkn2": 8, "x": 184, "y": 104, "name": "Sand Canyon 2 - 0 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 244, "unkn2": 11, "x": 56, "y": 152, "name": "Sand Canyon 2 - 0 Exit 1", "access_rule": []}], "entity_load": [[35, 16], [33, 16], [14, 23], [51, 16], [47, 16], [46, 16]], "locations": ["Sand Canyon 2 - Enemy 1 (KeKe)", "Sand Canyon 2 - Enemy 2 (Doka)", "Sand Canyon 2 - Enemy 3 (Boten)", "Sand Canyon 2 - Star 1", "Sand Canyon 2 - Star 2", "Sand Canyon 2 - Star 3", "Sand Canyon 2 - Star 4", "Sand Canyon 2 - Star 5", "Sand Canyon 2 - Star 6", "Sand Canyon 2 - Star 7", "Sand Canyon 2 - Star 8", "Sand Canyon 2 - Star 9", "Sand Canyon 2 - Star 10", "Sand Canyon 2 - Star 11"], "music": 21}, {"name": "Sand Canyon 2 - 1", "level": 3, "stage": 2, "room": 1, "pointer": 2952738, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 10, "unkn2": 6, "x": 2872, "y": 136, "name": "Sand Canyon 2 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 2 - Animal 1", "Sand Canyon 2 - Animal 2"], "music": 40}, {"name": "Sand Canyon 2 - 2", "level": 3, "stage": 2, "room": 2, "pointer": 3531156, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 9, "unkn1": 45, "unkn2": 60, "x": 88, "y": 184, "name": "Sand Canyon 2 - 2 Exit 0", "access_rule": []}], "entity_load": [[6, 23], [89, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 4 (Propeller)", "Sand Canyon 2 - Star 12", "Sand Canyon 2 - Star 13", "Sand Canyon 2 - Star 14", "Sand Canyon 2 - Star 15", "Sand Canyon 2 - Star 16", "Sand Canyon 2 - Star 17"], "music": 21}, {"name": "Sand Canyon 2 - 3", "level": 3, "stage": 2, "room": 3, "pointer": 3263731, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sparky", "Sasuke", "Como"], "default_exits": [{"room": 4, "unkn1": 63, "unkn2": 5, "x": 88, "y": 184, "name": "Sand Canyon 2 - 3 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 68, "unkn2": 5, "x": 184, "y": 184, "name": "Sand Canyon 2 - 3 Exit 1", "access_rule": []}, {"room": 4, "unkn1": 73, "unkn2": 5, "x": 248, "y": 184, "name": "Sand Canyon 2 - 3 Exit 2", "access_rule": []}, {"room": 5, "unkn1": 130, "unkn2": 9, "x": 72, "y": 312, "name": "Sand Canyon 2 - 3 Exit 3", "access_rule": []}], "entity_load": [[41, 16], [8, 16], [30, 16], [0, 16]], "locations": ["Sand Canyon 2 - Enemy 5 (Waddle Dee)", "Sand Canyon 2 - Enemy 6 (Sparky)", "Sand Canyon 2 - Enemy 7 (Sasuke)", "Sand Canyon 2 - Enemy 8 (Como)"], "music": 21}, {"name": "Sand Canyon 2 - 4", "level": 3, "stage": 2, "room": 4, "pointer": 3076300, "animal_pointers": [], "consumables": [{"idx": 19, "pointer": 228, "x": 168, "y": 72, "etype": 22, "vtype": 0, "name": "Sand Canyon 2 - 1-Up (Enclave)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 3, "unkn1": 5, "unkn2": 11, "x": 1016, "y": 88, "name": "Sand Canyon 2 - 4 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 10, "unkn2": 11, "x": 1112, "y": 88, "name": "Sand Canyon 2 - 4 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 15, "unkn2": 11, "x": 1176, "y": 88, "name": "Sand Canyon 2 - 4 Exit 2", "access_rule": []}], "entity_load": [[14, 23], [0, 22], [4, 22]], "locations": ["Sand Canyon 2 - Star 18", "Sand Canyon 2 - Star 19", "Sand Canyon 2 - 1-Up (Enclave)"], "music": 21}, {"name": "Sand Canyon 2 - 5", "level": 3, "stage": 2, "room": 5, "pointer": 3302133, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Ice)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Parasol)", "Bukiset (Spark)", "Bukiset (Cutter)"], "default_exits": [{"room": 6, "unkn1": 63, "unkn2": 15, "x": 120, "y": 216, "name": "Sand Canyon 2 - 5 Exit 0", "access_rule": []}, {"room": 8, "unkn1": 71, "unkn2": 18, "x": 152, "y": 136, "name": "Sand Canyon 2 - 5 Exit 1", "access_rule": []}, {"room": 2, "unkn1": 130, "unkn2": 19, "x": 72, "y": 952, "name": "Sand Canyon 2 - 5 Exit 2", "access_rule": []}, {"room": 7, "unkn1": 56, "unkn2": 23, "x": 88, "y": 216, "name": "Sand Canyon 2 - 5 Exit 3", "access_rule": []}], "entity_load": [[80, 16], [78, 16], [81, 16], [83, 16], [79, 16], [82, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 9 (Bukiset (Ice))", "Sand Canyon 2 - Enemy 10 (Bukiset (Needle))", "Sand Canyon 2 - Enemy 11 (Bukiset (Clean))", "Sand Canyon 2 - Enemy 12 (Bukiset (Parasol))", "Sand Canyon 2 - Enemy 13 (Bukiset (Spark))", "Sand Canyon 2 - Enemy 14 (Bukiset (Cutter))", "Sand Canyon 2 - Star 20", "Sand Canyon 2 - Star 21", "Sand Canyon 2 - Star 22", "Sand Canyon 2 - Star 23", "Sand Canyon 2 - Star 24", "Sand Canyon 2 - Star 25", "Sand Canyon 2 - Star 26", "Sand Canyon 2 - Star 27", "Sand Canyon 2 - Star 28", "Sand Canyon 2 - Star 29", "Sand Canyon 2 - Star 30", "Sand Canyon 2 - Star 31", "Sand Canyon 2 - Star 32", "Sand Canyon 2 - Star 33"], "music": 21}, {"name": "Sand Canyon 2 - 6", "level": 3, "stage": 2, "room": 6, "pointer": 3800612, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 5, "unkn1": 17, "unkn2": 13, "x": 1144, "y": 248, "name": "Sand Canyon 2 - 6 Exit 0", "access_rule": []}], "entity_load": [[28, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 15 (Nidoo)", "Sand Canyon 2 - Star 34"], "music": 21}, {"name": "Sand Canyon 2 - 7", "level": 3, "stage": 2, "room": 7, "pointer": 3073888, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 15, "unkn2": 8, "x": 1016, "y": 296, "name": "Sand Canyon 2 - 7 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 5, "unkn2": 13, "x": 904, "y": 376, "name": "Sand Canyon 2 - 7 Exit 1", "access_rule": []}], "entity_load": [[15, 19]], "locations": [], "music": 21}, {"name": "Sand Canyon 2 - 8", "level": 3, "stage": 2, "room": 8, "pointer": 3061270, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mariel"], "default_exits": [{"room": 5, "unkn1": 19, "unkn2": 13, "x": 1256, "y": 376, "name": "Sand Canyon 2 - 8 Exit 0", "access_rule": []}], "entity_load": [[45, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 16 (Mariel)", "Sand Canyon 2 - Star 35"], "music": 21}, {"name": "Sand Canyon 2 - 9", "level": 3, "stage": 2, "room": 9, "pointer": 3453286, "animal_pointers": [], "consumables": [{"idx": 51, "pointer": 240, "x": 1408, "y": 216, "etype": 22, "vtype": 2, "name": "Sand Canyon 2 - Maxim Tomato (Underwater)"}], "consumables_pointer": 128, "enemies": ["Yaban", "Wapod", "Squishy", "Pteran"], "default_exits": [{"room": 10, "unkn1": 246, "unkn2": 10, "x": 56, "y": 152, "name": "Sand Canyon 2 - 9 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [22, 16], [2, 22], [39, 16], [88, 16], [32, 16]], "locations": ["Sand Canyon 2 - Enemy 17 (Yaban)", "Sand Canyon 2 - Enemy 18 (Wapod)", "Sand Canyon 2 - Enemy 19 (Squishy)", "Sand Canyon 2 - Enemy 20 (Pteran)", "Sand Canyon 2 - Star 36", "Sand Canyon 2 - Star 37", "Sand Canyon 2 - Star 38", "Sand Canyon 2 - Star 39", "Sand Canyon 2 - Star 40", "Sand Canyon 2 - Star 41", "Sand Canyon 2 - Star 42", "Sand Canyon 2 - Star 43", "Sand Canyon 2 - Star 44", "Sand Canyon 2 - Star 45", "Sand Canyon 2 - Star 46", "Sand Canyon 2 - Star 47", "Sand Canyon 2 - Star 48", "Sand Canyon 2 - Maxim Tomato (Underwater)"], "music": 21}, {"name": "Sand Canyon 2 - 10", "level": 3, "stage": 2, "room": 10, "pointer": 2994821, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 2 - 10 Exit 0", "access_rule": []}], "entity_load": [[15, 19], [42, 19]], "locations": ["Sand Canyon 2 - Auntie"], "music": 8}, {"name": "Sand Canyon 2 - 11", "level": 3, "stage": 2, "room": 11, "pointer": 2891799, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 2 - Complete"], "music": 5}, {"name": "Sand Canyon 3 - 0", "level": 3, "stage": 3, "room": 0, "pointer": 3544676, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "Broom Hatter", "Rocky", "Gabon"], "default_exits": [{"room": 1, "unkn1": 6, "unkn2": 57, "x": 104, "y": 152, "name": "Sand Canyon 3 - 0 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 11, "unkn2": 57, "x": 200, "y": 152, "name": "Sand Canyon 3 - 0 Exit 1", "access_rule": []}, {"room": 1, "unkn1": 16, "unkn2": 57, "x": 296, "y": 152, "name": "Sand Canyon 3 - 0 Exit 2", "access_rule": []}, {"room": 2, "unkn1": 23, "unkn2": 109, "x": 104, "y": 72, "name": "Sand Canyon 3 - 0 Exit 3", "access_rule": []}], "entity_load": [[24, 16], [3, 16], [11, 16], [27, 16]], "locations": ["Sand Canyon 3 - Enemy 1 (Sir Kibble)", "Sand Canyon 3 - Enemy 2 (Broom Hatter)", "Sand Canyon 3 - Enemy 3 (Rocky)", "Sand Canyon 3 - Enemy 4 (Gabon)"], "music": 16}, {"name": "Sand Canyon 3 - 1", "level": 3, "stage": 3, "room": 1, "pointer": 2965655, "animal_pointers": [212, 220, 228], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 5, "unkn2": 9, "x": 88, "y": 920, "name": "Sand Canyon 3 - 1 Exit 0", "access_rule": []}, {"room": 0, "unkn1": 11, "unkn2": 9, "x": 168, "y": 920, "name": "Sand Canyon 3 - 1 Exit 1", "access_rule": []}, {"room": 0, "unkn1": 17, "unkn2": 9, "x": 248, "y": 920, "name": "Sand Canyon 3 - 1 Exit 2", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 3 - Animal 1", "Sand Canyon 3 - Animal 2", "Sand Canyon 3 - Animal 3"], "music": 39}, {"name": "Sand Canyon 3 - 2", "level": 3, "stage": 3, "room": 2, "pointer": 3726270, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kany"], "default_exits": [{"room": 3, "unkn1": 184, "unkn2": 20, "x": 104, "y": 232, "name": "Sand Canyon 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[63, 16], [55, 16], [14, 23], [4, 23], [29, 16]], "locations": ["Sand Canyon 3 - Enemy 5 (Kany)", "Sand Canyon 3 - Star 1", "Sand Canyon 3 - Star 2", "Sand Canyon 3 - Star 3", "Sand Canyon 3 - Star 4", "Sand Canyon 3 - Star 5", "Sand Canyon 3 - Star 6"], "music": 16}, {"name": "Sand Canyon 3 - 3", "level": 3, "stage": 3, "room": 3, "pointer": 3416806, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 8, "unkn1": 130, "unkn2": 14, "x": 40, "y": 152, "name": "Sand Canyon 3 - 3 Exit 0", "access_rule": []}], "entity_load": [[26, 16], [6, 23], [5, 23]], "locations": ["Sand Canyon 3 - Enemy 6 (Galbo)"], "music": 16}, {"name": "Sand Canyon 3 - 4", "level": 3, "stage": 3, "room": 4, "pointer": 3564419, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Sasuke"], "default_exits": [{"room": 5, "unkn1": 135, "unkn2": 11, "x": 56, "y": 152, "name": "Sand Canyon 3 - 4 Exit 0", "access_rule": []}], "entity_load": [[68, 16], [30, 16], [14, 23], [89, 16], [24, 16]], "locations": ["Sand Canyon 3 - Enemy 7 (Propeller)", "Sand Canyon 3 - Enemy 8 (Sasuke)", "Sand Canyon 3 - Star 7", "Sand Canyon 3 - Star 8", "Sand Canyon 3 - Star 9", "Sand Canyon 3 - Star 10"], "music": 16}, {"name": "Sand Canyon 3 - 5", "level": 3, "stage": 3, "room": 5, "pointer": 2973891, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[16, 19]], "locations": ["Sand Canyon 3 - Caramello"], "music": 8}, {"name": "Sand Canyon 3 - 6", "level": 3, "stage": 3, "room": 6, "pointer": 3247969, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod", "Bobo", "Babut", "Magoo"], "default_exits": [{"room": 4, "unkn1": 95, "unkn2": 8, "x": 88, "y": 424, "name": "Sand Canyon 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[53, 16], [88, 16], [46, 16], [47, 16], [5, 16], [43, 16]], "locations": ["Sand Canyon 3 - Enemy 9 (Wapod)", "Sand Canyon 3 - Enemy 10 (Bobo)", "Sand Canyon 3 - Enemy 11 (Babut)", "Sand Canyon 3 - Enemy 12 (Magoo)"], "music": 16}, {"name": "Sand Canyon 3 - 7", "level": 3, "stage": 3, "room": 7, "pointer": 2887702, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 3 - Complete"], "music": 5}, {"name": "Sand Canyon 3 - 8", "level": 3, "stage": 3, "room": 8, "pointer": 2968057, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 104, "y": 120, "name": "Sand Canyon 3 - 8 Exit 0", "access_rule": []}], "entity_load": [[16, 19]], "locations": [], "music": 31}, {"name": "Sand Canyon 4 - 0", "level": 3, "stage": 4, "room": 0, "pointer": 3521954, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Popon Ball", "Mariel", "Chilly"], "default_exits": [{"room": 1, "unkn1": 95, "unkn2": 8, "x": 216, "y": 88, "name": "Sand Canyon 4 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 199, "unkn2": 15, "x": 104, "y": 88, "name": "Sand Canyon 4 - 0 Exit 1", "access_rule": []}], "entity_load": [[6, 16], [45, 16], [6, 23], [50, 16], [14, 23]], "locations": ["Sand Canyon 4 - Enemy 1 (Popon Ball)", "Sand Canyon 4 - Enemy 2 (Mariel)", "Sand Canyon 4 - Enemy 3 (Chilly)", "Sand Canyon 4 - Star 1", "Sand Canyon 4 - Star 2", "Sand Canyon 4 - Star 3", "Sand Canyon 4 - Star 4", "Sand Canyon 4 - Star 5", "Sand Canyon 4 - Star 6", "Sand Canyon 4 - Star 7", "Sand Canyon 4 - Star 8", "Sand Canyon 4 - Star 9", "Sand Canyon 4 - Star 10", "Sand Canyon 4 - Star 11"], "music": 18}, {"name": "Sand Canyon 4 - 1", "level": 3, "stage": 4, "room": 1, "pointer": 2956289, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 12, "unkn2": 5, "x": 1544, "y": 136, "name": "Sand Canyon 4 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 4 - Animal 1", "Sand Canyon 4 - Animal 2", "Sand Canyon 4 - Animal 3"], "music": 39}, {"name": "Sand Canyon 4 - 2", "level": 3, "stage": 4, "room": 2, "pointer": 3505360, "animal_pointers": [], "consumables": [{"idx": 11, "pointer": 328, "x": 2024, "y": 72, "etype": 22, "vtype": 2, "name": "Sand Canyon 4 - Maxim Tomato (Pacto)"}], "consumables_pointer": 160, "enemies": ["Tick", "Bronto Burt", "Babut"], "default_exits": [{"room": 3, "unkn1": 149, "unkn2": 14, "x": 88, "y": 216, "name": "Sand Canyon 4 - 2 Exit 0", "access_rule": []}], "entity_load": [[62, 16], [2, 22], [43, 16], [2, 16], [48, 16]], "locations": ["Sand Canyon 4 - Enemy 4 (Tick)", "Sand Canyon 4 - Enemy 5 (Bronto Burt)", "Sand Canyon 4 - Enemy 6 (Babut)", "Sand Canyon 4 - Maxim Tomato (Pacto)"], "music": 18}, {"name": "Sand Canyon 4 - 3", "level": 3, "stage": 4, "room": 3, "pointer": 3412361, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobin", "Joe", "Mony", "Blipper"], "default_exits": [{"room": 4, "unkn1": 115, "unkn2": 6, "x": 776, "y": 120, "name": "Sand Canyon 4 - 3 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 29, "unkn2": 8, "x": 72, "y": 88, "name": "Sand Canyon 4 - 3 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 105, "unkn2": 14, "x": 104, "y": 216, "name": "Sand Canyon 4 - 3 Exit 2", "access_rule": []}, {"room": 4, "unkn1": 115, "unkn2": 20, "x": 776, "y": 248, "name": "Sand Canyon 4 - 3 Exit 3", "access_rule": []}, {"room": 4, "unkn1": 64, "unkn2": 21, "x": 408, "y": 232, "name": "Sand Canyon 4 - 3 Exit 4", "access_rule": []}], "entity_load": [[73, 16], [20, 16], [91, 16], [14, 23], [21, 16]], "locations": ["Sand Canyon 4 - Enemy 7 (Bobin)", "Sand Canyon 4 - Enemy 8 (Joe)", "Sand Canyon 4 - Enemy 9 (Mony)", "Sand Canyon 4 - Enemy 10 (Blipper)", "Sand Canyon 4 - Star 12", "Sand Canyon 4 - Star 13", "Sand Canyon 4 - Star 14", "Sand Canyon 4 - Star 15"], "music": 18}, {"name": "Sand Canyon 4 - 4", "level": 3, "stage": 4, "room": 4, "pointer": 3307804, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 60, "unkn2": 6, "x": 88, "y": 104, "name": "Sand Canyon 4 - 4 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 60, "unkn2": 16, "x": 88, "y": 360, "name": "Sand Canyon 4 - 4 Exit 1", "access_rule": []}], "entity_load": [[21, 16], [20, 16], [91, 16], [73, 16]], "locations": [], "music": 18}, {"name": "Sand Canyon 4 - 5", "level": 3, "stage": 4, "room": 5, "pointer": 2955407, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 16, "unkn2": 13, "x": 88, "y": 232, "name": "Sand Canyon 4 - 5 Exit 0", "access_rule": []}], "entity_load": [[5, 27]], "locations": ["Sand Canyon 4 - Miniboss 1 (Haboki)"], "music": 4}, {"name": "Sand Canyon 4 - 6", "level": 3, "stage": 4, "room": 6, "pointer": 3094851, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 24, "unkn2": 6, "x": 56, "y": 56, "name": "Sand Canyon 4 - 6 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 24, "unkn2": 14, "x": 56, "y": 104, "name": "Sand Canyon 4 - 6 Exit 1", "access_rule": []}, {"room": 7, "unkn1": 24, "unkn2": 22, "x": 56, "y": 152, "name": "Sand Canyon 4 - 6 Exit 2", "access_rule": []}], "entity_load": [[17, 19], [4, 23], [5, 23]], "locations": [], "music": 18}, {"name": "Sand Canyon 4 - 7", "level": 3, "stage": 4, "room": 7, "pointer": 3483428, "animal_pointers": [], "consumables": [{"idx": 25, "pointer": 200, "x": 344, "y": 144, "etype": 22, "vtype": 0, "name": "Sand Canyon 4 - 1-Up (Clean)"}, {"idx": 26, "pointer": 336, "x": 1656, "y": 144, "etype": 22, "vtype": 2, "name": "Sand Canyon 4 - Maxim Tomato (Needle)"}], "consumables_pointer": 128, "enemies": ["Togezo", "Rocky", "Bobo"], "default_exits": [{"room": 8, "unkn1": 266, "unkn2": 9, "x": 56, "y": 152, "name": "Sand Canyon 4 - 7 Exit 0", "access_rule": []}], "entity_load": [[6, 22], [3, 16], [14, 23], [5, 16], [2, 16], [18, 16], [0, 22], [2, 22]], "locations": ["Sand Canyon 4 - Enemy 11 (Togezo)", "Sand Canyon 4 - Enemy 12 (Rocky)", "Sand Canyon 4 - Enemy 13 (Bobo)", "Sand Canyon 4 - Star 16", "Sand Canyon 4 - Star 17", "Sand Canyon 4 - Star 18", "Sand Canyon 4 - Star 19", "Sand Canyon 4 - Star 20", "Sand Canyon 4 - Star 21", "Sand Canyon 4 - Star 22", "Sand Canyon 4 - Star 23", "Sand Canyon 4 - 1-Up (Clean)", "Sand Canyon 4 - Maxim Tomato (Needle)"], "music": 18}, {"name": "Sand Canyon 4 - 8", "level": 3, "stage": 4, "room": 8, "pointer": 3002086, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[17, 19], [42, 19]], "locations": ["Sand Canyon 4 - Donbe & Hikari"], "music": 8}, {"name": "Sand Canyon 4 - 9", "level": 3, "stage": 4, "room": 9, "pointer": 2885774, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 4 - Complete"], "music": 5}, {"name": "Sand Canyon 5 - 0", "level": 3, "stage": 5, "room": 0, "pointer": 3662486, "animal_pointers": [], "consumables": [{"idx": 0, "pointer": 450, "x": 2256, "y": 232, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Falling Block)"}], "consumables_pointer": 208, "enemies": ["Wapod", "Dogon", "Tick"], "default_exits": [{"room": 3, "unkn1": 151, "unkn2": 15, "x": 184, "y": 56, "name": "Sand Canyon 5 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 83, "unkn2": 16, "x": 72, "y": 120, "name": "Sand Canyon 5 - 0 Exit 1", "access_rule": []}], "entity_load": [[55, 16], [48, 16], [0, 22], [14, 23], [90, 16], [88, 16]], "locations": ["Sand Canyon 5 - Enemy 1 (Wapod)", "Sand Canyon 5 - Enemy 2 (Dogon)", "Sand Canyon 5 - Enemy 3 (Tick)", "Sand Canyon 5 - Star 1", "Sand Canyon 5 - Star 2", "Sand Canyon 5 - Star 3", "Sand Canyon 5 - Star 4", "Sand Canyon 5 - Star 5", "Sand Canyon 5 - Star 6", "Sand Canyon 5 - Star 7", "Sand Canyon 5 - Star 8", "Sand Canyon 5 - Star 9", "Sand Canyon 5 - Star 10", "Sand Canyon 5 - 1-Up (Falling Block)"], "music": 13}, {"name": "Sand Canyon 5 - 1", "level": 3, "stage": 5, "room": 1, "pointer": 3052622, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Rocky", "Bobo", "Chilly", "Sparky", "Togezo"], "default_exits": [{"room": 9, "unkn1": 6, "unkn2": 9, "x": 216, "y": 1128, "name": "Sand Canyon 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[5, 16], [6, 16], [3, 16], [8, 16], [18, 16]], "locations": ["Sand Canyon 5 - Enemy 4 (Rocky)", "Sand Canyon 5 - Enemy 5 (Bobo)", "Sand Canyon 5 - Enemy 6 (Chilly)", "Sand Canyon 5 - Enemy 7 (Sparky)", "Sand Canyon 5 - Enemy 8 (Togezo)"], "music": 13}, {"name": "Sand Canyon 5 - 2", "level": 3, "stage": 5, "room": 2, "pointer": 3057544, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 3, "unkn2": 7, "x": 1320, "y": 264, "name": "Sand Canyon 5 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 5 - Animal 1"], "music": 40}, {"name": "Sand Canyon 5 - 3", "level": 3, "stage": 5, "room": 3, "pointer": 3419011, "animal_pointers": [], "consumables": [{"idx": 11, "pointer": 192, "x": 56, "y": 648, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Ice 2)"}, {"idx": 14, "pointer": 200, "x": 56, "y": 664, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Ice 3)"}, {"idx": 15, "pointer": 208, "x": 56, "y": 632, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Ice 1)"}, {"idx": 12, "pointer": 368, "x": 320, "y": 264, "etype": 22, "vtype": 2, "name": "Sand Canyon 5 - Maxim Tomato (Pit)"}], "consumables_pointer": 304, "enemies": ["Bronto Burt", "Sasuke"], "default_exits": [{"room": 4, "unkn1": 12, "unkn2": 66, "x": 88, "y": 88, "name": "Sand Canyon 5 - 3 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [30, 16], [2, 16], [0, 22], [2, 22], [14, 23]], "locations": ["Sand Canyon 5 - Enemy 9 (Bronto Burt)", "Sand Canyon 5 - Enemy 10 (Sasuke)", "Sand Canyon 5 - Star 11", "Sand Canyon 5 - Star 12", "Sand Canyon 5 - Star 13", "Sand Canyon 5 - Star 14", "Sand Canyon 5 - Star 15", "Sand Canyon 5 - Star 16", "Sand Canyon 5 - Star 17", "Sand Canyon 5 - 1-Up (Ice 2)", "Sand Canyon 5 - 1-Up (Ice 3)", "Sand Canyon 5 - 1-Up (Ice 1)", "Sand Canyon 5 - Maxim Tomato (Pit)"], "music": 13}, {"name": "Sand Canyon 5 - 4", "level": 3, "stage": 5, "room": 4, "pointer": 3644149, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Oro", "Galbo", "Nidoo"], "default_exits": [{"room": 5, "unkn1": 76, "unkn2": 9, "x": 88, "y": 120, "name": "Sand Canyon 5 - 4 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 116, "unkn2": 14, "x": 200, "y": 1256, "name": "Sand Canyon 5 - 4 Exit 1", "access_rule": []}], "entity_load": [[54, 16], [26, 16], [28, 16], [25, 16], [14, 23]], "locations": ["Sand Canyon 5 - Enemy 11 (Oro)", "Sand Canyon 5 - Enemy 12 (Galbo)", "Sand Canyon 5 - Enemy 13 (Nidoo)", "Sand Canyon 5 - Star 18"], "music": 13}, {"name": "Sand Canyon 5 - 5", "level": 3, "stage": 5, "room": 5, "pointer": 3044100, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 4, "unkn2": 7, "x": 1240, "y": 152, "name": "Sand Canyon 5 - 5 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 5 - Animal 2"], "music": 39}, {"name": "Sand Canyon 5 - 6", "level": 3, "stage": 5, "room": 6, "pointer": 3373481, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 7, "unkn1": 25, "unkn2": 5, "x": 56, "y": 152, "name": "Sand Canyon 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [31, 16], [89, 16], [6, 16]], "locations": ["Sand Canyon 5 - Enemy 14 (Propeller)"], "music": 13}, {"name": "Sand Canyon 5 - 7", "level": 3, "stage": 5, "room": 7, "pointer": 2964028, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[18, 19], [42, 19]], "locations": ["Sand Canyon 5 - Nyupun"], "music": 8}, {"name": "Sand Canyon 5 - 8", "level": 3, "stage": 5, "room": 8, "pointer": 2890112, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 5 - Complete"], "music": 5}, {"name": "Sand Canyon 5 - 9", "level": 3, "stage": 5, "room": 9, "pointer": 3647264, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "KeKe", "Kabu"], "default_exits": [{"room": 6, "unkn1": 12, "unkn2": 6, "x": 72, "y": 760, "name": "Sand Canyon 5 - 9 Exit 0", "access_rule": ["Cutter", "Cutter Ability"]}, {"room": 1, "unkn1": 12, "unkn2": 70, "x": 120, "y": 152, "name": "Sand Canyon 5 - 9 Exit 1", "access_rule": []}], "entity_load": [[27, 16], [61, 16], [4, 22], [14, 23], [51, 16], [19, 16]], "locations": ["Sand Canyon 5 - Enemy 15 (Sir Kibble)", "Sand Canyon 5 - Enemy 16 (KeKe)", "Sand Canyon 5 - Enemy 17 (Kabu)", "Sand Canyon 5 - Star 19", "Sand Canyon 5 - Star 20", "Sand Canyon 5 - Star 21", "Sand Canyon 5 - Star 22", "Sand Canyon 5 - Star 23", "Sand Canyon 5 - Star 24", "Sand Canyon 5 - Star 25", "Sand Canyon 5 - Star 26", "Sand Canyon 5 - Star 27", "Sand Canyon 5 - Star 28", "Sand Canyon 5 - Star 29", "Sand Canyon 5 - Star 30", "Sand Canyon 5 - Star 31", "Sand Canyon 5 - Star 32", "Sand Canyon 5 - Star 33", "Sand Canyon 5 - Star 34", "Sand Canyon 5 - Star 35", "Sand Canyon 5 - Star 36", "Sand Canyon 5 - Star 37", "Sand Canyon 5 - Star 38", "Sand Canyon 5 - Star 39", "Sand Canyon 5 - Star 40"], "music": 13}, {"name": "Sand Canyon 6 - 0", "level": 3, "stage": 6, "room": 0, "pointer": 3314761, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Doka", "Cappy", "Pteran"], "default_exits": [{"room": 2, "unkn1": 132, "unkn2": 16, "x": 56, "y": 136, "name": "Sand Canyon 6 - 0 Exit 0", "access_rule": []}], "entity_load": [[8, 16], [35, 16], [39, 16], [12, 16]], "locations": ["Sand Canyon 6 - Enemy 1 (Sparky)", "Sand Canyon 6 - Enemy 2 (Doka)", "Sand Canyon 6 - Enemy 3 (Cappy)", "Sand Canyon 6 - Enemy 4 (Pteran)"], "music": 14}, {"name": "Sand Canyon 6 - 1", "level": 3, "stage": 6, "room": 1, "pointer": 2976913, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 22, "unkn1": 24, "unkn2": 13, "x": 168, "y": 216, "name": "Sand Canyon 6 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 6 - Animal 1", "Sand Canyon 6 - Animal 2", "Sand Canyon 6 - Animal 3"], "music": 39}, {"name": "Sand Canyon 6 - 2", "level": 3, "stage": 6, "room": 2, "pointer": 2978757, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 55, "unkn2": 6, "x": 104, "y": 104, "name": "Sand Canyon 6 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 3", "level": 3, "stage": 6, "room": 3, "pointer": 3178082, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 3 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 3 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 3 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 3 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 3 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 3 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 3 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 3 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 4", "level": 3, "stage": 6, "room": 4, "pointer": 3181563, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 4 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 4 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 4 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 4 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 4 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 4 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 4 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 4 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 5", "level": 3, "stage": 6, "room": 5, "pointer": 3177209, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 5 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 5 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 5 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 5 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 5 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 5 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 5 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 5 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 6", "level": 3, "stage": 6, "room": 6, "pointer": 3183291, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 6 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 6 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 6 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 6 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 6 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 6 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 6 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 6 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 7", "level": 3, "stage": 6, "room": 7, "pointer": 3175453, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 7 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 7 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 7 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 7 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 7 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 7 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 7 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 7 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 8", "level": 3, "stage": 6, "room": 8, "pointer": 3179826, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 8 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 8 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 8 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 8 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 8 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 8 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 8 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 8 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 9", "level": 3, "stage": 6, "room": 9, "pointer": 3176334, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 9 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 9 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 9 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 9 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 9 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 9 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 9 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 9 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 10", "level": 3, "stage": 6, "room": 10, "pointer": 3178954, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 10 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 10 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 10 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 10 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 10 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 10 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 10 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 10 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 11", "level": 3, "stage": 6, "room": 11, "pointer": 2989149, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 37, "unkn2": 6, "x": 88, "y": 264, "name": "Sand Canyon 6 - 11 Exit 0", "access_rule": []}], "entity_load": [[59, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 12", "level": 3, "stage": 6, "room": 12, "pointer": 3015947, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 4, "unkn2": 8, "x": 440, "y": 264, "name": "Sand Canyon 6 - 12 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 86, "unkn2": 8, "x": 72, "y": 104, "name": "Sand Canyon 6 - 12 Exit 1", "access_rule": []}], "entity_load": [[64, 16], [59, 16], [55, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 13", "level": 3, "stage": 6, "room": 13, "pointer": 2976539, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 23, "unkn1": 55, "unkn2": 8, "x": 56, "y": 152, "name": "Sand Canyon 6 - 13 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 14", "level": 3, "stage": 6, "room": 14, "pointer": 3030336, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 15, "unkn1": 5, "unkn2": 9, "x": 200, "y": 152, "name": "Sand Canyon 6 - 14 Exit 0", "access_rule": []}, {"room": 8, "unkn1": 35, "unkn2": 9, "x": 152, "y": 392, "name": "Sand Canyon 6 - 14 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 15", "level": 3, "stage": 6, "room": 15, "pointer": 2972361, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 14, "unkn1": 13, "unkn2": 9, "x": 104, "y": 152, "name": "Sand Canyon 6 - 15 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 16", "level": 3, "stage": 6, "room": 16, "pointer": 2953186, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 10, "unkn2": 19, "x": 248, "y": 152, "name": "Sand Canyon 6 - 16 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 10, "unkn2": 44, "x": 264, "y": 88, "name": "Sand Canyon 6 - 16 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 17", "level": 3, "stage": 6, "room": 17, "pointer": 2953633, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 10, "unkn2": 19, "x": 248, "y": 152, "name": "Sand Canyon 6 - 17 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 10, "unkn2": 44, "x": 264, "y": 88, "name": "Sand Canyon 6 - 17 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 18", "level": 3, "stage": 6, "room": 18, "pointer": 2991707, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 17, "unkn1": 14, "unkn2": 9, "x": 184, "y": 312, "name": "Sand Canyon 6 - 18 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 19", "level": 3, "stage": 6, "room": 19, "pointer": 2974651, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 5, "unkn2": 9, "x": 376, "y": 392, "name": "Sand Canyon 6 - 19 Exit 0", "access_rule": []}, {"room": 20, "unkn1": 35, "unkn2": 9, "x": 88, "y": 152, "name": "Sand Canyon 6 - 19 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 20", "level": 3, "stage": 6, "room": 20, "pointer": 2969638, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 19, "unkn1": 4, "unkn2": 9, "x": 552, "y": 152, "name": "Sand Canyon 6 - 20 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 21", "level": 3, "stage": 6, "room": 21, "pointer": 2976163, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 22, "unkn1": 9, "unkn2": 13, "x": 296, "y": 216, "name": "Sand Canyon 6 - 21 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 6 - Animal 4", "Sand Canyon 6 - Animal 5", "Sand Canyon 6 - Animal 6"], "music": 40}, {"name": "Sand Canyon 6 - 22", "level": 3, "stage": 6, "room": 22, "pointer": 2950938, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 38, "unkn1": 14, "unkn2": 8, "x": 168, "y": 376, "name": "Sand Canyon 6 - 22 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 9, "unkn2": 13, "x": 376, "y": 216, "name": "Sand Canyon 6 - 22 Exit 1", "access_rule": []}, {"room": 21, "unkn1": 19, "unkn2": 13, "x": 168, "y": 216, "name": "Sand Canyon 6 - 22 Exit 2", "access_rule": []}], "entity_load": [[4, 22]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 23", "level": 3, "stage": 6, "room": 23, "pointer": 3311993, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 43, "unkn1": 145, "unkn2": 7, "x": 72, "y": 152, "name": "Sand Canyon 6 - 23 Exit 0", "access_rule": []}], "entity_load": [[14, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 24", "level": 3, "stage": 6, "room": 24, "pointer": 3079078, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 39, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 24 Exit 0", "access_rule": []}, {"room": 25, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 24 Exit 1", "access_rule": []}, {"room": 39, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 24 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 25", "level": 3, "stage": 6, "room": 25, "pointer": 3065898, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 26, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 25 Exit 0", "access_rule": []}, {"room": 40, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 25 Exit 1", "access_rule": []}, {"room": 40, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 25 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 26", "level": 3, "stage": 6, "room": 26, "pointer": 3063347, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 41, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 26 Exit 0", "access_rule": []}, {"room": 41, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 26 Exit 1", "access_rule": []}, {"room": 27, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 26 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 27", "level": 3, "stage": 6, "room": 27, "pointer": 3066916, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 28, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 27 Exit 0", "access_rule": []}, {"room": 42, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 27 Exit 1", "access_rule": []}, {"room": 42, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 27 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 28", "level": 3, "stage": 6, "room": 28, "pointer": 3067425, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 29, "unkn1": 7, "unkn2": 8, "x": 120, "y": 344, "name": "Sand Canyon 6 - 28 Exit 0", "access_rule": []}, {"room": 29, "unkn1": 11, "unkn2": 8, "x": 184, "y": 344, "name": "Sand Canyon 6 - 28 Exit 1", "access_rule": []}, {"room": 29, "unkn1": 15, "unkn2": 8, "x": 248, "y": 344, "name": "Sand Canyon 6 - 28 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 29", "level": 3, "stage": 6, "room": 29, "pointer": 2950032, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 30, "unkn1": 11, "unkn2": 8, "x": 168, "y": 136, "name": "Sand Canyon 6 - 29 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 30", "level": 3, "stage": 6, "room": 30, "pointer": 2986500, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 10, "unkn2": 44, "x": 136, "y": 152, "name": "Sand Canyon 6 - 30 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 31", "level": 3, "stage": 6, "room": 31, "pointer": 3070930, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 32, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 31 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 648, "name": "Sand Canyon 6 - 31 Exit 1", "access_rule": []}, {"room": 32, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 31 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 32", "level": 3, "stage": 6, "room": 32, "pointer": 3054817, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Parasol)", "Bukiset (Cutter)"], "default_exits": [{"room": 33, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 32 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 536, "name": "Sand Canyon 6 - 32 Exit 1", "access_rule": []}, {"room": 33, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 32 Exit 2", "access_rule": []}], "entity_load": [[81, 16], [83, 16]], "locations": ["Sand Canyon 6 - Enemy 5 (Bukiset (Parasol))", "Sand Canyon 6 - Enemy 6 (Bukiset (Cutter))"], "music": 14}, {"name": "Sand Canyon 6 - 33", "level": 3, "stage": 6, "room": 33, "pointer": 3053173, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Clean)", "Bukiset (Spark)"], "default_exits": [{"room": 34, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 33 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 440, "name": "Sand Canyon 6 - 33 Exit 1", "access_rule": []}, {"room": 34, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 33 Exit 2", "access_rule": []}], "entity_load": [[80, 16], [82, 16]], "locations": ["Sand Canyon 6 - Enemy 7 (Bukiset (Clean))", "Sand Canyon 6 - Enemy 8 (Bukiset (Spark))"], "music": 14}, {"name": "Sand Canyon 6 - 34", "level": 3, "stage": 6, "room": 34, "pointer": 3053721, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Ice)", "Bukiset (Needle)"], "default_exits": [{"room": 35, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 34 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 344, "name": "Sand Canyon 6 - 34 Exit 1", "access_rule": []}, {"room": 35, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 34 Exit 2", "access_rule": []}], "entity_load": [[79, 16], [78, 16]], "locations": ["Sand Canyon 6 - Enemy 9 (Bukiset (Ice))", "Sand Canyon 6 - Enemy 10 (Bukiset (Needle))"], "music": 14}, {"name": "Sand Canyon 6 - 35", "level": 3, "stage": 6, "room": 35, "pointer": 3054269, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Burning)", "Bukiset (Stone)"], "default_exits": [{"room": 37, "unkn1": 7, "unkn2": 8, "x": 120, "y": 344, "name": "Sand Canyon 6 - 35 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 248, "name": "Sand Canyon 6 - 35 Exit 1", "access_rule": []}, {"room": 37, "unkn1": 15, "unkn2": 8, "x": 248, "y": 344, "name": "Sand Canyon 6 - 35 Exit 2", "access_rule": []}], "entity_load": [[77, 16], [76, 16]], "locations": ["Sand Canyon 6 - Enemy 11 (Bukiset (Burning))", "Sand Canyon 6 - Enemy 12 (Bukiset (Stone))"], "music": 14}, {"name": "Sand Canyon 6 - 36", "level": 3, "stage": 6, "room": 36, "pointer": 2986164, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 10, "unkn2": 44, "x": 392, "y": 152, "name": "Sand Canyon 6 - 36 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 37", "level": 3, "stage": 6, "room": 37, "pointer": 3074377, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 152, "name": "Sand Canyon 6 - 37 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 38", "level": 3, "stage": 6, "room": 38, "pointer": 2971589, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 10, "unkn2": 5, "x": 264, "y": 440, "name": "Sand Canyon 6 - 38 Exit 0", "access_rule": []}, {"room": 22, "unkn1": 10, "unkn2": 23, "x": 248, "y": 136, "name": "Sand Canyon 6 - 38 Exit 1", "access_rule": []}], "entity_load": [[76, 16], [77, 16], [78, 16], [79, 16], [80, 16], [81, 16], [82, 16], [83, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 39", "level": 3, "stage": 6, "room": 39, "pointer": 3063858, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 40, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 39 Exit 0", "access_rule": []}, {"room": 40, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 39 Exit 1", "access_rule": []}, {"room": 40, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 39 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 40", "level": 3, "stage": 6, "room": 40, "pointer": 3064368, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 41, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 40 Exit 0", "access_rule": []}, {"room": 41, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 40 Exit 1", "access_rule": []}, {"room": 41, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 40 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 41", "level": 3, "stage": 6, "room": 41, "pointer": 3064878, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 42, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 41 Exit 0", "access_rule": []}, {"room": 42, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 41 Exit 1", "access_rule": []}, {"room": 42, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 41 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 42", "level": 3, "stage": 6, "room": 42, "pointer": 3068939, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 29, "unkn1": 7, "unkn2": 8, "x": 120, "y": 344, "name": "Sand Canyon 6 - 42 Exit 0", "access_rule": []}, {"room": 29, "unkn1": 15, "unkn2": 8, "x": 248, "y": 344, "name": "Sand Canyon 6 - 42 Exit 1", "access_rule": []}], "entity_load": [[28, 16]], "locations": ["Sand Canyon 6 - Enemy 13 (Nidoo)"], "music": 14}, {"name": "Sand Canyon 6 - 43", "level": 3, "stage": 6, "room": 43, "pointer": 2988495, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 44, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 6 - 43 Exit 0", "access_rule": []}], "entity_load": [[19, 19], [42, 19]], "locations": ["Sand Canyon 6 - Professor Hector & R.O.B"], "music": 8}, {"name": "Sand Canyon 6 - 44", "level": 3, "stage": 6, "room": 44, "pointer": 2886979, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 6 - Complete"], "music": 5}, {"name": "Sand Canyon Boss - 0", "level": 3, "stage": 7, "room": 0, "pointer": 2991389, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[6, 18]], "locations": ["Sand Canyon - Boss (Pon & Con) Purified", "Level 3 Boss - Defeated", "Level 3 Boss - Purified"], "music": 2}, {"name": "Cloudy Park 1 - 0", "level": 4, "stage": 1, "room": 0, "pointer": 3172795, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "KeKe", "Cappy"], "default_exits": [{"room": 1, "unkn1": 95, "unkn2": 5, "x": 56, "y": 152, "name": "Cloudy Park 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[0, 16], [12, 16], [51, 16], [1, 23], [0, 23]], "locations": ["Cloudy Park 1 - Enemy 1 (Waddle Dee)", "Cloudy Park 1 - Enemy 2 (KeKe)", "Cloudy Park 1 - Enemy 3 (Cappy)"], "music": 17}, {"name": "Cloudy Park 1 - 1", "level": 4, "stage": 1, "room": 1, "pointer": 3078163, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 14, "unkn2": 8, "x": 88, "y": 424, "name": "Cloudy Park 1 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 1"], "music": 39}, {"name": "Cloudy Park 1 - 2", "level": 4, "stage": 1, "room": 2, "pointer": 3285882, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Togezo"], "default_exits": [{"room": 3, "unkn1": 95, "unkn2": 8, "x": 40, "y": 104, "name": "Cloudy Park 1 - 2 Exit 0", "access_rule": []}], "entity_load": [[32, 16], [4, 16], [18, 16], [6, 23]], "locations": ["Cloudy Park 1 - Enemy 4 (Yaban)", "Cloudy Park 1 - Enemy 5 (Togezo)"], "music": 17}, {"name": "Cloudy Park 1 - 3", "level": 4, "stage": 1, "room": 3, "pointer": 3062831, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 15, "unkn2": 6, "x": 56, "y": 264, "name": "Cloudy Park 1 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 2"], "music": 39}, {"name": "Cloudy Park 1 - 4", "level": 4, "stage": 1, "room": 4, "pointer": 3396729, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 7, "unkn1": 92, "unkn2": 16, "x": 56, "y": 152, "name": "Cloudy Park 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[54, 16], [0, 16], [14, 23], [68, 16], [26, 16], [4, 22]], "locations": ["Cloudy Park 1 - Enemy 6 (Galbo)", "Cloudy Park 1 - Star 1"], "music": 17}, {"name": "Cloudy Park 1 - 5", "level": 4, "stage": 1, "room": 5, "pointer": 3038805, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 6, "unkn2": 5, "x": 2344, "y": 232, "name": "Cloudy Park 1 - 5 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 3", "Cloudy Park 1 - Animal 4"], "music": 39}, {"name": "Cloudy Park 1 - 6", "level": 4, "stage": 1, "room": 6, "pointer": 3535736, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Como"], "default_exits": [{"room": 8, "unkn1": 5, "unkn2": 8, "x": 72, "y": 104, "name": "Cloudy Park 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[54, 16], [14, 23], [0, 16], [41, 16], [8, 16]], "locations": ["Cloudy Park 1 - Enemy 7 (Sparky)", "Cloudy Park 1 - Enemy 8 (Como)", "Cloudy Park 1 - Star 2"], "music": 17}, {"name": "Cloudy Park 1 - 7", "level": 4, "stage": 1, "room": 7, "pointer": 2954080, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 14, "unkn2": 6, "x": 88, "y": 104, "name": "Cloudy Park 1 - 7 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 5"], "music": 39}, {"name": "Cloudy Park 1 - 8", "level": 4, "stage": 1, "room": 8, "pointer": 3465407, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt"], "default_exits": [{"room": 12, "unkn1": 181, "unkn2": 6, "x": 56, "y": 152, "name": "Cloudy Park 1 - 8 Exit 0", "access_rule": []}], "entity_load": [[21, 19], [2, 16], [51, 16], [1, 23], [14, 23]], "locations": ["Cloudy Park 1 - Enemy 9 (Bronto Burt)", "Cloudy Park 1 - Star 3", "Cloudy Park 1 - Star 4", "Cloudy Park 1 - Star 5", "Cloudy Park 1 - Star 6", "Cloudy Park 1 - Star 7", "Cloudy Park 1 - Star 8", "Cloudy Park 1 - Star 9", "Cloudy Park 1 - Star 10", "Cloudy Park 1 - Star 11"], "music": 17}, {"name": "Cloudy Park 1 - 9", "level": 4, "stage": 1, "room": 9, "pointer": 3078621, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 14, "unkn2": 9, "x": 56, "y": 264, "name": "Cloudy Park 1 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 6"], "music": 39}, {"name": "Cloudy Park 1 - 10", "level": 4, "stage": 1, "room": 10, "pointer": 3281366, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon", "Sir Kibble"], "default_exits": [{"room": 9, "unkn1": 99, "unkn2": 8, "x": 56, "y": 152, "name": "Cloudy Park 1 - 10 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [24, 16], [27, 16], [4, 23]], "locations": ["Cloudy Park 1 - Enemy 10 (Gabon)", "Cloudy Park 1 - Enemy 11 (Sir Kibble)"], "music": 17}, {"name": "Cloudy Park 1 - 11", "level": 4, "stage": 1, "room": 11, "pointer": 3519608, "animal_pointers": [], "consumables": [{"idx": 19, "pointer": 192, "x": 216, "y": 600, "etype": 22, "vtype": 0, "name": "Cloudy Park 1 - 1-Up (Shotzo)"}, {"idx": 18, "pointer": 456, "x": 856, "y": 408, "etype": 22, "vtype": 2, "name": "Cloudy Park 1 - Maxim Tomato (Mariel)"}], "consumables_pointer": 352, "enemies": ["Mariel", "Nruff"], "default_exits": [{"room": 5, "unkn1": 64, "unkn2": 6, "x": 104, "y": 216, "name": "Cloudy Park 1 - 11 Exit 0", "access_rule": []}], "entity_load": [[14, 16], [27, 16], [15, 16], [45, 16], [14, 23], [2, 22], [0, 22], [6, 22]], "locations": ["Cloudy Park 1 - Enemy 12 (Mariel)", "Cloudy Park 1 - Enemy 13 (Nruff)", "Cloudy Park 1 - Star 12", "Cloudy Park 1 - Star 13", "Cloudy Park 1 - Star 14", "Cloudy Park 1 - Star 15", "Cloudy Park 1 - Star 16", "Cloudy Park 1 - Star 17", "Cloudy Park 1 - Star 18", "Cloudy Park 1 - Star 19", "Cloudy Park 1 - Star 20", "Cloudy Park 1 - Star 21", "Cloudy Park 1 - Star 22", "Cloudy Park 1 - Star 23", "Cloudy Park 1 - 1-Up (Shotzo)", "Cloudy Park 1 - Maxim Tomato (Mariel)"], "music": 17}, {"name": "Cloudy Park 1 - 12", "level": 4, "stage": 1, "room": 12, "pointer": 2958914, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 13, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 1 - 12 Exit 0", "access_rule": []}], "entity_load": [[21, 19], [42, 19]], "locations": ["Cloudy Park 1 - Hibanamodoki"], "music": 8}, {"name": "Cloudy Park 1 - 13", "level": 4, "stage": 1, "room": 13, "pointer": 2886015, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 1 - Complete"], "music": 5}, {"name": "Cloudy Park 2 - 0", "level": 4, "stage": 2, "room": 0, "pointer": 3142443, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Chilly", "Sasuke"], "default_exits": [{"room": 6, "unkn1": 96, "unkn2": 7, "x": 56, "y": 88, "name": "Cloudy Park 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[23, 16], [7, 16], [30, 16], [6, 16], [14, 23]], "locations": ["Cloudy Park 2 - Enemy 1 (Chilly)", "Cloudy Park 2 - Enemy 2 (Sasuke)", "Cloudy Park 2 - Star 1", "Cloudy Park 2 - Star 2", "Cloudy Park 2 - Star 3", "Cloudy Park 2 - Star 4", "Cloudy Park 2 - Star 5", "Cloudy Park 2 - Star 6"], "music": 19}, {"name": "Cloudy Park 2 - 1", "level": 4, "stage": 2, "room": 1, "pointer": 3235925, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sparky", "Broom Hatter"], "default_exits": [{"room": 2, "unkn1": 146, "unkn2": 5, "x": 88, "y": 88, "name": "Cloudy Park 2 - 1 Exit 0", "access_rule": []}], "entity_load": [[0, 16], [14, 23], [8, 16], [11, 16]], "locations": ["Cloudy Park 2 - Enemy 3 (Waddle Dee)", "Cloudy Park 2 - Enemy 4 (Sparky)", "Cloudy Park 2 - Enemy 5 (Broom Hatter)", "Cloudy Park 2 - Star 7", "Cloudy Park 2 - Star 8", "Cloudy Park 2 - Star 9", "Cloudy Park 2 - Star 10", "Cloudy Park 2 - Star 11", "Cloudy Park 2 - Star 12", "Cloudy Park 2 - Star 13", "Cloudy Park 2 - Star 14", "Cloudy Park 2 - Star 15"], "music": 19}, {"name": "Cloudy Park 2 - 2", "level": 4, "stage": 2, "room": 2, "pointer": 3297758, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "Pteran"], "default_exits": [{"room": 3, "unkn1": 147, "unkn2": 8, "x": 72, "y": 136, "name": "Cloudy Park 2 - 2 Exit 0", "access_rule": []}], "entity_load": [[27, 16], [39, 16], [1, 23]], "locations": ["Cloudy Park 2 - Enemy 6 (Sir Kibble)", "Cloudy Park 2 - Enemy 7 (Pteran)"], "music": 19}, {"name": "Cloudy Park 2 - 3", "level": 4, "stage": 2, "room": 3, "pointer": 3341087, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Dogon"], "default_exits": [{"room": 4, "unkn1": 145, "unkn2": 12, "x": 72, "y": 136, "name": "Cloudy Park 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[4, 16], [14, 23], [90, 16], [89, 16]], "locations": ["Cloudy Park 2 - Enemy 8 (Propeller)", "Cloudy Park 2 - Enemy 9 (Dogon)", "Cloudy Park 2 - Star 16", "Cloudy Park 2 - Star 17", "Cloudy Park 2 - Star 18", "Cloudy Park 2 - Star 19", "Cloudy Park 2 - Star 20", "Cloudy Park 2 - Star 21", "Cloudy Park 2 - Star 22", "Cloudy Park 2 - Star 23", "Cloudy Park 2 - Star 24", "Cloudy Park 2 - Star 25", "Cloudy Park 2 - Star 26", "Cloudy Park 2 - Star 27", "Cloudy Park 2 - Star 28", "Cloudy Park 2 - Star 29", "Cloudy Park 2 - Star 30", "Cloudy Park 2 - Star 31", "Cloudy Park 2 - Star 32", "Cloudy Park 2 - Star 33", "Cloudy Park 2 - Star 34", "Cloudy Park 2 - Star 35", "Cloudy Park 2 - Star 36", "Cloudy Park 2 - Star 37"], "music": 19}, {"name": "Cloudy Park 2 - 4", "level": 4, "stage": 2, "room": 4, "pointer": 3457404, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Togezo", "Oro", "Bronto Burt", "Rocky"], "default_exits": [{"room": 5, "unkn1": 165, "unkn2": 5, "x": 72, "y": 104, "name": "Cloudy Park 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [3, 16], [14, 23], [18, 16], [25, 16]], "locations": ["Cloudy Park 2 - Enemy 10 (Togezo)", "Cloudy Park 2 - Enemy 11 (Oro)", "Cloudy Park 2 - Enemy 12 (Bronto Burt)", "Cloudy Park 2 - Enemy 13 (Rocky)", "Cloudy Park 2 - Star 38", "Cloudy Park 2 - Star 39", "Cloudy Park 2 - Star 40", "Cloudy Park 2 - Star 41", "Cloudy Park 2 - Star 42", "Cloudy Park 2 - Star 43", "Cloudy Park 2 - Star 44", "Cloudy Park 2 - Star 45", "Cloudy Park 2 - Star 46", "Cloudy Park 2 - Star 47"], "music": 19}, {"name": "Cloudy Park 2 - 5", "level": 4, "stage": 2, "room": 5, "pointer": 3273878, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo", "Kapar"], "default_exits": [{"room": 7, "unkn1": 96, "unkn2": 8, "x": 56, "y": 88, "name": "Cloudy Park 2 - 5 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [62, 16], [26, 16], [67, 16]], "locations": ["Cloudy Park 2 - Enemy 14 (Galbo)", "Cloudy Park 2 - Enemy 15 (Kapar)", "Cloudy Park 2 - Star 48", "Cloudy Park 2 - Star 49", "Cloudy Park 2 - Star 50", "Cloudy Park 2 - Star 51", "Cloudy Park 2 - Star 52", "Cloudy Park 2 - Star 53", "Cloudy Park 2 - Star 54"], "music": 19}, {"name": "Cloudy Park 2 - 6", "level": 4, "stage": 2, "room": 6, "pointer": 2984453, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 25, "unkn2": 5, "x": 72, "y": 152, "name": "Cloudy Park 2 - 6 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 2 - Animal 1", "Cloudy Park 2 - Animal 2", "Cloudy Park 2 - Animal 3"], "music": 38}, {"name": "Cloudy Park 2 - 7", "level": 4, "stage": 2, "room": 7, "pointer": 2985482, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 15, "unkn2": 5, "x": 40, "y": 88, "name": "Cloudy Park 2 - 7 Exit 0", "access_rule": []}], "entity_load": [[22, 19]], "locations": [], "music": 19}, {"name": "Cloudy Park 2 - 8", "level": 4, "stage": 2, "room": 8, "pointer": 2990753, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 2 - 8 Exit 0", "access_rule": []}], "entity_load": [[22, 19], [42, 19]], "locations": ["Cloudy Park 2 - Piyo & Keko"], "music": 8}, {"name": "Cloudy Park 2 - 9", "level": 4, "stage": 2, "room": 9, "pointer": 2889630, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 2 - Complete"], "music": 5}, {"name": "Cloudy Park 3 - 0", "level": 4, "stage": 3, "room": 0, "pointer": 3100859, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Mopoo", "Poppy Bros Jr."], "default_exits": [{"room": 2, "unkn1": 145, "unkn2": 8, "x": 56, "y": 136, "name": "Cloudy Park 3 - 0 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [74, 16], [47, 16], [7, 16], [14, 23]], "locations": ["Cloudy Park 3 - Enemy 1 (Bronto Burt)", "Cloudy Park 3 - Enemy 2 (Mopoo)", "Cloudy Park 3 - Enemy 3 (Poppy Bros Jr.)", "Cloudy Park 3 - Star 1", "Cloudy Park 3 - Star 2", "Cloudy Park 3 - Star 3", "Cloudy Park 3 - Star 4", "Cloudy Park 3 - Star 5", "Cloudy Park 3 - Star 6", "Cloudy Park 3 - Star 7"], "music": 15}, {"name": "Cloudy Park 3 - 1", "level": 4, "stage": 3, "room": 1, "pointer": 3209719, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como"], "default_exits": [{"room": 5, "unkn1": 13, "unkn2": 14, "x": 56, "y": 152, "name": "Cloudy Park 3 - 1 Exit 0", "access_rule": []}], "entity_load": [[10, 23], [31, 16], [4, 22], [41, 16], [14, 23]], "locations": ["Cloudy Park 3 - Enemy 4 (Como)", "Cloudy Park 3 - Star 8", "Cloudy Park 3 - Star 9"], "music": 15}, {"name": "Cloudy Park 3 - 2", "level": 4, "stage": 3, "room": 2, "pointer": 3216185, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Bobin", "Loud", "Kapar"], "default_exits": [{"room": 1, "unkn1": 145, "unkn2": 6, "x": 216, "y": 1064, "name": "Cloudy Park 3 - 2 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 24, "unkn2": 14, "x": 104, "y": 152, "name": "Cloudy Park 3 - 2 Exit 1", "access_rule": []}], "entity_load": [[67, 16], [40, 16], [73, 16], [14, 23], [16, 16]], "locations": ["Cloudy Park 3 - Enemy 5 (Glunk)", "Cloudy Park 3 - Enemy 6 (Bobin)", "Cloudy Park 3 - Enemy 7 (Loud)", "Cloudy Park 3 - Enemy 8 (Kapar)", "Cloudy Park 3 - Star 10", "Cloudy Park 3 - Star 11", "Cloudy Park 3 - Star 12", "Cloudy Park 3 - Star 13", "Cloudy Park 3 - Star 14", "Cloudy Park 3 - Star 15", "Cloudy Park 3 - Star 16"], "music": 15}, {"name": "Cloudy Park 3 - 3", "level": 4, "stage": 3, "room": 3, "pointer": 2994208, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 5, "unkn2": 9, "x": 408, "y": 232, "name": "Cloudy Park 3 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 3 - Animal 1", "Cloudy Park 3 - Animal 2", "Cloudy Park 3 - Animal 3"], "music": 40}, {"name": "Cloudy Park 3 - 4", "level": 4, "stage": 3, "room": 4, "pointer": 3229151, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo", "Batamon", "Bouncy"], "default_exits": [{"room": 6, "unkn1": 156, "unkn2": 6, "x": 56, "y": 152, "name": "Cloudy Park 3 - 4 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 126, "unkn2": 9, "x": 56, "y": 152, "name": "Cloudy Park 3 - 4 Exit 1", "access_rule": []}], "entity_load": [[7, 16], [26, 16], [14, 23], [13, 16], [68, 16]], "locations": ["Cloudy Park 3 - Enemy 9 (Galbo)", "Cloudy Park 3 - Enemy 10 (Batamon)", "Cloudy Park 3 - Enemy 11 (Bouncy)", "Cloudy Park 3 - Star 17", "Cloudy Park 3 - Star 18", "Cloudy Park 3 - Star 19", "Cloudy Park 3 - Star 20", "Cloudy Park 3 - Star 21", "Cloudy Park 3 - Star 22"], "music": 15}, {"name": "Cloudy Park 3 - 5", "level": 4, "stage": 3, "room": 5, "pointer": 2969244, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 14, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[23, 19]], "locations": [], "music": 31}, {"name": "Cloudy Park 3 - 6", "level": 4, "stage": 3, "room": 6, "pointer": 4128530, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[23, 19]], "locations": ["Cloudy Park 3 - Mr. Ball"], "music": 8}, {"name": "Cloudy Park 3 - 7", "level": 4, "stage": 3, "room": 7, "pointer": 2885051, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 3 - Complete"], "music": 5}, {"name": "Cloudy Park 4 - 0", "level": 4, "stage": 4, "room": 0, "pointer": 3072905, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 95, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[4, 23], [1, 23], [0, 23], [31, 16]], "locations": [], "music": 21}, {"name": "Cloudy Park 4 - 1", "level": 4, "stage": 4, "room": 1, "pointer": 3074863, "animal_pointers": [208, 224], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 21, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 4 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 4 - Animal 1", "Cloudy Park 4 - Animal 2"], "music": 38}, {"name": "Cloudy Park 4 - 2", "level": 4, "stage": 4, "room": 2, "pointer": 3309209, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon", "Como", "Wapod", "Cappy"], "default_exits": [{"room": 3, "unkn1": 145, "unkn2": 9, "x": 104, "y": 152, "name": "Cloudy Park 4 - 2 Exit 0", "access_rule": []}], "entity_load": [[88, 16], [24, 16], [12, 16], [14, 23], [41, 16], [4, 22]], "locations": ["Cloudy Park 4 - Enemy 1 (Gabon)", "Cloudy Park 4 - Enemy 2 (Como)", "Cloudy Park 4 - Enemy 3 (Wapod)", "Cloudy Park 4 - Enemy 4 (Cappy)", "Cloudy Park 4 - Star 1", "Cloudy Park 4 - Star 2", "Cloudy Park 4 - Star 3", "Cloudy Park 4 - Star 4", "Cloudy Park 4 - Star 5", "Cloudy Park 4 - Star 6", "Cloudy Park 4 - Star 7", "Cloudy Park 4 - Star 8", "Cloudy Park 4 - Star 9", "Cloudy Park 4 - Star 10", "Cloudy Park 4 - Star 11", "Cloudy Park 4 - Star 12"], "music": 21}, {"name": "Cloudy Park 4 - 3", "level": 4, "stage": 4, "room": 3, "pointer": 3296291, "animal_pointers": [], "consumables": [{"idx": 33, "pointer": 776, "x": 1880, "y": 152, "etype": 22, "vtype": 0, "name": "Cloudy Park 4 - 1-Up (Windy)"}, {"idx": 34, "pointer": 912, "x": 2160, "y": 152, "etype": 22, "vtype": 2, "name": "Cloudy Park 4 - Maxim Tomato (Windy)"}], "consumables_pointer": 304, "enemies": ["Sparky", "Togezo"], "default_exits": [{"room": 5, "unkn1": 144, "unkn2": 9, "x": 56, "y": 136, "name": "Cloudy Park 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[18, 16], [8, 16], [31, 16], [14, 23], [4, 22], [4, 16], [0, 22], [2, 22]], "locations": ["Cloudy Park 4 - Enemy 5 (Sparky)", "Cloudy Park 4 - Enemy 6 (Togezo)", "Cloudy Park 4 - Star 13", "Cloudy Park 4 - Star 14", "Cloudy Park 4 - Star 15", "Cloudy Park 4 - Star 16", "Cloudy Park 4 - Star 17", "Cloudy Park 4 - Star 18", "Cloudy Park 4 - Star 19", "Cloudy Park 4 - Star 20", "Cloudy Park 4 - Star 21", "Cloudy Park 4 - Star 22", "Cloudy Park 4 - Star 23", "Cloudy Park 4 - Star 24", "Cloudy Park 4 - Star 25", "Cloudy Park 4 - Star 26", "Cloudy Park 4 - Star 27", "Cloudy Park 4 - Star 28", "Cloudy Park 4 - Star 29", "Cloudy Park 4 - Star 30", "Cloudy Park 4 - 1-Up (Windy)", "Cloudy Park 4 - Maxim Tomato (Windy)"], "music": 21}, {"name": "Cloudy Park 4 - 4", "level": 4, "stage": 4, "room": 4, "pointer": 3330996, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "KeKe", "Bouncy"], "default_exits": [{"room": 7, "unkn1": 4, "unkn2": 15, "x": 72, "y": 152, "name": "Cloudy Park 4 - 4 Exit 0", "access_rule": []}], "entity_load": [[13, 16], [51, 16], [17, 16], [14, 23], [2, 16]], "locations": ["Cloudy Park 4 - Enemy 7 (Bronto Burt)", "Cloudy Park 4 - Enemy 8 (KeKe)", "Cloudy Park 4 - Enemy 9 (Bouncy)", "Cloudy Park 4 - Star 31", "Cloudy Park 4 - Star 32", "Cloudy Park 4 - Star 33", "Cloudy Park 4 - Star 34", "Cloudy Park 4 - Star 35", "Cloudy Park 4 - Star 36", "Cloudy Park 4 - Star 37", "Cloudy Park 4 - Star 38"], "music": 21}, {"name": "Cloudy Park 4 - 5", "level": 4, "stage": 4, "room": 5, "pointer": 3332300, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "Mariel"], "default_exits": [{"room": 4, "unkn1": 21, "unkn2": 51, "x": 2328, "y": 120, "name": "Cloudy Park 4 - 5 Exit 0", "access_rule": []}], "entity_load": [[45, 16], [46, 16], [27, 16], [14, 23]], "locations": ["Cloudy Park 4 - Enemy 10 (Sir Kibble)", "Cloudy Park 4 - Enemy 11 (Mariel)", "Cloudy Park 4 - Star 39", "Cloudy Park 4 - Star 40", "Cloudy Park 4 - Star 41", "Cloudy Park 4 - Star 42", "Cloudy Park 4 - Star 43"], "music": 21}, {"name": "Cloudy Park 4 - 6", "level": 4, "stage": 4, "room": 6, "pointer": 3253310, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kabu", "Wappa"], "default_exits": [{"room": 9, "unkn1": 3, "unkn2": 6, "x": 72, "y": 152, "name": "Cloudy Park 4 - 6 Exit 0", "access_rule": []}], "entity_load": [[44, 16], [31, 16], [19, 16], [14, 23]], "locations": ["Cloudy Park 4 - Enemy 12 (Kabu)", "Cloudy Park 4 - Enemy 13 (Wappa)", "Cloudy Park 4 - Star 44", "Cloudy Park 4 - Star 45", "Cloudy Park 4 - Star 46", "Cloudy Park 4 - Star 47", "Cloudy Park 4 - Star 48", "Cloudy Park 4 - Star 49", "Cloudy Park 4 - Star 50"], "music": 21}, {"name": "Cloudy Park 4 - 7", "level": 4, "stage": 4, "room": 7, "pointer": 2967658, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 12, "unkn2": 9, "x": 152, "y": 120, "name": "Cloudy Park 4 - 7 Exit 0", "access_rule": []}], "entity_load": [[3, 27]], "locations": ["Cloudy Park 4 - Miniboss 1 (Jumper Shoot)"], "music": 4}, {"name": "Cloudy Park 4 - 8", "level": 4, "stage": 4, "room": 8, "pointer": 2981644, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 13, "unkn2": 9, "x": 344, "y": 680, "name": "Cloudy Park 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[24, 19]], "locations": [], "music": 21}, {"name": "Cloudy Park 4 - 9", "level": 4, "stage": 4, "room": 9, "pointer": 3008001, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 13, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 4 - 9 Exit 0", "access_rule": []}], "entity_load": [[24, 19], [42, 19]], "locations": ["Cloudy Park 4 - Mikarin & Kagami Mocchi"], "music": 8}, {"name": "Cloudy Park 4 - 10", "level": 4, "stage": 4, "room": 10, "pointer": 2888184, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 4 - Complete"], "music": 5}, {"name": "Cloudy Park 5 - 0", "level": 4, "stage": 5, "room": 0, "pointer": 3192630, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Sir Kibble", "Cappy", "Wappa"], "default_exits": [{"room": 1, "unkn1": 146, "unkn2": 6, "x": 168, "y": 616, "name": "Cloudy Park 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[32, 16], [27, 16], [44, 16], [12, 16], [14, 23]], "locations": ["Cloudy Park 5 - Enemy 1 (Yaban)", "Cloudy Park 5 - Enemy 2 (Sir Kibble)", "Cloudy Park 5 - Enemy 3 (Cappy)", "Cloudy Park 5 - Enemy 4 (Wappa)", "Cloudy Park 5 - Star 1", "Cloudy Park 5 - Star 2"], "music": 17}, {"name": "Cloudy Park 5 - 1", "level": 4, "stage": 5, "room": 1, "pointer": 3050956, "animal_pointers": [], "consumables": [{"idx": 5, "pointer": 264, "x": 480, "y": 720, "etype": 22, "vtype": 2, "name": "Cloudy Park 5 - Maxim Tomato (Pillars)"}], "consumables_pointer": 288, "enemies": ["Galbo", "Bronto Burt", "KeKe"], "default_exits": [{"room": 2, "unkn1": 32, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [51, 16], [26, 16], [14, 23], [2, 22]], "locations": ["Cloudy Park 5 - Enemy 5 (Galbo)", "Cloudy Park 5 - Enemy 6 (Bronto Burt)", "Cloudy Park 5 - Enemy 7 (KeKe)", "Cloudy Park 5 - Star 3", "Cloudy Park 5 - Star 4", "Cloudy Park 5 - Star 5", "Cloudy Park 5 - Maxim Tomato (Pillars)"], "music": 17}, {"name": "Cloudy Park 5 - 2", "level": 4, "stage": 5, "room": 2, "pointer": 3604194, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 17, "unkn2": 9, "x": 72, "y": 88, "name": "Cloudy Park 5 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 5 - Animal 1", "Cloudy Park 5 - Animal 2"], "music": 40}, {"name": "Cloudy Park 5 - 3", "level": 4, "stage": 5, "room": 3, "pointer": 3131242, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Klinko"], "default_exits": [{"room": 4, "unkn1": 98, "unkn2": 5, "x": 72, "y": 120, "name": "Cloudy Park 5 - 3 Exit 0", "access_rule": []}], "entity_load": [[89, 16], [4, 16], [42, 16], [4, 23]], "locations": ["Cloudy Park 5 - Enemy 8 (Propeller)", "Cloudy Park 5 - Enemy 9 (Klinko)"], "music": 17}, {"name": "Cloudy Park 5 - 4", "level": 4, "stage": 5, "room": 4, "pointer": 2990116, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod"], "default_exits": [{"room": 5, "unkn1": 23, "unkn2": 7, "x": 216, "y": 744, "name": "Cloudy Park 5 - 4 Exit 0", "access_rule": []}], "entity_load": [[88, 16]], "locations": ["Cloudy Park 5 - Enemy 10 (Wapod)"], "music": 17}, {"name": "Cloudy Park 5 - 5", "level": 4, "stage": 5, "room": 5, "pointer": 2975410, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 5, "unkn2": 4, "x": 104, "y": 296, "name": "Cloudy Park 5 - 5 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [14, 23], [2, 16]], "locations": ["Cloudy Park 5 - Star 6"], "music": 17}, {"name": "Cloudy Park 5 - 6", "level": 4, "stage": 5, "room": 6, "pointer": 3173683, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Pteran"], "default_exits": [{"room": 7, "unkn1": 115, "unkn2": 6, "x": 56, "y": 136, "name": "Cloudy Park 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [1, 23], [39, 16], [70, 16]], "locations": ["Cloudy Park 5 - Enemy 11 (Pteran)"], "music": 17}, {"name": "Cloudy Park 5 - 7", "level": 4, "stage": 5, "room": 7, "pointer": 2992340, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 8, "x": 72, "y": 120, "name": "Cloudy Park 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[25, 19], [42, 19]], "locations": ["Cloudy Park 5 - Pick"], "music": 8}, {"name": "Cloudy Park 5 - 8", "level": 4, "stage": 5, "room": 8, "pointer": 2891558, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 5 - Complete"], "music": 5}, {"name": "Cloudy Park 6 - 0", "level": 4, "stage": 6, "room": 0, "pointer": 3269847, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 65, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 6 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 23]], "locations": ["Cloudy Park 6 - Star 1"], "music": 11}, {"name": "Cloudy Park 6 - 1", "level": 4, "stage": 6, "room": 1, "pointer": 3252248, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 3, "unkn2": 25, "x": 72, "y": 72, "name": "Cloudy Park 6 - 1 Exit 0", "access_rule": []}], "entity_load": [[14, 16], [55, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 2", "level": 4, "stage": 6, "room": 2, "pointer": 3028494, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Madoo"], "default_exits": [{"room": 0, "unkn1": 4, "unkn2": 9, "x": 1032, "y": 152, "name": "Cloudy Park 6 - 2 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 13, "unkn2": 9, "x": 56, "y": 72, "name": "Cloudy Park 6 - 2 Exit 1", "access_rule": []}], "entity_load": [[58, 16], [14, 23]], "locations": ["Cloudy Park 6 - Enemy 1 (Madoo)", "Cloudy Park 6 - Star 2", "Cloudy Park 6 - Star 3", "Cloudy Park 6 - Star 4", "Cloudy Park 6 - Star 5"], "music": 11}, {"name": "Cloudy Park 6 - 3", "level": 4, "stage": 6, "room": 3, "pointer": 3131911, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 4, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 3 Exit 0", "access_rule": []}, {"room": 10, "unkn1": 8, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 3 Exit 1", "access_rule": []}, {"room": 10, "unkn1": 12, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 3 Exit 2", "access_rule": []}, {"room": 10, "unkn1": 16, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 3 Exit 3", "access_rule": []}, {"room": 10, "unkn1": 20, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 3 Exit 4", "access_rule": []}], "entity_load": [[58, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 4", "level": 4, "stage": 6, "room": 4, "pointer": 3115416, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Tick", "Como"], "default_exits": [{"room": 8, "unkn1": 20, "unkn2": 4, "x": 72, "y": 488, "name": "Cloudy Park 6 - 4 Exit 0", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 4 Exit 1", "access_rule": []}, {"room": 11, "unkn1": 8, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 4 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 12, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 4 Exit 3", "access_rule": []}, {"room": 11, "unkn1": 16, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 4 Exit 4", "access_rule": []}, {"room": 11, "unkn1": 20, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 4 Exit 5", "access_rule": []}], "entity_load": [[55, 16], [48, 16], [41, 16], [14, 23]], "locations": ["Cloudy Park 6 - Enemy 2 (Tick)", "Cloudy Park 6 - Enemy 3 (Como)", "Cloudy Park 6 - Star 6", "Cloudy Park 6 - Star 7", "Cloudy Park 6 - Star 8"], "music": 11}, {"name": "Cloudy Park 6 - 5", "level": 4, "stage": 6, "room": 5, "pointer": 3245809, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee Drawing", "Bronto Burt Drawing", "Bouncy Drawing"], "default_exits": [{"room": 15, "unkn1": 65, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 5 Exit 0", "access_rule": []}], "entity_load": [[84, 16], [85, 16], [86, 16], [14, 23], [4, 22]], "locations": ["Cloudy Park 6 - Enemy 4 (Waddle Dee Drawing)", "Cloudy Park 6 - Enemy 5 (Bronto Burt Drawing)", "Cloudy Park 6 - Enemy 6 (Bouncy Drawing)", "Cloudy Park 6 - Star 9", "Cloudy Park 6 - Star 10", "Cloudy Park 6 - Star 11", "Cloudy Park 6 - Star 12", "Cloudy Park 6 - Star 13"], "music": 11}, {"name": "Cloudy Park 6 - 6", "level": 4, "stage": 6, "room": 6, "pointer": 3237044, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 7, "unkn1": 56, "unkn2": 9, "x": 72, "y": 136, "name": "Cloudy Park 6 - 6 Exit 0", "access_rule": []}], "entity_load": [[89, 16]], "locations": ["Cloudy Park 6 - Enemy 7 (Propeller)"], "music": 11}, {"name": "Cloudy Park 6 - 7", "level": 4, "stage": 6, "room": 7, "pointer": 3262705, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mopoo"], "default_exits": [{"room": 13, "unkn1": 12, "unkn2": 8, "x": 184, "y": 216, "name": "Cloudy Park 6 - 7 Exit 0", "access_rule": []}, {"room": 14, "unkn1": 57, "unkn2": 8, "x": 184, "y": 216, "name": "Cloudy Park 6 - 7 Exit 1", "access_rule": []}, {"room": 9, "unkn1": 64, "unkn2": 8, "x": 72, "y": 120, "name": "Cloudy Park 6 - 7 Exit 2", "access_rule": []}], "entity_load": [[74, 16], [14, 23]], "locations": ["Cloudy Park 6 - Enemy 8 (Mopoo)", "Cloudy Park 6 - Star 14", "Cloudy Park 6 - Star 15", "Cloudy Park 6 - Star 16", "Cloudy Park 6 - Star 17"], "music": 11}, {"name": "Cloudy Park 6 - 8", "level": 4, "stage": 6, "room": 8, "pointer": 3027259, "animal_pointers": [], "consumables": [{"idx": 22, "pointer": 312, "x": 224, "y": 256, "etype": 22, "vtype": 0, "name": "Cloudy Park 6 - 1-Up (Cutter)"}], "consumables_pointer": 304, "enemies": ["Bukiset (Burning)", "Bukiset (Ice)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Cutter)"], "default_exits": [{"room": 12, "unkn1": 13, "unkn2": 4, "x": 88, "y": 152, "name": "Cloudy Park 6 - 8 Exit 0", "access_rule": []}], "entity_load": [[78, 16], [80, 16], [76, 16], [79, 16], [83, 16], [14, 23], [4, 22], [0, 22]], "locations": ["Cloudy Park 6 - Enemy 9 (Bukiset (Burning))", "Cloudy Park 6 - Enemy 10 (Bukiset (Ice))", "Cloudy Park 6 - Enemy 11 (Bukiset (Needle))", "Cloudy Park 6 - Enemy 12 (Bukiset (Clean))", "Cloudy Park 6 - Enemy 13 (Bukiset (Cutter))", "Cloudy Park 6 - Star 18", "Cloudy Park 6 - Star 19", "Cloudy Park 6 - Star 20", "Cloudy Park 6 - Star 21", "Cloudy Park 6 - Star 22", "Cloudy Park 6 - Star 23", "Cloudy Park 6 - Star 24", "Cloudy Park 6 - Star 25", "Cloudy Park 6 - Star 26", "Cloudy Park 6 - Star 27", "Cloudy Park 6 - Star 28", "Cloudy Park 6 - Star 29", "Cloudy Park 6 - 1-Up (Cutter)"], "music": 11}, {"name": "Cloudy Park 6 - 9", "level": 4, "stage": 6, "room": 9, "pointer": 3089504, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 35, "unkn2": 7, "x": 72, "y": 72, "name": "Cloudy Park 6 - 9 Exit 0", "access_rule": []}], "entity_load": [[41, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 10", "level": 4, "stage": 6, "room": 10, "pointer": 3132579, "animal_pointers": [242, 250, 258], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 20, "unkn2": 4, "x": 72, "y": 152, "name": "Cloudy Park 6 - 10 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 4, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 6 - 10 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 8, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 10 Exit 2", "access_rule": []}, {"room": 3, "unkn1": 12, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 10 Exit 3", "access_rule": []}, {"room": 3, "unkn1": 16, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 10 Exit 4", "access_rule": []}, {"room": 3, "unkn1": 20, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 10 Exit 5", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 6 - Animal 1", "Cloudy Park 6 - Animal 2", "Cloudy Park 6 - Animal 3"], "music": 40}, {"name": "Cloudy Park 6 - 11", "level": 4, "stage": 6, "room": 11, "pointer": 3017866, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 4, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 11 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 8, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 11 Exit 1", "access_rule": []}, {"room": 4, "unkn1": 12, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 11 Exit 2", "access_rule": []}, {"room": 4, "unkn1": 16, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 11 Exit 3", "access_rule": []}, {"room": 4, "unkn1": 20, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 11 Exit 4", "access_rule": []}], "entity_load": [[58, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 12", "level": 4, "stage": 6, "room": 12, "pointer": 3036404, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 4, "unkn2": 9, "x": 200, "y": 72, "name": "Cloudy Park 6 - 12 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 13, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 6 - 12 Exit 1", "access_rule": []}], "entity_load": [[58, 16], [14, 23]], "locations": ["Cloudy Park 6 - Star 30", "Cloudy Park 6 - Star 31", "Cloudy Park 6 - Star 32", "Cloudy Park 6 - Star 33"], "music": 11}, {"name": "Cloudy Park 6 - 13", "level": 4, "stage": 6, "room": 13, "pointer": 2965251, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 10, "unkn2": 13, "x": 216, "y": 136, "name": "Cloudy Park 6 - 13 Exit 0", "access_rule": []}], "entity_load": [[26, 19]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 14", "level": 4, "stage": 6, "room": 14, "pointer": 3077236, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 10, "unkn2": 13, "x": 936, "y": 136, "name": "Cloudy Park 6 - 14 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 15", "level": 4, "stage": 6, "room": 15, "pointer": 3061794, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 13, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 6 - 15 Exit 0", "access_rule": []}], "entity_load": [[26, 19], [42, 19]], "locations": ["Cloudy Park 6 - HB-007"], "music": 8}, {"name": "Cloudy Park 6 - 16", "level": 4, "stage": 6, "room": 16, "pointer": 2888907, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 6 - Complete"], "music": 5}, {"name": "Cloudy Park Boss - 0", "level": 4, "stage": 7, "room": 0, "pointer": 2998682, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[3, 18]], "locations": ["Cloudy Park - Boss (Ado) Purified", "Level 4 Boss - Defeated", "Level 4 Boss - Purified"], "music": 2}, {"name": "Iceberg 1 - 0", "level": 5, "stage": 1, "room": 0, "pointer": 3363111, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Klinko", "KeKe"], "default_exits": [{"room": 1, "unkn1": 104, "unkn2": 8, "x": 312, "y": 1384, "name": "Iceberg 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[42, 16], [0, 16], [51, 16]], "locations": ["Iceberg 1 - Enemy 1 (Waddle Dee)", "Iceberg 1 - Enemy 2 (Klinko)", "Iceberg 1 - Enemy 3 (KeKe)"], "music": 18}, {"name": "Iceberg 1 - 1", "level": 5, "stage": 1, "room": 1, "pointer": 3596524, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como", "Galbo", "Rocky"], "default_exits": [{"room": 2, "unkn1": 20, "unkn2": 8, "x": 72, "y": 344, "name": "Iceberg 1 - 1 Exit 0", "access_rule": []}], "entity_load": [[3, 16], [41, 16], [26, 16], [14, 23], [4, 22], [6, 23]], "locations": ["Iceberg 1 - Enemy 4 (Como)", "Iceberg 1 - Enemy 5 (Galbo)", "Iceberg 1 - Enemy 6 (Rocky)", "Iceberg 1 - Star 1", "Iceberg 1 - Star 2", "Iceberg 1 - Star 3", "Iceberg 1 - Star 4", "Iceberg 1 - Star 5", "Iceberg 1 - Star 6"], "music": 18}, {"name": "Iceberg 1 - 2", "level": 5, "stage": 1, "room": 2, "pointer": 3288880, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kapar"], "default_exits": [{"room": 3, "unkn1": 49, "unkn2": 9, "x": 184, "y": 152, "name": "Iceberg 1 - 2 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 94, "unkn2": 21, "x": 120, "y": 168, "name": "Iceberg 1 - 2 Exit 1", "access_rule": []}], "entity_load": [[28, 19], [46, 16], [47, 16], [17, 16], [67, 16]], "locations": ["Iceberg 1 - Enemy 7 (Kapar)"], "music": 18}, {"name": "Iceberg 1 - 3", "level": 5, "stage": 1, "room": 3, "pointer": 3068439, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 10, "unkn2": 9, "x": 808, "y": 152, "name": "Iceberg 1 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 1 - Animal 1", "Iceberg 1 - Animal 2"], "music": 38}, {"name": "Iceberg 1 - 4", "level": 5, "stage": 1, "room": 4, "pointer": 3233681, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mopoo", "Babut", "Wappa"], "default_exits": [{"room": 6, "unkn1": 74, "unkn2": 4, "x": 56, "y": 152, "name": "Iceberg 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[44, 16], [43, 16], [74, 16]], "locations": ["Iceberg 1 - Enemy 8 (Mopoo)", "Iceberg 1 - Enemy 9 (Babut)", "Iceberg 1 - Enemy 10 (Wappa)"], "music": 18}, {"name": "Iceberg 1 - 5", "level": 5, "stage": 1, "room": 5, "pointer": 3406133, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Chilly", "Poppy Bros Jr."], "default_exits": [{"room": 4, "unkn1": 196, "unkn2": 9, "x": 72, "y": 744, "name": "Iceberg 1 - 5 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [7, 16], [2, 16], [0, 16]], "locations": ["Iceberg 1 - Enemy 11 (Bronto Burt)", "Iceberg 1 - Enemy 12 (Chilly)", "Iceberg 1 - Enemy 13 (Poppy Bros Jr.)"], "music": 18}, {"name": "Iceberg 1 - 6", "level": 5, "stage": 1, "room": 6, "pointer": 2985823, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[28, 19], [42, 19]], "locations": ["Iceberg 1 - Kogoesou"], "music": 8}, {"name": "Iceberg 1 - 7", "level": 5, "stage": 1, "room": 7, "pointer": 2892040, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 1 - Complete"], "music": 5}, {"name": "Iceberg 2 - 0", "level": 5, "stage": 2, "room": 0, "pointer": 3106800, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon", "Nruff"], "default_exits": [{"room": 1, "unkn1": 113, "unkn2": 36, "x": 88, "y": 152, "name": "Iceberg 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[15, 16], [0, 16], [24, 16]], "locations": ["Iceberg 2 - Enemy 1 (Gabon)", "Iceberg 2 - Enemy 2 (Nruff)"], "music": 20}, {"name": "Iceberg 2 - 1", "level": 5, "stage": 2, "room": 1, "pointer": 3334841, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Chilly", "Pteran"], "default_exits": [{"room": 2, "unkn1": 109, "unkn2": 20, "x": 88, "y": 72, "name": "Iceberg 2 - 1 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [0, 16], [4, 16], [39, 16]], "locations": ["Iceberg 2 - Enemy 3 (Waddle Dee)", "Iceberg 2 - Enemy 4 (Chilly)", "Iceberg 2 - Enemy 5 (Pteran)"], "music": 20}, {"name": "Iceberg 2 - 2", "level": 5, "stage": 2, "room": 2, "pointer": 3473408, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Galbo", "Babut", "Magoo"], "default_exits": [{"room": 6, "unkn1": 102, "unkn2": 5, "x": 88, "y": 152, "name": "Iceberg 2 - 2 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 24, "unkn2": 18, "x": 88, "y": 152, "name": "Iceberg 2 - 2 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 37, "unkn2": 26, "x": 200, "y": 184, "name": "Iceberg 2 - 2 Exit 2", "access_rule": []}, {"room": 4, "unkn1": 55, "unkn2": 26, "x": 200, "y": 184, "name": "Iceberg 2 - 2 Exit 3", "access_rule": []}, {"room": 5, "unkn1": 73, "unkn2": 26, "x": 200, "y": 184, "name": "Iceberg 2 - 2 Exit 4", "access_rule": []}], "entity_load": [[53, 16], [26, 16], [43, 16], [14, 23], [16, 16]], "locations": ["Iceberg 2 - Enemy 6 (Glunk)", "Iceberg 2 - Enemy 7 (Galbo)", "Iceberg 2 - Enemy 8 (Babut)", "Iceberg 2 - Enemy 9 (Magoo)", "Iceberg 2 - Star 1", "Iceberg 2 - Star 2"], "music": 20}, {"name": "Iceberg 2 - 3", "level": 5, "stage": 2, "room": 3, "pointer": 3037006, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 11, "x": 616, "y": 424, "name": "Iceberg 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [14, 23]], "locations": ["Iceberg 2 - Star 3"], "music": 20}, {"name": "Iceberg 2 - 4", "level": 5, "stage": 2, "room": 4, "pointer": 3035198, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 11, "x": 904, "y": 424, "name": "Iceberg 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [14, 23]], "locations": ["Iceberg 2 - Star 4", "Iceberg 2 - Star 5"], "music": 20}, {"name": "Iceberg 2 - 5", "level": 5, "stage": 2, "room": 5, "pointer": 3128551, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 11, "x": 1192, "y": 424, "name": "Iceberg 2 - 5 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [14, 23]], "locations": ["Iceberg 2 - Star 6", "Iceberg 2 - Star 7", "Iceberg 2 - Star 8"], "music": 20}, {"name": "Iceberg 2 - 6", "level": 5, "stage": 2, "room": 6, "pointer": 3270857, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Nidoo", "Oro"], "default_exits": [{"room": 7, "unkn1": 65, "unkn2": 9, "x": 88, "y": 152, "name": "Iceberg 2 - 6 Exit 0", "access_rule": []}], "entity_load": [[62, 16], [4, 22], [89, 16], [28, 16], [25, 16]], "locations": ["Iceberg 2 - Enemy 10 (Propeller)", "Iceberg 2 - Enemy 11 (Nidoo)", "Iceberg 2 - Enemy 12 (Oro)"], "music": 20}, {"name": "Iceberg 2 - 7", "level": 5, "stage": 2, "room": 7, "pointer": 3212501, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Klinko", "Bronto Burt"], "default_exits": [{"room": 8, "unkn1": 124, "unkn2": 8, "x": 72, "y": 152, "name": "Iceberg 2 - 7 Exit 0", "access_rule": []}], "entity_load": [[42, 16], [6, 16], [14, 23], [2, 16], [4, 23]], "locations": ["Iceberg 2 - Enemy 13 (Klinko)", "Iceberg 2 - Enemy 14 (Bronto Burt)", "Iceberg 2 - Star 9", "Iceberg 2 - Star 10", "Iceberg 2 - Star 11", "Iceberg 2 - Star 12", "Iceberg 2 - Star 13", "Iceberg 2 - Star 14", "Iceberg 2 - Star 15", "Iceberg 2 - Star 16", "Iceberg 2 - Star 17", "Iceberg 2 - Star 18", "Iceberg 2 - Star 19"], "music": 20}, {"name": "Iceberg 2 - 8", "level": 5, "stage": 2, "room": 8, "pointer": 2994515, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 2 - 8 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [42, 19]], "locations": ["Iceberg 2 - Samus"], "music": 8}, {"name": "Iceberg 2 - 9", "level": 5, "stage": 2, "room": 9, "pointer": 3058084, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 4, "unkn2": 9, "x": 408, "y": 296, "name": "Iceberg 2 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 2 - Animal 1", "Iceberg 2 - Animal 2"], "music": 39}, {"name": "Iceberg 2 - 10", "level": 5, "stage": 2, "room": 10, "pointer": 2887220, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 2 - Complete"], "music": 5}, {"name": "Iceberg 3 - 0", "level": 5, "stage": 3, "room": 0, "pointer": 3455346, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Corori", "Bouncy", "Chilly", "Pteran"], "default_exits": [{"room": 1, "unkn1": 98, "unkn2": 6, "x": 200, "y": 232, "name": "Iceberg 3 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 217, "unkn2": 7, "x": 40, "y": 120, "name": "Iceberg 3 - 0 Exit 1", "access_rule": []}], "entity_load": [[60, 16], [6, 16], [39, 16], [13, 16], [14, 23]], "locations": ["Iceberg 3 - Enemy 1 (Corori)", "Iceberg 3 - Enemy 2 (Bouncy)", "Iceberg 3 - Enemy 3 (Chilly)", "Iceberg 3 - Enemy 4 (Pteran)", "Iceberg 3 - Star 1", "Iceberg 3 - Star 2"], "music": 14}, {"name": "Iceberg 3 - 1", "level": 5, "stage": 3, "room": 1, "pointer": 3019769, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 11, "unkn2": 14, "x": 1592, "y": 104, "name": "Iceberg 3 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 3 - Animal 1", "Iceberg 3 - Animal 2", "Iceberg 3 - Animal 3"], "music": 38}, {"name": "Iceberg 3 - 2", "level": 5, "stage": 3, "room": 2, "pointer": 3618121, "animal_pointers": [], "consumables": [{"idx": 2, "pointer": 352, "x": 1776, "y": 104, "etype": 22, "vtype": 2, "name": "Iceberg 3 - Maxim Tomato (Ceiling)"}], "consumables_pointer": 128, "enemies": ["Raft Waddle Dee", "Kapar", "Blipper"], "default_exits": [{"room": 3, "unkn1": 196, "unkn2": 11, "x": 72, "y": 152, "name": "Iceberg 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[2, 22], [71, 16], [57, 16], [21, 16], [67, 16], [14, 23]], "locations": ["Iceberg 3 - Enemy 5 (Raft Waddle Dee)", "Iceberg 3 - Enemy 6 (Kapar)", "Iceberg 3 - Enemy 7 (Blipper)", "Iceberg 3 - Star 3", "Iceberg 3 - Maxim Tomato (Ceiling)"], "music": 14}, {"name": "Iceberg 3 - 3", "level": 5, "stage": 3, "room": 3, "pointer": 3650368, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod"], "default_exits": [{"room": 5, "unkn1": 116, "unkn2": 11, "x": 40, "y": 152, "name": "Iceberg 3 - 3 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 63, "unkn2": 13, "x": 184, "y": 136, "name": "Iceberg 3 - 3 Exit 1", "access_rule": []}], "entity_load": [[1, 16], [14, 23], [4, 22], [6, 16], [88, 16]], "locations": ["Iceberg 3 - Enemy 8 (Wapod)", "Iceberg 3 - Star 4", "Iceberg 3 - Star 5", "Iceberg 3 - Star 6", "Iceberg 3 - Star 7", "Iceberg 3 - Star 8", "Iceberg 3 - Star 9", "Iceberg 3 - Star 10"], "music": 14}, {"name": "Iceberg 3 - 4", "level": 5, "stage": 3, "room": 4, "pointer": 3038208, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 10, "unkn2": 8, "x": 1032, "y": 216, "name": "Iceberg 3 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 3 - Animal 4", "Iceberg 3 - Animal 5"], "music": 39}, {"name": "Iceberg 3 - 5", "level": 5, "stage": 3, "room": 5, "pointer": 3013938, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[30, 19]], "locations": [], "music": 31}, {"name": "Iceberg 3 - 6", "level": 5, "stage": 3, "room": 6, "pointer": 3624789, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Icicle"], "default_exits": [{"room": 7, "unkn1": 211, "unkn2": 7, "x": 40, "y": 152, "name": "Iceberg 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [16, 16], [14, 23], [36, 16], [4, 22]], "locations": ["Iceberg 3 - Enemy 9 (Glunk)", "Iceberg 3 - Enemy 10 (Icicle)", "Iceberg 3 - Star 11", "Iceberg 3 - Star 12", "Iceberg 3 - Star 13", "Iceberg 3 - Star 14", "Iceberg 3 - Star 15", "Iceberg 3 - Star 16", "Iceberg 3 - Star 17", "Iceberg 3 - Star 18", "Iceberg 3 - Star 19", "Iceberg 3 - Star 20", "Iceberg 3 - Star 21"], "music": 14}, {"name": "Iceberg 3 - 7", "level": 5, "stage": 3, "room": 7, "pointer": 2989472, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 3 - 7 Exit 0", "access_rule": []}], "entity_load": [[30, 19]], "locations": ["Iceberg 3 - Chef Kawasaki"], "music": 8}, {"name": "Iceberg 3 - 8", "level": 5, "stage": 3, "room": 8, "pointer": 2889871, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 3 - Complete"], "music": 5}, {"name": "Iceberg 4 - 0", "level": 5, "stage": 4, "room": 0, "pointer": 3274879, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Galbo", "Klinko", "Chilly"], "default_exits": [{"room": 1, "unkn1": 111, "unkn2": 8, "x": 72, "y": 104, "name": "Iceberg 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[42, 16], [6, 16], [2, 16], [26, 16]], "locations": ["Iceberg 4 - Enemy 1 (Bronto Burt)", "Iceberg 4 - Enemy 2 (Galbo)", "Iceberg 4 - Enemy 3 (Klinko)", "Iceberg 4 - Enemy 4 (Chilly)"], "music": 19}, {"name": "Iceberg 4 - 1", "level": 5, "stage": 4, "room": 1, "pointer": 3371780, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Babut", "Wappa"], "default_exits": [{"room": 2, "unkn1": 60, "unkn2": 36, "x": 216, "y": 88, "name": "Iceberg 4 - 1 Exit 0", "access_rule": []}], "entity_load": [[43, 16], [4, 22], [44, 16]], "locations": ["Iceberg 4 - Enemy 5 (Babut)", "Iceberg 4 - Enemy 6 (Wappa)"], "music": 19}, {"name": "Iceberg 4 - 2", "level": 5, "stage": 4, "room": 2, "pointer": 3284378, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 8, "unkn2": 39, "x": 168, "y": 152, "name": "Iceberg 4 - 2 Exit 0", "access_rule": ["Burning", "Burning Ability"]}, {"room": 3, "unkn1": 13, "unkn2": 39, "x": 88, "y": 136, "name": "Iceberg 4 - 2 Exit 1", "access_rule": []}, {"room": 17, "unkn1": 18, "unkn2": 39, "x": 120, "y": 152, "name": "Iceberg 4 - 2 Exit 2", "access_rule": ["Burning", "Burning Ability"]}], "entity_load": [[26, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 3", "level": 5, "stage": 4, "room": 3, "pointer": 3162957, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 44, "unkn2": 8, "x": 216, "y": 104, "name": "Iceberg 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[69, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 4", "level": 5, "stage": 4, "room": 4, "pointer": 3261679, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Icicle"], "default_exits": [{"room": 5, "unkn1": 4, "unkn2": 42, "x": 104, "y": 840, "name": "Iceberg 4 - 4 Exit 0", "access_rule": []}], "entity_load": [[36, 16]], "locations": ["Iceberg 4 - Enemy 7 (Icicle)"], "music": 19}, {"name": "Iceberg 4 - 5", "level": 5, "stage": 4, "room": 5, "pointer": 3217398, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Corori"], "default_exits": [{"room": 6, "unkn1": 19, "unkn2": 5, "x": 72, "y": 120, "name": "Iceberg 4 - 5 Exit 0", "access_rule": []}], "entity_load": [[60, 16], [44, 16], [4, 22]], "locations": ["Iceberg 4 - Enemy 8 (Corori)"], "music": 19}, {"name": "Iceberg 4 - 6", "level": 5, "stage": 4, "room": 6, "pointer": 3108265, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 61, "unkn2": 7, "x": 456, "y": 72, "name": "Iceberg 4 - 6 Exit 0", "access_rule": []}], "entity_load": [[62, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 7", "level": 5, "stage": 4, "room": 7, "pointer": 3346202, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 39, "unkn2": 6, "x": 168, "y": 104, "name": "Iceberg 4 - 7 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 4, "unkn2": 21, "x": 88, "y": 168, "name": "Iceberg 4 - 7 Exit 1", "access_rule": ["Burning", "Burning Ability"]}, {"room": 13, "unkn1": 21, "unkn2": 21, "x": 280, "y": 168, "name": "Iceberg 4 - 7 Exit 2", "access_rule": ["Burning", "Burning Ability"]}], "entity_load": [[14, 23], [4, 22], [4, 23]], "locations": ["Iceberg 4 - Star 1", "Iceberg 4 - Star 2"], "music": 19}, {"name": "Iceberg 4 - 8", "level": 5, "stage": 4, "room": 8, "pointer": 3055911, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 10, "unkn2": 5, "x": 648, "y": 104, "name": "Iceberg 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[26, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 9", "level": 5, "stage": 4, "room": 9, "pointer": 3056457, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 13, "unkn2": 9, "x": 136, "y": 136, "name": "Iceberg 4 - 9 Exit 0", "access_rule": []}], "entity_load": [[1, 27]], "locations": ["Iceberg 4 - Miniboss 1 (Yuki)"], "music": 4}, {"name": "Iceberg 4 - 10", "level": 5, "stage": 4, "room": 10, "pointer": 3257516, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 8, "unkn2": 37, "x": 88, "y": 40, "name": "Iceberg 4 - 10 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 15, "unkn2": 37, "x": 200, "y": 40, "name": "Iceberg 4 - 10 Exit 1", "access_rule": []}], "entity_load": [[31, 19]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 11", "level": 5, "stage": 4, "room": 11, "pointer": 3083322, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon"], "default_exits": [{"room": 12, "unkn1": 46, "unkn2": 8, "x": 88, "y": 120, "name": "Iceberg 4 - 11 Exit 0", "access_rule": []}], "entity_load": [[24, 16]], "locations": ["Iceberg 4 - Enemy 9 (Gabon)"], "music": 19}, {"name": "Iceberg 4 - 12", "level": 5, "stage": 4, "room": 12, "pointer": 3147724, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kabu"], "default_exits": [{"room": 18, "unkn1": 64, "unkn2": 7, "x": 72, "y": 456, "name": "Iceberg 4 - 12 Exit 0", "access_rule": []}], "entity_load": [[19, 16], [61, 16]], "locations": ["Iceberg 4 - Enemy 10 (Kabu)"], "music": 19}, {"name": "Iceberg 4 - 13", "level": 5, "stage": 4, "room": 13, "pointer": 3370077, "animal_pointers": [232, 240, 248], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 15, "unkn1": 31, "unkn2": 4, "x": 216, "y": 120, "name": "Iceberg 4 - 13 Exit 0", "access_rule": []}, {"room": 10, "unkn1": 46, "unkn2": 10, "x": 72, "y": 88, "name": "Iceberg 4 - 13 Exit 1", "access_rule": []}, {"room": 10, "unkn1": 57, "unkn2": 10, "x": 312, "y": 88, "name": "Iceberg 4 - 13 Exit 2", "access_rule": []}, {"room": 14, "unkn1": 28, "unkn2": 21, "x": 152, "y": 136, "name": "Iceberg 4 - 13 Exit 3", "access_rule": []}, {"room": 14, "unkn1": 34, "unkn2": 21, "x": 280, "y": 136, "name": "Iceberg 4 - 13 Exit 4", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 4 - Animal 1", "Iceberg 4 - Animal 2", "Iceberg 4 - Animal 3"], "music": 19}, {"name": "Iceberg 4 - 14", "level": 5, "stage": 4, "room": 14, "pointer": 3057002, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Broom Hatter", "Sasuke"], "default_exits": [{"room": 15, "unkn1": 4, "unkn2": 8, "x": 88, "y": 360, "name": "Iceberg 4 - 14 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 10, "unkn2": 8, "x": 440, "y": 344, "name": "Iceberg 4 - 14 Exit 1", "access_rule": []}, {"room": 13, "unkn1": 16, "unkn2": 8, "x": 568, "y": 344, "name": "Iceberg 4 - 14 Exit 2", "access_rule": []}, {"room": 15, "unkn1": 22, "unkn2": 8, "x": 344, "y": 360, "name": "Iceberg 4 - 14 Exit 3", "access_rule": []}], "entity_load": [[11, 16], [30, 16]], "locations": ["Iceberg 4 - Enemy 11 (Broom Hatter)", "Iceberg 4 - Enemy 12 (Sasuke)"], "music": 19}, {"name": "Iceberg 4 - 15", "level": 5, "stage": 4, "room": 15, "pointer": 3116124, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 13, "unkn1": 13, "unkn2": 6, "x": 520, "y": 72, "name": "Iceberg 4 - 15 Exit 0", "access_rule": []}, {"room": 14, "unkn1": 4, "unkn2": 22, "x": 88, "y": 136, "name": "Iceberg 4 - 15 Exit 1", "access_rule": []}, {"room": 14, "unkn1": 22, "unkn2": 22, "x": 344, "y": 136, "name": "Iceberg 4 - 15 Exit 2", "access_rule": []}], "entity_load": [[4, 22]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 16", "level": 5, "stage": 4, "room": 16, "pointer": 3069937, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 9, "x": 152, "y": 632, "name": "Iceberg 4 - 16 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 4 - Animal 4"], "music": 38}, {"name": "Iceberg 4 - 17", "level": 5, "stage": 4, "room": 17, "pointer": 3072413, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 6, "unkn2": 9, "x": 280, "y": 632, "name": "Iceberg 4 - 17 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 4 - Animal 5"], "music": 38}, {"name": "Iceberg 4 - 18", "level": 5, "stage": 4, "room": 18, "pointer": 3404593, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nruff"], "default_exits": [{"room": 19, "unkn1": 94, "unkn2": 12, "x": 72, "y": 152, "name": "Iceberg 4 - 18 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [43, 16], [30, 16], [15, 16]], "locations": ["Iceberg 4 - Enemy 13 (Nruff)", "Iceberg 4 - Star 3"], "music": 19}, {"name": "Iceberg 4 - 19", "level": 5, "stage": 4, "room": 19, "pointer": 3075826, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 20, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 4 - 19 Exit 0", "access_rule": []}], "entity_load": [[31, 19], [42, 19]], "locations": ["Iceberg 4 - Name"], "music": 8}, {"name": "Iceberg 4 - 20", "level": 5, "stage": 4, "room": 20, "pointer": 2887943, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 4 - Complete"], "music": 5}, {"name": "Iceberg 5 - 0", "level": 5, "stage": 5, "room": 0, "pointer": 3316135, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Burning)", "Bukiset (Stone)", "Bukiset (Ice)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Parasol)", "Bukiset (Spark)", "Bukiset (Cutter)"], "default_exits": [{"room": 30, "unkn1": 75, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[79, 16], [76, 16], [81, 16], [78, 16], [77, 16], [82, 16], [80, 16], [83, 16]], "locations": ["Iceberg 5 - Enemy 1 (Bukiset (Burning))", "Iceberg 5 - Enemy 2 (Bukiset (Stone))", "Iceberg 5 - Enemy 3 (Bukiset (Ice))", "Iceberg 5 - Enemy 4 (Bukiset (Needle))", "Iceberg 5 - Enemy 5 (Bukiset (Clean))", "Iceberg 5 - Enemy 6 (Bukiset (Parasol))", "Iceberg 5 - Enemy 7 (Bukiset (Spark))", "Iceberg 5 - Enemy 8 (Bukiset (Cutter))"], "music": 16}, {"name": "Iceberg 5 - 1", "level": 5, "stage": 5, "room": 1, "pointer": 3037607, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 12, "unkn2": 6, "x": 72, "y": 104, "name": "Iceberg 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[1, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 2", "level": 5, "stage": 5, "room": 2, "pointer": 3103842, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk"], "default_exits": [{"room": 25, "unkn1": 27, "unkn2": 6, "x": 72, "y": 200, "name": "Iceberg 5 - 2 Exit 0", "access_rule": []}], "entity_load": [[16, 16]], "locations": ["Iceberg 5 - Enemy 9 (Glunk)"], "music": 16}, {"name": "Iceberg 5 - 3", "level": 5, "stage": 5, "room": 3, "pointer": 3135899, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod"], "default_exits": [{"room": 4, "unkn1": 20, "unkn2": 7, "x": 72, "y": 88, "name": "Iceberg 5 - 3 Exit 0", "access_rule": []}], "entity_load": [[88, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 10 (Wapod)", "Iceberg 5 - Star 1", "Iceberg 5 - Star 2"], "music": 16}, {"name": "Iceberg 5 - 4", "level": 5, "stage": 5, "room": 4, "pointer": 3180695, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Tick"], "default_exits": [{"room": 5, "unkn1": 26, "unkn2": 5, "x": 56, "y": 104, "name": "Iceberg 5 - 4 Exit 0", "access_rule": []}], "entity_load": [[48, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 11 (Tick)", "Iceberg 5 - Star 3", "Iceberg 5 - Star 4", "Iceberg 5 - Star 5", "Iceberg 5 - Star 6", "Iceberg 5 - Star 7", "Iceberg 5 - Star 8"], "music": 16}, {"name": "Iceberg 5 - 5", "level": 5, "stage": 5, "room": 5, "pointer": 3106064, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Madoo"], "default_exits": [{"room": 24, "unkn1": 14, "unkn2": 6, "x": 168, "y": 152, "name": "Iceberg 5 - 5 Exit 0", "access_rule": []}], "entity_load": [[58, 16], [14, 23], [4, 22]], "locations": ["Iceberg 5 - Enemy 12 (Madoo)", "Iceberg 5 - Star 9", "Iceberg 5 - Star 10", "Iceberg 5 - Star 11", "Iceberg 5 - Star 12", "Iceberg 5 - Star 13", "Iceberg 5 - Star 14"], "music": 16}, {"name": "Iceberg 5 - 6", "level": 5, "stage": 5, "room": 6, "pointer": 3276800, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 59, "unkn2": 12, "x": 72, "y": 120, "name": "Iceberg 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[55, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 7", "level": 5, "stage": 5, "room": 7, "pointer": 3104585, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 26, "unkn1": 25, "unkn2": 7, "x": 72, "y": 136, "name": "Iceberg 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[23, 16], [7, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 8", "level": 5, "stage": 5, "room": 8, "pointer": 3195121, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 35, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 8 Exit 0", "access_rule": []}], "entity_load": [[4, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 9", "level": 5, "stage": 5, "room": 9, "pointer": 3087198, "animal_pointers": [], "consumables": [{"idx": 16, "pointer": 200, "x": 256, "y": 88, "etype": 22, "vtype": 0, "name": "Iceberg 5 - 1-Up (Boulder)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 28, "unkn1": 20, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 9 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [54, 16]], "locations": ["Iceberg 5 - 1-Up (Boulder)"], "music": 16}, {"name": "Iceberg 5 - 10", "level": 5, "stage": 5, "room": 10, "pointer": 3321612, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 32, "unkn1": 45, "unkn2": 15, "x": 72, "y": 120, "name": "Iceberg 5 - 10 Exit 0", "access_rule": []}], "entity_load": [[14, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 11", "level": 5, "stage": 5, "room": 11, "pointer": 3139178, "animal_pointers": [], "consumables": [{"idx": 17, "pointer": 192, "x": 152, "y": 168, "etype": 22, "vtype": 0, "name": "Iceberg 5 - 1-Up (Floor)"}], "consumables_pointer": 176, "enemies": ["Yaban"], "default_exits": [{"room": 12, "unkn1": 17, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 11 Exit 0", "access_rule": []}], "entity_load": [[32, 16], [0, 22]], "locations": ["Iceberg 5 - Enemy 13 (Yaban)", "Iceberg 5 - 1-Up (Floor)"], "music": 16}, {"name": "Iceberg 5 - 12", "level": 5, "stage": 5, "room": 12, "pointer": 3118231, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 13, "unkn1": 13, "unkn2": 7, "x": 72, "y": 104, "name": "Iceberg 5 - 12 Exit 0", "access_rule": []}], "entity_load": [[89, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 14 (Propeller)", "Iceberg 5 - Star 15", "Iceberg 5 - Star 16", "Iceberg 5 - Star 17", "Iceberg 5 - Star 18", "Iceberg 5 - Star 19", "Iceberg 5 - Star 20"], "music": 16}, {"name": "Iceberg 5 - 13", "level": 5, "stage": 5, "room": 13, "pointer": 3021658, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mariel"], "default_exits": [{"room": 27, "unkn1": 16, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 13 Exit 0", "access_rule": []}], "entity_load": [[45, 16]], "locations": ["Iceberg 5 - Enemy 15 (Mariel)"], "music": 16}, {"name": "Iceberg 5 - 14", "level": 5, "stage": 5, "room": 14, "pointer": 3025398, "animal_pointers": [], "consumables": [{"idx": 24, "pointer": 200, "x": 208, "y": 88, "etype": 22, "vtype": 0, "name": "Iceberg 5 - 1-Up (Peloo)"}], "consumables_pointer": 176, "enemies": [], "default_exits": [{"room": 15, "unkn1": 13, "unkn2": 9, "x": 72, "y": 216, "name": "Iceberg 5 - 14 Exit 0", "access_rule": []}], "entity_load": [[64, 16], [0, 22]], "locations": ["Iceberg 5 - 1-Up (Peloo)"], "music": 16}, {"name": "Iceberg 5 - 15", "level": 5, "stage": 5, "room": 15, "pointer": 3167445, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Pteran"], "default_exits": [{"room": 16, "unkn1": 13, "unkn2": 13, "x": 72, "y": 152, "name": "Iceberg 5 - 15 Exit 0", "access_rule": []}], "entity_load": [[39, 16]], "locations": ["Iceberg 5 - Enemy 16 (Pteran)"], "music": 16}, {"name": "Iceberg 5 - 16", "level": 5, "stage": 5, "room": 16, "pointer": 3033990, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 33, "unkn1": 36, "unkn2": 9, "x": 168, "y": 152, "name": "Iceberg 5 - 16 Exit 0", "access_rule": []}], "entity_load": [[68, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 17", "level": 5, "stage": 5, "room": 17, "pointer": 3100111, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 20, "unkn1": 40, "unkn2": 7, "x": 72, "y": 200, "name": "Iceberg 5 - 17 Exit 0", "access_rule": []}], "entity_load": [[52, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 18", "level": 5, "stage": 5, "room": 18, "pointer": 3030947, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 17, "unkn1": 13, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 18 Exit 0", "access_rule": []}], "entity_load": [[26, 16]], "locations": ["Iceberg 5 - Enemy 17 (Galbo)"], "music": 16}, {"name": "Iceberg 5 - 19", "level": 5, "stage": 5, "room": 19, "pointer": 3105326, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["KeKe"], "default_exits": [{"room": 21, "unkn1": 13, "unkn2": 4, "x": 72, "y": 152, "name": "Iceberg 5 - 19 Exit 0", "access_rule": []}], "entity_load": [[51, 16]], "locations": ["Iceberg 5 - Enemy 18 (KeKe)"], "music": 16}, {"name": "Iceberg 5 - 20", "level": 5, "stage": 5, "room": 20, "pointer": 3118928, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 19, "unkn1": 13, "unkn2": 6, "x": 72, "y": 264, "name": "Iceberg 5 - 20 Exit 0", "access_rule": []}], "entity_load": [[28, 16]], "locations": ["Iceberg 5 - Enemy 19 (Nidoo)"], "music": 16}, {"name": "Iceberg 5 - 21", "level": 5, "stage": 5, "room": 21, "pointer": 3202517, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee Drawing", "Bronto Burt Drawing", "Bouncy Drawing"], "default_exits": [{"room": 22, "unkn1": 29, "unkn2": 9, "x": 72, "y": 72, "name": "Iceberg 5 - 21 Exit 0", "access_rule": []}], "entity_load": [[84, 16], [85, 16], [86, 16]], "locations": ["Iceberg 5 - Enemy 20 (Waddle Dee Drawing)", "Iceberg 5 - Enemy 21 (Bronto Burt Drawing)", "Iceberg 5 - Enemy 22 (Bouncy Drawing)"], "music": 16}, {"name": "Iceberg 5 - 22", "level": 5, "stage": 5, "room": 22, "pointer": 3014656, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Joe"], "default_exits": [{"room": 23, "unkn1": 13, "unkn2": 4, "x": 72, "y": 104, "name": "Iceberg 5 - 22 Exit 0", "access_rule": []}], "entity_load": [[91, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 23 (Joe)", "Iceberg 5 - Star 21", "Iceberg 5 - Star 22", "Iceberg 5 - Star 23", "Iceberg 5 - Star 24", "Iceberg 5 - Star 25"], "music": 16}, {"name": "Iceberg 5 - 23", "level": 5, "stage": 5, "room": 23, "pointer": 3166550, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kapar"], "default_exits": [{"room": 34, "unkn1": 27, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 23 Exit 0", "access_rule": []}], "entity_load": [[67, 16]], "locations": ["Iceberg 5 - Enemy 24 (Kapar)"], "music": 16}, {"name": "Iceberg 5 - 24", "level": 5, "stage": 5, "room": 24, "pointer": 3029110, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gansan"], "default_exits": [{"room": 31, "unkn1": 10, "unkn2": 4, "x": 72, "y": 88, "name": "Iceberg 5 - 24 Exit 0", "access_rule": []}], "entity_load": [[75, 16]], "locations": ["Iceberg 5 - Enemy 25 (Gansan)"], "music": 16}, {"name": "Iceberg 5 - 25", "level": 5, "stage": 5, "room": 25, "pointer": 3156420, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sasuke"], "default_exits": [{"room": 3, "unkn1": 25, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 25 Exit 0", "access_rule": []}], "entity_load": [[30, 16]], "locations": ["Iceberg 5 - Enemy 26 (Sasuke)"], "music": 16}, {"name": "Iceberg 5 - 26", "level": 5, "stage": 5, "room": 26, "pointer": 3127877, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Togezo"], "default_exits": [{"room": 8, "unkn1": 24, "unkn2": 8, "x": 72, "y": 152, "name": "Iceberg 5 - 26 Exit 0", "access_rule": []}], "entity_load": [[18, 16]], "locations": ["Iceberg 5 - Enemy 27 (Togezo)"], "music": 16}, {"name": "Iceberg 5 - 27", "level": 5, "stage": 5, "room": 27, "pointer": 3256471, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Bobin"], "default_exits": [{"room": 14, "unkn1": 26, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 27 Exit 0", "access_rule": []}], "entity_load": [[8, 16], [73, 16]], "locations": ["Iceberg 5 - Enemy 28 (Sparky)", "Iceberg 5 - Enemy 29 (Bobin)"], "music": 16}, {"name": "Iceberg 5 - 28", "level": 5, "stage": 5, "room": 28, "pointer": 3029723, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Chilly"], "default_exits": [{"room": 10, "unkn1": 13, "unkn2": 9, "x": 72, "y": 248, "name": "Iceberg 5 - 28 Exit 0", "access_rule": []}], "entity_load": [[6, 16]], "locations": ["Iceberg 5 - Enemy 30 (Chilly)"], "music": 16}, {"name": "Iceberg 5 - 29", "level": 5, "stage": 5, "room": 29, "pointer": 3526568, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 36, "unkn1": 10, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 29 Exit 0", "access_rule": []}], "entity_load": [[10, 23], [31, 16], [14, 23]], "locations": ["Iceberg 5 - Star 26", "Iceberg 5 - Star 27", "Iceberg 5 - Star 28", "Iceberg 5 - Star 29", "Iceberg 5 - Star 30", "Iceberg 5 - Star 31", "Iceberg 5 - Star 32"], "music": 16}, {"name": "Iceberg 5 - 30", "level": 5, "stage": 5, "room": 30, "pointer": 3022285, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 13, "unkn2": 5, "x": 88, "y": 104, "name": "Iceberg 5 - 30 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 1", "Iceberg 5 - Animal 2"], "music": 40}, {"name": "Iceberg 5 - 31", "level": 5, "stage": 5, "room": 31, "pointer": 3026019, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 13, "unkn2": 9, "x": 72, "y": 200, "name": "Iceberg 5 - 31 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 3", "Iceberg 5 - Animal 4"], "music": 40}, {"name": "Iceberg 5 - 32", "level": 5, "stage": 5, "room": 32, "pointer": 3039996, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 13, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 32 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 5", "Iceberg 5 - Animal 6"], "music": 40}, {"name": "Iceberg 5 - 33", "level": 5, "stage": 5, "room": 33, "pointer": 3015302, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 10, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 33 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 7", "Iceberg 5 - Animal 8"], "music": 40}, {"name": "Iceberg 5 - 34", "level": 5, "stage": 5, "room": 34, "pointer": 3185868, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Peran"], "default_exits": [{"room": 35, "unkn1": 35, "unkn2": 9, "x": 200, "y": 328, "name": "Iceberg 5 - 34 Exit 0", "access_rule": []}], "entity_load": [[72, 16], [4, 22], [14, 23]], "locations": ["Iceberg 5 - Enemy 31 (Peran)", "Iceberg 5 - Star 33", "Iceberg 5 - Star 34"], "music": 16}, {"name": "Iceberg 5 - 35", "level": 5, "stage": 5, "room": 35, "pointer": 3865635, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 29, "unkn1": 12, "unkn2": 7, "x": 168, "y": 1384, "name": "Iceberg 5 - 35 Exit 0", "access_rule": []}], "entity_load": [[17, 16], [4, 22]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 36", "level": 5, "stage": 5, "room": 36, "pointer": 3024154, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 37, "unkn1": 13, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 5 - 36 Exit 0", "access_rule": []}], "entity_load": [[32, 19], [42, 19]], "locations": ["Iceberg 5 - Shiro"], "music": 8}, {"name": "Iceberg 5 - 37", "level": 5, "stage": 5, "room": 37, "pointer": 2890594, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 5 - Complete"], "music": 5}, {"name": "Iceberg 6 - 0", "level": 5, "stage": 6, "room": 0, "pointer": 3385305, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nruff"], "default_exits": [{"room": 1, "unkn1": 6, "unkn2": 28, "x": 136, "y": 184, "name": "Iceberg 6 - 0 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 12, "unkn2": 28, "x": 248, "y": 184, "name": "Iceberg 6 - 0 Exit 1", "access_rule": []}, {"room": 1, "unkn1": 18, "unkn2": 28, "x": 360, "y": 184, "name": "Iceberg 6 - 0 Exit 2", "access_rule": []}], "entity_load": [[15, 16]], "locations": ["Iceberg 6 - Enemy 1 (Nruff)"], "music": 12}, {"name": "Iceberg 6 - 1", "level": 5, "stage": 6, "room": 1, "pointer": 3197599, "animal_pointers": [212, 220, 228], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 8, "unkn2": 5, "x": 152, "y": 184, "name": "Iceberg 6 - 1 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 1 Exit 1", "access_rule": []}, {"room": 2, "unkn1": 22, "unkn2": 5, "x": 344, "y": 184, "name": "Iceberg 6 - 1 Exit 2", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 6 - Animal 1", "Iceberg 6 - Animal 2", "Iceberg 6 - Animal 3"], "music": 12}, {"name": "Iceberg 6 - 2", "level": 5, "stage": 6, "room": 2, "pointer": 3097113, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 9, "unkn2": 5, "x": 136, "y": 184, "name": "Iceberg 6 - 2 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 2 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 21, "unkn2": 5, "x": 360, "y": 184, "name": "Iceberg 6 - 2 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 12}, {"name": "Iceberg 6 - 3", "level": 5, "stage": 6, "room": 3, "pointer": 3198422, "animal_pointers": [212, 220, 228], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 8, "unkn2": 5, "x": 152, "y": 184, "name": "Iceberg 6 - 3 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 3 Exit 1", "access_rule": []}, {"room": 6, "unkn1": 22, "unkn2": 5, "x": 344, "y": 184, "name": "Iceberg 6 - 3 Exit 2", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 6 - Animal 4", "Iceberg 6 - Animal 5", "Iceberg 6 - Animal 6"], "music": 12}, {"name": "Iceberg 6 - 4", "level": 5, "stage": 6, "room": 4, "pointer": 3210507, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 9, "unkn2": 5, "x": 136, "y": 184, "name": "Iceberg 6 - 4 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 4 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 21, "unkn2": 5, "x": 360, "y": 184, "name": "Iceberg 6 - 4 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 12}, {"name": "Iceberg 6 - 5", "level": 5, "stage": 6, "room": 5, "pointer": 3196776, "animal_pointers": [], "consumables": [{"idx": 0, "pointer": 212, "x": 136, "y": 120, "etype": 22, "vtype": 2, "name": "Iceberg 6 - Maxim Tomato (Left)"}, {"idx": 1, "pointer": 220, "x": 248, "y": 120, "etype": 22, "vtype": 0, "name": "Iceberg 6 - 1-Up (Middle)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 4, "unkn1": 8, "unkn2": 5, "x": 152, "y": 184, "name": "Iceberg 6 - 5 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 5 Exit 1", "access_rule": []}, {"room": 4, "unkn1": 22, "unkn2": 5, "x": 344, "y": 184, "name": "Iceberg 6 - 5 Exit 2", "access_rule": []}], "entity_load": [[2, 22], [0, 22], [14, 23]], "locations": ["Iceberg 6 - Star 1", "Iceberg 6 - Maxim Tomato (Left)", "Iceberg 6 - 1-Up (Middle)"], "music": 12}, {"name": "Iceberg 6 - 6", "level": 5, "stage": 6, "room": 6, "pointer": 3208130, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 7, "unkn1": 9, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 6 Exit 0", "access_rule": []}], "entity_load": [[28, 16]], "locations": ["Iceberg 6 - Enemy 2 (Nidoo)"], "music": 12}, {"name": "Iceberg 6 - 7", "level": 5, "stage": 6, "room": 7, "pointer": 3124478, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky"], "default_exits": [{"room": 8, "unkn1": 17, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 7 Exit 0", "access_rule": []}], "entity_load": [[8, 16]], "locations": ["Iceberg 6 - Enemy 3 (Sparky)"], "music": 12}, {"name": "Iceberg 6 - 8", "level": 5, "stage": 6, "room": 8, "pointer": 3110431, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 7, "unkn2": 5, "x": 152, "y": 168, "name": "Iceberg 6 - 8 Exit 0", "access_rule": []}, {"room": 14, "unkn1": 14, "unkn2": 5, "x": 296, "y": 136, "name": "Iceberg 6 - 8 Exit 1", "access_rule": []}], "entity_load": [[4, 22], [33, 19]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 9", "level": 5, "stage": 6, "room": 9, "pointer": 3139832, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 16, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 9 Exit 0", "access_rule": []}], "entity_load": [[2, 27]], "locations": ["Iceberg 6 - Miniboss 1 (Blocky)"], "music": 12}, {"name": "Iceberg 6 - 10", "level": 5, "stage": 6, "room": 10, "pointer": 3119624, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 7, "unkn2": 5, "x": 152, "y": 168, "name": "Iceberg 6 - 10 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 11", "level": 5, "stage": 6, "room": 11, "pointer": 3141139, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 12, "unkn1": 16, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 11 Exit 0", "access_rule": []}], "entity_load": [[3, 27]], "locations": ["Iceberg 6 - Miniboss 2 (Jumper Shoot)"], "music": 12}, {"name": "Iceberg 6 - 12", "level": 5, "stage": 6, "room": 12, "pointer": 3123788, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 13, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 12 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 13", "level": 5, "stage": 6, "room": 13, "pointer": 3143741, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 14, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 13 Exit 0", "access_rule": []}], "entity_load": [[1, 27]], "locations": ["Iceberg 6 - Miniboss 3 (Yuki)"], "music": 12}, {"name": "Iceberg 6 - 14", "level": 5, "stage": 6, "room": 14, "pointer": 3120319, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 15, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 14 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 15", "level": 5, "stage": 6, "room": 15, "pointer": 3135238, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble"], "default_exits": [{"room": 16, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 15 Exit 0", "access_rule": []}], "entity_load": [[27, 16]], "locations": ["Iceberg 6 - Enemy 4 (Sir Kibble)"], "music": 12}, {"name": "Iceberg 6 - 16", "level": 5, "stage": 6, "room": 16, "pointer": 3123096, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 17, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 16 Exit 0", "access_rule": []}], "entity_load": [[4, 22], [33, 19]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 17", "level": 5, "stage": 6, "room": 17, "pointer": 3144389, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 17 Exit 0", "access_rule": []}], "entity_load": [[5, 27]], "locations": ["Iceberg 6 - Miniboss 4 (Haboki)"], "music": 12}, {"name": "Iceberg 6 - 18", "level": 5, "stage": 6, "room": 18, "pointer": 3121014, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 19, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 18 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 19", "level": 5, "stage": 6, "room": 19, "pointer": 3017228, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 20, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 19 Exit 0", "access_rule": []}], "entity_load": [[4, 27]], "locations": ["Iceberg 6 - Miniboss 5 (Boboo)"], "music": 12}, {"name": "Iceberg 6 - 20", "level": 5, "stage": 6, "room": 20, "pointer": 3121709, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 21, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 20 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 21", "level": 5, "stage": 6, "room": 21, "pointer": 3145036, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 22, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 21 Exit 0", "access_rule": []}], "entity_load": [[0, 27]], "locations": ["Iceberg 6 - Miniboss 6 (Captain Stitch)"], "music": 12}, {"name": "Iceberg 6 - 22", "level": 5, "stage": 6, "room": 22, "pointer": 3116830, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 23, "unkn1": 7, "unkn2": 5, "x": 136, "y": 152, "name": "Iceberg 6 - 22 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 23", "level": 5, "stage": 6, "room": 23, "pointer": 3045263, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 24, "unkn1": 17, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 6 - 23 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [42, 19]], "locations": ["Iceberg 6 - Angel"], "music": 8}, {"name": "Iceberg 6 - 24", "level": 5, "stage": 6, "room": 24, "pointer": 2889389, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 6 - Complete"], "music": 5}, {"name": "Iceberg Boss - 0", "level": 5, "stage": 7, "room": 0, "pointer": 2980207, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[8, 18]], "locations": ["Iceberg - Boss (Dedede) Purified", "Level 5 Boss - Defeated", "Level 5 Boss - Purified"], "music": 7}] \ No newline at end of file diff --git a/worlds/kdl3/data/kdl3_basepatch.bsdiff4 b/worlds/kdl3/data/kdl3_basepatch.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..cd002121cd38dcb319ba2148ced46c9592c3905b GIT binary patch literal 2411 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 R2S2ZGPW5pM7JXgjzX0o>8Uz3U literal 0 HcmV?d00001 diff --git a/worlds/kdl3/docs/en_Kirby's Dream Land 3.md b/worlds/kdl3/docs/en_Kirby's Dream Land 3.md new file mode 100644 index 000000000000..c1e36fed546a --- /dev/null +++ b/worlds/kdl3/docs/en_Kirby's Dream Land 3.md @@ -0,0 +1,38 @@ +# Kirby's Dream Land 3 + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? +Kirby will be unable to absorb Copy Abilities and meet up with his animal friends until they are sent to him. Items such +as Heart Stars, 1-Ups, and Invincibility Candy will be shuffled into the pool for Kirby to receive. + +## What is considered a location check in Kirby's Dream Land 3? +- Completing a stage for the first time +- Completing the given task of a stage and receiving a Heart Star +- Purifying a boss after acquiring a certain number of Heart Stars + (indicated by their portrait flashing in the level select) +- If enabled, 1-Ups and Maxim Tomatoes + +## When the player receives an item, what happens? +A sound effect will play, and Kirby will immediately receive the effects of that item, such as being able to receive Copy Abilities from enemies that +give said Copy Ability. Animal Friends will require leaving the room you are currently in before they will appear. + +## What is the goal of Kirby's Dream Land 3? +Under the Zero goal, players must collect enough Heart Stars to purify the five bosses and gain access to the Hyper Zone, +where Zero can be found and defeated. + +Under the Boss Butch goal, players must collect enough Heart Stars to purify the five bosses +and then complete the Boss Butch game mode accessible from the main menu. + +Under the MG5 goal, players must collect enough Heart Stars to purify the five bosses +and then perfect the Super MG5 game mode accessible from the main menu. + +Under the Jumping goal, players must collect enough Heart Stars to purify the five bosses +and then reach a target score in the Jumping game mode accessible from the main menu. + +## Why is EmuHawk resizing itself while I'm playing? +Kirby's Dream Land 3 changes the SNES's display resolution from 1x to 2x many times during gameplay (particularly in rooms with foreground effects). +To counter-act this resizing, set SNES -> Options -> "Always use double-size frame buffer". diff --git a/worlds/kdl3/docs/setup_en.md b/worlds/kdl3/docs/setup_en.md new file mode 100644 index 000000000000..a13a0f1a74cf --- /dev/null +++ b/worlds/kdl3/docs/setup_en.md @@ -0,0 +1,148 @@ +# Kirby's Dream Land 3 Randomizer Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI with ROM access. Any one of the following will work: + - snes9x-emunwa from: [snes9x-emunwa Releases Page](https://github.com/Skarsnik/snes9x-emunwa/releases) + - snes9x-rr from: [snes9x-rr Releases Page](https://github.com/gocha/snes9x-rr/releases) + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) + - bsnes-plus-nwa from: [bsnes-plus GitHub](https://github.com/black-sliver/bsnes-plus) + - **RetroArch is currently incompatible with Kirby's Dream Land 3** + - Or SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other + compatible hardware. +- Your KDL3 ROM file, probably named either `Kirby's Dream Land 3 (USA).sfc` or `Hoshi no Kirby 3 (J).sfc` + +## Installation Procedures + +1. Download and install Archipelago from the link above, making sure to install the most recent version. + **The installer file is located in the assets section at the bottom of the version information**. + - During generation/patching, you will be asked to locate your base ROM file. This is your Kirby's Dream Land 3 ROM file. + +2. If you are using an emulator, you should assign your SNI-compatible emulator as your default program for launching ROM + files. + 1. Extract your emulator's folder to your Desktop, or somewhere you will remember. + 2. Right-click on a ROM file and select **Open with...** + 3. Check the box next to **Always use this app to open .sfc files** + 4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC** + 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you + extracted in step one. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +Your config file contains a set of configuration options which provide the generator with information about how it +should generate your game. Each player of a multiworld will provide their own config file. This setup allows each player +to enjoy an experience customized for their taste, and different players in the same multiworld can all have different +options. + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page on the website allows you to configure +your personal settings and export a config file from them. + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the +[YAML Validator](/mysterycheck) page. + +## Generating a Single-Player Game + +1. Navigate to the [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page, configure your options, + and click the "Generate Game" button. +2. You will be presented with a "Seed Info" page. +3. Click the "Create New Room" link. +4. You will be presented with a server page, from which you can download your patch file. +5. Double-click on your patch file, and SNIClient will launch automatically, create your ROM from the patch file, and + open your emulator for you. + +## Joining a MultiWorld Game + +### Obtain your patch file and create your ROM + +When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that is done, +the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch +files. Your patch file should have a `.apkdl3` extension. + +Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the +client, and will also create your ROM in the same place as your patch file. + +### Connect to the client + +#### With an emulator + +When the client launched automatically, SNI should have also automatically launched in the background. If this is its +first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +##### snes9x-rr + +1. Load your ROM file if it hasn't already been loaded. +2. Click on the File menu and hover on **Lua Scripting** +3. Click on **New Lua Script Window...** +4. In the new window, click **Browse...** +5. Select the connector lua file included with your client + - Look in the Archipelago folder for `/SNI/lua/Connector.lua` +6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of +the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. + +##### BizHawk + +1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these + menu options: + `Config --> Cores --> SNES --> BSNES` + Once you have changed the loaded core, you must restart BizHawk. +2. Load your ROM file if it hasn't already been loaded. +3. Click on the Tools menu and click on **Lua Console** +4. Click Script -> Open Script... +5. Select the `Connector.lua` file you downloaded above + - Look in the Archipelago folder for `/SNI/lua/Connector.lua` + +##### bsnes-plus-nwa and snes9x-nwa + +These should automatically connect to SNI. If this is the first time launching, you may be prompted to allow it to +communicate through the Windows Firewall. + +#### With hardware + +This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do +this now. SD2SNES and FXPak Pro users may download the appropriate firmware +[here](https://github.com/RedGuyyyy/sd2snes/releases). Other hardware may find helpful information +[on this page](http://usb2snes.com/#supported-platforms). + +1. Close your emulator, which may have auto-launched. +2. Power on your device and load the ROM. + +### Connect to the Archipelago Server + +The patch file which launched your client should have automatically connected you to the AP Server. There are a few +reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the +client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it +into the "Server" input field then press enter. + +The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". + +### Play the game + +When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on +successfully joining a multiworld game! You can execute various commands in your client. For more information regarding +these commands you can use `/help` for local client commands and `!help` for server commands. + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our [hosting service](/generate). The process is relatively simple: + +1. Collect config files from your players. +2. Create a zip file containing your players' config files. +3. Upload that zip file to the website linked above. +4. Wait a moment while the seed is generated. +5. When the seed is generated, you will be redirected to a "Seed Info" page. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so + they may download their patch files from there. +7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all + players in the game. Any observers may also be given the link to this page. +8. Once all players have joined, you may begin playing. diff --git a/worlds/kdl3/src/kdl3_basepatch.asm b/worlds/kdl3/src/kdl3_basepatch.asm new file mode 100644 index 000000000000..e419d0632f0e --- /dev/null +++ b/worlds/kdl3/src/kdl3_basepatch.asm @@ -0,0 +1,1237 @@ +fullsa1rom + +!GAME_STATUS = $36D0 + +; SNES hardware registers +!VMADDL = $002116 +!VMDATAL = $002118 +!MDMAEN = $00420B +!DMAP0 = $004300 +!BBAD0 = $004301 +!A1T0L = $004302 +!A1B0 = $004304 +!DAS0L = $004305 + +org $008033 + JSL WriteBWRAM + NOP #5 + +org $00A245 +HSVPatch: + BRA .Jump + PHX + LDA $6CA0,X + TAX + LDA $6E22,X + JSR $A25B + PLX + INX + .Jump: + JSL HeartStarVisual + + +org $00A3FC + JSL NintenHalken + NOP + +org $00EAE4 + JSL MainLoopHook + NOP + +org $00F85E + JSL SpeedTrap + NOP #2 + +org $00FFC0 + db "KDL3_BASEPATCH_ARCHI" + +org $00FFD8 + db $06 + +org $018405 + JSL PauseMenu + +org $01AFC8 + JSL HeartStarCheck + NOP #13 + +org $01B013 + SEC ; Remove Dedede Bad Ending + +org $02B7B0 ; Zero unlock + LDA $80A0 + CMP #$0001 + +org $02C238 + LDA #$0006 + JSL OpenWorldUnlock + NOP #5 + +org $02C27C + JSL HeartStarSelectFix + NOP #2 + +org $02C317 + JSL LoadFont + +org $02C39D + JSL StageCompleteSet + NOP #2 + +org $02C3D9 + JSL StrictBosses + NOP #2 + +org $02C3F0 + JSL OpenWorldBossUnlock + NOP #6 + +org $02C463 + JSL NormalGoalSet + NOP #2 + +org $049CD7 + JSL AnimalFriendSpawn + +org $06801E + JSL ConsumableSet + +org $068518 + JSL CopyAbilityOverride + NOP #2 + +org $099F35 + JSL HeartStarCutsceneFix + +org $09A01F + JSL HeartStarGraphicFix + NOP #2 + db $B0 + +org $09A0AE + JSL HeartStarGraphicFix + NOP #2 + db $90 + +org $0A87E8 + JSL CopyAbilityAnimalOverride + NOP #2 + +org $12B238 + JSL FinalIcebergFix + NOP #10 + db $B0 + +org $14A3EB + LDA $07A2, Y + JSL StarsSet + NOP #3 + +org $15BC13 + JML GiftGiving + NOP + +org $0799A0 +CopyAbilityOverride: + LDA $54F3, Y + PHA + ASL + TAX + PLA + CMP $8020, X + NOP #2 + BEQ .StoreAbilityK + LDA #$0000 + .StoreAbilityK: + STA $54A9, Y + RTL + NOP #4 +CopyAbilityAnimalOverride: + PHA + ASL + TAY + PLA + CMP $8020, Y + NOP + BEQ .StoreAbilityA + LDA #$0000 + .StoreAbilityA: + STA $54A9, X + STA $39DF, X + RTL + +org $079A00 +HeartStarCheck: + TXA + CMP #$0000 ; is this level 1 + BEQ .PassToX + LSR + LSR + INC + .PassToX: + TAX + LDA $8070 ; heart stars + CLC + CMP $07D00A ;Compare to goal heart stars + BCC .CompareWorldHS ; we don't have enough + PHX + LDA #$0014 + STA $7F62 ; play sound fx 0x14 + LDA $07D012 ; goal + CMP #$0000 ; are we on zero goal? + BEQ .ZeroGoal ; we are + LDA #$0001 + LDX $3617 ; current save + STA $53DD, X ; boss butch + STA $53DF, X ; MG5 + STA $53E1, X ; Jumping + BRA .PullX + .ZeroGoal: + LDA #$0001 + STA $80A0 ; zero unlock address + .PullX: + PLX + .CompareWorldHS: + LDA $8070 ; current heart stars + CMP $07D000, X ; compare to world heart stars + BCS .ReturnTrue + CLC + RTL + .ReturnTrue: + SEC + RTL + +org $079A80 +OpenWorldUnlock: + PHX + LDX $900E ; Are we on open world? + BNE .Open ; Branch if we are + LDA #$0001 + .Open: + STA $5AC1 ;(cutscene) + STA $53CD ;(unlocked stages) + INC + STA $5AB9 ;(currently selectable stages) + CPX #$0001 + BNE .Return ; Return if we aren't on open world + LDA #$0001 + STA $5A9D + STA $5A9F + STA $5AA1 + STA $5AA3 + STA $5AA5 + .Return: + PLX + RTL + +org $079B00 +MainLoopHook: + STA $D4 + INC $3524 + JSL ParseItemQueue + LDA $7F62 ; sfx to be played + BEQ .Traps ; skip if 0 + JSL $00D927 ; play sfx + STZ $7F62 + .Traps: + LDA $36D0 + CMP #$FFFF ; are we in menus? + BEQ .Return ; return if we are + LDA $5541 ; gooey status + BPL .Slowness ; gooey is already spawned + LDA $8080 + CMP #$0000 ; did we get a gooey trap + BEQ .Slowness ; branch if we did not + JSL GooeySpawn + STZ $8080 + .Slowness: + LDA $8082 ; slowness + BEQ .Eject ; are we under the effects of a slowness trap + DEC + STA $8082 ; dec by 1 each frame + .Eject: + PHX + PHY + LDA $54A9 ; copy ability + BEQ .PullVars ; branch if we do not have a copy ability + LDA $8084 ; eject ability + BEQ .PullVars ; branch if we haven't received eject + LDA #$2000 ; select button press + STA $60C1 ; write to controller mirror + STZ $8084 + .PullVars: + PLY + PLX + .Return: + RTL + +org $079B80 +HeartStarGraphicFix: + LDA #$0000 + PHX + PHY + LDX $363F ; current level + LDY $3641 ; current stage + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + INC #6 + DEX + BRA .LoopLevel ; return to loop head + .LoopStage: + CPY #$0000 + BEQ .EndLoop + INC + DEY + BRA .LoopStage ; return to loop head + .EndLoop + ASL + TAX + LDA $07D080, X ; table of original stage number + CMP #$0003 ; is the current stage a minigame stage? + BEQ .ReturnTrue ; branch if so + CLC + BRA .Return + .ReturnTrue: + SEC + .Return: + PLY + PLX + RTL + +org $079BF0 +ParseItemQueue: +; Local item queue parsing + NOP + LDX #$0000 + .LoopHead: + LDA $C000,X + BIT #$0010 + BNE .Ability + BIT #$0020 + BNE .Animal + BIT #$0040 + BNE .Positive + BIT #$0080 + BNE .Negative + .LoopCheck: + INX + INX + CPX #$000F + BCC .LoopHead + RTL + .Ability: + JSL .ApplyAbility + RTL + .Animal: + JSL .ApplyAnimal + RTL + .Positive: + LDY $36D0 + CPY #$FFFF + BEQ .LoopCheck + JSL .ApplyPositive + RTL + .Negative: + AND #$000F + ASL + TAY + LDA $8080,Y + BNE .LoopCheck + JSL .ApplyNegative + RTL + .ApplyAbility: + AND #$000F + PHA + ASL + TAY + PLA + STA $8020,Y + LDA #$0032 + BRA .PlaySFX + .ApplyAnimal: + AND #$000F + PHA + ASL + TAY + PLA + INC + STA $8000,Y + LDA #$0032 + BRA .PlaySFX + .PlaySFX: + STA $7F62 + STZ $C000,X + .Return: + RTL + .ApplyPositive: + LDY $36D0 + CPY #$FFFF + BEQ .Return + AND #$000F + BEQ .HeartStar + CMP #$0004 + BCS .StarBit + CMP #$0002 + BCS .Not1UP + LDA $39CF + INC + STA $39CF + STA $39E3 + LDA #$0033 + BRA .PlaySFX + .Not1UP: + CMP #$0003 + BEQ .Invincibility + LDA $39D3 + BEQ .JustKirby + LDA #$0008 + STA $39D1 + STA $39D3 + BRA .PlayPositive + .JustKirby: + LDA #$000A + STA $39D1 + BRA .PlayPositive + .Invincibility: + LDA #$0384 + STA $54B1 + BRA .PlayPositive + .HeartStar: + INC $8070 + LDA #$0016 + BRA .PlaySFX + .StarBit: + SEC + SBC #$0004 + ASL + INC + CLC + ADC $39D7 + ORA #$8000 + STA $39D7 + .PlayPositive: + LDA #$0026 + .PlaySFXLong + BRA .PlaySFX + .ApplyNegative: + CPY #$0005 + BCS .PlayNone + LDA $8080,Y + BNE .Return + LDA #$0384 + STA $8080,Y + LDA #$00A7 + BRA .PlaySFXLong + .PlayNone: + LDA #$0000 + BRA .PlaySFXLong + +org $079D00 +AnimalFriendSpawn: + PHA + CPX #$0002 ; is this an animal friend? + BNE .Return + XBA + PHA + ASL + TAY + PLA + INC + CMP $8000, Y ; do we have this animal friend + BEQ .Return ; we have this animal friend + INX + .Return: + PLY + LDA #$9999 + RTL + +org $079E00 +WriteBWRAM: + LDY #$6001 ;starting addr + LDA #$1FFE ;bytes to write + MVN $40, $40 ;copy $406000 from 406001 to 407FFE + LDX #$0000 + LDY #$0014 + .LoopHead: + LDA $8100, X ; rom header + CMP $07C000, X ; compare to real rom name + BNE .InitializeRAM ; area is uninitialized or corrupt, reset + INX + DEY + BMI .Return ; if Y is negative, rom header matches, valid bwram + BRA .LoopHead ; else continue loop + .InitializeRAM: + LDA #$0000 + STA $8000 ; initialize first byte that gets copied + LDX #$8000 + LDY #$8001 + LDA #$7FFD + MVN $40, $40 ; initialize 0x8000 onward + LDX #$D000 ; seed info 0x3D000 + LDY #$9000 ; target location + LDA #$1000 + MVN $40, $07 + LDX #$C000 ; ROM name + LDY #$8100 ; target + LDA #$0015 + MVN $40, $07 + .Return: + RTL + +org $079E80 +ConsumableSet: + PHA + PHX + PHY + AND #$00FF + PHA + LDX $53CF + LDY $53D3 + LDA #$0000 + DEY + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + CLC + ADC #$0007 + DEX + BRA .LoopLevel ; return to loop head + .LoopStage: + CPY #$0000 + BEQ .EndLoop + INC + DEY + BRA .LoopStage ; return to loop head + .EndLoop: + ASL + TAX + LDA $07D020, X ; current stage + DEC + ASL #6 + TAX + PLA + .LoopHead: + CMP #$0000 + BEQ .ApplyCheck + INX + DEC + BRA .LoopHead ; return to loop head + .ApplyCheck: + LDA $A000, X ; consumables index + ORA #$0001 + STA $A000, X + PLY + PLX + PLA + XBA + AND #$00FF + RTL + +org $079F00 +NormalGoalSet: + PHX + LDA $07D012 + CMP #$0000 + BEQ .ZeroGoal + LDA #$0001 + LDX $3617 ; current save + STA $53DD, X ; Boss butch + STA $53DF, X ; MG5 + STA $53D1, X ; Jumping + BRA .Return + .ZeroGoal: + LDA #$0001 + STA $80A0 + .Return: + PLX + LDA #$0006 + STA $5AC1 ; cutscene + RTL + +org $079F80 +FinalIcebergFix: + PHX + PHY + LDA #$0000 + LDX $363F + LDY $3641 + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + INC #7 + DEX + BRA .LoopLevel ; return to loop head + .LoopStage: + CPY #$0000 + BEQ .CheckStage + INC + DEY + BRA .LoopStage ; return to loop head + .CheckStage: + ASL + TAX + LDA $07D020, X + CMP #$001E + BEQ .ReturnTrue + CLC + BRA .Return + .ReturnTrue: + SEC + .Return: + PLY + PLX + RTL + +org $07A000 +StrictBosses: + PHX + LDA $901E ; Do we have strict bosses enabled? + BEQ .ReturnTrue ; Return True if we don't, always unlock the boss in this case + LDA $53CB ; unlocked level + CMP #$0005 ; have we unlocked level 5? + BCS .ReturnFalse ; we don't need to do anything if so + NOP #5 ;unsure when these got here + LDX $53CB + DEX + TXA + ASL + TAX + LDA $8070 ; current heart stars + CMP $07D000, X ; do we have enough HS to purify? + BCS .ReturnTrue ; branch if we do + .ReturnFalse: + SEC + BRA .Return + .ReturnTrue: + CLC + .Return: + PLX + LDA $53CD + RTL + +org $07A030 +NintenHalken: + LDX #$0005 + .Halken: + LDA $00A405, X ; loop head (halken) + STA $4080F0, X + DEX + BPL .Halken ; branch if more letters to copy + LDX #$0005 + .Ninten: + LDA $00A40B, X ; loop head (ninten) + STA $408FF0, X + DEX + BPL .Ninten ; branch if more letters to copy + REP #$20 + LDA #$0001 + RTL + +org $07A080 +StageCompleteSet: + PHX + LDA $5AC1 ; completed stage cutscene + BEQ .Return ; we have not completed a stage + LDA #$0000 + LDX $53CF ; current level + .LoopLevel: + CPX #$0000 + BEQ .StageStart + DEX + INC #7 + BRA .LoopLevel ; return to loop head + .StageStart: + LDX $53D3 ; current stage + CPX #$0007 ; is this a boss stage + BEQ .Return ; return if so + DEX + .LoopStage: + CPX #$0000 + BEQ .LoopEnd + INC + DEX + BRA .LoopStage ; return to loop head + .LoopEnd: + ASL + TAX + LDA $9020, X ; load the stage we completed + DEC + ASL + TAX + LDA #$0001 + ORA $8200, X + STA $8200, X + .Return: + PLX + LDA $53CF + CMP $53CB + RTL + +org $07A100 +OpenWorldBossUnlock: + PHX + PHY + LDA $900E ; Are we on open world? + BEQ .ReturnTrue ; Return true if we aren't, always unlock boss + LDA $53CD + CMP #$0006 + BNE .ReturnFalse ; return if we aren't on stage 6 + LDA $53CF + INC + CMP $53CB ; are we on the most unlocked level? + BNE .ReturnFalse ; return if we aren't + LDA #$0000 + LDX $53CF + .LoopLevel: + CPX #$0000 + BEQ .LoopStages + ADC #$0006 + DEX + BRA .LoopLevel ; return to loop head + .LoopStages: + ASL + TAX + LDA #$0000 + LDY #$0006 + PHY + PHX + .LoopStage: + PLX + LDY $9020, X ; get stage id + DEY + INX + INX + PHA + TYA + ASL + TAY + PLA + ADC $8200, Y ; add current stage value to total + PLY + DEY + PHY + PHX + CPY #$0000 + BNE .LoopStage ; return to loop head + PLX + PLY + SEC + SBC $9016 + BCC .ReturnFalse + .ReturnTrue + LDA $53CD + INC + STA $53CD + STA $5AC1 + BRA .Return + .ReturnFalse: + STZ $5AC1 + .Return: + PLY + PLX + RTL + +org $07A180 +GooeySpawn: + PHY + PHX + LDX #$0000 + LDY #$0000 + STA $5543 + LDA $1922,Y + STA $C0 + LDA $19A2,Y + STA $C2 + LDA #$0008 + STA $C4 + LDA #$0002 + STA $352A + LDA #$0003 + JSL $00F54F + STX $5541 + LDA #$FFFF + STA $0622,X + JSL $00BAEF + JSL $C4883C + LDX $39D1 + CPX #$0001 + BEQ .Return + LDA #$FFFF + CPX #$0002 + BEQ .Call + DEC + .Call: + JSL $C43C22 + .Return: + PLX + PLY + RTL + +org $07A200 +SpeedTrap: + PHX + LDX $8082 ; do we have slowness + BEQ .Apply ; branch if we do not + LSR + .Apply: + PLX + STA $1F22, Y ; player max speed + EOR #$FFFF + RTL + +org $07A280 +HeartStarVisual: + CPX #$0000 + BEQ .SkipInx + INX + .SkipInx + CPX $651E + BCC .Return + CPX #$0000 + BEQ .Return + LDA $4036D0 + AND #$00FF + BEQ .ReturnTrue + LDA $3000 + AND #$0200 + CMP #$0000 + BNE .ReturnTrue + PHY + LDA $3000 + TAY + CLC + ADC #$0020 + STA $3000 + LDA $408070 + LDX #$0000 + .LoopHead: + CMP #$000A + BCC .LoopEnd + SEC + SBC #$000A + INX + BRA .LoopHead + .LoopEnd: + PHX + TAX + PLA + ORA #$2500 + PHA + LDA #$2C70 + STA $0000, Y + PLA + INY + INY + STA $0000, Y + INY + INY + TXA + ORA #$2500 + PHA + LDA #$2C78 + STA $0000, Y + INY + INY + PLA + STA $0000, Y + INY + INY + JSL HeartStarVisual2 ; we ran out of room + PLY + .ReturnTrue: + SEC + .Return: + RTL + +org $07A300 +LoadFont: + JSL $00D29F ; play sfx + PHX + PHB + LDA #$0000 + PHA + PLB + PLB + LDA #$7000 + STA $2116 + LDX #$0000 + .LoopHead: + CPX #$0140 + BEQ .LoopEnd + LDA $D92F50, X + STA $2118 + INX + INX + BRA .LoopHead + .LoopEnd: + LDX #$0000 + .2LoopHead: + CPX #$0020 + BEQ .2LoopEnd + LDA $D92E10, X + STA $2118 + INX + INX + BRA .2LoopHead + .2LoopEnd: + PHY + LDA $07D012 + ASL + TAX + LDA $07E000, X + TAX + LDY #$0000 + .3LoopHead: + CPY #$0020 + BEQ .3LoopEnd + LDA $D93170, X + STA $2118 + INX + INX + INY + INY + BRA .3LoopHead + .3LoopEnd: + LDA $07D00C + ASL + TAX + LDA $07E010, X + TAX + LDY #$0000 + .4LoopHead: + CPY #$0020 + BEQ .4LoopEnd + LDA $D93170, X + STA $2118 + INX + INX + INY + INY + BRA .4LoopHead + .4LoopEnd: + PLY + PLB + PLX + RTL + +org $07A380 +HeartStarVisual2: + LDA #$2C80 + STA $0000, Y + INY + INY + LDA #$250A + STA $0000, Y + INY + INY + LDA $4053CF + ASL + TAX + .LoopHead: + LDA $409000, X + CMP #$FFFF + BNE .LoopEnd + DEX + DEX + BRA .LoopHead + .LoopEnd: + LDX #$0000 + .2LoopHead: + CMP #$000A + BCC .2LoopEnd + SEC + SBC #$000A + INX + BRA .2LoopHead ; return to loop head + .2LoopEnd: + PHX + TAX + PLA + ORA #$2500 + PHA + LDA #$2C88 + STA $0000, Y + PLA + INY + INY + STA $0000, Y + INY + INY + TXA + ORA #$2500 + PHA + LDA #$2C90 + STA $0000, Y + INY + INY + PLA + STA $0000, Y + INY + INY + LDA #$14D8 + STA $0000, Y + INY + INY + LDA #$250B + STA $0000, Y + INY + INY + LDA #$14E0 + STA $0000, Y + INY + INY + LDA #$250A + STA $0000, Y + INY + INY + LDA #$14E8 + STA $0000, Y + INY + INY + LDA #$250C + STA $0000, Y + INY + INY + LDA $3000 + SEC + SBC #$3040 + LSR + LSR + .3LoopHead: + CMP #$0004 + BCC .3LoopEnd + DEC #4 + BRA .3LoopHead ; return to loop head + .3LoopEnd: + STA $3240 + LDA #$0004 + SEC + SBC $3240 + TAX + LDA #$00FF + .4LoopHead: + CPX #$0000 + BEQ .4LoopEnd + LSR + LSR + DEX + BRA .4LoopHead + .4LoopEnd: + LDY $3002 + AND $0000, Y + STA $0000, Y + INY + LDA #$0000 + STA $0000, Y + INY + INY + STA $0000, Y + RTL + +org $07A480 +HeartStarSelectFix: + PHX + TXA + ASL + TAX + LDA $9020, X + DEC + TAX + .LoopHead: + CMP #$0006 + BMI .LoopEnd + INX + SEC + SBC #$0006 + BRA .LoopHead + .LoopEnd: + LDA $53A7, X + PLX + AND #$00FF + RTL + +org $07A500 +HeartStarCutsceneFix: + TAX + LDA $53D3 + DEC + STA $5AC3 + RTL + +org $07A510 +GiftGiving: + CMP #$0008 + .This: + BCS .This ; this intentionally safe-crashes the game if hit + PHX + LDX $901C + BEQ .Return + PLX + STA $8086 + LDA #$0026 + JML $CABC99 + .Return: + PLX + JML $CABC18 + +org $07A550 +PauseMenu: + JSL $00D29F + PHX + PHY + LDA #$3300 + STA !VMADDL + LDA #$0007 + STA !A1B0 + LDA #$F000 + STA !A1T0L + LDA #$01C0 + STA !DAS0L + SEP #$20 + LDA #$01 + STA !DMAP0 + LDA #$18 + STA !BBAD0 + LDA #$01 + STA !MDMAEN + REP #$20 + LDY #$0000 + .LoopHead: + INY ; loop head + CPY #$0009 + BPL .LoopEnd + TYA + ASL + TAX + LDA $8020, X + BEQ .LoopHead ; return to loop head + TYA + CLC + ADC #$31E2 + STA !VMADDL + LDA $07E020, X + STA !VMDATAL + BRA .LoopHead ; return to loop head + .LoopEnd: + LDY #$FFFF + .2LoopHead: + INY ; loop head + CPY #$0007 + BPL .2LoopEnd + TYA + ASL + TAX + LDA $8000, X + BEQ .2LoopHead ; return to loop head + TYA + CLC + ADC #$3203 + STA !VMADDL + LDA $07E040, X + STA !VMDATAL + BRA .2LoopHead ; return to loop head + .2LoopEnd: + PLY + PLX + RTL + +org $07A600 +StarsSet: + PHA + PHX + PHY + LDX $901A + BEQ .ApplyStar + AND #$00FF + PHA + LDX $53CF + LDY $53D3 + LDA #$0000 + DEY + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + CLC + ADC #$0007 + DEX + BRA .LoopLevel + .LoopStage: + CPY #$0000 + BEQ .LoopEnd + INC + DEY + BRA .LoopStage + .LoopEnd: + ASL + TAX + LDA $07D020, X + DEC + ASL + ASL + ASL + ASL + ASL + ASL + TAX + PLA + .2LoopHead: + CMP #$0000 + BEQ .2LoopEnd + INX + DEC + BRA .2LoopHead + .2LoopEnd: + LDA $B000, X + ORA #$0001 + STA $B000, X + .Return: + PLY + PLX + PLA + XBA + AND #$00FF + RTL + .ApplyStar: + LDA $39D7 + INC + ORA #$8000 + STA $39D7 + BRA .Return + + +org $07C000 + db "KDL3_BASEPATCH_ARCHI" + +org $07E000 + db $20, $03 + db $20, $00 + db $80, $01 + db $20, $01 + db $00, $00 + db $00, $00 + db $00, $00 + db $00, $00 + db $A0, $01 + db $A0, $00 + +; Pause Icons + +org $07E020 + db $00, $0C + db $30, $09 + db $31, $09 + db $32, $09 + db $33, $09 + db $34, $09 + db $35, $09 + db $36, $09 + db $37, $09 + +org $07E040 + db $38, $05 + db $39, $05 + db $3A, $01 + db $3B, $05 + db $3C, $05 + db $3D, $05 \ No newline at end of file diff --git a/worlds/kdl3/test/__init__.py b/worlds/kdl3/test/__init__.py new file mode 100644 index 000000000000..11a17e63b7fa --- /dev/null +++ b/worlds/kdl3/test/__init__.py @@ -0,0 +1,37 @@ +import typing +from argparse import Namespace + +from BaseClasses import MultiWorld, PlandoOptions, CollectionState +from test.TestBase import WorldTestBase +from test.general import gen_steps +from worlds import AutoWorld +from worlds.AutoWorld import call_all + + +class KDL3TestBase(WorldTestBase): + game = "Kirby's Dream Land 3" + + def world_setup(self, seed: typing.Optional[int] = None) -> None: + if type(self) is WorldTestBase or \ + (hasattr(WorldTestBase, self._testMethodName) + and not self.run_default_tests and + getattr(self, self._testMethodName).__code__ is + getattr(WorldTestBase, self._testMethodName, None).__code__): + return # setUp gets called for tests defined in the base class. We skip world_setup here. + if not hasattr(self, "game"): + raise NotImplementedError("didn't define game name") + self.multiworld = MultiWorld(1) + self.multiworld.game[1] = self.game + self.multiworld.player_name = {1: "Tester"} + self.multiworld.set_seed(seed) + self.multiworld.state = CollectionState(self.multiworld) + args = Namespace() + for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): + setattr(args, name, { + 1: option.from_any(self.options.get(name, getattr(option, "default"))) + }) + self.multiworld.set_options(args) + self.multiworld.plando_options = PlandoOptions.connections + self.multiworld.plando_connections = self.options["plando_connections"] if "plando_connections" in self.options.keys() else [] + for step in gen_steps: + call_all(self.multiworld, step) diff --git a/worlds/kdl3/test/test_goal.py b/worlds/kdl3/test/test_goal.py new file mode 100644 index 000000000000..ce53642a9716 --- /dev/null +++ b/worlds/kdl3/test/test_goal.py @@ -0,0 +1,64 @@ +from . import KDL3TestBase + + +class TestFastGoal(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "fast", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect_by_name("Kine") # Ensure a little more progress, but leave out cutter and burning + self.collect(heart_stars[15:]) + self.assertBeatable(True) + + +class TestNormalGoal(KDL3TestBase): + # TODO: open world tests + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) diff --git a/worlds/kdl3/test/test_locations.py b/worlds/kdl3/test/test_locations.py new file mode 100644 index 000000000000..543f0d83926d --- /dev/null +++ b/worlds/kdl3/test/test_locations.py @@ -0,0 +1,68 @@ +from . import KDL3TestBase +from worlds.generic import PlandoConnection +from ..Names import LocationName +import typing + + +class TestLocations(KDL3TestBase): + options = { + "open_world": True, + "ow_boss_requirement": 1, + "strict_bosses": False + # these ensure we can always reach all stages physically + } + + def test_simple_heart_stars(self): + self.run_location_test(LocationName.grass_land_muchi, ["ChuChu"]) + self.run_location_test(LocationName.grass_land_chao, ["Stone"]) + self.run_location_test(LocationName.grass_land_mine, ["Kine"]) + self.run_location_test(LocationName.ripple_field_kamuribana, ["Pitch", "Clean"]) + self.run_location_test(LocationName.ripple_field_bakasa, ["Kine", "Parasol"]) + self.run_location_test(LocationName.ripple_field_toad, ["Needle"]) + self.run_location_test(LocationName.ripple_field_mama_pitch, ["Pitch", "Kine", "Burning", "Stone"]) + self.run_location_test(LocationName.sand_canyon_auntie, ["Clean"]) + self.run_location_test(LocationName.sand_canyon_nyupun, ["ChuChu", "Cutter"]) + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"]) + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]), + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]), + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]), + self.run_location_test(LocationName.cloudy_park_hibanamodoki, ["Coo", "Clean"]) + self.run_location_test(LocationName.cloudy_park_piyokeko, ["Needle"]) + self.run_location_test(LocationName.cloudy_park_mikarin, ["Coo"]) + self.run_location_test(LocationName.cloudy_park_pick, ["Rick"]) + self.run_location_test(LocationName.iceberg_kogoesou, ["Burning"]) + self.run_location_test(LocationName.iceberg_samus, ["Ice"]) + self.run_location_test(LocationName.iceberg_name, ["Burning", "Coo", "ChuChu"]) + self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", "Stone", "Ice"]) + + def run_location_test(self, location: str, itempool: typing.List[str]): + items = itempool.copy() + while len(itempool) > 0: + self.assertFalse(self.can_reach_location(location), str(self.multiworld.seed)) + self.collect_by_name(itempool.pop()) + self.assertTrue(self.can_reach_location(location), str(self.multiworld.seed)) + self.remove(self.get_items_by_name(items)) + + +class TestShiro(KDL3TestBase): + options = { + "open_world": False, + "plando_connections": [ + [], + [ + PlandoConnection("Grass Land 1", "Iceberg 5", "both"), + PlandoConnection("Grass Land 2", "Ripple Field 5", "both"), + PlandoConnection("Grass Land 3", "Grass Land 1", "both") + ]], + "stage_shuffle": "shuffled", + "plando_options": "connections" + } + + def test_shiro(self): + self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) + self.collect_by_name("Nago") + self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) + # despite Shiro only requiring Nago for logic, it cannot be in logic because our two accessible stages + # do not actually give the player access to Nago, thus we need Kine to pass 2-5 + self.collect_by_name("Kine") + self.assertTrue(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) diff --git a/worlds/kdl3/test/test_shuffles.py b/worlds/kdl3/test/test_shuffles.py new file mode 100644 index 000000000000..d676b641b056 --- /dev/null +++ b/worlds/kdl3/test/test_shuffles.py @@ -0,0 +1,245 @@ +from typing import List, Tuple +from . import KDL3TestBase +from ..Room import KDL3Room + + +class TestCopyAbilityShuffle(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + "copy_ability_randomization": "enabled", + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter_and_burning_reachable(self): + rooms = self.multiworld.worlds[1].rooms + copy_abilities = self.multiworld.worlds[1].copy_abilities + sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) + assert isinstance(sand_canyon_5, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < sand_canyon_5.level) + or (room.level == sand_canyon_5.level and room.stage < sand_canyon_5.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Cutter Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Cutter Ability before Sand Canyon 5!") + iceberg_4 = self.multiworld.get_region("Iceberg 4 - 7", 1) + assert isinstance(iceberg_4, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < iceberg_4.level) + or (room.level == iceberg_4.level and room.stage < iceberg_4.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Burning Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Burning Ability before Iceberg 4!") + + def test_valid_abilities_for_ROB(self): + # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings + self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach + # first we need to identify our bukiset requirements + groups = [ + ({"Parasol Ability", "Cutter Ability"}, {'Bukiset (Parasol)', 'Bukiset (Cutter)'}), + ({"Spark Ability", "Clean Ability"}, {'Bukiset (Spark)', 'Bukiset (Clean)'}), + ({"Ice Ability", "Needle Ability"}, {'Bukiset (Ice)', 'Bukiset (Needle)'}), + ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), + ] + copy_abilities = self.multiworld.worlds[1].copy_abilities + required_abilities: List[Tuple[str]] = [] + for abilities, bukisets in groups: + potential_abilities: List[str] = list() + for bukiset in bukisets: + if copy_abilities[bukiset] in abilities: + potential_abilities.append(copy_abilities[bukiset]) + required_abilities.append(tuple(potential_abilities)) + collected_abilities = list() + for group in required_abilities: + self.assertFalse(len(group) == 0, str(self.multiworld.seed)) + collected_abilities.append(group[0]) + self.collect_by_name([ability.replace(" Ability", "") for ability in collected_abilities]) + if "Parasol Ability" not in collected_abilities or "Stone Ability" not in collected_abilities: + # required for non-Bukiset related portions + self.collect_by_name(["Parasol", "Stone"]) + + if "Cutter Ability" not in collected_abilities: + # we can't actually reach 3-6 without Cutter + self.assertFalse(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed)) + self.collect_by_name(["Cutter"]) + + self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), + ''.join(str(self.multiworld.seed)).join(collected_abilities)) + + +class TestAnimalShuffle(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + "animal_randomization": "full", + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + + def test_locked_animals(self): + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) + + +class TestAllShuffle(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + "animal_randomization": "full", + "copy_ability_randomization": "enabled", + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + + def test_locked_animals(self): + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) + + def test_cutter_and_burning_reachable(self): + rooms = self.multiworld.worlds[1].rooms + copy_abilities = self.multiworld.worlds[1].copy_abilities + sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) + assert isinstance(sand_canyon_5, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < sand_canyon_5.level) + or (room.level == sand_canyon_5.level and room.stage < sand_canyon_5.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Cutter Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Cutter Ability before Sand Canyon 5!") + iceberg_4 = self.multiworld.get_region("Iceberg 4 - 7", 1) + assert isinstance(iceberg_4, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < iceberg_4.level) + or (room.level == iceberg_4.level and room.stage < iceberg_4.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Burning Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Burning Ability before Iceberg 4!") + + def test_valid_abilities_for_ROB(self): + # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings + self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach + # first we need to identify our bukiset requirements + groups = [ + ({"Parasol Ability", "Cutter Ability"}, {'Bukiset (Parasol)', 'Bukiset (Cutter)'}), + ({"Spark Ability", "Clean Ability"}, {'Bukiset (Spark)', 'Bukiset (Clean)'}), + ({"Ice Ability", "Needle Ability"}, {'Bukiset (Ice)', 'Bukiset (Needle)'}), + ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), + ] + copy_abilities = self.multiworld.worlds[1].copy_abilities + required_abilities: List[Tuple[str]] = [] + for abilities, bukisets in groups: + potential_abilities: List[str] = list() + for bukiset in bukisets: + if copy_abilities[bukiset] in abilities: + potential_abilities.append(copy_abilities[bukiset]) + required_abilities.append(tuple(potential_abilities)) + collected_abilities = list() + for group in required_abilities: + self.assertFalse(len(group) == 0, str(self.multiworld.seed)) + collected_abilities.append(group[0]) + self.collect_by_name([ability.replace(" Ability", "") for ability in collected_abilities]) + if "Parasol Ability" not in collected_abilities or "Stone Ability" not in collected_abilities: + # required for non-Bukiset related portions + self.collect_by_name(["Parasol", "Stone"]) + + if "Cutter Ability" not in collected_abilities: + # we can't actually reach 3-6 without Cutter + self.assertFalse(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed)) + self.collect_by_name(["Cutter"]) + + self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), + ''.join(str(self.multiworld.seed)).join(collected_abilities)) From 6926f384145c6fdac87ee1035212d78000c6a421 Mon Sep 17 00:00:00 2001 From: Seldom <38388947+Seldom-SE@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:35:41 -0700 Subject: [PATCH 51/66] Terraria: Broken Hero Sword reqs mech bosses (#2879) --- worlds/terraria/Rules.dsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/terraria/Rules.dsv b/worlds/terraria/Rules.dsv index b511db54de99..43a21b49571b 100644 --- a/worlds/terraria/Rules.dsv +++ b/worlds/terraria/Rules.dsv @@ -385,7 +385,7 @@ Armored Digger; Calamity | Location | Item; Temple Raider; Achievement; #Plantera; Lihzahrd Temple; ; #Plantera | (Plantera & Actuator) | @pickaxe(210) | (@calamity & Hardmode Anvil & Soul of Light & Soul of Night); Solar Eclipse; ; Lihzahrd Temple & Wall of Flesh; -Broken Hero Sword; ; (Solar Eclipse & Plantera) | (@calamity & #Calamitas Clone); +Broken Hero Sword; ; (Solar Eclipse & Plantera & @mech_boss(3)) | (@calamity & #Calamitas Clone); Terra Blade; ; Hardmode Anvil & True Night's Edge & True Excalibur & Broken Hero Sword & (~@calamity | Living Shard); Sword of the Hero; Achievement; Terra Blade; Kill the Sun; Achievement; Solar Eclipse; From bf60e905ec2a07a7291c05e7e927ee1c884e1b9b Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:51:29 +0100 Subject: [PATCH 52/66] The Witness: Fix absolute world import (#2905) --- worlds/witness/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 18aa76d95ae9..3b3f3b2155c2 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -4,7 +4,7 @@ from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions, OptionDict -from worlds.witness.static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic +from .static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic class DisableNonRandomizedPuzzles(Toggle): From b147c5bf8a6a37d81fb3baca1b074f1512cdc15f Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:53:00 +0100 Subject: [PATCH 53/66] The Witness: Bump required client version (#2897) Bump required client version from 0.4.4 to 0.4.5. The [client](https://github.com/NewSoupVi/The-Witness-Randomizer-for-Archipelago/releases/tag/v5.0.0p14) now connects with version 0.4.5. --- worlds/witness/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index e985dde353aa..c3f1192b92da 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -56,7 +56,7 @@ class WitnessWorld(World): item_name_groups = StaticWitnessItems.item_groups location_name_groups = StaticWitnessLocations.AREA_LOCATION_GROUPS - required_client_version = (0, 4, 4) + required_client_version = (0, 4, 5) def __init__(self, multiworld: "MultiWorld", player: int): super().__init__(multiworld, player) From 4ddfb7ce8bc2f249b45e2ede375d0b6f3fe08a13 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:54:02 +0100 Subject: [PATCH 54/66] The Witness: Laser Hints (#2895) --- worlds/witness/__init__.py | 46 ++++++++++++++++++++++---------------- worlds/witness/hints.py | 35 ++++++++++++++++++++++++++--- worlds/witness/options.py | 7 ++++++ worlds/witness/presets.py | 3 +++ 4 files changed, 69 insertions(+), 22 deletions(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index c3f1192b92da..bd877a16efee 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -3,22 +3,22 @@ """ import dataclasses -from typing import Dict, Optional +from typing import Dict, Optional, cast from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState from Options import PerGameCommonOptions, Toggle from .presets import witness_option_presets from worlds.AutoWorld import World, WebWorld from .player_logic import WitnessPlayerLogic -from .static_logic import StaticWitnessLogic +from .static_logic import StaticWitnessLogic, ItemCategory, DoorItemDefinition from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \ - make_extra_location_hints, create_all_hints + make_extra_location_hints, create_all_hints, make_laser_hints, make_compact_hint_data, CompactItemData from .locations import WitnessPlayerLocations, StaticWitnessLocations from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData from .regions import WitnessRegions from .rules import set_rules from .options import TheWitnessOptions -from .utils import get_audio_logs +from .utils import get_audio_logs, get_laser_shuffle from logging import warning, error @@ -66,7 +66,8 @@ def __init__(self, multiworld: "MultiWorld", player: int): self.items = None self.regio = None - self.log_ids_to_hints = None + self.log_ids_to_hints: Dict[int, CompactItemData] = dict() + self.laser_ids_to_hints: Dict[int, CompactItemData] = dict() self.items_placed_early = [] self.own_itempool = [] @@ -81,6 +82,7 @@ def _get_slot_data(self): 'symbols_not_in_the_game': self.items.get_symbol_ids_not_in_pool(), 'disabled_entities': [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES], 'log_ids_to_hints': self.log_ids_to_hints, + 'laser_ids_to_hints': self.laser_ids_to_hints, 'progressive_item_lists': self.items.get_progressive_item_ids_in_pool(), 'obelisk_side_id_to_EPs': StaticWitnessLogic.OBELISK_SIDE_ID_TO_EP_HEXES, 'precompleted_puzzles': [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS], @@ -100,8 +102,6 @@ def generate_early(self): ) self.regio: WitnessRegions = WitnessRegions(self.locat, self) - self.log_ids_to_hints = dict() - interacts_with_multiworld = ( self.options.shuffle_symbols or self.options.shuffle_doors or @@ -272,11 +272,25 @@ def create_items(self): self.options.local_items.value.add(item_name) def fill_slot_data(self) -> dict: + already_hinted_locations = set() + + # Laser hints + + if self.options.laser_hints: + laser_hints = make_laser_hints(self, StaticWitnessItems.item_groups["Lasers"]) + + for item_name, hint in laser_hints.items(): + item_def = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]) + self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player) + already_hinted_locations.add(hint.location) + + # Audio Log Hints + hint_amount = self.options.hint_amount.value credits_hint = ( "This Randomizer is brought to you by\n" - "NewSoupVi, Jarno, blastron,\n", + "NewSoupVi, Jarno, blastron,\n" "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1 ) @@ -285,25 +299,19 @@ def fill_slot_data(self) -> dict: if hint_amount: area_hints = round(self.options.area_hint_percentage / 100 * hint_amount) - generated_hints = create_all_hints(self, hint_amount, area_hints) + generated_hints = create_all_hints(self, hint_amount, area_hints, already_hinted_locations) self.random.shuffle(audio_logs) duplicates = min(3, len(audio_logs) // hint_amount) for hint in generated_hints: - location = hint.location - area_amount = hint.area_amount - - # None if junk hint, address if location hint, area string if area hint - arg_1 = location.address if location else (hint.area if hint.area else None) - - # self.player if junk hint, player if location hint, progression amount if area hint - arg_2 = area_amount if area_amount is not None else (location.player if location else self.player) + hint = generated_hints.pop(0) + compact_hint_data = make_compact_hint_data(hint, self.player) for _ in range(0, duplicates): audio_log = audio_logs.pop() - self.log_ids_to_hints[int(audio_log, 16)] = (hint.wording, arg_1, arg_2) + self.log_ids_to_hints[int(audio_log, 16)] = compact_hint_data if audio_logs: audio_log = audio_logs.pop() @@ -315,7 +323,7 @@ def fill_slot_data(self) -> dict: audio_log = audio_logs.pop() self.log_ids_to_hints[int(audio_log, 16)] = joke_hints.pop() - # generate hints done + # Options for the client & auto-tracker slot_data = self._get_slot_data() diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 545aef221677..4b40ba32dfda 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -1,6 +1,6 @@ import logging from dataclasses import dataclass -from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional +from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional, Union from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState from . import StaticWitnessLogic from .utils import weighted_sample @@ -8,6 +8,8 @@ if TYPE_CHECKING: from . import WitnessWorld +CompactItemData = Tuple[str, Union[str, int], int] + joke_hints = [ "Quaternions break my brain", "Eclipse has nothing, but you should do it anyway.", @@ -634,14 +636,15 @@ def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations return hints, unhinted_locations_per_area -def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -> List[WitnessWordedHint]: +def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int, + already_hinted_locations: Set[Location]) -> List[WitnessWordedHint]: generated_hints: List[WitnessWordedHint] = [] state = CollectionState(world.multiworld) # Keep track of already hinted locations. Consider early Tutorial as "already hinted" - already_hinted_locations = { + already_hinted_locations |= { loc for loc in world.multiworld.get_reachable_locations(state, world.player) if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" } @@ -721,3 +724,29 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) - f"Generated {len(generated_hints)} instead.") return generated_hints + + +def make_compact_hint_data(hint: WitnessWordedHint, local_player_number: int) -> CompactItemData: + location = hint.location + area_amount = hint.area_amount + + # None if junk hint, address if location hint, area string if area hint + arg_1 = location.address if location else (hint.area if hint.area else None) + + # self.player if junk hint, player if location hint, progression amount if area hint + arg_2 = area_amount if area_amount is not None else (location.player if location else local_player_number) + + return hint.wording, arg_1, arg_2 + + +def make_laser_hints(world: "WitnessWorld", laser_names: List[str]) -> Dict[str, WitnessWordedHint]: + laser_hints_by_name = dict() + + for item_name in laser_names: + location_hint = hint_from_item(world, item_name, world.own_itempool) + if not location_hint: + continue + + laser_hints_by_name[item_name] = word_direct_hint(world, location_hint) + + return laser_hints_by_name diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 3b3f3b2155c2..a24896e1d057 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -225,6 +225,12 @@ class AreaHintPercentage(Range): default = 33 +class LaserHints(Toggle): + """If on, lasers will tell you where their items are if you walk close to them in-game. + Only applies if laser shuffle is enabled.""" + display_name = "Laser Hints" + + class DeathLink(Toggle): """If on: Whenever you fail a puzzle (with some exceptions), everyone who is also on Death Link dies. The effect of a "death" in The Witness is a Bonk Trap.""" @@ -264,5 +270,6 @@ class TheWitnessOptions(PerGameCommonOptions): puzzle_skip_amount: PuzzleSkipAmount hint_amount: HintAmount area_hint_percentage: AreaHintPercentage + laser_hints: LaserHints death_link: DeathLink death_link_amnesty: DeathLinkAmnesty diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py index 3f02de550b15..0f37fd50a393 100644 --- a/worlds/witness/presets.py +++ b/worlds/witness/presets.py @@ -33,6 +33,7 @@ "puzzle_skip_amount": PuzzleSkipAmount.default, "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, + "laser_hints": LaserHints.default, "death_link": DeathLink.default, }, @@ -66,6 +67,7 @@ "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, + "laser_hints": LaserHints.default, "death_link": DeathLink.default, }, @@ -99,6 +101,7 @@ "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, + "laser_hints": LaserHints.default, "death_link": DeathLink.default, }, } From 938beb34df65827998079cf4f8cc8121824317ff Mon Sep 17 00:00:00 2001 From: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:55:59 -0500 Subject: [PATCH 55/66] TLOZ: use proper rule for completion condition (#2872) Was pointed out that using `state.locations.checked` is not the best solution, even if it's for a completion condition and the Ganon event location would always have the Triforce of Power event item. So let's just check for the Triforce of Power instead. Thank you Zunawe for pointing it out and Silvris for providing the proper rule to use. --- worlds/tloz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/tloz/__init__.py b/worlds/tloz/__init__.py index 27230654b8ce..b2f23ae2ca91 100644 --- a/worlds/tloz/__init__.py +++ b/worlds/tloz/__init__.py @@ -180,7 +180,7 @@ def generate_basic(self): self.multiworld.get_location("Zelda", self.player).place_locked_item(self.create_event("Rescued Zelda!")) add_rule(self.multiworld.get_location("Zelda", self.player), - lambda state: ganon in state.locations_checked) + lambda state: state.has("Triforce of Power", self.player)) self.multiworld.completion_condition[self.player] = lambda state: state.has("Rescued Zelda!", self.player) def apply_base_patch(self, rom): From bfa9e7da006272065143ff501ae0a2bdb8501a48 Mon Sep 17 00:00:00 2001 From: Zach Parks Date: Tue, 5 Mar 2024 15:59:34 -0600 Subject: [PATCH 56/66] Generate: Trim slot names again after 16 character limitation slice. (#2906) --- Generate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Generate.py b/Generate.py index 725a7e9fec35..ecdc81833a15 100644 --- a/Generate.py +++ b/Generate.py @@ -302,7 +302,9 @@ def handle_name(name: str, player: int, name_counter: Counter): NUMBER=(number if number > 1 else ''), player=player, PLAYER=(player if player > 1 else ''))) - new_name = new_name.strip()[:16] + # Run .strip twice for edge case where after the initial .slice new_name has a leading whitespace. + # Could cause issues for some clients that cannot handle the additional whitespace. + new_name = new_name.strip()[:16].strip() if new_name == "Archipelago": raise Exception(f"You cannot name yourself \"{new_name}\"") return new_name From a5a1494a969fc032d4a167f36190c28f939a2d22 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:01:45 -0500 Subject: [PATCH 57/66] Pokemon R/B: The Big Door Shuffle Update (#2861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Perhaps most critically, adds the ability for the door shuffle code to catch door shuffle exceptions and try again. Will try up to 10 times. Should mean Door Shuffle does not need to be disallowed in the big async🤞 - Door Shuffle code has been made drastically faster by searching for the first dead end instead of sorting the whole list of entrances by whether they are dead ends. - Renames Full to Interiors, and adds a new Full door shuffle that shuffles interior-to-interior doors separately from exterior-to-interior doors. - Adds a new Decoupled door shuffle. - Warp Tile Shuffle now has 3 separate options, Vanilla, Shuffle, and Mixed. Shuffle shuffles the warp tiles among themselves, Mixed mixes them into the Door Shuffle pool. - Safari Zone connections are now shuffled on Full, Insanity, and Decoupled. - On Simple Door Shuffle, the Town Map is updated to show the new dungeon locations. The Town Map has been updated to show the locations of dungeons that previously were not shown unless you opened the map within them, and the Sea Cottage has been removed from it. - Adds Auto Level Scaling that chooses the level scaling mode based on the Door Shuffle choice. - Fixes issues with Flash and Fly move interventions (where it ensures an available Pokémon that can learn it is reachable depending on settings). - Fixes a possible generation crash with type chart randomization. - Should fix an issue where `stage_fill_hook` was able to remove the wrong item from the item pool resulting in a duplicated item reference existing. - Adds a stage_post_fill function which searches for Pokémon in order of spheres, setting all but the first advancement Pokémon event found to `useful` so that spoiler playthrough calculation skips them. In a solo game gen test, this cut gen time from 15 seconds to 10 seconds with same seed number. Difference is likely to be much more massive in larger multiworlds. --- worlds/pokemon_rb/__init__.py | 66 +-- worlds/pokemon_rb/basepatch_blue.bsdiff4 | Bin 45872 -> 46356 bytes worlds/pokemon_rb/basepatch_red.bsdiff4 | Bin 45839 -> 46344 bytes .../docs/en_Pokemon Red and Blue.md | 2 + worlds/pokemon_rb/level_scaling.py | 25 +- worlds/pokemon_rb/locations.py | 20 +- worlds/pokemon_rb/options.py | 70 +-- worlds/pokemon_rb/regions.py | 505 ++++++++++++------ worlds/pokemon_rb/rom.py | 25 +- worlds/pokemon_rb/rom_addresses.py | 330 ++++++------ 10 files changed, 624 insertions(+), 419 deletions(-) diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 56502f50299c..beb2010b58d3 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -195,11 +195,11 @@ def encode_name(name, t): normals -= subtract_amounts[2] while super_effectives + not_very_effectives + normals > 225 - immunities: r = self.multiworld.random.randint(0, 2) - if r == 0: + if r == 0 and super_effectives: super_effectives -= 1 - elif r == 1: + elif r == 1 and not_very_effectives: not_very_effectives -= 1 - else: + elif normals: normals -= 1 chart = [] for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives], @@ -249,14 +249,18 @@ def stage_fill_hook(cls, multiworld, progitempool, usefulitempool, filleritempoo itempool = progitempool + usefulitempool + filleritempool multiworld.random.shuffle(itempool) unplaced_items = [] - for item in itempool: + for i, item in enumerate(itempool): if item.player == loc.player and loc.can_fill(multiworld.state, item, False): - if item in progitempool: - progitempool.remove(item) - elif item in usefulitempool: - usefulitempool.remove(item) - elif item in filleritempool: - filleritempool.remove(item) + if item.advancement: + pool = progitempool + elif item.useful: + pool = usefulitempool + else: + pool = filleritempool + for i, check_item in enumerate(pool): + if item is check_item: + pool.pop(i) + break if item.advancement: state = sweep_from_pool(multiworld.state, progitempool + unplaced_items) if (not item.advancement) or state.can_reach(loc, "Location", loc.player): @@ -416,16 +420,16 @@ def number_of_zones(mon): self.multiworld.victory_road_condition[self.player]) > 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")))): intervene_move = "Cut" - elif ((not logic.can_learn_hm(test_state, "Flash", self.player)) and self.multiworld.dark_rock_tunnel_logic[self.player] - and (((self.multiworld.accessibility[self.player] != "minimal" or - (self.multiworld.trainersanity[self.player] or self.multiworld.extra_key_items[self.player])) or - self.multiworld.door_shuffle[self.player]))): + elif ((not logic.can_learn_hm(test_state, "Flash", self.player)) + and self.multiworld.dark_rock_tunnel_logic[self.player] + and (self.multiworld.accessibility[self.player] != "minimal" + or self.multiworld.door_shuffle[self.player])): intervene_move = "Flash" # If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps # as reachable, and if on no door shuffle or simple, fly is simply never necessary. # We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been # considered in door shuffle. - elif ((not logic.can_learn_hm(test_state, "Fly", self.player)) and logic.can_learn_hm(test_state, "Fly", self.player) + elif ((not logic.can_learn_hm(test_state, "Fly", self.player)) and self.multiworld.door_shuffle[self.player] not in ("off", "simple") and [self.fly_map, self.town_map_fly_map] != ["Pallet Town", "Pallet Town"]): intervene_move = "Fly" @@ -554,23 +558,21 @@ def number_of_zones(mon): else: raise Exception("Failed to remove corresponding item while deleting unreachable Dexsanity location") - - if self.multiworld.door_shuffle[self.player] == "decoupled": - swept_state = self.multiworld.state.copy() - swept_state.sweep_for_events(player=self.player) - locations = [location for location in - self.multiworld.get_reachable_locations(swept_state, self.player) if location.item is - None] - self.multiworld.random.shuffle(locations) - while len(locations) > 10: - location = locations.pop() - location.progress_type = LocationProgressType.EXCLUDED - - if self.multiworld.key_items_only[self.player]: - locations = [location for location in self.multiworld.get_unfilled_locations(self.player) if - location.progress_type == LocationProgressType.DEFAULT] - for location in locations: - location.progress_type = LocationProgressType.PRIORITY + @classmethod + def stage_post_fill(cls, multiworld): + # Convert all but one of each instance of a wild Pokemon to useful classification. + # This cuts down on time spent calculating the spoiler playthrough. + found_mons = set() + for sphere in multiworld.get_spheres(): + for location in sphere: + if (location.game == "Pokemon Red and Blue" and (location.item.name in poke_data.pokemon_data.keys() + or "Static " in location.item.name) + and location.item.advancement): + key = (location.player, location.item.name) + if key in found_mons: + location.item.classification = ItemClassification.useful + else: + found_mons.add(key) def create_regions(self): if (self.multiworld.old_man[self.player] == "vanilla" or diff --git a/worlds/pokemon_rb/basepatch_blue.bsdiff4 b/worlds/pokemon_rb/basepatch_blue.bsdiff4 index 5ccf4e9bbaf80f0167471e02dd64384b5bb0e622..0f65564a737be4e77c18cf94336ac9ba859345a9 100644 GIT binary patch literal 46356 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

%ff+)8u7 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 z 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

^yJTP6oc_cjvKX=i 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 literal 45872 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 AWB>pF diff --git a/worlds/pokemon_rb/basepatch_red.bsdiff4 b/worlds/pokemon_rb/basepatch_red.bsdiff4 index 26d2eb0c2869e020058689cee8e18abbca5e3c04..826b7bf8b4e5ff0dd0926ca876c9930eebe4eefc 100644 GIT binary patch literal 46344 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 DjU7nf literal 45839 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 O;_gVN3K9a`DoQ|XZiR;c diff --git a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md index b164d4b0fef6..dc55aca0f09f 100644 --- a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md +++ b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md @@ -41,6 +41,8 @@ and repeatable source of money. * You can disable and re-enable experience gains by talking to an aide in Oak's Lab. * You can reset static encounters (Poké Flute encounter, legendaries, and the trap Poké Ball battles in Power Plant) for any Pokémon you have defeated but not caught, by talking to an aide in Oak's Lab. +* Dungeons normally hidden on the Town Map are now present, and the "Sea Cottage" has been removed. This is to allow +Simple Door Shuffle to update the locations of all of the dungeons on the Town Map. ## What items and locations get shuffled? diff --git a/worlds/pokemon_rb/level_scaling.py b/worlds/pokemon_rb/level_scaling.py index 5f3dfc1acd7c..79cda394724a 100644 --- a/worlds/pokemon_rb/level_scaling.py +++ b/worlds/pokemon_rb/level_scaling.py @@ -10,7 +10,9 @@ def level_scaling(multiworld): while locations: sphere = set() for world in multiworld.get_game_worlds("Pokemon Red and Blue"): - if multiworld.level_scaling[world.player] != "by_spheres_and_distance": + if (multiworld.level_scaling[world.player] != "by_spheres_and_distance" + and (multiworld.level_scaling[world.player] != "auto" or multiworld.door_shuffle[world.player] + in ("off", "simple"))): continue regions = {multiworld.get_region("Menu", world.player)} checked_regions = set() @@ -45,18 +47,18 @@ def reachable(): return True if (("Rock Tunnel 1F - Wild Pokemon" in location.name and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state) - for e in ['Rock Tunnel 1F-NE to Route 10-N', - 'Rock Tunnel 1F-NE to Rock Tunnel B1F-E', - 'Rock Tunnel 1F-NW to Rock Tunnel B1F-E', - 'Rock Tunnel 1F-NW to Rock Tunnel B1F-W', - 'Rock Tunnel 1F-S to Route 10-S', - 'Rock Tunnel 1F-S to Rock Tunnel B1F-W']])) or + for e in ['Rock Tunnel 1F-NE 1 to Route 10-N', + 'Rock Tunnel 1F-NE 2 to Rock Tunnel B1F-E 1', + 'Rock Tunnel 1F-NW 1 to Rock Tunnel B1F-E 2', + 'Rock Tunnel 1F-NW 2 to Rock Tunnel B1F-W 1', + 'Rock Tunnel 1F-S 1 to Route 10-S', + 'Rock Tunnel 1F-S 2 to Rock Tunnel B1F-W 2']])) or ("Rock Tunnel B1F - Wild Pokemon" in location.name and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state) - for e in ['Rock Tunnel B1F-E to Rock Tunnel 1F-NE', - 'Rock Tunnel B1F-E to Rock Tunnel 1F-NW', - 'Rock Tunnel B1F-W to Rock Tunnel 1F-NW', - 'Rock Tunnel B1F-W to Rock Tunnel 1F-S']]))): + for e in ['Rock Tunnel B1F-E 1 to Rock Tunnel 1F-NE 2', + 'Rock Tunnel B1F-E 2 to Rock Tunnel 1F-NW 1', + 'Rock Tunnel B1F-W 1 to Rock Tunnel 1F-NW 2', + 'Rock Tunnel B1F-W 2 to Rock Tunnel 1F-S 2']]))): # Even if checks in Rock Tunnel are out of logic due to lack of Flash, it is very easy to # wander in the dark and encounter wild Pokémon, even unintentionally while attempting to # leave the way you entered. We'll count the wild Pokémon as reachable as soon as the Rock @@ -135,4 +137,3 @@ def reachable(): sphere_objects[object].level = level_list_copy.pop(0) for world in multiworld.get_game_worlds("Pokemon Red and Blue"): world.finished_level_scaling.set() - diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index 3fff3b88c1ea..abaa58fcf901 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -1036,25 +1036,25 @@ def __init__(self, flag): type="Wild Encounter", level=12), LocationData("Mt Moon B2F-Wild", "Wild Pokemon - 10", "Clefairy", rom_addresses["Wild_MtMoonB2F"] + 19, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route4"] + 1, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route4"] + 1, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route4"] + 3, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route4"] + 3, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route4"] + 5, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route4"] + 5, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 7, None, + LocationData("Route 4-E", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 7, None, event=True, type="Wild Encounter", level=6), - LocationData("Route 4-Grass", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route4"] + 9, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route4"] + 9, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 11, None, + LocationData("Route 4-E", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 11, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route4"] + 13, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route4"] + 13, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route4"] + 15, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route4"] + 15, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 17, None, + LocationData("Route 4-E", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 17, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 19, None, + LocationData("Route 4-E", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 19, None, event=True, type="Wild Encounter", level=12), LocationData("Route 24", "Wild Pokemon - 1", ["Weedle", "Caterpie"], rom_addresses["Wild_Route24"] + 1, None, event=True, type="Wild Encounter", level=7), diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index 8afe91b86741..bd6515913aca 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -228,7 +228,7 @@ class SplitCardKey(Choice): class AllElevatorsLocked(Toggle): """Adds requirements to the Celadon Department Store elevator and Silph Co elevators to have the Lift Key. - No logical implications normally, but may have a significant impact on Insanity Door Shuffle.""" + No logical implications normally, but may have a significant impact on some Door Shuffle options.""" display_name = "All Elevators Locked" default = 1 @@ -317,42 +317,42 @@ class TownMapFlyLocation(Toggle): class DoorShuffle(Choice): """Simple: entrances are randomized together in groups: Pokemarts, Gyms, single exit dungeons, dual exit dungeons, single exit misc interiors, dual exit misc interiors are all shuffled separately. Safari Zone is not shuffled. - Full: Any outdoor entrance may lead to any interior. - Insanity: All rooms in the game are shuffled.""" + On Simple only, the Town Map will be updated to show the new locations for each dungeon. + Interiors: Any outdoor entrance may lead to any interior, but intra-interior doors are not shuffled. Previously + named Full. + Full: Exterior to interior entrances are shuffled, and interior to interior doors are shuffled, separately. + Insanity: All doors in the game are shuffled. + Decoupled: Doors may be decoupled from each other, so that leaving through an exit may not return you to the + door you entered from.""" display_name = "Door Shuffle" option_off = 0 option_simple = 1 - option_full = 2 - option_insanity = 3 - # Disabled for now, has issues with elevators that need to be resolved - # option_decoupled = 4 - default = 0 - - # remove assertions that blow up checks for decoupled - def __eq__(self, other): - if isinstance(other, self.__class__): - return other.value == self.value - elif isinstance(other, str): - return other == self.current_key - elif isinstance(other, int): - return other == self.value - elif isinstance(other, bool): - return other == bool(self.value) - else: - raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}") - - -class WarpTileShuffle(Toggle): - """Shuffle the warp tiles in Silph Co and Sabrina's Gym among themselves, separately. - On Insanity, turning this off means they are mixed into the general door shuffle instead of only being shuffled - among themselves.""" + option_interiors = 2 + option_full = 3 + option_insanity = 4 + option_decoupled = 5 + default = 0 + + +class WarpTileShuffle(Choice): + """Vanilla: The warp tiles in Silph Co and Sabrina's Gym are not changed. + Shuffle: The warp tile destinations are shuffled among themselves. + Mixed: The warp tiles are mixed into the pool of available doors for Full, Insanity, and Decoupled. Same as Shuffle + for any other door shuffle option.""" display_name = "Warp Tile Shuffle" default = 0 + option_vanilla = 0 + option_shuffle = 1 + option_mixed = 2 + alias_true = 1 + alias_on = 1 + alias_off = 0 + alias_false = 0 class RandomizeRockTunnel(Toggle): - """Randomize the layout of Rock Tunnel. - If Insanity Door Shuffle is on, this will cause only the main entrances to Rock Tunnel to be shuffled.""" + """Randomize the layout of Rock Tunnel. If Full, Insanity, or Decoupled Door Shuffle is on, this will cause only the + main entrances to Rock Tunnel to be shuffled.""" display_name = "Randomize Rock Tunnel" default = 0 @@ -401,15 +401,17 @@ class Stonesanity(Toggle): class LevelScaling(Choice): """Off: Encounters use vanilla game levels. By Spheres: Levels are scaled by access sphere. Areas reachable in later spheres will have higher levels. - Spheres and Distance: Levels are scaled by access spheres as well as distance from Pallet Town, measured by number - of internal region connections. This is a much more severe curving of levels and may lead to much less variation in - levels found in a particular map. However, it may make the higher door shuffle settings significantly more bearable, - as these options more often result in a smaller number of larger access spheres.""" + By Spheres and Distance: Levels are scaled by access spheres as well as distance from Pallet Town, measured by + number of internal region connections. This is a much more severe curving of levels and may lead to much less + variation in levels found in a particular map. However, it may make the higher door shuffle settings significantly + more bearable, as these options more often result in a smaller number of larger access spheres. + Auto: Scales by Spheres if Door Shuffle is off or on Simple, otherwise scales by Spheres and Distance""" display_name = "Level Scaling" option_off = 0 option_by_spheres = 1 option_by_spheres_and_distance = 2 - default = 1 + option_auto = 3 + default = 3 class ExpModifier(NamedRange): diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index 97e63c05573d..afeb301c9b94 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -256,6 +256,22 @@ "Indigo Plateau Agatha's Room": 0xF7, } +town_map_coords = { + "Route 2-SW": ("Viridian Forest South Gate to Route 2-SW", 2, 4, (3,), "Viridian Forest", 4), #ViridianForestName + "Route 2-NE": ("Diglett's Cave Route 2 to Route 2-NE", 3, 4, (48,), "Diglett's Cave", 5), #DiglettsCaveName + "Route 4-W": ("Mt Moon 1F to Route 4-W", 6, 2, (5,), "Mt Moon 1F", 8), #MountMoonName + "Cerulean City-Cave": ("Cerulean Cave 1F-SE to Cerulean City-Cave", 9, 1, (54,), "Cerulean Cave 1F", 11), #CeruleanCaveName + "Vermilion City-Dock": ("Vermilion Dock to Vermilion City-Dock", 9, 10, (19,), "S.S. Anne 1F", 17), #SSAnneName + "Route 10-N": ("Rock Tunnel 1F-NE 1 to Route 10-N", 14, 3, (13, 57), "Rock Tunnel Pokemon Center", 19), #RockTunnelName + "Lavender Town": ("Pokemon Tower 1F to Lavender Town", 15, 5, (27,), "Pokemon Tower 2F", 22), #PokemonTowerName + "Celadon Game Corner-Hidden Stairs": ("Rocket Hideout B1F to Celadon Game Corner-Hidden Stairs", 7, 5, (50,), "Rocket Hideout B1F", 26), #RocketHQName + "Saffron City-Silph": ("Silph Co 1F to Saffron City-Silph", 10, 5, (51, 58), "Silph Co 2F", 28), #SilphCoName + "Route 20-IE": ("Seafoam Islands 1F to Route 20-IE", 5, 15, (32,), "Seafoam Islands B1F", 40), #SeafoamIslandsName + "Cinnabar Island-M": ("Pokemon Mansion 1F to Cinnabar Island-M", 2, 15, (35, 52), "Pokemon Mansion 1F", 43), #PokemonMansionName + "Route 23-C": ("Victory Road 1F-S to Route 23-C", 0, 4, (20, 45, 49), "Victory Road 1F", 47), #VictoryRoadName + "Route 10-P": ("Power Plant to Route 10-P", 15, 4, (14,), "Power Plant", 49), #PowerPlantName +} + warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishing': [], 'Fossil Level': [], 'Pokedex': [], 'Fossil': [], 'Celadon City': [ {'name': 'Celadon City to Celadon Department Store 1F W', 'address': 'Warps_CeladonCity', 'id': 0, @@ -461,15 +477,21 @@ {'address': 'Warps_PokemonMansion3F', 'id': 0, 'to': {'map': 'Pokemon Mansion 2F', 'id': 1}}], 'Pokemon Mansion B1F': [ {'address': 'Warps_PokemonMansionB1F', 'id': 0, 'to': {'map': 'Pokemon Mansion 1F-SE', 'id': 5}}], - 'Rock Tunnel 1F-NE': [{'address': 'Warps_RockTunnel1F', 'id': 0, 'to': {'map': 'Route 10-N', 'id': 1}}, - {'address': 'Warps_RockTunnel1F', 'id': 4, - 'to': {'map': 'Rock Tunnel B1F-E', 'id': 0}}], 'Rock Tunnel 1F-NW': [ - {'address': 'Warps_RockTunnel1F', 'id': 5, 'to': {'map': 'Rock Tunnel B1F-E', 'id': 1}}, - {'address': 'Warps_RockTunnel1F', 'id': 6, 'to': {'map': 'Rock Tunnel B1F-W', 'id': 2}}], - 'Rock Tunnel 1F-S': [{'address': 'Warps_RockTunnel1F', 'id': 2, 'to': {'map': 'Route 10-S', 'id': 2}}, + 'Rock Tunnel 1F-NE 1': [{'address': 'Warps_RockTunnel1F', 'id': 0, 'to': {'map': 'Route 10-N', 'id': 1}}], + 'Rock Tunnel 1F-NE 2': + [{'address': 'Warps_RockTunnel1F', 'id': 4, + 'to': {'map': 'Rock Tunnel B1F-E 1', 'id': 0}}], 'Rock Tunnel 1F-NW 1': [ + {'address': 'Warps_RockTunnel1F', 'id': 5, 'to': {'map': 'Rock Tunnel B1F-E 2', 'id': 1}}], + 'Rock Tunnel 1F-NW 2': [ + {'address': 'Warps_RockTunnel1F', 'id': 6, 'to': {'map': 'Rock Tunnel B1F-W 1', 'id': 2}}], + 'Rock Tunnel 1F-S 1': [{'address': 'Warps_RockTunnel1F', 'id': 2, 'to': {'map': 'Route 10-S', 'id': 2}}], + 'Rock Tunnel 1F-S 2': [ {'address': 'Warps_RockTunnel1F', 'id': 7, - 'to': {'map': 'Rock Tunnel B1F-W', 'id': 3}}], 'Rock Tunnel 1F-Wild': [], - 'Rock Tunnel B1F-Wild': [], 'Seafoam Islands 1F': [ + 'to': {'map': 'Rock Tunnel B1F-W 2', 'id': 3}}], 'Rock Tunnel 1F-Wild': [], + 'Rock Tunnel B1F-Wild': [], + 'Rock Tunnel 1F-NE': [], 'Rock Tunnel 1F-NW': [], 'Rock Tunnel 1F-S': [], 'Rock Tunnel B1F-E': [], + 'Rock Tunnel B1F-W': [], + 'Seafoam Islands 1F': [ {'address': 'Warps_SeafoamIslands1F', 'id': (2, 3), 'to': {'map': 'Route 20-IE', 'id': 1}}, {'address': 'Warps_SeafoamIslands1F', 'id': 4, 'to': {'map': 'Seafoam Islands B1F', 'id': 1}}, {'address': 'Warps_SeafoamIslands1F', 'id': 5, 'to': {'map': 'Seafoam Islands B1F-NE', 'id': 6}}], @@ -569,12 +591,14 @@ {'address': 'Warps_CeruleanCave2F', 'id': 3, 'to': {'map': 'Cerulean Cave 1F-N', 'id': 5}}], 'Cerulean Cave B1F': [ {'address': 'Warps_CeruleanCaveB1F', 'id': 0, 'to': {'map': 'Cerulean Cave 1F-NW', 'id': 8}}], - 'Cerulean Cave B1F-E': [], 'Rock Tunnel B1F-E': [ - {'address': 'Warps_RockTunnelB1F', 'id': 0, 'to': {'map': 'Rock Tunnel 1F-NE', 'id': 4}}, - {'address': 'Warps_RockTunnelB1F', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NW', 'id': 5}}], - 'Rock Tunnel B1F-W': [ - {'address': 'Warps_RockTunnelB1F', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-NW', 'id': 6}}, - {'address': 'Warps_RockTunnelB1F', 'id': 3, 'to': {'map': 'Rock Tunnel 1F-S', 'id': 7}}], + 'Cerulean Cave B1F-E': [], 'Rock Tunnel B1F-E 1': [ + {'address': 'Warps_RockTunnelB1F', 'id': 0, 'to': {'map': 'Rock Tunnel 1F-NE 2', 'id': 4}}], + 'Rock Tunnel B1F-E 2': [ + {'address': 'Warps_RockTunnelB1F', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NW 1', 'id': 5}}], + 'Rock Tunnel B1F-W 1': [ + {'address': 'Warps_RockTunnelB1F', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-NW 2', 'id': 6}}], + 'Rock Tunnel B1F-W 2': [ + {'address': 'Warps_RockTunnelB1F', 'id': 3, 'to': {'map': 'Rock Tunnel 1F-S 2', 'id': 7}}], 'Seafoam Islands B1F': [ {'address': 'Warps_SeafoamIslandsB1F', 'id': 0, 'to': {'map': 'Seafoam Islands B2F-NW', 'id': 0}}, {'address': 'Warps_SeafoamIslandsB1F', 'id': 1, 'to': {'map': 'Seafoam Islands 1F', 'id': 4}}, @@ -802,7 +826,7 @@ 'Route 4-W': [{'address': 'Warps_Route4', 'id': 0, 'to': {'map': 'Route 4 Pokemon Center', 'id': 0}}, {'address': 'Warps_Route4', 'id': 1, 'to': {'map': 'Mt Moon 1F', 'id': 0}}], 'Route 4-C': [{'address': 'Warps_Route4', 'id': 2, 'to': {'map': 'Mt Moon B1F-NE', 'id': 7}}], - 'Route 4-E': [], 'Route 4-Lass': [], 'Route 4-Grass': [], + 'Route 4-Lass': [], 'Route 4-E': [], 'Route 5': [{'address': 'Warps_Route5', 'id': (1, 0), 'to': {'map': 'Route 5 Gate-N', 'id': (3, 2)}}, {'address': 'Warps_Route5', 'id': 3, 'to': {'map': 'Underground Path Route 5', 'id': 0}}, {'address': 'Warps_Route5', 'id': 4, 'to': {'map': 'Daycare', 'id': 0}}], 'Route 9': [], @@ -838,8 +862,8 @@ {'address': 'Warps_Route8', 'id': 4, 'to': {'map': 'Underground Path Route 8', 'id': 0}}], 'Route 8-Grass': [], 'Route 10-N': [{'address': 'Warps_Route10', 'id': 0, 'to': {'map': 'Rock Tunnel Pokemon Center', 'id': 0}}, - {'address': 'Warps_Route10', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NE', 'id': 0}}], - 'Route 10-S': [{'address': 'Warps_Route10', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-S', 'id': 2}}], + {'address': 'Warps_Route10', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NE 1', 'id': 0}}], + 'Route 10-S': [{'address': 'Warps_Route10', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-S 1', 'id': 2}}], 'Route 10-P': [{'address': 'Warps_Route10', 'id': 3, 'to': {'map': 'Power Plant', 'id': 0}}], 'Route 10-C': [], 'Route 11': [{'address': 'Warps_Route11', 'id': 4, 'to': {'map': "Diglett's Cave Route 11", 'id': 0}}], @@ -1293,7 +1317,7 @@ def pair(a, b): return (f"{a} to {b}", f"{b} to {a}") -mandatory_connections = { +safari_zone_connections = { pair("Safari Zone Center-S", "Safari Zone Gate-N"), pair("Safari Zone East", "Safari Zone North"), pair("Safari Zone East", "Safari Zone Center-S"), @@ -1302,14 +1326,8 @@ def pair(a, b): pair("Safari Zone North", "Safari Zone West-NW"), pair("Safari Zone West", "Safari Zone Center-NW"), } -insanity_mandatory_connections = { - # pair("Seafoam Islands B1F-NE", "Seafoam Islands 1F"), - # pair("Seafoam Islands 1F", "Seafoam Islands B1F"), - # pair("Seafoam Islands B2F-NW", "Seafoam Islands B1F"), - # pair("Seafoam Islands B3F-SE", "Seafoam Islands B2F-SE"), - # pair("Seafoam Islands B3F-NE", "Seafoam Islands B2F-NE"), - # pair("Seafoam Islands B4F", "Seafoam Islands B3F-NE"), - # pair("Seafoam Islands B4F", "Seafoam Islands B3F"), + +full_mandatory_connections = { pair("Player's House 1F", "Player's House 2F"), pair("Indigo Plateau Lorelei's Room", "Indigo Plateau Lobby-N"), pair("Indigo Plateau Bruno's Room", "Indigo Plateau Lorelei's Room"), @@ -1338,7 +1356,7 @@ def pair(a, b): unsafe_connecting_interior_dungeons = [ ["Seafoam Islands 1F to Route 20-IE", "Seafoam Islands 1F-SE to Route 20-IW"], - ["Rock Tunnel 1F-NE to Route 10-N", "Rock Tunnel 1F-S to Route 10-S"], + ["Rock Tunnel 1F-NE 1 to Route 10-N", "Rock Tunnel 1F-S 1 to Route 10-S"], ["Victory Road 1F-S to Route 23-C", "Victory Road 2F-E to Route 23-N"], ] @@ -1357,7 +1375,7 @@ def pair(a, b): ["Route 2-NE to Diglett's Cave Route 2", "Route 11 to Diglett's Cave Route 11"], ['Route 20-IE to Seafoam Islands 1F', 'Route 20-IW to Seafoam Islands 1F-SE'], ['Route 4-W to Mt Moon 1F', 'Route 4-C to Mt Moon B1F-NE'], - ['Route 10-N to Rock Tunnel 1F-NE', 'Route 10-S to Rock Tunnel 1F-S'], + ['Route 10-N to Rock Tunnel 1F-NE 1', 'Route 10-S to Rock Tunnel 1F-S 1'], ['Route 23-C to Victory Road 1F-S', 'Route 23-N to Victory Road 2F-E'], ] @@ -1454,7 +1472,6 @@ def pair(a, b): ] unreachable_outdoor_entrances = [ - "Route 4-C to Mt Moon B1F-NE", "Fuchsia City-Good Rod House Backyard to Fuchsia Good Rod House", "Cerulean City-Badge House Backyard to Cerulean Badge House", # TODO: This doesn't need to be forced if fly location is Pokemon League? @@ -1496,7 +1513,6 @@ def create_regions(self): start_inventory["Exp. All"] = 1 self.multiworld.push_precollected(self.create_item("Exp. All")) - # locations = [location for location in location_data if location.type in ("Item", "Trainer Parties")] self.item_pool = [] combined_traps = (self.multiworld.poison_trap_weight[self.player].value + self.multiworld.fire_trap_weight[self.player].value @@ -1556,7 +1572,6 @@ def create_regions(self): if event: location_object.place_locked_item(item) if location.type == "Trainer Parties": - # loc.item.classification = ItemClassification.filler location_object.party_data = deepcopy(location.party_data) else: self.item_pool.append(item) @@ -1566,7 +1581,7 @@ def create_regions(self): + [item.name for item in self.multiworld.precollected_items[self.player] if item.advancement] self.total_key_items = len( - # The stonesanity items are not checekd for here and instead just always added as the `+ 4` + # The stonesanity items are not checked for here and instead just always added as the `+ 4` # They will always exist, but if stonesanity is off, then only as events. # We don't want to just add 4 if stonesanity is off while still putting them in this list in case # the player puts stones in their start inventory, in which case they would be double-counted here. @@ -1619,16 +1634,15 @@ def create_regions(self): connect(multiworld, player, "Pewter City-E", "Route 3", lambda state: logic.route_3(state, player), one_way=True) connect(multiworld, player, "Route 3", "Pewter City-E", one_way=True) connect(multiworld, player, "Route 4-W", "Route 3") - connect(multiworld, player, "Route 24", "Cerulean City-Water", one_way=True) + connect(multiworld, player, "Route 24", "Cerulean City-Water", lambda state: logic.can_surf(state, player)) connect(multiworld, player, "Cerulean City-Water", "Route 4-Lass", lambda state: logic.can_surf(state, player), one_way=True) connect(multiworld, player, "Mt Moon B2F", "Mt Moon B2F-Wild", one_way=True) connect(multiworld, player, "Mt Moon B2F-NE", "Mt Moon B2F-Wild", one_way=True) connect(multiworld, player, "Mt Moon B2F-C", "Mt Moon B2F-Wild", one_way=True) - connect(multiworld, player, "Route 4-Lass", "Route 4-E", one_way=True) + connect(multiworld, player, "Route 4-Lass", "Route 4-C", one_way=True) connect(multiworld, player, "Route 4-C", "Route 4-E", one_way=True) - connect(multiworld, player, "Route 4-E", "Route 4-Grass", one_way=True) - connect(multiworld, player, "Route 4-Grass", "Cerulean City", one_way=True) - connect(multiworld, player, "Cerulean City", "Route 24", one_way=True) + connect(multiworld, player, "Route 4-E", "Cerulean City") + connect(multiworld, player, "Cerulean City", "Route 24") connect(multiworld, player, "Cerulean City", "Cerulean City-T", lambda state: state.has("Help Bill", player)) connect(multiworld, player, "Cerulean City-Outskirts", "Cerulean City", one_way=True) connect(multiworld, player, "Cerulean City", "Cerulean City-Outskirts", lambda state: logic.can_cut(state, player), one_way=True) @@ -1785,7 +1799,6 @@ def create_regions(self): connect(multiworld, player, "Seafoam Islands B3F-SE", "Seafoam Islands B3F-Wild", one_way=True) connect(multiworld, player, "Seafoam Islands B4F", "Seafoam Islands B4F-W", lambda state: logic.can_surf(state, player), one_way=True) connect(multiworld, player, "Seafoam Islands B4F-W", "Seafoam Islands B4F", one_way=True) - # This really shouldn't be necessary since if the boulders are reachable you can drop, but might as well be thorough connect(multiworld, player, "Seafoam Islands B3F", "Seafoam Islands B3F-SE", lambda state: logic.can_surf(state, player) and logic.can_strength(state, player) and state.has("Seafoam Exit Boulder", player, 6)) connect(multiworld, player, "Viridian City", "Viridian City-N", lambda state: state.has("Oak's Parcel", player) or state.multiworld.old_man[player].value == 2 or logic.can_cut(state, player)) connect(multiworld, player, "Route 11", "Route 11-C", lambda state: logic.can_strength(state, player) or not state.multiworld.extra_strength_boulders[player]) @@ -1804,6 +1817,16 @@ def create_regions(self): connect(multiworld, player, "Pokemon Mansion 2F-E", "Pokemon Mansion 2F-Wild", one_way=True) connect(multiworld, player, "Pokemon Mansion 1F-SE", "Pokemon Mansion 1F-Wild", one_way=True) connect(multiworld, player, "Pokemon Mansion 1F", "Pokemon Mansion 1F-Wild", one_way=True) + connect(multiworld, player, "Rock Tunnel 1F-S 1", "Rock Tunnel 1F-S", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-S 2", "Rock Tunnel 1F-S", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NW 1", "Rock Tunnel 1F-NW", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NW 2", "Rock Tunnel 1F-NW", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NE 1", "Rock Tunnel 1F-NE", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NE 2", "Rock Tunnel 1F-NE", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-W 1", "Rock Tunnel B1F-W", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-W 2", "Rock Tunnel B1F-W", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-E 1", "Rock Tunnel B1F-E", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-E 2", "Rock Tunnel B1F-E", lambda state: logic.rock_tunnel(state, player)) connect(multiworld, player, "Rock Tunnel 1F-S", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) connect(multiworld, player, "Rock Tunnel 1F-NW", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) connect(multiworld, player, "Rock Tunnel 1F-NE", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) @@ -1860,7 +1883,6 @@ def create_regions(self): logic.has_badges(state, self.multiworld.cerulean_cave_badges_condition[player].value, player) and logic.has_key_items(state, self.multiworld.cerulean_cave_key_items_condition[player].total, player) and logic.can_surf(state, player)) - # access to any part of a city will enable flying to the Pokemon Center connect(multiworld, player, "Cerulean City-Cave", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) connect(multiworld, player, "Cerulean City-Badge House Backyard", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) @@ -1876,7 +1898,6 @@ def create_regions(self): connect(multiworld, player, "Cinnabar Island-G", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-G to Cinnabar Island (Fly)") connect(multiworld, player, "Cinnabar Island-M", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-M to Cinnabar Island (Fly)") - # drops connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F (Drop)") connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F-NE", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F-NE (Drop)") @@ -1904,14 +1925,50 @@ def create_regions(self): lambda state: logic.can_fly(state, player) and state.has("Town Map", player), one_way=True, name="Town Map Fly Location") + cache = multiworld.regions.entrance_cache[self.player].copy() + if multiworld.badgesanity[player] or multiworld.door_shuffle[player] in ("off", "simple"): + badges = None + badge_locs = None + else: + badges = [item for item in self.item_pool if "Badge" in item.name] + for badge in badges: + self.item_pool.remove(badge) + badge_locs = [multiworld.get_location(loc, player) for loc in [ + "Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize", "Vermilion Gym - Lt. Surge Prize", + "Celadon Gym - Erika Prize", "Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize", + "Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize" + ]] + for attempt in range(10): + try: + door_shuffle(self, multiworld, player, badges, badge_locs) + except DoorShuffleException as e: + if attempt == 9: + raise e + for region in self.multiworld.get_regions(player): + for entrance in reversed(region.exits): + if isinstance(entrance, PokemonRBWarp): + region.exits.remove(entrance) + multiworld.regions.entrance_cache[self.player] = cache + if badge_locs: + for loc in badge_locs: + loc.item = None + loc.locked = False + else: + break + + +def door_shuffle(world, multiworld, player, badges, badge_locs): entrances = [] + full_interiors = [] for region_name, region_entrances in warp_data.items(): + region = multiworld.get_region(region_name, player) for entrance_data in region_entrances: - region = multiworld.get_region(region_name, player) shuffle = True - if not outdoor_map(region.name) and not outdoor_map(entrance_data['to']['map']) and \ - multiworld.door_shuffle[player] not in ("insanity", "decoupled"): - shuffle = False + interior = False + if not outdoor_map(region.name) and not outdoor_map(entrance_data['to']['map']): + if multiworld.door_shuffle[player] not in ("full", "insanity", "decoupled"): + shuffle = False + interior = True if multiworld.door_shuffle[player] == "simple": if sorted([entrance_data['to']['map'], region.name]) == ["Celadon Game Corner-Hidden Stairs", "Rocket Hideout B1F"]: @@ -1921,11 +1978,14 @@ def create_regions(self): if (multiworld.randomize_rock_tunnel[player] and "Rock Tunnel" in region.name and "Rock Tunnel" in entrance_data['to']['map']): shuffle = False - if (f"{region.name} to {entrance_data['to']['map']}" if "name" not in entrance_data else + elif (f"{region.name} to {entrance_data['to']['map']}" if "name" not in entrance_data else entrance_data["name"]) in silph_co_warps + saffron_gym_warps: - if multiworld.warp_tile_shuffle[player] or multiworld.door_shuffle[player] in ("insanity", - "decoupled"): + if multiworld.warp_tile_shuffle[player]: shuffle = True + if multiworld.warp_tile_shuffle[player] == "mixed" and multiworld.door_shuffle[player] == "full": + interior = True + else: + interior = False else: shuffle = False elif not multiworld.door_shuffle[player]: @@ -1935,33 +1995,49 @@ def create_regions(self): entrance_data else entrance_data["name"], region, entrance_data["id"], entrance_data["address"], entrance_data["flags"] if "flags" in entrance_data else "") - # if "Rock Tunnel" in region_name: - # entrance.access_rule = lambda state: logic.rock_tunnel(state, player) - entrances.append(entrance) + if interior and multiworld.door_shuffle[player] == "full": + full_interiors.append(entrance) + else: + entrances.append(entrance) region.exits.append(entrance) else: - # connect(multiworld, player, region.name, entrance_data['to']['map'], one_way=True) - if "Rock Tunnel" in region.name: - connect(multiworld, player, region.name, entrance_data["to"]["map"], - lambda state: logic.rock_tunnel(state, player), one_way=True) - else: - connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True, - name=entrance_data["name"] if "name" in entrance_data else None) + connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True, + name=entrance_data["name"] if "name" in entrance_data else None) forced_connections = set() + one_way_forced_connections = set() if multiworld.door_shuffle[player]: - forced_connections.update(mandatory_connections.copy()) + if multiworld.door_shuffle[player] in ("full", "insanity", "decoupled"): + safari_zone_doors = [door for pair in safari_zone_connections for door in pair] + safari_zone_doors.sort() + order = ["Center", "East", "North", "West"] + multiworld.random.shuffle(order) + usable_doors = ["Safari Zone Gate-N to Safari Zone Center-S"] + for section in order: + section_doors = [door for door in safari_zone_doors if door.startswith(f"Safari Zone {section}")] + connect_door_a = multiworld.random.choice(usable_doors) + connect_door_b = multiworld.random.choice(section_doors) + usable_doors.remove(connect_door_a) + section_doors.remove(connect_door_b) + forced_connections.add((connect_door_a, connect_door_b)) + usable_doors += section_doors + multiworld.random.shuffle(usable_doors) + while usable_doors: + forced_connections.add((usable_doors.pop(), usable_doors.pop())) + else: + forced_connections.update(safari_zone_connections) + usable_safe_rooms = safe_rooms.copy() if multiworld.door_shuffle[player] == "simple": forced_connections.update(simple_mandatory_connections) else: usable_safe_rooms += pokemarts - if self.multiworld.key_items_only[self.player]: + if multiworld.key_items_only[player]: usable_safe_rooms.remove("Viridian Pokemart to Viridian City") - if multiworld.door_shuffle[player] in ("insanity", "decoupled"): - forced_connections.update(insanity_mandatory_connections) + if multiworld.door_shuffle[player] in ("full", "insanity", "decoupled"): + forced_connections.update(full_mandatory_connections) r = multiworld.random.randint(0, 3) if r == 2: forced_connections.add(("Pokemon Mansion 1F-SE to Pokemon Mansion B1F", @@ -1969,6 +2045,9 @@ def create_regions(self): forced_connections.add(("Pokemon Mansion 2F to Pokemon Mansion 3F", multiworld.random.choice(mansion_stair_destinations + mansion_dead_ends + ["Pokemon Mansion B1F to Pokemon Mansion 1F-SE"]))) + if multiworld.door_shuffle[player] == "full": + forced_connections.add(("Pokemon Mansion 1F to Pokemon Mansion 2F", + "Pokemon Mansion 3F to Pokemon Mansion 2F")) elif r == 3: dead_end = multiworld.random.randint(0, 1) forced_connections.add(("Pokemon Mansion 3F-SE to Pokemon Mansion 2F-E", @@ -1987,7 +2066,8 @@ def create_regions(self): multiworld.random.choice(mansion_stair_destinations + ["Pokemon Mansion B1F to Pokemon Mansion 1F-SE"]))) - usable_safe_rooms += insanity_safe_rooms + if multiworld.door_shuffle[player] in ("insanity", "decoupled"): + usable_safe_rooms += insanity_safe_rooms safe_rooms_sample = multiworld.random.sample(usable_safe_rooms, 6) pallet_safe_room = safe_rooms_sample[-1] @@ -1995,16 +2075,28 @@ def create_regions(self): for a, b in zip(multiworld.random.sample(["Pallet Town to Player's House 1F", "Pallet Town to Oak's Lab", "Pallet Town to Rival's House"], 3), ["Oak's Lab to Pallet Town", "Player's House 1F to Pallet Town", pallet_safe_room]): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) + + if multiworld.door_shuffle[player] == "decoupled": + for a, b in zip(["Oak's Lab to Pallet Town", "Player's House 1F to Pallet Town", pallet_safe_room], + multiworld.random.sample(["Pallet Town to Player's House 1F", "Pallet Town to Oak's Lab", + "Pallet Town to Rival's House"], 3)): + one_way_forced_connections.add((a, b)) + for a, b in zip(safari_zone_houses, safe_rooms_sample): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) + if multiworld.door_shuffle[player] == "decoupled": + for a, b in zip(multiworld.random.sample(safe_rooms_sample[:-1], len(safe_rooms_sample) - 1), + safari_zone_houses): + one_way_forced_connections.add((a, b)) + if multiworld.door_shuffle[player] == "simple": # force Indigo Plateau Lobby to vanilla location on simple, otherwise shuffle with Pokemon Centers. for a, b in zip(multiworld.random.sample(pokemon_center_entrances[0:-1], 11), pokemon_centers[0:-1]): forced_connections.add((a, b)) forced_connections.add((pokemon_center_entrances[-1], pokemon_centers[-1])) forced_pokemarts = multiworld.random.sample(pokemart_entrances, 8) - if self.multiworld.key_items_only[self.player]: + if multiworld.key_items_only[player]: forced_pokemarts.sort(key=lambda i: i[0] != "Viridian Pokemart to Viridian City") for a, b in zip(forced_pokemarts, pokemarts): forced_connections.add((a, b)) @@ -2014,15 +2106,19 @@ def create_regions(self): # warping outside an entrance that isn't the Pokemon Center, just always put Pokemon Centers at Pokemon # Center entrances for a, b in zip(multiworld.random.sample(pokemon_center_entrances, 12), pokemon_centers): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) # Ensure a Pokemart is available at the beginning of the game if multiworld.key_items_only[player]: - forced_connections.add((multiworld.random.choice(initial_doors), "Viridian Pokemart to Viridian City")) + one_way_forced_connections.add((multiworld.random.choice(initial_doors), + "Viridian Pokemart to Viridian City")) + elif "Pokemart" not in pallet_safe_room: - forced_connections.add((multiworld.random.choice(initial_doors), multiworld.random.choice( - [mart for mart in pokemarts if mart not in safe_rooms_sample]))) + one_way_forced_connections.add((multiworld.random.choice(initial_doors), multiworld.random.choice( + [mart for mart in pokemarts if mart not in safe_rooms_sample]))) - if multiworld.warp_tile_shuffle[player]: + if multiworld.warp_tile_shuffle[player] == "shuffle" or (multiworld.warp_tile_shuffle[player] == "mixed" + and multiworld.door_shuffle[player] + in ("off", "simple", "interiors")): warps = multiworld.random.sample(silph_co_warps, len(silph_co_warps)) # The only warp tiles never reachable from the stairs/elevators are the two 7F-NW warps (where the rival is) # and the final 11F-W warp. As long as the two 7F-NW warps aren't connected to each other, everything should @@ -2055,13 +2151,38 @@ def create_regions(self): while warps: forced_connections.add((warps.pop(), warps.pop(),)) + dc_destinations = None + if multiworld.door_shuffle[player] == "decoupled": + dc_destinations = entrances.copy() + for pair in one_way_forced_connections: + entrance_a = multiworld.get_entrance(pair[0], player) + entrance_b = multiworld.get_entrance(pair[1], player) + entrance_a.connect(entrance_b) + entrances.remove(entrance_a) + dc_destinations.remove(entrance_b) + else: + forced_connections.update(one_way_forced_connections) + for pair in forced_connections: entrance_a = multiworld.get_entrance(pair[0], player) entrance_b = multiworld.get_entrance(pair[1], player) entrance_a.connect(entrance_b) entrance_b.connect(entrance_a) - entrances.remove(entrance_a) - entrances.remove(entrance_b) + if entrance_a in entrances: + entrances.remove(entrance_a) + elif entrance_a in full_interiors: + full_interiors.remove(entrance_a) + else: + raise DoorShuffleException("Attempted to force connection with entrance not in any entrance pool, likely because it tried to force an entrance to connect twice.") + if entrance_b in entrances: + entrances.remove(entrance_b) + elif entrance_b in full_interiors: + full_interiors.remove(entrance_b) + else: + raise DoorShuffleException("Attempted to force connection with entrance not in any entrance pool, likely because it tried to force an entrance to connect twice.") + if multiworld.door_shuffle[player] == "decoupled": + dc_destinations.remove(entrance_a) + dc_destinations.remove(entrance_b) if multiworld.door_shuffle[player] == "simple": def connect_connecting_interiors(interior_exits, exterior_entrances): @@ -2069,7 +2190,7 @@ def connect_connecting_interiors(interior_exits, exterior_entrances): for a, b in zip(interior, exterior): entrance_a = multiworld.get_entrance(a, player) if b is None: - #entrance_b = multiworld.get_entrance(entrances[0], player) + # entrance_b = multiworld.get_entrance(entrances[0], player) # should just be able to use the entrance_b from the previous link? pass else: @@ -2102,7 +2223,7 @@ def connect_interiors(interior_exits, exterior_entrances): single_entrance_dungeon_entrances = dungeon_entrances.copy() for i in range(2): - if True or not multiworld.random.randint(0, 2): + if not multiworld.random.randint(0, 2): placed_connecting_interior_dungeons.append(multi_purpose_dungeons[i]) interior_dungeon_entrances.append([multi_purpose_dungeon_entrances[i], None]) else: @@ -2185,7 +2306,7 @@ def cerulean_city_problem(): and interiors[0] in connecting_interiors[13:17] # Saffron Gate at Underground Path North South and interiors[13] in connecting_interiors[13:17] # Saffron Gate at Route 5 Saffron Gate and multi_purpose_dungeons[0] == placed_connecting_interior_dungeons[4] # Pokémon Mansion at Rock Tunnel, which is - and (not multiworld.tea[player]) # not traversable backwards + and (not multiworld.tea[player]) # not traversable backwards and multiworld.route_3_condition[player] == "defeat_brock" and multiworld.worlds[player].fly_map != "Cerulean City" and multiworld.worlds[player].town_map_fly_map != "Cerulean City"): @@ -2209,20 +2330,64 @@ def cerulean_city_problem(): entrance_b.connect(entrance_a) elif multiworld.door_shuffle[player]: if multiworld.door_shuffle[player] == "full": + multiworld.random.shuffle(full_interiors) + + def search_for_exit(entrance, region, checked_regions): + checked_regions.add(region) + for exit_candidate in region.exits: + if ((not exit_candidate.connected_region) + and exit_candidate in entrances and exit_candidate is not entrance): + return exit_candidate + for entrance_candidate in region.entrances: + if entrance_candidate.parent_region not in checked_regions: + found_exit = search_for_exit(entrance, entrance_candidate.parent_region, checked_regions) + if found_exit is not None: + return found_exit + return None + + while True: + for entrance_a in full_interiors: + if search_for_exit(entrance_a, entrance_a.parent_region, set()) is None: + for entrance_b in full_interiors: + if search_for_exit(entrance_b, entrance_b.parent_region, set()): + entrance_a.connect(entrance_b) + entrance_b.connect(entrance_a) + # Yes, it removes from full_interiors while iterating through it, but it immediately + # breaks out, from both loops. + full_interiors.remove(entrance_a) + full_interiors.remove(entrance_b) + break + else: + raise DoorShuffleException("No non-dead end interior sections found in Pokemon Red and Blue door shuffle.") + break + else: + break + + loop_out_interiors = [] + multiworld.random.shuffle(entrances) + for entrance in reversed(entrances): + if not outdoor_map(entrance.parent_region.name): + found_exit = search_for_exit(entrance, entrance.parent_region, set()) + if found_exit is None: + continue + loop_out_interiors.append([found_exit, entrance]) + entrances.remove(entrance) + + if len(loop_out_interiors) == 2: + break + + for entrance_a, entrance_b in zip(full_interiors[:len(full_interiors) // 2], + full_interiors[len(full_interiors) // 2:]): + entrance_a.connect(entrance_b) + entrance_b.connect(entrance_a) + + elif multiworld.door_shuffle[player] == "interiors": loop_out_interiors = [[multiworld.get_entrance(e[0], player), multiworld.get_entrance(e[1], player)] for e in multiworld.random.sample(unsafe_connecting_interior_dungeons + safe_connecting_interior_dungeons, 2)] entrances.remove(loop_out_interiors[0][1]) entrances.remove(loop_out_interiors[1][1]) if not multiworld.badgesanity[player]: - badges = [item for item in self.item_pool if "Badge" in item.name] - for badge in badges: - self.item_pool.remove(badge) - badge_locs = [] - for loc in ["Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize", "Vermilion Gym - Lt. Surge Prize", - "Celadon Gym - Erika Prize", "Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize", - "Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize"]: - badge_locs.append(multiworld.get_location(loc, player)) multiworld.random.shuffle(badges) while badges[3].name == "Cascade Badge" and multiworld.badges_needed_for_hm_moves[player]: multiworld.random.shuffle(badges) @@ -2233,7 +2398,7 @@ def cerulean_city_problem(): for item, data in item_table.items(): if (data.id or item in poke_data.pokemon_data) and data.classification == ItemClassification.progression \ and ("Badge" not in item or multiworld.badgesanity[player]): - state.collect(self.create_item(item)) + state.collect(world.create_item(item)) multiworld.random.shuffle(entrances) reachable_entrances = [] @@ -2269,22 +2434,23 @@ def cerulean_city_problem(): "Defeat Viridian Gym Giovanni", ] - event_locations = self.multiworld.get_filled_locations(player) + event_locations = multiworld.get_filled_locations(player) - def adds_reachable_entrances(entrances_copy, item, dead_end_cache): - ret = dead_end_cache.get(item.name) - if (ret != None): - return ret + def adds_reachable_entrances(item): state_copy = state.copy() state_copy.collect(item, True) state.sweep_for_events(locations=event_locations) - ret = len([entrance for entrance in entrances_copy if entrance in reachable_entrances or - entrance.parent_region.can_reach(state_copy)]) > len(reachable_entrances) - dead_end_cache[item.name] = ret - return ret + new_reachable_entrances = len([entrance for entrance in entrances if entrance in reachable_entrances or + entrance.parent_region.can_reach(state_copy)]) + return new_reachable_entrances > len(reachable_entrances) - def dead_end(entrances_copy, e, dead_end_cache): + def dead_end(e): + if e.can_reach(state): + return True + elif multiworld.door_shuffle[player] == "decoupled": + # Any unreachable exit in decoupled is not a dead end + return False region = e.parent_region check_warps = set() checked_regions = {region} @@ -2292,93 +2458,105 @@ def dead_end(entrances_copy, e, dead_end_cache): check_warps.remove(e) for location in region.locations: if location.item and location.item.name in relevant_events and \ - adds_reachable_entrances(entrances_copy, location.item, dead_end_cache): + adds_reachable_entrances(location.item): return False while check_warps: warp = check_warps.pop() warp = warp if warp not in reachable_entrances: - if "Rock Tunnel" not in warp.name or logic.rock_tunnel(state, player): - # confirm warp is in entrances list to ensure it's not a loop-out interior - if warp.connected_region is None and warp in entrances_copy: - return False - elif (isinstance(warp, PokemonRBWarp) and ("Rock Tunnel" not in warp.name or - logic.rock_tunnel(state, player))) or warp.access_rule(state): - if warp.connected_region and warp.connected_region not in checked_regions: - checked_regions.add(warp.connected_region) - check_warps.update(warp.connected_region.exits) - for location in warp.connected_region.locations: - if (location.item and location.item.name in relevant_events and - adds_reachable_entrances(entrances_copy, location.item, dead_end_cache)): - return False + # confirm warp is in entrances list to ensure it's not a loop-out interior + if warp.connected_region is None and warp in entrances: + return False + elif isinstance(warp, PokemonRBWarp) or warp.access_rule(state): + if warp.connected_region and warp.connected_region not in checked_regions: + checked_regions.add(warp.connected_region) + check_warps.update(warp.connected_region.exits) + for location in warp.connected_region.locations: + if (location.item and location.item.name in relevant_events and + adds_reachable_entrances(location.item)): + return False return True starting_entrances = len(entrances) - dc_connected = [] - rock_tunnel_entrances = [entrance for entrance in entrances if "Rock Tunnel" in entrance.name] - entrances = [entrance for entrance in entrances if entrance not in rock_tunnel_entrances] + while entrances: state.update_reachable_regions(player) state.sweep_for_events(locations=event_locations) - if rock_tunnel_entrances and logic.rock_tunnel(state, player): - entrances += rock_tunnel_entrances - rock_tunnel_entrances = None + multiworld.random.shuffle(entrances) + + if multiworld.door_shuffle[player] == "decoupled": + multiworld.random.shuffle(dc_destinations) + else: + entrances.sort(key=lambda e: e.name not in entrance_only) reachable_entrances = [entrance for entrance in entrances if entrance in reachable_entrances or entrance.parent_region.can_reach(state)] - assert reachable_entrances, \ - "Ran out of reachable entrances in Pokemon Red and Blue door shuffle" - multiworld.random.shuffle(entrances) - if multiworld.door_shuffle[player] == "decoupled" and len(entrances) == 1: - entrances += dc_connected - entrances[-1].connect(entrances[0]) - while len(entrances) > 1: - entrances.pop(0).connect(entrances[0]) - break - if multiworld.door_shuffle[player] == "full" or len(entrances) != len(reachable_entrances): - entrances.sort(key=lambda e: e.name not in entrance_only) - dead_end_cache = {} + entrances.sort(key=lambda e: e in reachable_entrances) + + if not reachable_entrances: + raise DoorShuffleException("Ran out of reachable entrances in Pokemon Red and Blue door shuffle") + + entrance_a = reachable_entrances.pop(0) + entrances.remove(entrance_a) + + is_outdoor_map = outdoor_map(entrance_a.parent_region.name) + + if multiworld.door_shuffle[player] in ("interiors", "full") or len(entrances) != len(reachable_entrances): + + find_dead_end = False + if (len(reachable_entrances) > + (1 if multiworld.door_shuffle[player] in ("insanity", "decoupled") else 8) and len(entrances) + <= (starting_entrances - 3)): + find_dead_end = True + + if (multiworld.door_shuffle[player] in ("interiors", "full") and len(entrances) < 48 + and not is_outdoor_map): + # Try to prevent a situation where the only remaining outdoor entrances are ones that cannot be + # reached except by connecting directly to it. + entrances.sort(key=lambda e: e.name not in unreachable_outdoor_entrances) + if entrances[0].name in unreachable_outdoor_entrances and len([entrance for entrance + in reachable_entrances if not outdoor_map(entrance.parent_region.name)]) > 1: + find_dead_end = True - # entrances list is empty while it's being sorted, must pass a copy to iterate through - entrances_copy = entrances.copy() if multiworld.door_shuffle[player] == "decoupled": - entrances.sort(key=lambda e: 1 if e.connected_region is not None else 2 if e not in - reachable_entrances else 0) - assert entrances[0].connected_region is None,\ - "Ran out of valid reachable entrances in Pokemon Red and Blue door shuffle" - elif len(reachable_entrances) > (1 if multiworld.door_shuffle[player] == "insanity" else 8) and len( - entrances) <= (starting_entrances - 3): - entrances.sort(key=lambda e: 0 if e in reachable_entrances else 2 if - dead_end(entrances_copy, e, dead_end_cache) else 1) + destinations = dc_destinations + elif multiworld.door_shuffle[player] in ("interiors", "full"): + destinations = [entrance for entrance in entrances if outdoor_map(entrance.parent_region.name) is + not is_outdoor_map] + if not destinations: + raise DoorShuffleException("Ran out of connectable destinations in Pokemon Red and Blue door shuffle") else: - entrances.sort(key=lambda e: 0 if e in reachable_entrances else 1 if - dead_end(entrances_copy, e, dead_end_cache) else 2) - if multiworld.door_shuffle[player] == "full": - outdoor = outdoor_map(entrances[0].parent_region.name) - if len(entrances) < 48 and not outdoor: - # Prevent a situation where the only remaining outdoor entrances are ones that cannot be reached - # except by connecting directly to it. - entrances.sort(key=lambda e: e.name in unreachable_outdoor_entrances) - - entrances.sort(key=lambda e: outdoor_map(e.parent_region.name) != outdoor) - assert entrances[0] in reachable_entrances, \ - "Ran out of valid reachable entrances in Pokemon Red and Blue door shuffle" - if (multiworld.door_shuffle[player] == "decoupled" and len(reachable_entrances) > 8 and len(entrances) - <= (starting_entrances - 3)): - entrance_b = entrances.pop(1) + destinations = entrances + + destinations.sort(key=lambda e: e == entrance_a) + for entrance in destinations: + if (dead_end(entrance) is find_dead_end and (multiworld.door_shuffle[player] != "decoupled" + or entrance.parent_region.name.split("-")[0] != + entrance_a.parent_region.name.split("-")[0])): + entrance_b = entrance + destinations.remove(entrance) + break + else: + entrance_b = destinations.pop(0) + + if multiworld.door_shuffle[player] in ("interiors", "full"): + # on Interiors/Full, the destinations variable does not point to the entrances list, so we need to + # remove from that list here. + entrances.remove(entrance_b) else: - entrance_b = entrances.pop() - entrance_a = entrances.pop(0) + # Everything is reachable. Just start connecting the rest of the doors at random. + if multiworld.door_shuffle[player] == "decoupled": + entrance_b = dc_destinations.pop(0) + else: + entrance_b = entrances.pop(0) + entrance_a.connect(entrance_b) - if multiworld.door_shuffle[player] == "decoupled": - entrances.append(entrance_b) - dc_connected.append(entrance_a) - else: + if multiworld.door_shuffle[player] != "decoupled": entrance_b.connect(entrance_a) - if multiworld.door_shuffle[player] == "full": + if multiworld.door_shuffle[player] in ("interiors", "full"): for pair in loop_out_interiors: pair[1].connected_region = pair[0].connected_region pair[1].parent_region.entrances.append(pair[0]) @@ -2443,11 +2621,18 @@ def connect(self, entrance): def access_rule(self, state): if self.connected_region is None: return False - if "Rock Tunnel" in self.parent_region.name or "Rock Tunnel" in self.connected_region.name: - return logic.rock_tunnel(state, self.player) + if "Elevator" in self.parent_region.name and ( + (state.multiworld.all_elevators_locked[self.player] + or "Rocket Hideout" in self.parent_region.name) + and not state.has("Lift Key", self.player)): + return False return True +class DoorShuffleException(Exception): + pass + + class PokemonRBRegion(Region): def __init__(self, name, player, multiworld): super().__init__(name, player, multiworld) diff --git a/worlds/pokemon_rb/rom.py b/worlds/pokemon_rb/rom.py index 81ab6648dd19..b6c1221a29f4 100644 --- a/worlds/pokemon_rb/rom.py +++ b/worlds/pokemon_rb/rom.py @@ -9,9 +9,10 @@ from .pokemon import set_mon_palettes from .rock_tunnel import randomize_rock_tunnel from .rom_addresses import rom_addresses -from .regions import PokemonRBWarp, map_ids +from .regions import PokemonRBWarp, map_ids, town_map_coords from . import poke_data + def write_quizzes(self, data, random): def get_quiz(q, a): @@ -204,19 +205,21 @@ def generate_output(self, output_directory: str): basemd5 = hashlib.md5() basemd5.update(data) - lab_loc = self.multiworld.get_entrance("Oak's Lab to Pallet Town", self.player).target + pallet_connections = {entrance: self.multiworld.get_entrance(f"Pallet Town to {entrance}", + self.player).connected_region.name for + entrance in ["Player's House 1F", "Oak's Lab", + "Rival's House"]} paths = None - if lab_loc == 0: # Player's House + if pallet_connections["Player's House 1F"] == "Oak's Lab": paths = ((0x00, 4, 0x80, 5, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x20, 5, 0x80, 5, 0xFF)) - elif lab_loc == 1: # Rival's House + elif pallet_connections["Rival's House"] == "Oak's Lab": paths = ((0x00, 4, 0xC0, 3, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x10, 3, 0x80, 5, 0xFF)) if paths: write_bytes(data, paths[0], rom_addresses["Path_Pallet_Oak"]) write_bytes(data, paths[1], rom_addresses["Path_Pallet_Player"]) - home_loc = self.multiworld.get_entrance("Player's House 1F to Pallet Town", self.player).target - if home_loc == 1: # Rival's House + if pallet_connections["Rival's House"] == "Player's House 1F": write_bytes(data, [0x2F, 0xC7, 0x06, 0x0D, 0x00, 0x01], rom_addresses["Pallet_Fly_Coords"]) - elif home_loc == 2: # Oak's Lab + elif pallet_connections["Oak's Lab"] == "Player's House 1F": write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"]) for region in self.multiworld.get_regions(self.player): @@ -238,6 +241,14 @@ def generate_output(self, output_directory: str): data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i] data[address + 1] = map_ids[connected_map_name] + if self.multiworld.door_shuffle[self.player] == "simple": + for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values(): + destination = self.multiworld.get_entrance(entrance, self.player).connected_region.name + (_, x, y, _, _, map_order_entry) = town_map_coords[destination] + for map_coord_entry in map_coords_entries: + data[rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1] = (y << 4) | x + data[rom_addresses["Town_Map_Order"] + map_order_entry] = map_ids[map_name] + if not self.multiworld.key_items_only[self.player]: for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM", "Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM", diff --git a/worlds/pokemon_rb/rom_addresses.py b/worlds/pokemon_rb/rom_addresses.py index ffb89a4dfcdf..e5c073971d5d 100644 --- a/worlds/pokemon_rb/rom_addresses.py +++ b/worlds/pokemon_rb/rom_addresses.py @@ -1,10 +1,10 @@ rom_addresses = { "Option_Encounter_Minimum_Steps": 0x3c1, - "Option_Pitch_Black_Rock_Tunnel": 0x75c, - "Option_Blind_Trainers": 0x30c7, - "Option_Trainersanity1": 0x3157, - "Option_Split_Card_Key": 0x3e10, - "Option_Fix_Combat_Bugs": 0x3e11, + "Option_Pitch_Black_Rock_Tunnel": 0x76a, + "Option_Blind_Trainers": 0x30d5, + "Option_Trainersanity1": 0x3165, + "Option_Split_Card_Key": 0x3e1e, + "Option_Fix_Combat_Bugs": 0x3e1f, "Option_Lose_Money": 0x40d4, "Base_Stats_Mew": 0x4260, "Title_Mon_First": 0x4373, @@ -131,49 +131,49 @@ "Starter2_K": 0x19611, "Starter3_K": 0x19619, "Event_Rocket_Thief": 0x19733, - "Option_Cerulean_Cave_Badges": 0x19857, - "Option_Cerulean_Cave_Key_Items": 0x1985e, - "Text_Cerulean_Cave_Badges": 0x198c3, - "Text_Cerulean_Cave_Key_Items": 0x198d1, - "Event_Stranded_Man": 0x19b28, - "Event_Rivals_Sister": 0x19cfb, - "Warps_BluesHouse": 0x19d51, - "Warps_VermilionTradeHouse": 0x19da8, - "Require_Pokedex_D": 0x19e3f, - "Option_Elite_Four_Key_Items": 0x19e89, - "Option_Elite_Four_Pokedex": 0x19e90, - "Option_Elite_Four_Badges": 0x19e97, - "Text_Elite_Four_Badges": 0x19f33, - "Text_Elite_Four_Key_Items": 0x19f3d, - "Text_Elite_Four_Pokedex": 0x19f50, - "Shop10": 0x1a004, - "Warps_IndigoPlateauLobby": 0x1a030, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a158, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a166, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a174, - "Event_SKC4F": 0x1a187, - "Warps_SilphCo4F": 0x1a209, - "Missable_Silph_Co_4F_Item_1": 0x1a249, - "Missable_Silph_Co_4F_Item_2": 0x1a250, - "Missable_Silph_Co_4F_Item_3": 0x1a257, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3af, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3bd, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3cb, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3d9, - "Event_SKC5F": 0x1a3ec, - "Warps_SilphCo5F": 0x1a496, - "Missable_Silph_Co_5F_Item_1": 0x1a4de, - "Missable_Silph_Co_5F_Item_2": 0x1a4e5, - "Missable_Silph_Co_5F_Item_3": 0x1a4ec, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a61c, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a62a, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a638, - "Event_SKC6F": 0x1a659, - "Warps_SilphCo6F": 0x1a737, - "Missable_Silph_Co_6F_Item_1": 0x1a787, - "Missable_Silph_Co_6F_Item_2": 0x1a78e, - "Path_Pallet_Oak": 0x1a914, - "Path_Pallet_Player": 0x1a921, + "Option_Cerulean_Cave_Badges": 0x19861, + "Option_Cerulean_Cave_Key_Items": 0x19868, + "Text_Cerulean_Cave_Badges": 0x198d7, + "Text_Cerulean_Cave_Key_Items": 0x198e5, + "Event_Stranded_Man": 0x19b3c, + "Event_Rivals_Sister": 0x19d0f, + "Warps_BluesHouse": 0x19d65, + "Warps_VermilionTradeHouse": 0x19dbc, + "Require_Pokedex_D": 0x19e53, + "Option_Elite_Four_Key_Items": 0x19e9d, + "Option_Elite_Four_Pokedex": 0x19ea4, + "Option_Elite_Four_Badges": 0x19eab, + "Text_Elite_Four_Badges": 0x19f47, + "Text_Elite_Four_Key_Items": 0x19f51, + "Text_Elite_Four_Pokedex": 0x19f64, + "Shop10": 0x1a018, + "Warps_IndigoPlateauLobby": 0x1a044, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a16c, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a17a, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a188, + "Event_SKC4F": 0x1a19b, + "Warps_SilphCo4F": 0x1a21d, + "Missable_Silph_Co_4F_Item_1": 0x1a25d, + "Missable_Silph_Co_4F_Item_2": 0x1a264, + "Missable_Silph_Co_4F_Item_3": 0x1a26b, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3c3, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3d1, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3df, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3ed, + "Event_SKC5F": 0x1a400, + "Warps_SilphCo5F": 0x1a4aa, + "Missable_Silph_Co_5F_Item_1": 0x1a4f2, + "Missable_Silph_Co_5F_Item_2": 0x1a4f9, + "Missable_Silph_Co_5F_Item_3": 0x1a500, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a630, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a63e, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a64c, + "Event_SKC6F": 0x1a66d, + "Warps_SilphCo6F": 0x1a74b, + "Missable_Silph_Co_6F_Item_1": 0x1a79b, + "Missable_Silph_Co_6F_Item_2": 0x1a7a2, + "Path_Pallet_Oak": 0x1a928, + "Path_Pallet_Player": 0x1a935, "Warps_CinnabarIsland": 0x1c026, "Warps_Route1": 0x1c0e9, "Option_Extra_Key_Items_B": 0x1ca46, @@ -1074,112 +1074,112 @@ "Missable_Route_25_Item": 0x5080b, "Warps_IndigoPlateau": 0x5093a, "Warps_SaffronCity": 0x509e0, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_0_ITEM": 0x50d63, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_1_ITEM": 0x50d71, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_2_ITEM": 0x50d7f, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_3_ITEM": 0x50d8d, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_4_ITEM": 0x50d9b, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_5_ITEM": 0x50da9, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_6_ITEM": 0x50db7, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_7_ITEM": 0x50dc5, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_8_ITEM": 0x50dd3, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_9_ITEM": 0x50de1, - "Starter2_B": 0x50ffe, - "Starter3_B": 0x51000, - "Starter1_B": 0x51002, - "Starter2_A": 0x5111d, - "Starter3_A": 0x5111f, - "Starter1_A": 0x51121, - "Option_Route23_Badges": 0x5126e, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_0_ITEM": 0x51384, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_1_ITEM": 0x51392, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_2_ITEM": 0x513a0, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_3_ITEM": 0x513ae, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_4_ITEM": 0x513bc, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_5_ITEM": 0x513ca, - "Event_Nugget_Bridge": 0x513e1, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_0_ITEM": 0x51569, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_1_ITEM": 0x51577, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_2_ITEM": 0x51585, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_3_ITEM": 0x51593, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_4_ITEM": 0x515a1, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_5_ITEM": 0x515af, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_6_ITEM": 0x515bd, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_7_ITEM": 0x515cb, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_8_ITEM": 0x515d9, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0_ITEM": 0x51772, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1_ITEM": 0x51780, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2_ITEM": 0x5178e, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3_ITEM": 0x5179c, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4_ITEM": 0x517aa, - "Trainersanity_EVENT_BEAT_MOLTRES_ITEM": 0x517b8, - "Warps_VictoryRoad2F": 0x51855, - "Static_Encounter_Moltres": 0x5189f, - "Missable_Victory_Road_2F_Item_1": 0x518a7, - "Missable_Victory_Road_2F_Item_2": 0x518ae, - "Missable_Victory_Road_2F_Item_3": 0x518b5, - "Missable_Victory_Road_2F_Item_4": 0x518bc, - "Warps_MtMoonB1F": 0x5198d, - "Starter2_L": 0x51beb, - "Starter3_L": 0x51bf3, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_0_ITEM": 0x51ca4, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_1_ITEM": 0x51cb2, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_2_ITEM": 0x51cc0, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_3_ITEM": 0x51cce, - "Gift_Lapras": 0x51cef, - "Event_SKC7F": 0x51d7a, - "Warps_SilphCo7F": 0x51e49, - "Missable_Silph_Co_7F_Item_1": 0x51ea5, - "Missable_Silph_Co_7F_Item_2": 0x51eac, - "Trainersanity_EVENT_BEAT_MANSION_2_TRAINER_0_ITEM": 0x51fd2, - "Warps_PokemonMansion2F": 0x52045, - "Missable_Pokemon_Mansion_2F_Item": 0x52063, - "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_0_ITEM": 0x52213, - "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_1_ITEM": 0x52221, - "Warps_PokemonMansion3F": 0x5225e, - "Missable_Pokemon_Mansion_3F_Item_1": 0x52280, - "Missable_Pokemon_Mansion_3F_Item_2": 0x52287, - "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_0_ITEM": 0x523c9, - "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_1_ITEM": 0x523d7, - "Warps_PokemonMansionB1F": 0x52414, - "Missable_Pokemon_Mansion_B1F_Item_1": 0x5242e, - "Missable_Pokemon_Mansion_B1F_Item_2": 0x52435, - "Missable_Pokemon_Mansion_B1F_Item_3": 0x5243c, - "Missable_Pokemon_Mansion_B1F_Item_4": 0x52443, - "Missable_Pokemon_Mansion_B1F_Item_5": 0x52450, - "Option_Safari_Zone_Battle_Type": 0x52565, - "Prize_Mon_A2": 0x527ef, - "Prize_Mon_B2": 0x527f0, - "Prize_Mon_C2": 0x527f1, - "Prize_Mon_D2": 0x527fa, - "Prize_Mon_E2": 0x527fb, - "Prize_Mon_F2": 0x527fc, - "Prize_Item_A": 0x52805, - "Prize_Item_B": 0x52806, - "Prize_Item_C": 0x52807, - "Prize_Mon_A": 0x5293c, - "Prize_Mon_B": 0x5293e, - "Prize_Mon_C": 0x52940, - "Prize_Mon_D": 0x52942, - "Prize_Mon_E": 0x52944, - "Prize_Mon_F": 0x52946, - "Start_Inventory": 0x52a7b, - "Map_Fly_Location": 0x52c75, - "Reset_A": 0x52d21, - "Reset_B": 0x52d4d, - "Reset_C": 0x52d79, - "Reset_D": 0x52da5, - "Reset_E": 0x52dd1, - "Reset_F": 0x52dfd, - "Reset_G": 0x52e29, - "Reset_H": 0x52e55, - "Reset_I": 0x52e81, - "Reset_J": 0x52ead, - "Reset_K": 0x52ed9, - "Reset_L": 0x52f05, - "Reset_M": 0x52f31, - "Reset_N": 0x52f5d, - "Reset_O": 0x52f89, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_0_ITEM": 0x50d8b, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_1_ITEM": 0x50d99, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_2_ITEM": 0x50da7, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_3_ITEM": 0x50db5, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_4_ITEM": 0x50dc3, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_5_ITEM": 0x50dd1, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_6_ITEM": 0x50ddf, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_7_ITEM": 0x50ded, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_8_ITEM": 0x50dfb, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_9_ITEM": 0x50e09, + "Starter2_B": 0x51026, + "Starter3_B": 0x51028, + "Starter1_B": 0x5102a, + "Starter2_A": 0x51145, + "Starter3_A": 0x51147, + "Starter1_A": 0x51149, + "Option_Route23_Badges": 0x51296, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_0_ITEM": 0x513ac, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_1_ITEM": 0x513ba, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_2_ITEM": 0x513c8, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_3_ITEM": 0x513d6, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_4_ITEM": 0x513e4, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_5_ITEM": 0x513f2, + "Event_Nugget_Bridge": 0x51409, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_0_ITEM": 0x51591, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_1_ITEM": 0x5159f, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_2_ITEM": 0x515ad, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_3_ITEM": 0x515bb, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_4_ITEM": 0x515c9, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_5_ITEM": 0x515d7, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_6_ITEM": 0x515e5, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_7_ITEM": 0x515f3, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_8_ITEM": 0x51601, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0_ITEM": 0x5179a, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1_ITEM": 0x517a8, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2_ITEM": 0x517b6, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3_ITEM": 0x517c4, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4_ITEM": 0x517d2, + "Trainersanity_EVENT_BEAT_MOLTRES_ITEM": 0x517e0, + "Warps_VictoryRoad2F": 0x5187d, + "Static_Encounter_Moltres": 0x518c7, + "Missable_Victory_Road_2F_Item_1": 0x518cf, + "Missable_Victory_Road_2F_Item_2": 0x518d6, + "Missable_Victory_Road_2F_Item_3": 0x518dd, + "Missable_Victory_Road_2F_Item_4": 0x518e4, + "Warps_MtMoonB1F": 0x519b5, + "Starter2_L": 0x51c13, + "Starter3_L": 0x51c1b, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_0_ITEM": 0x51ccc, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_1_ITEM": 0x51cda, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_2_ITEM": 0x51ce8, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_3_ITEM": 0x51cf6, + "Gift_Lapras": 0x51d17, + "Event_SKC7F": 0x51da2, + "Warps_SilphCo7F": 0x51e71, + "Missable_Silph_Co_7F_Item_1": 0x51ecd, + "Missable_Silph_Co_7F_Item_2": 0x51ed4, + "Trainersanity_EVENT_BEAT_MANSION_2_TRAINER_0_ITEM": 0x51ffa, + "Warps_PokemonMansion2F": 0x5206d, + "Missable_Pokemon_Mansion_2F_Item": 0x5208b, + "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_0_ITEM": 0x5223b, + "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_1_ITEM": 0x52249, + "Warps_PokemonMansion3F": 0x52286, + "Missable_Pokemon_Mansion_3F_Item_1": 0x522a8, + "Missable_Pokemon_Mansion_3F_Item_2": 0x522af, + "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_0_ITEM": 0x523f1, + "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_1_ITEM": 0x523ff, + "Warps_PokemonMansionB1F": 0x5243c, + "Missable_Pokemon_Mansion_B1F_Item_1": 0x52456, + "Missable_Pokemon_Mansion_B1F_Item_2": 0x5245d, + "Missable_Pokemon_Mansion_B1F_Item_3": 0x52464, + "Missable_Pokemon_Mansion_B1F_Item_4": 0x5246b, + "Missable_Pokemon_Mansion_B1F_Item_5": 0x52478, + "Option_Safari_Zone_Battle_Type": 0x5258d, + "Prize_Mon_A2": 0x52817, + "Prize_Mon_B2": 0x52818, + "Prize_Mon_C2": 0x52819, + "Prize_Mon_D2": 0x52822, + "Prize_Mon_E2": 0x52823, + "Prize_Mon_F2": 0x52824, + "Prize_Item_A": 0x5282d, + "Prize_Item_B": 0x5282e, + "Prize_Item_C": 0x5282f, + "Prize_Mon_A": 0x52964, + "Prize_Mon_B": 0x52966, + "Prize_Mon_C": 0x52968, + "Prize_Mon_D": 0x5296a, + "Prize_Mon_E": 0x5296c, + "Prize_Mon_F": 0x5296e, + "Start_Inventory": 0x52aa3, + "Map_Fly_Location": 0x52c9d, + "Reset_A": 0x52d49, + "Reset_B": 0x52d75, + "Reset_C": 0x52da1, + "Reset_D": 0x52dcd, + "Reset_E": 0x52df9, + "Reset_F": 0x52e25, + "Reset_G": 0x52e51, + "Reset_H": 0x52e7d, + "Reset_I": 0x52ea9, + "Reset_J": 0x52ed5, + "Reset_K": 0x52f01, + "Reset_L": 0x52f2d, + "Reset_M": 0x52f59, + "Reset_N": 0x52f85, + "Reset_O": 0x52fb1, "Warps_Route2": 0x54026, "Missable_Route_2_Item_1": 0x5404a, "Missable_Route_2_Item_2": 0x54051, @@ -1539,16 +1539,18 @@ "Event_SKC11F": 0x623bd, "Warps_SilphCo11F": 0x62446, "Ghost_Battle4": 0x708e1, - "Trade_Terry": 0x71b77, - "Trade_Marcel": 0x71b85, - "Trade_Sailor": 0x71ba1, - "Trade_Dux": 0x71baf, - "Trade_Marc": 0x71bbd, - "Trade_Lola": 0x71bcb, - "Trade_Doris": 0x71bd9, - "Trade_Crinkles": 0x71be7, - "Trade_Spot": 0x71bf5, - "Mon_Palettes": 0x725d3, + "Town_Map_Order": 0x70f0f, + "Town_Map_Coords": 0x71381, + "Trade_Terry": 0x71b7a, + "Trade_Marcel": 0x71b88, + "Trade_Sailor": 0x71ba4, + "Trade_Dux": 0x71bb2, + "Trade_Marc": 0x71bc0, + "Trade_Lola": 0x71bce, + "Trade_Doris": 0x71bdc, + "Trade_Crinkles": 0x71bea, + "Trade_Spot": 0x71bf8, + "Mon_Palettes": 0x725d6, "Badge_Viridian_Gym": 0x749d9, "Event_Viridian_Gym": 0x749ed, "Trainersanity_EVENT_BEAT_VIRIDIAN_GYM_TRAINER_0_ITEM": 0x74a48, From db30a0116e66abdef37d0c83fadba8a194c4f526 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Tue, 5 Mar 2024 18:55:56 -0500 Subject: [PATCH 58/66] Celeste 64: Implement New Game (#2798) Co-authored-by: chandler05 <66492208+chandler05@users.noreply.github.com> Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com> Co-authored-by: Zach Parks --- README.md | 1 + docs/CODEOWNERS | 3 + worlds/celeste64/Items.py | 58 ++++++++++ worlds/celeste64/Locations.py | 142 +++++++++++++++++++++++++ worlds/celeste64/Names/ItemName.py | 11 ++ worlds/celeste64/Names/LocationName.py | 31 ++++++ worlds/celeste64/Names/__init__.py | 0 worlds/celeste64/Options.py | 25 +++++ worlds/celeste64/Regions.py | 11 ++ worlds/celeste64/Rules.py | 104 ++++++++++++++++++ worlds/celeste64/__init__.py | 92 ++++++++++++++++ worlds/celeste64/docs/en_Celeste 64.md | 24 +++++ worlds/celeste64/docs/guide_en.md | 32 ++++++ 13 files changed, 534 insertions(+) create mode 100644 worlds/celeste64/Items.py create mode 100644 worlds/celeste64/Locations.py create mode 100644 worlds/celeste64/Names/ItemName.py create mode 100644 worlds/celeste64/Names/LocationName.py create mode 100644 worlds/celeste64/Names/__init__.py create mode 100644 worlds/celeste64/Options.py create mode 100644 worlds/celeste64/Regions.py create mode 100644 worlds/celeste64/Rules.py create mode 100644 worlds/celeste64/__init__.py create mode 100644 worlds/celeste64/docs/en_Celeste 64.md create mode 100644 worlds/celeste64/docs/guide_en.md diff --git a/README.md b/README.md index 4a3c53548c38..3c3c41475bab 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Currently, the following games are supported: * Final Fantasy Mystic Quest * TUNIC * Kirby's Dream Land 3 +* Celeste 64 For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 6ec3802edea6..d6730b7308ae 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -28,6 +28,9 @@ # Bumper Stickers /worlds/bumpstik/ @FelicitusNeko +# Celeste 64 +/worlds/celeste64/ @PoryGone + # ChecksFinder /worlds/checksfinder/ @jonloveslegos diff --git a/worlds/celeste64/Items.py b/worlds/celeste64/Items.py new file mode 100644 index 000000000000..94db0e8ef4d2 --- /dev/null +++ b/worlds/celeste64/Items.py @@ -0,0 +1,58 @@ +from typing import Dict, NamedTuple, Optional + +from BaseClasses import Item, ItemClassification +from .Names import ItemName + + +celeste_64_base_id: int = 0xCA0000 + + +class Celeste64Item(Item): + game = "Celeste 64" + + +class Celeste64ItemData(NamedTuple): + code: Optional[int] = None + type: ItemClassification = ItemClassification.filler + + +item_data_table: Dict[str, Celeste64ItemData] = { + ItemName.strawberry: Celeste64ItemData( + code = celeste_64_base_id + 0, + type=ItemClassification.progression_skip_balancing, + ), + ItemName.dash_refill: Celeste64ItemData( + code = celeste_64_base_id + 1, + type=ItemClassification.progression, + ), + ItemName.double_dash_refill: Celeste64ItemData( + code = celeste_64_base_id + 2, + type=ItemClassification.progression, + ), + ItemName.feather: Celeste64ItemData( + code = celeste_64_base_id + 3, + type=ItemClassification.progression, + ), + ItemName.coin: Celeste64ItemData( + code = celeste_64_base_id + 4, + type=ItemClassification.progression, + ), + ItemName.cassette: Celeste64ItemData( + code = celeste_64_base_id + 5, + type=ItemClassification.progression, + ), + ItemName.traffic_block: Celeste64ItemData( + code = celeste_64_base_id + 6, + type=ItemClassification.progression, + ), + ItemName.spring: Celeste64ItemData( + code = celeste_64_base_id + 7, + type=ItemClassification.progression, + ), + ItemName.breakables: Celeste64ItemData( + code = celeste_64_base_id + 8, + type=ItemClassification.progression, + ) +} + +item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None} diff --git a/worlds/celeste64/Locations.py b/worlds/celeste64/Locations.py new file mode 100644 index 000000000000..92ca425f8383 --- /dev/null +++ b/worlds/celeste64/Locations.py @@ -0,0 +1,142 @@ +from typing import Dict, NamedTuple, Optional + +from BaseClasses import Location +from .Names import LocationName + + +celeste_64_base_id: int = 0xCA0000 + + +class Celeste64Location(Location): + game = "Celeste 64" + + +class Celeste64LocationData(NamedTuple): + region: str + address: Optional[int] = None + + +location_data_table: Dict[str, Celeste64LocationData] = { + LocationName.strawberry_1 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 0, + ), + LocationName.strawberry_2 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 1, + ), + LocationName.strawberry_3 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 2, + ), + LocationName.strawberry_4 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 3, + ), + LocationName.strawberry_5 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 4, + ), + LocationName.strawberry_6 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 5, + ), + LocationName.strawberry_7 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 6, + ), + LocationName.strawberry_8 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 7, + ), + LocationName.strawberry_9 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 8, + ), + LocationName.strawberry_10 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 9, + ), + LocationName.strawberry_11 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 10, + ), + LocationName.strawberry_12 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 11, + ), + LocationName.strawberry_13 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 12, + ), + LocationName.strawberry_14 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 13, + ), + LocationName.strawberry_15 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 14, + ), + LocationName.strawberry_16 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 15, + ), + LocationName.strawberry_17 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 16, + ), + LocationName.strawberry_18 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 17, + ), + LocationName.strawberry_19 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 18, + ), + LocationName.strawberry_20 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 19, + ), + LocationName.strawberry_21 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 20, + ), + LocationName.strawberry_22 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 21, + ), + LocationName.strawberry_23 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 22, + ), + LocationName.strawberry_24 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 23, + ), + LocationName.strawberry_25 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 24, + ), + LocationName.strawberry_26 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 25, + ), + LocationName.strawberry_27 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 26, + ), + LocationName.strawberry_28 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 27, + ), + LocationName.strawberry_29 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 28, + ), + LocationName.strawberry_30 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 29, + ) +} + +location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None} diff --git a/worlds/celeste64/Names/ItemName.py b/worlds/celeste64/Names/ItemName.py new file mode 100644 index 000000000000..5e4daf8e4f2a --- /dev/null +++ b/worlds/celeste64/Names/ItemName.py @@ -0,0 +1,11 @@ +strawberry = "Strawberry" + +dash_refill = "Dash Refills" +double_dash_refill = "Double Dash Refills" +feather = "Feathers" +coin = "Coins" +cassette = "Cassettes" + +traffic_block = "Traffic Blocks" +spring = "Springs" +breakables = "Breakable Blocks" diff --git a/worlds/celeste64/Names/LocationName.py b/worlds/celeste64/Names/LocationName.py new file mode 100644 index 000000000000..a9902f70f7ab --- /dev/null +++ b/worlds/celeste64/Names/LocationName.py @@ -0,0 +1,31 @@ +# Strawberry Locations +strawberry_1 = "First Strawberry" +strawberry_2 = "Floating Blocks Strawberry" +strawberry_3 = "South-East Tower Top Strawberry" +strawberry_4 = "Theo Strawberry" +strawberry_5 = "Fall Through Spike Floor Strawberry" +strawberry_6 = "Troll Strawberry" +strawberry_7 = "Falling Blocks Strawberry" +strawberry_8 = "Traffic Block Strawberry" +strawberry_9 = "South-West Dash Refills Strawberry" +strawberry_10 = "South-East Tower Side Strawberry" +strawberry_11 = "Girders Strawberry" +strawberry_12 = "North-East Tower Bottom Strawberry" +strawberry_13 = "Breakable Blocks Strawberry" +strawberry_14 = "Feather Maze Strawberry" +strawberry_15 = "Feather Chain Strawberry" +strawberry_16 = "Feather Hidden Strawberry" +strawberry_17 = "Double Dash Puzzle Strawberry" +strawberry_18 = "Double Dash Spike Climb Strawberry" +strawberry_19 = "Double Dash Spring Strawberry" +strawberry_20 = "North-East Tower Breakable Bottom Strawberry" +strawberry_21 = "Theo Tower Lower Cassette Strawberry" +strawberry_22 = "Theo Tower Upper Cassette Strawberry" +strawberry_23 = "South End of Bridge Cassette Strawberry" +strawberry_24 = "You Are Ready Cassette Strawberry" +strawberry_25 = "Cassette Hidden in the House Strawberry" +strawberry_26 = "North End of Bridge Cassette Strawberry" +strawberry_27 = "Distant Feather Cassette Strawberry" +strawberry_28 = "Feather Arches Cassette Strawberry" +strawberry_29 = "North-East Tower Cassette Strawberry" +strawberry_30 = "Badeline Cassette Strawberry" diff --git a/worlds/celeste64/Names/__init__.py b/worlds/celeste64/Names/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/celeste64/Options.py b/worlds/celeste64/Options.py new file mode 100644 index 000000000000..f94fbb02931f --- /dev/null +++ b/worlds/celeste64/Options.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass + +from Options import Range, DeathLink, PerGameCommonOptions + + +class StrawberriesRequired(Range): + """How many Strawberries you must receive to finish""" + display_name = "Strawberries Required" + range_start = 0 + range_end = 20 + default = 15 + +class DeathLinkAmnesty(Range): + """How many deaths it takes to send a DeathLink""" + display_name = "Death Link Amnesty" + range_start = 1 + range_end = 30 + default = 10 + + +@dataclass +class Celeste64Options(PerGameCommonOptions): + death_link: DeathLink + death_link_amnesty: DeathLinkAmnesty + strawberries_required: StrawberriesRequired diff --git a/worlds/celeste64/Regions.py b/worlds/celeste64/Regions.py new file mode 100644 index 000000000000..6f01c873a4f9 --- /dev/null +++ b/worlds/celeste64/Regions.py @@ -0,0 +1,11 @@ +from typing import Dict, List, NamedTuple + + +class Celeste64RegionData(NamedTuple): + connecting_regions: List[str] = [] + + +region_data_table: Dict[str, Celeste64RegionData] = { + "Menu": Celeste64RegionData(["Forsaken City"]), + "Forsaken City": Celeste64RegionData(), +} diff --git a/worlds/celeste64/Rules.py b/worlds/celeste64/Rules.py new file mode 100644 index 000000000000..3baa231892ad --- /dev/null +++ b/worlds/celeste64/Rules.py @@ -0,0 +1,104 @@ +from worlds.generic.Rules import set_rule + +from . import Celeste64World +from .Names import ItemName, LocationName + + +def set_rules(world: Celeste64World): + set_rule(world.multiworld.get_location(LocationName.strawberry_4, world.player), + lambda state: state.has_all({ItemName.traffic_block, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_5, world.player), + lambda state: state.has(ItemName.breakables, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_6, world.player), + lambda state: state.has(ItemName.dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_8, world.player), + lambda state: state.has(ItemName.traffic_block, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_9, world.player), + lambda state: state.has(ItemName.dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_11, world.player), + lambda state: state.has(ItemName.dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_12, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_13, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_14, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_15, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_16, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_17, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.double_dash_refill, + ItemName.feather, + ItemName.traffic_block}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_18, world.player), + lambda state: state.has(ItemName.double_dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_19, world.player), + lambda state: state.has_all({ItemName.double_dash_refill, + ItemName.spring}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_20, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather, + ItemName.breakables}, world.player)) + + set_rule(world.multiworld.get_location(LocationName.strawberry_21, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.traffic_block, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_22, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_23, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill, + ItemName.coin}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_24, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.traffic_block, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_25, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_26, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_27, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.coin, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_28, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.coin, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_29, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.coin, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_30, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.traffic_block, + ItemName.spring, + ItemName.breakables, + ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) + + # Completion condition. + world.multiworld.completion_condition[world.player] = lambda state: (state.has(ItemName.strawberry,world.player,world.options.strawberries_required.value) and + state.has_all({ItemName.feather, + ItemName.traffic_block, + ItemName.breakables, + ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) diff --git a/worlds/celeste64/__init__.py b/worlds/celeste64/__init__.py new file mode 100644 index 000000000000..0d3b5d015829 --- /dev/null +++ b/worlds/celeste64/__init__.py @@ -0,0 +1,92 @@ +from typing import List + +from BaseClasses import ItemClassification, Region, Tutorial +from worlds.AutoWorld import WebWorld, World +from .Items import Celeste64Item, item_data_table, item_table +from .Locations import Celeste64Location, location_data_table, location_table +from .Names import ItemName +from .Options import Celeste64Options + + +class Celeste64WebWorld(WebWorld): + theme = "ice" + + setup_en = Tutorial( + tutorial_name="Start Guide", + description="A guide to playing Celeste 64 in Archipelago.", + language="English", + file_name="guide_en.md", + link="guide/en", + authors=["PoryGone"] + ) + + tutorials = [setup_en] + + +class Celeste64World(World): + """Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer. + Created in a week(ish) by the Celeste team to celebrate the game’s sixth anniversary 🍓✨""" + + game = "Celeste 64" + web = Celeste64WebWorld() + options_dataclass = Celeste64Options + options: Celeste64Options + location_name_to_id = location_table + item_name_to_id = item_table + + + def create_item(self, name: str) -> Celeste64Item: + # Only make required amount of strawberries be Progression + if getattr(self, "options", None) and name == ItemName.strawberry: + classification: ItemClassification = ItemClassification.filler + self.prog_strawberries = getattr(self, "prog_strawberries", 0) + if self.prog_strawberries < self.options.strawberries_required.value: + classification = ItemClassification.progression_skip_balancing + self.prog_strawberries += 1 + + return Celeste64Item(name, classification, item_data_table[name].code, self.player) + else: + return Celeste64Item(name, item_data_table[name].type, item_data_table[name].code, self.player) + + def create_items(self) -> None: + item_pool: List[Celeste64Item] = [] + + item_pool += [self.create_item(name) for name in item_data_table.keys()] + + item_pool += [self.create_item(ItemName.strawberry) for _ in range(21)] + + self.multiworld.itempool += item_pool + + + def create_regions(self) -> None: + from .Regions import region_data_table + # Create regions. + for region_name in region_data_table.keys(): + region = Region(region_name, self.player, self.multiworld) + self.multiworld.regions.append(region) + + # Create locations. + for region_name, region_data in region_data_table.items(): + region = self.multiworld.get_region(region_name, self.player) + region.add_locations({ + location_name: location_data.address for location_name, location_data in location_data_table.items() + if location_data.region == region_name + }, Celeste64Location) + region.add_exits(region_data_table[region_name].connecting_regions) + + + def get_filler_item_name(self) -> str: + return ItemName.strawberry + + + def set_rules(self) -> None: + from .Rules import set_rules + set_rules(self) + + + def fill_slot_data(self): + return { + "death_link": self.options.death_link.value, + "death_link_amnesty": self.options.death_link_amnesty.value, + "strawberries_required": self.options.strawberries_required.value + } diff --git a/worlds/celeste64/docs/en_Celeste 64.md b/worlds/celeste64/docs/en_Celeste 64.md new file mode 100644 index 000000000000..efc42bfe56eb --- /dev/null +++ b/worlds/celeste64/docs/en_Celeste 64.md @@ -0,0 +1,24 @@ +# Celeste 64 + +## What is this game? + +Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer. +Created in a week(ish) by the Celeste team to celebrate the game's sixth anniversary. + +Ported to Archipelago in a week(ish) by PoryGone, this World provides the following as unlockable items: +- Strawberries +- Dash Refills +- Double Dash Refills +- Feathers +- Coins +- Cassettes +- Traffic Blocks +- Springs +- Breakable Blocks + +The goal is to collect a certain number of Strawberries, then visit Badeline on her floating island. + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure +and export a config file. diff --git a/worlds/celeste64/docs/guide_en.md b/worlds/celeste64/docs/guide_en.md new file mode 100644 index 000000000000..116a3b13e91b --- /dev/null +++ b/worlds/celeste64/docs/guide_en.md @@ -0,0 +1,32 @@ +# Celeste 64 Setup Guide + +## Required Software +- Archipelago Build of Celeste 64 from: [Celeste 64 Archipelago Releases Page](https://github.com/PoryGoneDev/Celeste64/releases/) + +## Installation Procedures (Windows) + +1. Download the above release and extract it. + +## Joining a MultiWorld Game + +1. Before launching the game, edit the `AP.json` file in the root of the Celeste 64 install. + +2. For the `Url` field, enter the address of the server, such as `archipelago.gg:38281`. Your server host should be able to tell you this. + +3. For the `SlotName` field, enter your "name" field from the yaml or website config. + +4. For the `Password` field, enter the server password if one exists; otherwise leave this field blank. + +5. Save the file, and run `Celeste64.exe`. If you can continue past the title screen, then you are successfully connected. + +An Example `AP.json` file: + +``` +{ + "Url": "archipelago:12345", + "SlotName": "Maddy", + "Password": "" +} +``` + + From e33ea0147bd19bcf4b03bb2cd25ff1d18aaa89f8 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:43:34 -0500 Subject: [PATCH 59/66] LTTP: Missed per_slot_random change (#2907) --- worlds/alttp/Shops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 1d548d8fdb53..dbe8cc1f9dfa 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -453,7 +453,7 @@ def get_price(multiworld, item, player: int, price_type=None): price = item["price"] if multiworld.randomize_shop_prices[player]: adjust = 2 if price < 100 else 5 - price = int((price / adjust) * (0.5 + multiworld.random.random() * 1.5)) * adjust + price = int((price / adjust) * (0.5 + multiworld.per_slot_randoms[player].random() * 1.5)) * adjust multiworld.per_slot_randoms[player].shuffle(price_types) for p_type in price_types: if any(x in item['item'] for x in price_blacklist[p_type]): From ac791f299975cdc0ef6ce34f03c215bc6d830992 Mon Sep 17 00:00:00 2001 From: BadMagic100 Date: Wed, 6 Mar 2024 23:24:08 -0800 Subject: [PATCH 60/66] CI: Avoid race condition in labeler workflow (#2910) --- .github/workflows/label-pull-requests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/label-pull-requests.yml b/.github/workflows/label-pull-requests.yml index 42881aa49d9b..e26f6f34a4d2 100644 --- a/.github/workflows/label-pull-requests.yml +++ b/.github/workflows/label-pull-requests.yml @@ -18,6 +18,7 @@ jobs: sync-labels: true peer_review: name: 'Apply peer review label' + needs: labeler if: >- (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'ready_for_review') && !github.event.pull_request.draft @@ -30,6 +31,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} unblock_draft_prs: name: 'Remove waiting-on labels' + needs: labeler if: github.event.action == 'converted_to_draft' || github.event.action == 'closed' runs-on: ubuntu-latest steps: From c6b1039e0fc5a8b41c934ecb96534c9e937985eb Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Thu, 7 Mar 2024 01:40:09 -0600 Subject: [PATCH 61/66] Core: call from_any on the options class when creating item links options (#2783) --- worlds/AutoWorld.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index dd0f46f6a6d1..41796371994a 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -422,9 +422,9 @@ def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set An example case is ItemLinks creating these.""" # TODO remove loop when worlds use options dataclass for option_key, option in cls.options_dataclass.type_hints.items(): - getattr(multiworld, option_key)[new_player_id] = option(option.default) + getattr(multiworld, option_key)[new_player_id] = option.from_any(option.default) group = cls(multiworld, new_player_id) - group.options = cls.options_dataclass(**{option_key: option(option.default) + group.options = cls.options_dataclass(**{option_key: option.from_any(option.default) for option_key, option in cls.options_dataclass.type_hints.items()}) return group From 862d77820d0a2826a8a426869c3b7149173a336a Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu, 7 Mar 2024 02:48:55 -0500 Subject: [PATCH 62/66] Fill: Improve clarity of remaining_fill messages (#2894) * Update Fill.py Make the fill message more descriptive * Removing kwarg --- Fill.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Fill.py b/Fill.py index ae44710469e4..2d6257eae30a 100644 --- a/Fill.py +++ b/Fill.py @@ -208,7 +208,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati def remaining_fill(multiworld: MultiWorld, locations: typing.List[Location], - itempool: typing.List[Item]) -> None: + itempool: typing.List[Item], + name: str = "Remaining") -> None: unplaced_items: typing.List[Item] = [] placements: typing.List[Location] = [] swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter() @@ -265,10 +266,10 @@ def remaining_fill(multiworld: MultiWorld, placements.append(spot_to_fill) placed += 1 if not placed % 1000: - _log_fill_progress("Remaining", placed, total) + _log_fill_progress(name, placed, total) if total > 1000: - _log_fill_progress("Remaining", placed, total) + _log_fill_progress(name, placed, total) if unplaced_items and locations: # There are leftover unplaceable items and locations that won't accept them @@ -466,7 +467,7 @@ def mark_for_locking(location: Location): inaccessible_location_rules(multiworld, multiworld.state, defaultlocations) - remaining_fill(multiworld, excludedlocations, filleritempool) + remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded") if excludedlocations: raise FillError( f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items") From b4bb88fcf7986859bb9efb2d6a159fcebcbab105 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Thu, 7 Mar 2024 03:18:22 -0600 Subject: [PATCH 63/66] SNIClient: dynamically generate patch file identifier (#2870) Co-authored-by: beauxq --- worlds/AutoSNIClient.py | 45 +++++++++++++++++++++++++++++++++++- worlds/LauncherComponents.py | 4 ---- worlds/alttp/Client.py | 1 + worlds/dkc3/Client.py | 1 + worlds/lufia2ac/Client.py | 1 + worlds/sm/Client.py | 1 + worlds/smw/Client.py | 1 + worlds/smz3/Client.py | 1 + 8 files changed, 50 insertions(+), 5 deletions(-) diff --git a/worlds/AutoSNIClient.py b/worlds/AutoSNIClient.py index a30dbbb46d1f..2b984d9c8846 100644 --- a/worlds/AutoSNIClient.py +++ b/worlds/AutoSNIClient.py @@ -1,11 +1,35 @@ from __future__ import annotations import abc -from typing import TYPE_CHECKING, ClassVar, Dict, Tuple, Any, Optional +from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union + +from typing_extensions import TypeGuard + +from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components if TYPE_CHECKING: from SNIClient import SNIContext +component = Component('SNI Client', 'SNIClient', component_type=Type.CLIENT, file_identifier=SuffixIdentifier(".apsoe")) +components.append(component) + + +def valid_patch_suffix(obj: object) -> TypeGuard[Union[str, Iterable[str]]]: + """ make sure this is a valid value for the class variable `patch_suffix` """ + + def valid_individual(one: object) -> TypeGuard[str]: + """ check an individual suffix """ + # TODO: decide: len(one) > 3 and one.startswith(".ap") ? + # or keep it more general? + return isinstance(one, str) and len(one) > 1 and one.startswith(".") + + if isinstance(obj, str): + return valid_individual(obj) + if not isinstance(obj, Iterable): + return False + obj_it: Iterable[object] = obj + return all(valid_individual(each) for each in obj_it) + class AutoSNIClientRegister(abc.ABCMeta): game_handlers: ClassVar[Dict[str, SNIClient]] = {} @@ -15,6 +39,22 @@ def __new__(cls, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> Aut new_class = super().__new__(cls, name, bases, dct) if "game" in dct: AutoSNIClientRegister.game_handlers[dct["game"]] = new_class() + + if "patch_suffix" in dct: + patch_suffix = dct["patch_suffix"] + assert valid_patch_suffix(patch_suffix), f"class {name} defining invalid {patch_suffix=}" + + existing_identifier = component.file_identifier + assert isinstance(existing_identifier, SuffixIdentifier), f"{existing_identifier=}" + new_suffixes = [*existing_identifier.suffixes] + + if isinstance(patch_suffix, str): + new_suffixes.append(patch_suffix) + else: + new_suffixes.extend(patch_suffix) + + component.file_identifier = SuffixIdentifier(*new_suffixes) + return new_class @staticmethod @@ -27,6 +67,9 @@ async def get_handler(ctx: SNIContext) -> Optional[SNIClient]: class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister): + patch_suffix: ClassVar[Union[str, Iterable[str]]] = () + """The file extension(s) this client is meant to open and patch (e.g. ".aplttp")""" + @abc.abstractmethod async def validate_rom(self, ctx: SNIContext) -> bool: """ TODO: interface documentation here """ diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index 7814aac5ae49..41c0bb83295f 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -85,10 +85,6 @@ def launch_textclient(): file_identifier=SuffixIdentifier('.archipelago', '.zip')), Component('Generate', 'Generate', cli=True), Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient), - # SNI - Component('SNI Client', 'SNIClient', - file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3', - '.apsmw', '.apl2ac', '.apkdl3')), Component('Links Awakening DX Client', 'LinksAwakeningClient', file_identifier=SuffixIdentifier('.apladx')), Component('LttP Adjuster', 'LttPAdjuster'), diff --git a/worlds/alttp/Client.py b/worlds/alttp/Client.py index edc68473b93f..5b27f559efd7 100644 --- a/worlds/alttp/Client.py +++ b/worlds/alttp/Client.py @@ -471,6 +471,7 @@ def new_check(location_id): class ALTTPSNIClient(SNIClient): game = "A Link to the Past" + patch_suffix = [".aplttp", ".apz3"] async def deathlink_kill_player(self, ctx): from SNIClient import DeathState, snes_read, snes_buffered_write, snes_flush_writes diff --git a/worlds/dkc3/Client.py b/worlds/dkc3/Client.py index 77ed51fecbd0..efa199e1d0c9 100644 --- a/worlds/dkc3/Client.py +++ b/worlds/dkc3/Client.py @@ -24,6 +24,7 @@ class DKC3SNIClient(SNIClient): game = "Donkey Kong Country 3" + patch_suffix = ".apdkc3" async def deathlink_kill_player(self, ctx): pass diff --git a/worlds/lufia2ac/Client.py b/worlds/lufia2ac/Client.py index ac0de19bfdd6..9025a1137b98 100644 --- a/worlds/lufia2ac/Client.py +++ b/worlds/lufia2ac/Client.py @@ -30,6 +30,7 @@ class L2ACSNIClient(SNIClient): game: str = "Lufia II Ancient Cave" + patch_suffix = ".apl2ac" async def validate_rom(self, ctx: SNIContext) -> bool: from SNIClient import snes_read diff --git a/worlds/sm/Client.py b/worlds/sm/Client.py index 756fd4bf36a7..ed3f2d5b3d30 100644 --- a/worlds/sm/Client.py +++ b/worlds/sm/Client.py @@ -37,6 +37,7 @@ class SMSNIClient(SNIClient): game = "Super Metroid" + patch_suffix = [".apsm", ".apm3"] async def deathlink_kill_player(self, ctx): from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py index 92aeac4d4a2b..50899abe3774 100644 --- a/worlds/smw/Client.py +++ b/worlds/smw/Client.py @@ -57,6 +57,7 @@ class SMWSNIClient(SNIClient): game = "Super Mario World" + patch_suffix = ".apsmw" async def deathlink_kill_player(self, ctx): from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read diff --git a/worlds/smz3/Client.py b/worlds/smz3/Client.py index b07aa850c31d..0a248aa5d3f2 100644 --- a/worlds/smz3/Client.py +++ b/worlds/smz3/Client.py @@ -32,6 +32,7 @@ class SMZ3SNIClient(SNIClient): game = "SMZ3" + patch_suffix = ".apsmz3" async def validate_rom(self, ctx): from SNIClient import snes_buffered_write, snes_flush_writes, snes_read From 3e0ff3f72dc9e3a6bc9817d64def2ada7a00eee8 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Fri, 8 Mar 2024 00:46:14 -0600 Subject: [PATCH 64/66] KDL3: Post-PR adjustments (#2917) * logic adjustments, add patch_suffix * forgot the other two consumables --- worlds/kdl3/Client.py | 4 +++- worlds/kdl3/Rules.py | 42 +++++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py index 3fef0429004d..c10dd6cebb72 100644 --- a/worlds/kdl3/Client.py +++ b/worlds/kdl3/Client.py @@ -97,6 +97,7 @@ def cmd_gift(self: "SNIClientCommandProcessor"): class KDL3SNIClient(SNIClient): game = "Kirby's Dream Land 3" + patch_suffix = ".apkdl3" levels = None consumables = None stars = None @@ -406,7 +407,8 @@ async def game_watcher(self, ctx) -> None: ctx.locations_checked.add(new_check_id) location = ctx.location_names[new_check_id] snes_logger.info( - f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + f'New Check: {location} ({len(ctx.locations_checked)}/' + f'{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) except Exception as ex: # we crashed, so print log and clean up diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/Rules.py index 81ad8f1f1fc0..91abc21d0623 100644 --- a/worlds/kdl3/Rules.py +++ b/worlds/kdl3/Rules.py @@ -206,13 +206,19 @@ def set_rules(world: "KDL3World") -> None: lambda state: can_reach_needle(state, world.player)) set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u3, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u4, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(LocationName.cloudy_park_6_u1, world.player), lambda state: can_reach_cutter(state, world.player)) @@ -242,7 +248,9 @@ def set_rules(world: "KDL3World") -> None: for i in range(12, 18): set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) for i in range(21, 23): set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), lambda state: can_reach_chuchu(state, world.player)) @@ -267,32 +275,32 @@ def set_rules(world: "KDL3World") -> None: # Kirby cannot eat enemies fully submerged in water. Vast majority of cases, the enemy can be brought to the surface # and eaten by inhaling while falling on top of them set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_2_E3, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_3_E6, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) # Ripple Field 4 E5, E7, and E8 are doable, but too strict to leave in logic set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E5, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E7, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E8, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E1, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E2, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E3, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E4, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E7, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E8, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E9, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E10, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified", "Level 3 Boss - Purified", "Level 4 Boss - Purified", From 3e3b4c6732be219f756f7db22e8973540a1ae57a Mon Sep 17 00:00:00 2001 From: Seafo <92278897+Seatori@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:15:36 -0500 Subject: [PATCH 65/66] Minecraft: Add Pickaxes to the documentation (#2688) --- worlds/minecraft/docs/en_Minecraft.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/worlds/minecraft/docs/en_Minecraft.md b/worlds/minecraft/docs/en_Minecraft.md index 1ef347983bc4..bfe10d8cc58e 100644 --- a/worlds/minecraft/docs/en_Minecraft.md +++ b/worlds/minecraft/docs/en_Minecraft.md @@ -64,12 +64,15 @@ sequence either by skipping it or watching hit play out. * Diamond Axe * Progessive Tools * Tier I + * Stone Pickaxe * Stone Shovel * Stone Hoe * Tier II + * Iron Pickaxe * Iron Shovel * Iron Hoe * Tier III + * Diamond Pickaxe * Diamond Shovel * Diamond Hoe * Netherite Ingot From 3c4ebb21140e5fd8ec6df63c1033915211ae8b01 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 9 Mar 2024 00:03:02 +0100 Subject: [PATCH 66/66] The Witness: Fix... I don't know how to explain this one (#2920) ``` for hint in generated_hints: hint = generated_hints.pop(0) ``` lmao --- worlds/witness/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index bd877a16efee..635a56796b2f 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -306,7 +306,6 @@ def fill_slot_data(self) -> dict: duplicates = min(3, len(audio_logs) // hint_amount) for hint in generated_hints: - hint = generated_hints.pop(0) compact_hint_data = make_compact_hint_data(hint, self.player) for _ in range(0, duplicates):