From b2f30d5fd001ec7a37ad56c3c989015a991a6089 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 3 Mar 2024 02:20:37 -0500 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] 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 17/20] 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 18/20] 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 19/20] 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 20/20] 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 zcmY+EX;jjQ8pi)DA}*Mumit9f0<%K?Tm_+0%m~OpqL9; zgG*x3sO6egnoELZ;?km(d8cKxS*|&Vnx(hy+;i@|&xiLs=lSxU=fm^!Vt5Dm`hvt+ z;4gK-|6>5szm3DJ=B#sdog|1`*Kw8fF$CWo*=8AqXpfU|e7&|Bs0sy8YKnsBc zm>6V=A%Fq+ZVMIWpnBYELjd0cMUxQeD198#!~;>sqeG-BXGAWHu3hEfr(Gi1$doc| zpSI_x$dgUvB4++zM!@a*%k=bM@y~y}wYo-Jl|H#%BY8AW5K*Uca|8T~XpJ#MyNU_DdEA+EuYv#eq;K#4(i54p{zcC}udOe=f6&mZ$@2AF zTgYkrb4gR9UR`T`m2>Jx6>Jx;;lZ`GhaJL%2^I=;){!{t+%N}W1TIL#o#SEp?51b{ z4uH7UC_r4HYgL{H1)k|bOc*UrKy73!f{fwd9={T{??rD#5`WRdh%BpDDY@*+b>vp< z`b7jCagl)?1rQQ*1cw76D!_i%8Vc?5W2CIg$avAoRU9x^G3` zP@e*TXi@K%|5z*qvyf}y?+hvq1uH;6g(9BI&qe5nlBDApl^F;m#G5BTxMnExL?AH{ zkx$D70HsucrczylVH1i(6o4oM5MRRo*G5Faxhre41I{LKZ|ulRHaLBJJn=Km%pPu9 z1=p$IOgbUXqbmSeFIxLYT_IZ1H#{}Ng%vo4He9H@uD&(& zVwc4me!yOg)QwLv4rht&nj)A@Hc|Xgd&+Z16y&Xxh&|+!dyxM}e0n=P`*T|JGC1HC z(y}L4-?x8odsO?tCC0|889cbB{(c5Pj!#KWEtRqkwb@So5&kgf4ECRlEQW%ziqg-x z%RDJh$5b!y$Uw23ayUAFEg#bYMIyjQ33n{F<1Jcd*b+OVJZA1ddZ8T5gS zxyy_z9k*)bvzxA7G&J+S4DHX(LHo7dv)TTc=Nnn2GvBkHYVlr}7E1SQ>6lL#83ymI z?Q4zK{I(aBk8LCwq}!}vTn|5I?dn^o%YIN~XyxTn5Rw!-Y`$yKSKLxaXmamL{wSU0 zdVLolmu>40eVQ>DR!09dGUz5}w4rpROMiPjWyh79sGyxJ`EuRgLG31o*-$NBX$fuG za;Y(+FX_|MYH8o<@*S7!Jo?4C*mt=ve8zQ=o`*<$3>rt9(Rc7C-*xPm>&wpS)5(v^ z1-EfJ9eQ~`h6N6M>V6(vu3l^WNp8=s9T>4|d2f8+=?k>P4tbxujzqmN>bE#A6zRDU z3HaE{TbmM0g!E=Bt7M4Uv^LvbujKrVLd!7fh2RyLZm}cl+j7H|cIo8YpWoCHQ#ER%;T6_oW#6i%%V-;ZL4Psb{7J-u=k5C zg?rB^;k1y){<{JrnH9!Gnxw8LkpZjUrEKfsM!e&;No4ahLu)_o`Lgzavn!QbZfhRS z4MiE8vNWHrhNSI7$oz&Xhty0C4A#QOw@Y|Nf--d0;Pt69oMQbzQ}X=Q;2njhin-7$ zUMGhp@PTf#0$_J)x7Y*@z&w8)Ijn1Cg?L!ubEhLb_DIWm!7bZLyg#lip7=lLIu_qX z10e2S$(8=<&%-OH52*c1niEMUMi2HJdA8nzTV_8?ozKZB>VaNrIL@d5&Fe4{BVP#$ zNPy8KaiDb#9R)z@=zpz#gEy#fVf14eTk7gqJG~Kw`e*CS&A-B%1OOlaK%|L^agvZt zp&p}*!C2M;F|N=&Q31pH=93h_Xx-PVLSR%X6_gQN^uA76%x1CkFV{?1*Gx=Fl0?8F zpn^>}1B);Wph~pB1CW~2lXy!g?Pd6*Z{y@tWQ)IeCrLR^%~*P#3K!Lrz!0o4)P>*W zu0+$J>pQLy5lBb3U|vbvO_v|c5m!4c_bJi*punIMPrB>%n-ug;_|#^-dJpy)YMNkt z5JI}=U3&m88bDl+o45+T!xCSdjsfvw`D}y4tO7DK*n7c(HTemyyMNr|qNV!km;l zD=Z8G%0l9t1$+K+>H7Fwx8T%ZRoC1Y-G7(*y*0~ZhL)-6R5U`o(>T}cAKQT zRtbg8OXaHZ5nM!DZPkiiP<_LNxAaR#b*!V>Q*U5nx^~mf4!^kFc%+l}dF9*RG!jBb zvb85+_N+c=y0rLu=!q$ALgMamYM*$;r{)Lw2CCO_BMrBuRQ5u?dAL&#EkX$&KO;a4 zqWV9SHwD|yciRCG;9=O}?eCBAz&T_%>TCmJq#`CHzNk0+)=`2D3~IYYg#R#32Q zdGj(IQ*ak^;KMv`c2ct3Q~UTG@knc+R)9ef(oshGCTk~_x- 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))